diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/html/account.php | 3 | ||||
-rw-r--r-- | web/lib/acctfuncs.inc.php | 153 |
2 files changed, 136 insertions, 20 deletions
diff --git a/web/html/account.php b/web/html/account.php index 0bb145c..c447de3 100644 --- a/web/html/account.php +++ b/web/html/account.php @@ -16,6 +16,7 @@ $need_userinfo = array( if (in_array($action, $need_userinfo)) { $row = account_details(in_request("ID"), in_request("U")); + $PK = implode("\n", account_get_ssh_keys($row["ID"])); } if ($action == "AccountInfo") { @@ -59,7 +60,7 @@ if (isset($_COOKIE["AURSID"])) { display_account_form("UpdateAccount", $row["Username"], $row["AccountTypeID"], $row["Suspended"], $row["Email"], "", "", $row["RealName"], $row["LangPreference"], - $row["IRCNick"], $row["PGPKey"], $row["SSHPubKey"], + $row["IRCNick"], $row["PGPKey"], $PK, $row["InactivityTS"] ? 1 : 0, $row["ID"]); } else { print __("You do not have permission to edit this account."); diff --git a/web/lib/acctfuncs.inc.php b/web/lib/acctfuncs.inc.php index 6b7d227..417ee6d 100644 --- a/web/lib/acctfuncs.inc.php +++ b/web/lib/acctfuncs.inc.php @@ -53,7 +53,7 @@ function html_format_pgp_fingerprint($fingerprint) { * @param string $L The language preference of the displayed user * @param string $I The IRC nickname of the displayed user * @param string $K The PGP key fingerprint of the displayed user - * @param string $PK The SSH public key of the displayed user + * @param string $PK The list of SSH public keys * @param string $J The inactivity status of the displayed user * @param string $UID The user ID of the displayed user * @@ -83,7 +83,7 @@ function display_account_form($A,$U="",$T="",$S="",$E="",$P="",$C="",$R="", * @param string $L The language preference of the user * @param string $I The IRC nickname of the user * @param string $K The PGP fingerprint of the user - * @param string $PK The SSH public key of the user + * @param string $PK The list of public SSH keys * @param string $J The inactivity status of the user * @param string $UID The user ID of the modified account * @@ -149,12 +149,32 @@ function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="",$P="",$C="", } if (!$error && !empty($PK)) { - if (valid_ssh_pubkey($PK)) { - $tokens = explode(" ", $PK); - $PK = $tokens[0] . " " . $tokens[1]; - } else { - $error = __("The SSH public key is invalid."); + $ssh_keys = array_filter(array_map('trim', explode("\n", $PK))); + $ssh_fingerprints = array(); + + foreach ($ssh_keys as &$ssh_key) { + if (!valid_ssh_pubkey($ssh_key)) { + $error = __("The SSH public key is invalid."); + break; + } + + $ssh_fingerprint = ssh_key_fingerprint($ssh_key); + if (!$ssh_fingerprint) { + $error = __("The SSH public key is invalid."); + break; + } + + $tokens = explode(" ", $ssh_key); + $ssh_key = $tokens[0] . " " . $tokens[1]; + + $ssh_fingerprints[] = $ssh_fingerprint; } + + /* + * Destroy last reference to prevent accidentally overwriting + * an array element. + */ + unset($ssh_key); } if (isset($_COOKIE['AURSID'])) { @@ -203,22 +223,24 @@ function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="",$P="",$C="", "<strong>", htmlspecialchars($E,ENT_QUOTES), "</strong>"); } } - if (!$error && !empty($PK)) { + if (!$error && count($ssh_keys) > 0) { /* - * Check whether the SSH public key is available. + * Check whether any of the SSH public keys is already in use. * TODO: Fix race condition. */ - $q = "SELECT COUNT(*) FROM Users "; - $q.= "WHERE SSHPubKey = " . $dbh->quote($PK); + $q = "SELECT Fingerprint FROM SSHPubKeys "; + $q.= "WHERE Fingerprint IN ("; + $q.= implode(',', array_map(array($dbh, 'quote'), $ssh_fingerprints)); + $q.= ")"; if ($TYPE == "edit") { - $q.= " AND ID != " . intval($UID); + $q.= " AND UserID != " . intval($UID); } $result = $dbh->query($q); $row = $result->fetch(PDO::FETCH_NUM); - if ($row[0]) { + if ($row) { $error = __("The SSH public key, %s%s%s, is already in use.", - "<strong>", htmlspecialchars($PK, ENT_QUOTES), "</strong>"); + "<strong>", htmlspecialchars($row[0], ENT_QUOTES), "</strong>"); } } @@ -247,13 +269,11 @@ function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="",$P="",$C="", $L = $dbh->quote($L); $I = $dbh->quote($I); $K = $dbh->quote(str_replace(" ", "", $K)); - $PK = empty($PK) ? "NULL" : $dbh->quote($PK); $q = "INSERT INTO Users (AccountTypeID, Suspended, "; $q.= "InactivityTS, Username, Email, Passwd, Salt, "; - $q.= "RealName, LangPreference, IRCNick, PGPKey, "; - $q.= "SSHPubKey) "; + $q.= "RealName, LangPreference, IRCNick, PGPKey) "; $q.= "VALUES (1, 0, 0, $U, $E, $P, $salt, $R, $L, "; - $q.= "$I, $K, $PK)"; + $q.= "$I, $K)"; $result = $dbh->exec($q); if (!$result) { print __("Error trying to create account, %s%s%s.", @@ -261,6 +281,9 @@ function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="",$P="",$C="", return; } + $uid = $dbh->lastInsertId(); + account_set_ssh_keys($uid, $ssh_keys, $ssh_fingerprints); + print __("The account, %s%s%s, has been successfully created.", "<strong>", htmlspecialchars($U,ENT_QUOTES), "</strong>"); print "<p>\n"; @@ -321,10 +344,12 @@ function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="",$P="",$C="", $q.= ", LangPreference = " . $dbh->quote($L); $q.= ", IRCNick = " . $dbh->quote($I); $q.= ", PGPKey = " . $dbh->quote(str_replace(" ", "", $K)); - $q.= ", SSHPubKey = " . $dbh->quote($PK); $q.= ", InactivityTS = " . $inactivity_ts; $q.= " WHERE ID = ".intval($UID); $result = $dbh->exec($q); + + account_set_ssh_keys($UID, $ssh_keys, $ssh_fingerprints); + if (!$result) { print __("No changes were made to the account, %s%s%s.", "<strong>", htmlspecialchars($U,ENT_QUOTES), "</strong>"); @@ -1194,3 +1219,93 @@ function can_edit_account($acctinfo) { $uid = $acctinfo['ID']; return has_credential(CRED_ACCOUNT_EDIT, array($uid)); } + +/* + * Compute the fingerprint of an SSH key. + * + * @param string $ssh_key The SSH public key to retrieve the fingerprint for + * + * @return string The SSH key fingerprint + */ +function ssh_key_fingerprint($ssh_key) { + $tmpfile = tempnam(sys_get_temp_dir(), "aurweb"); + file_put_contents($tmpfile, $ssh_key); + + /* + * The -l option of ssh-keygen can be used to show the fingerprint of + * the specified public key file. Expected output format: + * + * 2048 SHA256:uBBTXmCNjI2CnLfkuz9sG8F+e9/T4C+qQQwLZWIODBY user@host (RSA) + * + * ... where 2048 is the key length, the second token is the actual + * fingerprint, followed by the key comment and the key type. + */ + + $cmd = "/usr/bin/ssh-keygen -l -f " . escapeshellarg($tmpfile); + exec($cmd, $out, $ret); + if ($ret !== 0 || count($out) !== 1) { + return false; + } + + unlink($tmpfile); + + $tokens = explode(' ', $out[0]); + if (count($tokens) != 4) { + return false; + } + + $tokens = explode(':', $tokens[1]); + if (count($tokens) != 2 || $tokens[0] != 'SHA256') { + return false; + } + + return $tokens[1]; +} + +/* + * Get the SSH public keys associated with an account. + * + * @param int $uid The user ID of the account to retrieve the keys for. + * + * @return array An array representing the keys + */ +function account_get_ssh_keys($uid) { + $dbh = DB::connect(); + $q = "SELECT PubKey FROM SSHPubKeys WHERE UserID = " . intval($uid); + $result = $dbh->query($q); + + if ($result) { + return $result->fetchAll(PDO::FETCH_COLUMN, 0); + } else { + return array(); + } +} + +/* + * Set the SSH public keys associated with an account. + * + * @param int $uid The user ID of the account to assign the keys to. + * @param array $ssh_keys The SSH public keys. + * @param array $ssh_fingerprints The corresponding SSH key fingerprints. + * + * @return bool Boolean flag indicating success or failure. + */ +function account_set_ssh_keys($uid, $ssh_keys, $ssh_fingerprints) { + $dbh = DB::connect(); + + $q = sprintf("DELETE FROM SSHPubKeys WHERE UserID = %d", $uid); + $dbh->exec($q); + + $ssh_fingerprint = reset($ssh_fingerprints); + foreach ($ssh_keys as $ssh_key) { + $q = sprintf( + "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) " . + "VALUES (%d, %s, %s)", $uid, + $dbh->quote($ssh_fingerprint), $dbh->quote($ssh_key) + ); + $dbh->exec($q); + $ssh_fingerprint = next($ssh_fingerprints); + } + + return true; +} |