xaizek / rocketgit (License: AGPLv3+) (since 2018-12-09)
Light and fast Git hosting solution suitable to serve both as a hub or as a personal code storage with its tickets, pull requests, API and much more.
<root> / inc / keys.inc.php (4ceaf374c7ec46b8f9dd854aa0ff3f7f40638589) (7,998B) (mode 100644) [raw]
<?php
require_once($INC . "/sql.inc.php");
require_once($INC . "/state.inc.php");
require_once($INC . "/prof.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;
}

/*
 * 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
 */
function rg_keys_remove($db, $rg_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 = " . $rg_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);

		if (!isset($flags['no_dirty'])) {
			if (rg_keys_mark_dirty($db) !== TRUE)
				break;
		}

		$ret = TRUE;
	} while (0);

	rg_prof_end("keys_remove");
	return $ret;
}

/*
 * Remove multiple keys from database
 */
function rg_keys_remove_multi($db, $rg_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, $rg_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.
 */
function rg_keys_add($db, $rg_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, $rg_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, " . $rg_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);
		$id = $row['key_id'];
		rg_sql_free_result($res);

		// set dirty
		if (rg_keys_mark_dirty($db) != TRUE)
			break;

		$ret = $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);
	} 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 ($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, $rg_ui)
{
	rg_prof_start("keys_list");
	rg_log("keys_list: rg_uid[uid]=" . $rg_ui['uid']);

	$ret = FALSE;
	do {
		$sql = "SELECT * FROM keys WHERE uid = " . $rg_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;
}

?>
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/rocketgit

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/rocketgit

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master