<?php require_once($INC . "/sql.inc.php"); require_once($INC . "/state.inc.php"); require_once($INC . "/prof.inc.php"); require_once($INC . "/events.inc.php"); $rg_keys_error = ""; function rg_keys_set_error($str) { global $rg_keys_error; $rg_keys_error = $str; } function rg_keys_error() { global $rg_keys_error; return $rg_keys_error; } /* * Events functions */ $rg_keys_functions = array( 1000 => "rg_keys_event_new", 1001 => "rg_keys_event_del", 1002 => "rg_keys_event_mark_dirty", 1003 => "rg_keys_event_notify_user" ); rg_event_register_functions($rg_keys_functions); /* * Event for adding a new key */ function rg_keys_event_new($db, $event) { $ret = array(); $event['op'] = "new"; // mark keys dirty $ret[] = array_merge($event, array("category" => 1002, "prio" => 10)); // notify user $ret[] = array_merge($event, array("category" => 1003, "prio" => 100)); return $ret; } /* * Event for deleting a key */ function rg_keys_event_del($db, $event) { $ret = array(); $event['type'] = 1; $event['op'] = "del"; // mark keys dirty $ret[] = array_merge($event, array("category" => 1002, "prio" => 10)); // notify user $ret[] = array_merge($event, array("category" => 1003, "prio" => 100)); return $ret; } /* * Notify user that a new key was added/deleted to/from the keyring * TODO: Do not mark keys dirty from another place in this file */ function rg_keys_event_mark_dirty($db, $event) { $r = rg_keys_mark_dirty($db); if ($r === FALSE) return FALSE; return array(); } /* * Notify user that a new key was added to the keyring */ function rg_keys_event_notify_user($db, $event) { rg_prof_start("keys_event_notify_user"); rg_log("keys_event_notify_user: event=" . rg_array2string($event)); // TODO: load info about the keys and put the fingerprint in e-mail // TODO: Maybe put also the body in the e-mail, so it can be re-uploaded. // TODO: Maybe add also the statistics. $r = rg_mail("mail/user/key/" . $event['op'], $event); if ($r === FALSE) return FALSE; rg_prof_end("keys_event_notify_user"); return array(); } /* * Extracts info about a ssh key */ function rg_keys_info($key) { rg_prof_start("keys_info"); $ret = array(); $ret['ok'] = 0; do { if (strpos($key, "PRIVATE KEY") !== FALSE) { rg_keys_set_error("private instead of pulic key"); break; } $t = explode(" ", $key, 3); if (!isset($t[1])) { rg_keys_set_error("malformed ssh key (missing fields)"); break; } $ret['type'] = $t[0]; $ret['key'] = isset($t[1]) ? $t[1] : ""; $ret['comment'] = isset($t[2]) ? $t[2] : ""; $d = base64_decode($ret['key']); if ($d === FALSE) { rg_keys_set_error("malformed input (base64 failed)"); break; } $digest = md5($d); $a = array(); for ($i = 0; $i < 16; $i++) $a[] = substr($digest, $i * 2, 2); $ret['fingerprint'] = implode(":", $a); $ret['ok'] = 1; } while (0); rg_prof_end("keys_info"); return $ret; } /* * Mark state as dirty */ function rg_keys_mark_dirty($db) { rg_prof_start("keys_mark_dirty"); $ret = TRUE; if (rg_state_set($db, "authorized_keys", 1) === FALSE) { rg_keys_set_error("cannot make state dirty (" . rg_state_error() . ")!"); $ret = FALSE; } rg_prof_end("keys_mark_dirty"); return $ret; } /* * Remove a key from database for user 'ui' * TODO: Remove "multi" function and make this accepts an array of keys. * TODO: Here we must have a transaction! */ function rg_keys_remove($db, $ui, $key_id, $flags) { rg_prof_start("keys_remove"); rg_log("keys_remove: key_id=$key_id flags=" . rg_array2string($flags)); $ret = FALSE; do { // Do not move this to caller. // TODO: Do rg_var_array that will enforce index to be numeric? $e_key_id = sprintf("%u", $key_id); $sql = "DELETE FROM keys" . " WHERE uid = " . $ui['uid'] . " AND key_id = $e_key_id"; $res = rg_sql_query($db, $sql); if ($res === FALSE) { rg_keys_set_error("cannot delete key $key_id (" . rg_sql_error() . ")"); break; } rg_sql_free_result($res); $event = array("category" => 1001, "prio" => 50, "email" => $ui['email'], "IP" => rg_var_str("REMOTE_ADDR"), "key_id" => $key_id); $r = rg_event_add($db, $event); if ($r !== TRUE) { rg_keys_set_error("cannot add event" . " (" . rg_event_error() . ")"); break; } $ret = TRUE; } while (0); rg_prof_end("keys_remove"); return $ret; } /* * Remove multiple keys from database */ function rg_keys_remove_multi($db, $ui, $list) { rg_prof_start("keys_remove_multi"); rg_log("keys_remove_multi: list=" . rg_array2string($list)); $ret = FALSE; do { if (empty($list)) { rg_keys_set_error("no keys provided"); break; } $flags = array("no_dirty" => 1); foreach ($list as $key_id => $junk) { $r = rg_keys_remove($db, $ui, $key_id, $flags); if ($r !== TRUE) break; } if (rg_keys_mark_dirty($db) != TRUE) break; $ret = TRUE; } while (0); rg_prof_end("keys_remove_multi"); return $ret; } /* * Count the number of keys per user */ function rg_keys_count($db, $uid) { rg_prof_start("keys_count"); $ret = FALSE; do { $sql = "SELECT COUNT(*) AS count FROM keys WHERE uid = $uid"; $res = rg_sql_query($db, $sql); if ($res === FALSE) { rg_keys_set_error("cannot query (" . rg_sql_error() . ")"); break; } $row = rg_sql_fetch_array($res); rg_sql_free_result($res); $ret = $row['count']; } while (0); rg_prof_end("keys_count"); return $ret; } /* * Add a key * Returns the key_id of the key. * TODO: Transaction! */ function rg_keys_add($db, $ui, $key) { global $rg_max_ssh_keys; rg_prof_start("keys_add"); rg_log("keys_add: $key=$key"); $ret = FALSE; do { $itime = time(); $e_key = rg_sql_escape($db, $key); $ki = rg_keys_info($key); if ($ki['ok'] != 1) break; // check if we are over the maximum // the config after update may not have this defined. if ($rg_max_ssh_keys == 0) $rg_max_ssh_keys = 10; $no_of_keys = rg_keys_count($db, $ui['uid']); if ($no_of_keys === FALSE) break; if ($no_of_keys >= $rg_max_ssh_keys) { rg_keys_set_error("too many keys; please delete some"); break; } $sql = "INSERT INTO keys (itime, uid, key)" . " VALUES ($itime, " . $ui['uid'] . ", '$e_key')" . " RETURNING key_id"; $res = rg_sql_query($db, $sql); if ($res === FALSE) { rg_keys_set_error("cannot insert key (" . rg_sql_error() . ")"); break; } $row = rg_sql_fetch_array($res); $key_id = $row['key_id']; rg_sql_free_result($res); $event = array("category" => 1000, "prio" => 50, "email" => $ui['email'], "IP" => rg_var_str("REMOTE_ADDR"), "key_id" => $key_id); $r = rg_event_add($db, $event); if ($r !== TRUE) { rg_keys_set_error("cannot add event" . " (" . rg_event_error() . ")"); break; } $ret = $key_id; } while (0); rg_prof_end("keys_add"); return $ret; } /* * Update last_use and last_ip */ function rg_keys_update_use($db, $key_id, $ip) { rg_prof_start("keys_update_use"); rg_log("keys_update_use: key_id=$key_id, $ip=$ip"); $ret = FALSE; do { $now = time(); $e_ip = rg_sql_escape($db, $ip); $sql = "UPDATE keys SET last_use = $now, last_ip = '$e_ip'" . " WHERE key_id = $key_id"; $res = rg_sql_query($db, $sql); if ($res === FALSE) { rg_keys_set_error("cannot update key (" . rg_sql_error() . ")"); break; } rg_sql_free_result($res); $ret = TRUE; } while (0); rg_prof_end("keys_update_use"); return $ret; } /* * Regenerates authorized_keys files */ function rg_keys_regen($db) { global $rg_keys_file; global $rg_scripts; global $rg_ssh_paras; rg_prof_start("keys_regen"); $ret = FALSE; do { $dirty = rg_state_get($db, "authorized_keys"); if (!file_exists($rg_keys_file)) { rg_log("Keys file does not exists, force dirty 1."); $dirty = 1; } if ($dirty == 0) { // Skip generation because is not dirty $ret = TRUE; break; } // create .ssh folder if does not exists $dir = dirname($rg_keys_file); if (!file_exists($dir)) { if (!@mkdir($dir, 0700, TRUE)) { rg_keys_set_error("cannot create dir $dir ($php_errormsg)"); break; } } $tmp = $rg_keys_file . ".tmp"; $f = @fopen($tmp, "w"); if ($f === FALSE) { rg_keys_set_error("cannot open file $tmp ($php_errormsg)"); break; } if (chmod($tmp, 0600) === FALSE) { rg_keys_set_error("cannot chmod tmp file ($php_errmsg)"); fclose($f); break; } // mark file as clean rg_state_set($db, "authorized_keys", 0); $sql = "SELECT key_id, uid, key FROM keys"; $res = rg_sql_query($db, $sql); if ($res === FALSE) { rg_keys_set_error("cannot query (" . rg_sql_error() . ")"); break; } while (($row = rg_sql_fetch_array($res))) { rg_log("Writing key [" . $row['key'] . "] for uid " . $row['uid']); $buf = "command=\"/usr/bin/php " . $rg_scripts . "/scripts/remote.php" . " " . $row['uid'] . " " . $row['key_id'] . "\"" . "," . $rg_ssh_paras . " " . $row['key'] . "\n"; if (@fwrite($f, $buf) === FALSE) { rg_keys_set_error("cannot write. Disk space problems? ($php_errormsg)"); fclose($f); unlink($tmp); rg_sql_free_result($res); break; } } rg_sql_free_result($res); fclose($f); if (@rename($tmp, $rg_keys_file) === FALSE) { rg_keys_set_error("cannot rename $tmp to $rg_keys_file ($php_errormsg)"); unlink($tmp); break; } $ret = TRUE; } while (0); rg_prof_end("keys_regen"); return $ret; } /* * List keys */ function rg_keys_list($db, $ui) { rg_prof_start("keys_list"); rg_log("keys_list: uid=" . $ui['uid']); $ret = FALSE; do { $sql = "SELECT * FROM keys WHERE uid = " . $ui['uid'] . " ORDER BY itime DESC"; $res = rg_sql_query($db, $sql); if ($res === FALSE) { rg_keys_set_error("Cannot query (" . rg_sql_error() . ")"); break; } $ret = array(); while (($row = rg_sql_fetch_array($res))) { $ki = rg_keys_info($row['key']); if ($ki['ok'] != 1) { rg_internal_error("Invalid key in db!"); continue; } $t = $ki; $t['key_id'] = $row['key_id']; $t['itime'] = gmdate("Y-m-d H:i", $row['itime']); if (empty($row['last_ip'])) $t['last_ip'] = "-"; else $t['last_ip'] = $row['last_ip']; if ($row['last_use'] == 0) $t['last_use'] = "-"; else $t['last_use'] = gmdate("Y-m-d H:i", $row['last_use']); $ret[] = $t; } rg_sql_free_result($res); } while (0); rg_prof_end("keys_list"); return $ret; } ?>