diff --git a/addKey.php b/addKey.php index c90d6e2..4bb9b98 100644 --- a/addKey.php +++ b/addKey.php @@ -16,14 +16,10 @@ if($settings == "valid") } // Wie sites/userkeys.php: nur Superadmins dürfen Passkeys vergeben if($_SESSION["login"] == "valid" && isset($_GET["id"]) && ((int)substr($_SESSION["rights"], RIGHTS_SUPERADMIN, 1) & RIGHTS_CHANGE)){ - if(!mysqli_query($dbconn,"DELETE FROM addKey WHERE datetime < DATE_SUB(NOW(), INTERVAL 1 MINUTE);")){ - echo mysqli_error($dbconn); - } + db_abfrage($dbconn, "DELETE FROM addKey WHERE datetime < DATE_SUB(NOW(), INTERVAL 1 MINUTE)") or print(mysqli_error($dbconn)); if(isLocal()){ $accessKey = strval(random_int(0,99999999)); - if(!mysqli_query($dbconn,"INSERT INTO addKey SET accesskey=".$accessKey.", datetime=NOW();")){ - echo mysqli_error($dbconn); - } + db_abfrage($dbconn, "INSERT INTO addKey SET accesskey = ?, datetime = NOW()", array($accessKey)) or print(mysqli_error($dbconn)); header('Location: https://wm.el-wa.org/kunden?action=userkeys&id='.(int)$_GET["id"].'&addKey='.$accessKey); echo 'https://wm.el-wa.org/kunden?action=userkeys&id='.(int)$_GET["id"].'&addKey='.$accessKey; }else{ diff --git a/ajax/searchkdnrech.php b/ajax/searchkdnrech.php index b862865..535168e 100644 --- a/ajax/searchkdnrech.php +++ b/ajax/searchkdnrech.php @@ -123,7 +123,7 @@ if(!isset($err)) //OLD SKS RECHNUNGEN!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - if(getUserSetting($dbconn,"show_sks", mysql_escape_string($_SESSION["user"]), 1)) //show sks rechnungen???? + if(getUserSetting($dbconn,"show_sks", $_SESSION["user"], 1)) //show sks rechnungen???? { echo "

SKS Dokumente:

"; $queries=""; diff --git a/ajax/searchrech.php b/ajax/searchrech.php index 8132e9a..6a25454 100644 --- a/ajax/searchrech.php +++ b/ajax/searchrech.php @@ -118,7 +118,7 @@ if(!isset($err)) //OLD SKS RECHNUNGEN!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - if(getUserSetting($dbconn,"show_sks", mysql_escape_string($_SESSION["user"]), 1)) //show sks rechnungen???? + if(getUserSetting($dbconn,"show_sks", $_SESSION["user"], 1)) //show sks rechnungen???? { echo "

SKS Dokumente:

"; $queries = createRechQry($skspage,$pagesize,$src,$combiner,1); diff --git a/authServer.php b/authServer.php index 4d240ca..d1d296b 100644 --- a/authServer.php +++ b/authServer.php @@ -139,22 +139,20 @@ try { // ------------------------------------ } else if ($fn === 'getGetArgs') { - $result = mysqli_query($dbconn,"SELECT id FROM user WHERE nick = '".mysqli_real_escape_string($dbconn,$_GET["user"])."';"); - if ($result->num_rows > 0) { - $row = $result->fetch_assoc(); - $userId = $row["id"]; - }else{ + $userId = db_wert($dbconn, "SELECT id FROM user WHERE nick = ?", array((string)filter_input(INPUT_GET, 'user'))); + if ($userId === false) { + throw new Exception(mysqli_error($dbconn)); + } + if ($userId === null) { throw new Exception('Username not found'); } $ids = []; - $result = mysqli_query($dbconn,"SELECT credentialId FROM passkeys WHERE userId = '".$userId."';"); - if(!$result){ - $msg = "Error:
".mysqli_error($dbconn)."
"; + $zeilen = db_zeilen($dbconn, "SELECT credentialId FROM passkeys WHERE userId = ?", array($userId)); + if ($zeilen === false) { + throw new Exception(mysqli_error($dbconn)); } - if ($result->num_rows > 0) { - while($row = $result->fetch_assoc()) { - $ids[] = base64_decode($row["credentialId"]); - } + foreach ($zeilen as $row) { + $ids[] = base64_decode($row["credentialId"]); } if (count($ids) === 0) { @@ -197,7 +195,8 @@ try { if (!isset($_SESSION['registrations']) || !array_key_exists('registrations', $_SESSION) || !is_array($_SESSION['registrations'])) { $_SESSION['registrations'] = []; }*/ - if(!mysqli_query($dbconn,"INSERT INTO passkeys SET credentialId = '".base64_encode($data->credentialId)."', credentialPublicKey = '".base64_encode($data->credentialPublicKey)."', signatureCounter = '".base64_encode($data->signatureCounter)."', name = '".mysqli_real_escape_string($mysql,filter_input(INPUT_GET, 'name'))."', userId = '".(int)$passkeyFreigabe["user"]."';")){ + if(!db_abfrage($dbconn, "INSERT INTO passkeys SET credentialId = ?, credentialPublicKey = ?, signatureCounter = ?, name = ?, userId = ?, lastAuth = NOW(), authKey = 0", + array(base64_encode($data->credentialId), base64_encode($data->credentialPublicKey), base64_encode($data->signatureCounter), (string)filter_input(INPUT_GET, 'name'), $passkeyFreigabe["user"]))){ $msg = "Error:
".mysqli_error($dbconn)."
"; } else{ @@ -236,12 +235,11 @@ try { // looking up correspondending public key of the credential id // you should also validate that only ids of the given user name // are taken for the login. - $result = mysqli_query($dbconn,"SELECT credentialPublicKey, userId, name FROM passkeys WHERE credentialId = '".base64_encode($id)."';"); - if(!$result){ - $msg = "Error:
".mysqli_error($dbconn)."
"; + $row = db_zeile($dbconn, "SELECT credentialPublicKey, userId, name FROM passkeys WHERE credentialId = ?", array(base64_encode((string)$id))); + if ($row === false) { + throw new Exception(mysqli_error($dbconn)); } - if ($result->num_rows > 0) { - $row = $result->fetch_assoc(); + if ($row) { $userId = $row["userId"]; $credentialPublicKey = base64_decode($row["credentialPublicKey"]); $reg = (object) ['userId' => base64_decode($row["userId"])]; @@ -270,16 +268,15 @@ try { $authKey = strval(random_int(0,99999999)); - $result = mysqli_query($dbconn,"UPDATE passkeys SET authKey=".$authKey.", lastAuth=NOW() WHERE credentialId = '".base64_encode($id)."';"); + db_abfrage($dbconn, "UPDATE passkeys SET authKey = ?, lastAuth = NOW() WHERE credentialId = ?", array($authKey, base64_encode($id))); // Gesperrte Benutzer bleiben gesperrt, auch mit gültigem Passkey - $qry= mysqli_query($dbconn, "SELECT * FROM user WHERE id = '".mysqli_real_escape_string($dbconn,$userId)."' AND blocked = 0"); - if(!$qry || mysqli_num_rows($qry) != 1) + $udata = db_zeile($dbconn, "SELECT * FROM user WHERE id = ? AND blocked = 0", array($userId)); + if(!$udata) { throw new Exception('Benutzer nicht gefunden oder gesperrt.'); } else { - $udata = mysqli_fetch_assoc($qry); session_regenerate_id(true); $_SESSION["login"] = "valid"; $_SESSION["logedout"] = $udata["logedout"]; @@ -299,7 +296,7 @@ try { $_SESSION["zoom"] = getUserSetting($dbconn,"zoom", $_SESSION["user"], 1); $_SESSION["privateNotes"] = getUserSetting($dbconn,"privnotes", $_SESSION["user"], ""); - mysqli_query($dbconn, "UPDATE user SET wrongpw = 0, lastlogin = NOW(), numlogins = numlogins + 1, logedout = 0 WHERE id = '".mysqli_real_escape_string($dbconn,$userId)."'"); + db_abfrage($dbconn, "UPDATE user SET wrongpw = 0, lastlogin = NOW(), numlogins = numlogins + 1, logedout = 0 WHERE id = ?", array($userId)); } $return = new stdClass(); $return->success = true; diff --git a/incs/authHelper.php b/incs/authHelper.php index cfbbcaa..406d651 100644 --- a/incs/authHelper.php +++ b/incs/authHelper.php @@ -84,41 +84,17 @@ function isLocal(){ return $lokal; } -function checkLogin(){ - global $mysql; - //$mysql = new mysqli($GLOBALS["mysql_server"],$GLOBALS["mysql_user"],$GLOBALS["mysql_pass"],$GLOBALS["mysql_db"]); - if(isLocal()){ - $_SESSION["local"] =true; - return true; - } - if(isset($_SESSION["authKey"])){ - $res = mysqli_query($mysql,"SELECT id FROM users WHERE lastAuth > DATE_SUB(NOW(), INTERVAL 2 DAY) AND authKey = '".mysqli_real_escape_string($mysql,$_SESSION["authKey"])."' AND name = '".mysqli_real_escape_string($mysql,$_SESSION["user"])."';"); - if(!$res){ - echo mysqli_error($mysql); - return false; - } - if(mysqli_num_rows($res) == 1){ - return isset($_SESSION["Logged"]); - } - }else{ - return false; - } -} - +/* Ist $_GET["addKey"] eine gültige, höchstens eine Minute alte Freigabe? */ function checkAdduser(){ global $mysql; - //$mysql = new mysqli($GLOBALS["mysql_server"],$GLOBALS["mysql_user"],$GLOBALS["mysql_pass"],$GLOBALS["mysql_db"]); - if(!mysqli_query($mysql,"DELETE FROM addKey WHERE datetime < DATE_SUB(NOW(), INTERVAL 1 MINUTE);")){ - echo mysqli_error($mysql); - } - $result = mysqli_query($mysql,"SELECT * FROM addKey WHERE accesskey='".mysqli_real_escape_string($mysql,$_GET["addKey"])."';"); - if(!$result){ + db_abfrage($mysql, "DELETE FROM addKey WHERE datetime < DATE_SUB(NOW(), INTERVAL 1 MINUTE)") or print(mysqli_error($mysql)); + // Nur reine Ziffern: accesskey ist eine Zahlenspalte, MariaDB würde + // "12345678xyz" beim Vergleich stillschweigend zu 12345678 machen. + $key = $_GET["addKey"] ?? ""; + if(!is_string($key) || !ctype_digit($key)) return false; - } - if ($result->num_rows > 0) { - return true; - } - return false; + $anzahl = db_wert($mysql, "SELECT COUNT(*) FROM addKey WHERE accesskey = ?", array((int)$key)); + return $anzahl > 0; } ?> \ No newline at end of file diff --git a/incs/settings.php b/incs/settings.php index aef498b..4dc5942 100644 --- a/incs/settings.php +++ b/incs/settings.php @@ -18,30 +18,27 @@ function readsettings() } +/* + * Benutzereinstellungen. Werte und ids roh übergeben - nicht escapen, das + * erledigen die Platzhalter. + */ function getUserSetting($dbconn,$setting, $user, $default = 0) { - $qry2 = mysqli_query($dbconn, "SELECT val FROM setting WHERE opt = '".mysqli_real_escape_string($dbconn, $setting)."' AND user_id = '".mysqli_real_escape_string($dbconn, $user)."'") or die(mysqli_error($dbconn)); - if($res = mysqli_fetch_array($qry2)) - $ret = $res[0]; - else - $ret = $default; - - return $ret; + $wert = db_wert($dbconn, "SELECT val FROM setting WHERE opt = ? AND user_id = ?", array($setting, $user)); + if($wert === false) + die(mysqli_error($dbconn)); + return $wert === null ? $default : $wert; } function updateUserSetting($dbconn,$setting, $value, $user) { - $qry = mysqli_query($dbconn,"SELECT id FROM setting WHERE opt = '".mysqli_real_escape_string($dbconn, $setting)."' AND user_id = '".mysqli_real_escape_string($dbconn, $user)."'") or die(mysqli_error($dbconn)); - if(mysqli_num_rows($qry)) - { - $res = mysqli_fetch_array($qry); - mysqli_query($dbconn, "UPDATE setting SET val = '".mysqli_real_escape_string($dbconn, $value)."' WHERE id = ".$res[0]) or die($mysqli_error($dbconn)); - echo "update"; - } - else - { - mysqli_query($dbconn, "INSERT INTO setting SET val = '".mysqli_real_escape_string($dbconn, $value)."', user_id = '".mysqli_real_escape_string($dbconn, $user)."', opt = '".mysqli_real_escape_string($setting)."'") or die(mysqli_error($dbconn)); - } + $id = db_wert($dbconn, "SELECT id FROM setting WHERE opt = ? AND user_id = ?", array($setting, $user)); + if($id === false) + die(mysqli_error($dbconn)); + if($id !== null) + db_abfrage($dbconn, "UPDATE setting SET val = ? WHERE id = ?", array($value, $id)) or die(mysqli_error($dbconn)); + else + db_abfrage($dbconn, "INSERT INTO setting SET val = ?, user_id = ?, opt = ?", array($value, $user, $setting)) or die(mysqli_error($dbconn)); } diff --git a/sites/settings.php b/sites/settings.php index 08beb68..2412a52 100644 --- a/sites/settings.php +++ b/sites/settings.php @@ -76,12 +76,13 @@ else { if($_GET["formsent"] == 1) { - if(is_file("./styles/".$_POST["style"])) + // nur Dateien direkt in styles/, kein ../ + if(basename($_POST["style"]) === $_POST["style"] && is_file("./styles/".$_POST["style"])) { - updateUserSetting($dbconn,"style", mysqli_real_escape_string($dbconn,$_POST["style"]), mysqli_real_escape_string($dbconn,$_SESSION["user"])); + updateUserSetting($dbconn,"style", $_POST["style"], $_SESSION["user"]); $_SESSION["style"] = $_POST["style"]; } - updateUserSetting($dbconn,"show_sks", mysqi_real_escape_string($dbconn,$_POST["show_sks"]), mysqli_real_escape_string($dbconn,$_SESSION["user"])); + updateUserSetting($dbconn,"show_sks", (int)$_POST["show_sks"], $_SESSION["user"]); } if(substr($_SESSION["rights"],RIGHTS_SUPERADMIN,1) & RIGHTS_CHANGE || substr($_SESSION["rights"],RIGHTS_SUPERADMIN,1) & RIGHTS_ADD) $smarty->assign("superadmin","add"); @@ -129,7 +130,7 @@ else } - $show_sks = getUserSetting($dbconn,"show_sks", mysqli_real_escape_string($dbconn,$_SESSION["user"]), 1); + $show_sks = getUserSetting($dbconn,"show_sks", $_SESSION["user"], 1); $smarty->assign("show_sks",$show_sks); $smarty->assign("styles",$styles); diff --git a/sites/start.php b/sites/start.php index eb43930..bcac391 100644 --- a/sites/start.php +++ b/sites/start.php @@ -1,73 +1,57 @@ display("heading.tpl"); $smarty->assign("local",isLocal()); if(isset($_GET["formsent"]) && $_SESSION["login"] != "valid" && isLocal()) { - $qry= mysqli_query($dbconn, "SELECT salt FROM user WHERE LOWER(nick) = '".mysql_escape_string(strtolower($_POST["user"]))."' AND blocked = 0") or error("Es ist ein Fehler in der abfrage der Benutzerdaten:
".formaterror(mysqli_error($dbconn))); - if(mysqli_num_rows($qry) == 1) - { - $res = mysqli_fetch_array($qry); - $qry= mysqli_query($dbconn, "SELECT * FROM user WHERE nick = '".mysql_escape_string($_POST["user"])."' AND pass = '".crypt($_POST["pass"],$res[0])."'") or error("Es ist ein Fehler in der abfrage der Benutzerdaten:
".formaterror(mysqli_error($dbconn))); - if(mysqli_num_rows($qry) == 1) - { - $udata = mysqli_fetch_assoc($qry); - // neue Session-ID nach dem Login, sonst ist Session-Fixation möglich - session_regenerate_id(true); - $_SESSION["login"] = "valid"; - $_SESSION["logedout"] = $udata["logedout"]; - $_SESSION["user"] = $udata["id"]; - $_SESSION["nick"] = $udata["nick"]; - $_SESSION["vname"] = $udata["vname"]; - $_SESSION["nname"] = $udata["nname"]; - $_SESSION["rights"] = $udata["rights"]; - $_SESSION["lastlogin"] = $udata["lastlogin"]; - $_SESSION["numlogins"] = $udata["numlogins"]; - $_SESSION["style"] = getUserSetting($dbconn,"style", $udata["id"]); - if(!is_file("styles/".$_SESSION["style"])) - { - updateUserSetting($dbconn,"style", "default.css", $udata["id"]); - $_SESSION["style"] = getUserSetting($dbconn,"style", $udata["id"]); - } - $_SESSION["zoom"] = getUserSetting($dbconn,"zoom", $_SESSION["user"], 1); - $_SESSION["privateNotes"] = getUserSetting($dbconn,"privnotes", $_SESSION["user"], ""); - - mysqli_query($dbconn, "UPDATE user SET wrongpw = 0, lastlogin = NOW(), numlogins = numlogins + 1, logedout = 0 WHERE nick = '".mysql_escape_string($_POST["user"])."'"); - } - else - { - $qry= mysqli_query($dbconn, "SELECT wrongpw FROM user WHERE nick = '".mysql_escape_string($_POST["user"])."'") or error("Es ist ein Fehler in der abfrage der Benutzerdaten:
".formaterror(mysqli_error($dbconn)));; - if(mysqli_num_rows($qry)) - { - $wrongpw = mysqli_fetch_array($qry); - if($wrongpw[0] < 10) - { - mysqli_query($dbconn, "UPDATE user SET wrongpw = wrongpw + 1 WHERE nick = '".mysql_escape_string($_POST["user"])."'"); - $triesleft = 10 - $wrongpw[0]; - if($triesleft > 1) - error("Der Benuzername oder das Passwort sind falsch, bitte versuchen Sie es erneut (Nach weiteren $triesleft Versuchen wird der Benutzer deaktiviert und muss vom Hauptbenutzer wieder aktiviert werden, damit er sich anmelden kann.)"); - else - error("Der Benuzername oder das Passwort sind falsch, bitte versuchen Sie es erneut (Wenn der nächste Versuch falsch ist, wird der Benutzer für deaktiviert und muss vom Hauptbenutzer wieder aktiviert werden, damit er sich anmelden kann.)"); - } - else - { - mysqli_query($dbconn, "UPDATE user SET blocked = 1 WHERE nick = '".mysql_escape_string($_POST["user"])."'"); - error("Der Benuzer wurde deaktiviert, er muss vom Hauptbenutzer wieder aktiviert werden, damit er sich anmelden kann."); - } - } - - } - } - else + // Eingaben unverändert verwenden: Passwörter werden beim Anlegen roh + // gehasht, ein htmlspecialchars() hier ließ Passwörter mit & < > " ' + // nie passen. Ausgabe-Escaping übernimmt das Template. + $nick = isset($_POST["user"]) && is_string($_POST["user"]) ? $_POST["user"] : ""; + $pass = isset($_POST["pass"]) && is_string($_POST["pass"]) ? $_POST["pass"] : ""; + + $udata = db_zeile($dbconn, "SELECT * FROM user WHERE nick = ? AND blocked = 0", array($nick)); + if($udata === false) + error("Es ist ein Fehler in der abfrage der Benutzerdaten:
".formaterror(mysqli_error($dbconn))); + elseif($udata === null) error("Dieser Benutzername existiert nicht."); + elseif(hash_equals($udata["pass"], crypt($pass, $udata["salt"]))) + { + // neue Session-ID nach dem Login, sonst ist Session-Fixation möglich + session_regenerate_id(true); + $_SESSION["login"] = "valid"; + $_SESSION["logedout"] = $udata["logedout"]; + $_SESSION["user"] = $udata["id"]; + $_SESSION["nick"] = $udata["nick"]; + $_SESSION["vname"] = $udata["vname"]; + $_SESSION["nname"] = $udata["nname"]; + $_SESSION["rights"] = $udata["rights"]; + $_SESSION["lastlogin"] = $udata["lastlogin"]; + $_SESSION["numlogins"] = $udata["numlogins"]; + $_SESSION["style"] = getUserSetting($dbconn,"style", $udata["id"]); + if(!is_file("styles/".$_SESSION["style"])) + { + updateUserSetting($dbconn,"style", "default.css", $udata["id"]); + $_SESSION["style"] = getUserSetting($dbconn,"style", $udata["id"]); + } + $_SESSION["zoom"] = getUserSetting($dbconn,"zoom", $_SESSION["user"], 1); + $_SESSION["privateNotes"] = getUserSetting($dbconn,"privnotes", $_SESSION["user"], ""); + + db_abfrage($dbconn, "UPDATE user SET wrongpw = 0, lastlogin = NOW(), numlogins = numlogins + 1, logedout = 0 WHERE id = ?", array($udata["id"])); + } + elseif($udata["wrongpw"] < 10) + { + db_abfrage($dbconn, "UPDATE user SET wrongpw = wrongpw + 1 WHERE id = ?", array($udata["id"])); + $triesleft = 10 - $udata["wrongpw"]; + if($triesleft > 1) + error("Der Benuzername oder das Passwort sind falsch, bitte versuchen Sie es erneut (Nach weiteren $triesleft Versuchen wird der Benutzer deaktiviert und muss vom Hauptbenutzer wieder aktiviert werden, damit er sich anmelden kann.)"); + else + error("Der Benuzername oder das Passwort sind falsch, bitte versuchen Sie es erneut (Wenn der nächste Versuch falsch ist, wird der Benutzer für deaktiviert und muss vom Hauptbenutzer wieder aktiviert werden, damit er sich anmelden kann.)"); + } + else + { + db_abfrage($dbconn, "UPDATE user SET blocked = 1 WHERE id = ?", array($udata["id"])); + error("Der Benuzer wurde deaktiviert, er muss vom Hauptbenutzer wieder aktiviert werden, damit er sich anmelden kann."); + } } if($_SESSION["login"]=="valid"){ header('Location:?action=kunden'); diff --git a/sites/userkeys.php b/sites/userkeys.php index fe0a60f..9b71497 100644 --- a/sites/userkeys.php +++ b/sites/userkeys.php @@ -7,7 +7,7 @@ if(isset($_GET["id"]) && isset($_GET["addKey"])){ // in dieser Session (geprüft in authServer.php). Der addKey ist damit // verbraucht. $_SESSION["passkey_freigabe"] = array("user" => (int)$_GET["id"], "bis" => time() + 300); - mysqli_query($mysql, "DELETE FROM addKey WHERE accesskey = '".mysqli_real_escape_string($mysql, $_GET["addKey"])."'"); + db_abfrage($mysql, "DELETE FROM addKey WHERE accesskey = ?", array((int)$_GET["addKey"])); echo << @@ -78,15 +78,14 @@ if(isset($_GET["id"]) && isset($_GET["addKey"])){ $smarty->assign("heading","Passkeys für externen Zugang verwalten"); $smarty->display("heading.tpl"); -$qry = mysqli_query($_SESSION["dbconn"],"SELECT id, vname, nname FROM user WHERE blocked = 0 AND user.id = ".(int)$_GET["id"]) or die("Datenbank fehler: ".mysqli_error($_SESSION["dbconn"])); -$row=mysqli_fetch_array($qry); +$row = db_zeile($dbconn, "SELECT id, vname, nname FROM user WHERE blocked = 0 AND user.id = ?", array($_GET["id"])); +if($row === false) + die("Datenbank fehler: ".mysqli_error($dbconn)); $smarty->assign("user",$row); -$qry = mysqli_query($_SESSION["dbconn"],"SELECT passkeys.id , passkeys.name FROM user JOIN passkeys ON user.id = passkeys.userId WHERE blocked = 0 AND user.id = ".(int)$_GET["id"]) or die("Datenbank fehler: ".mysqli_error($_SESSION["dbconn"])); -$keys = array(); -while($row=mysqli_fetch_array($qry)){ - $keys[] = $row; -} +$keys = db_zeilen($dbconn, "SELECT passkeys.id, passkeys.name FROM user JOIN passkeys ON user.id = passkeys.userId WHERE blocked = 0 AND user.id = ?", array($_GET["id"])); +if($keys === false) + die("Datenbank fehler: ".mysqli_error($dbconn)); $smarty->assign("keys",$keys); $smarty->assign("local",isLocal()); $smarty->display("userkeys.tpl"); diff --git a/templates/userkeys.tpl b/templates/userkeys.tpl index 01ed6c4..6566e00 100644 --- a/templates/userkeys.tpl +++ b/templates/userkeys.tpl @@ -10,7 +10,7 @@ {foreach $keys as $key} - + {/foreach}
NameAktion
{$key.name}{$key.name|escape}