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 / ssh.inc.php (e5e09a04b6d55c35db60ccbc28352e94cfde0e05) (9,049B) (mode 100644) [raw]
<?php
//
// Description: This file deals with commands accepted by ssh
//
require_once($INC . '/sql.inc.php');
require_once($INC . '/state.inc.php');
require_once($INC . '/prof.inc.php');
require_once($INC . '/repo.inc.php');
require_once($INC . '/totp.inc.php');

function rg_ssh_status($db, $uid)
{
	rg_log('ssh_status');

	echo 'Here will be the status.' . "\n";

	// also details about payments: warn if disk space is low etc.
}

/*
 * List repos
 */
function rg_ssh_repos($db, $uid)
{
	rg_log('ssh_repos');

	$params = array('uid' => $uid);
	$sql = 'SELECT * FROM repos'
		. ' WHERE uid = @@uid@@'
		. ' AND deleted = 0'
		. ' ORDER BY name, itime';
	$pad = '                                              ';
	$res = rg_sql_query_params($db, $sql, $params);
	$rows = rg_sql_num_rows($res);
	if ($rows > 0) {
		echo 'Repositories (name, creation, disk used):' . "\n";
		while (($row = rg_sql_fetch_array($res))) {
			$_name = mb_substr($row['name'], 0, 40, 'UTF-8');
			echo mb_substr($_name . $pad, 0, 32, 'UTF-8')
				. ' ' . gmdate('Y-m-d', $row['itime'])
				. ' ' . rg_1024($row['disk_used_mb'] * 1024 * 1024)
				. "\n";
		}
	} else {
		echo 'You have no repository.' . "\n";
	}
	rg_sql_free_result($res);
}

/*
 * Info about a repo
 */
function rg_ssh_repo($db, $uid, $paras)
{
	rg_log('ssh_repo: ' . rg_array2string($paras));

	if (empty($paras)) {
		echo 'Please specify the repo name.' . "\n";
		exit(0);
	}

	$repo_name = trim($paras[0]);

	$ri = rg_repo_info($db, 0, $uid, $repo_name);
	if ($ri['exists'] != 1) {
		echo 'Error: unknown repo.' . "\n";
		exit(0);
	}

	echo 'Repo:		' . $ri['name'] . "\n";
	echo 'Repo type:	' . ($ri['public'] == 1 ? 'public' : 'private') . "\n";
	echo 'Creation time:	' .
		gmdate('Y-m-d', $ri['itime']) . ' UTC' . "\n";
	echo 'Disk used:	' .
		rg_1024($ri['disk_used_mb'] * 1024 * 1024) . "\n";

	if ($ri['master'] > 0) {
		$mri = rg_repo_info($db, $ri['master'], 0, '');
		if ($mri !== FALSE) {
			echo 'Master:		' . $mri['name'] . "\n";
		} else {
			echo 'Master:		' . 'Error getting info' . "\n";
		}
	}

	echo 'Description:' . "\n";
	$_d = explode("\n", $ri['description']);
	foreach ($_d as $_line)
		echo '  ' . $_line . "\n";

	$ls = rg_repo_lock_status($db, $ri['repo_id']);
	if ($ls['ok'] == 1) {
		if ($ls['status'] == 0) {
			echo 'Repository is not locked.' . "\n";
		} else {
			$_ui = rg_user_info($db, $ls['uid'], '', '');
			if ($_ui['exists'] == 1)
				$_u = $_ui['username'];
			else
				$_u = '?';

			$reason = '';
			$_r = explode("\n", $ls['reason']);
			foreach ($_r as $_line)
				$reason .= '  ' . $_line . "\n";

			echo 'Repository has been locked by user ' . $_u
				. ' at '
				. gmdate('Y-m-d H:i', $ls['itime']) . ' UTC.'
				. ' Reason:' . "\n"
				. $reason . "\n";
		}
	} else {
		echo 'Error: cannot get info about the lock status!' . "\n";
	}
}

/*
 * Helper for totp_verify_ip - mostly to not duplicate error messages
 */
function rg_ssh_totp_verify_ip($db, $uid, $ip)
{
	$ret = FALSE;
	while (1) {
		$r = rg_totp_verify_ip($db, $uid, $ip);
		if (($r['ok'] == 0) || (empty($r['ip_list']))) {
			echo 'Error: ' . rg_totp_error() . ".\n";
			break;
		}
		if ($r['enrolled'] == 0) {
			echo 'Info: You are not enrolled.' . "\n";
			break;
		}

		$ret = $r['ip_list'];
		break;
	}

	return $ret;
}

/*
 * Deal with TOTP stuff
 */
function rg_ssh_totp($db, $ip, $uid, $paras)
{
	rg_prof_start('ssh_totp');
	rg_log_enter('ssh_totp ip=' . $ip . ' uid=' . $uid
		. ' paras=' . rg_array2string($paras));

	$cmd = array_shift($paras);
	switch ($cmd) {
	case 'enroll': // this has nothing to do with scratch codes
		if (empty($paras)) {
			$secret = rg_totp_base32_generate(16);

			$r = rg_totp_enroll($db, $uid, 'SSH', $secret, $ip, 'f');
			if ($r !== TRUE) {
				echo 'Error: ' . rg_totp_error() . ".\n";
				break;
			}

			echo 'Scan the following code with the mobile application:' . "\n";
			echo rg_totp_text($secret) . "\n";
			echo 'or manually enter the following code: ' . $secret . ".\n";
			echo 'To finish the enrollment you will have to confirm';
			echo ' it by running the following command:';
			echo ' ssh ... totp enroll <6_digits_code_generated_by_device>' . "\n";
			break;
		}

		$token = array_shift($paras);

		$v = rg_totp_device_verify($db, $uid, $token);
		if ($v['token_valid'] != 1) {
			echo 'Error: ' . rg_totp_error() . ".\n";
			break;
		}

		echo 'Success!' . "\n";
		break;

	case 'val':
		$token = array_shift($paras);

		$days = 0;
		$hours = 0;
		$minutes = 0;
		if (empty($paras)) {
			$hours = 1;
		} else {
			$s_expire = array_shift($paras);
			$val = intval($s_expire);
			if (stristr($s_expire, 'w'))
				$days = 7 * $val;
			else if (stristr($s_expire, 'd'))
				$days = $val;
			else if (stristr($s_expire, 'h'))
				$hours = $val;
			else
				$minutes = $val;
		}
		//rg_log("token=$token days=$days hours=$hours minutes=$minutes");

		$v = rg_totp_verify_any($db, $uid, $token);
		if ($v['token_valid'] != 1) {
			echo 'Error: ' . rg_totp_error() . ".\n";
			break;
		}

		$expire_ts = gmmktime(gmdate('H') + $hours,
			gmdate('i') + $minutes, gmdate('s'), gmdate('m'),
			gmdate('d') + $days, gmdate('Y'));
		$r = rg_totp_add_ip($db, $uid, $v['id'], $ip, $expire_ts);
		if ($r === FALSE) {
			echo 'Error: ' . rg_totp_error() . ".\n";
			break;
		}

		echo 'Success! IP ' . $ip . ' added and is valid till '
			. gmdate('Y-m-d H:i:s', $expire_ts) . ' (UTC).' . "\n";
		break;

	case 'list-val':
		$r = rg_ssh_totp_verify_ip($db, $uid, $ip);
		if ($r === FALSE)
			break;

		echo 'Insert date (UTC)     Expire date (UTC)     IP' . "\n";
		foreach ($r as $t) {
			echo gmdate('Y-m-d H:i:s', $t['itime'])
				. '   ' . gmdate('Y-m-d H:i:s', $t['expire'])
				. '   ' . $t['ip']
				. "\n";
		}
		echo 'Done!' . "\n";
		break;

	case 'unenroll':
		$token = array_shift($paras);

		$v = rg_totp_verify_any($db, $uid, $token);
		if ($v['token_valid'] != 1) {
			echo 'Error: ' . rg_totp_error() . ".\n";
			break;
		}

		$r = rg_totp_unenroll($db, $uid);
		if ($r !== TRUE) {
			echo'Error: ' . rg_totp_error() . ".\n";
			break;
		}

		echo 'You are now unenrolled.' . "\n";
		break;

	case 'remove-device':
		$token = array_shift($paras);

		$v = rg_totp_device_verify($db, $uid, $token);
		if ($v['token_valid'] != 1) {
			echo 'Error: ' . rg_totp_error() . ".\n";
			break;
		}

		$a = array($v['id'] => '');
		$r = rg_totp_remove($db, $uid, $a);
		if ($r !== TRUE) {
			echo 'Error: ' . rg_totp_error() . ".\n";
			break;
		}

		echo 'Success!' . "\n";
		break;

	case 'inval':
		if (empty($paras)) {
			echo 'Error: Please specify the IP address or \'all\'.' . "\n";
			break;
		}

		$del_ip = array_shift($paras);

		if (rg_ssh_totp_verify_ip($db, $uid, $ip) === FALSE)
			break;

		$r = rg_totp_del_ip($db, $uid, $del_ip);
		if ($r['found'] != 1) {
			echo 'Error: ' . rg_totp_error() . ".\n";
			break;
		}

		echo 'Success!' . "\n";
		break;

	default:
		echo 'Posible TOTP commands:' . "\n";
		echo ' enroll <token> - adds a new device in the system' . "\n";
		echo ' val [X(w|d|h|m|s)] <token> - adds your IP to the allow list for X time' . "\n";
		echo '   the default is 1 hour; X is a number; defauls is \'minutes\';' . "\n";
		echo '   w=weeks, d=days, h=hours, m=minutes, and s for seconds' . "\n";
		echo ' list-val - lists the already validated IPs' . "\n";
		echo ' inval ip|all - invalidates IP address(es)' . "\n";
		echo ' remove-device <token> - removes a device from TOTP system' . "\n";
		echo ' unenroll <token> - removes all devices and scratch codes from TOTP system' . "\n";
		echo "\n";
		echo 'Notes:' . "\n";
		echo ' - <token> means a code generated by mobile device or a scratch code' . "\n";
		break;
	}

	rg_log_exit();
	rg_prof_end('ssh_totp');
}

/*
 * Returns TRUE if we need to stop execution
 */
function rg_ssh_dispatch($db, $ip, $uid, $orig_cmd)
{
	rg_log('ssh_dispatch orig_cmd=[' . $orig_cmd . ']');

	$paras = explode(' ', $orig_cmd);
	$cmd = array_shift($paras);

	// some commands are not executed here
	switch ($cmd) {
	case 'git-upload-pack'; return;
	case 'git-receive-pack': return;
	}

	// First, test if the IP is validated
	switch ($cmd) {
	case '': break;
	case 'totp': break; // totp will verify the ip only for some commands
	default:
		$r = rg_totp_verify_ip($db, $uid, $ip);
		if (($r['ok'] == 0)
			|| (($r['enrolled'] == 1) && (empty($r['ip_list'])))) {
			echo 'Error: ' . rg_totp_error() . ".\n";
			return TRUE; // = we must exit'
		}
		break;
	}

	// Now, we can safely execute the command
	switch ($cmd) {
		case 'status': rg_ssh_status($db, $uid); return TRUE;
		case 'repos':	rg_ssh_repos($db, $uid); return TRUE;
		case 'repo':	rg_ssh_repo($db, $uid, $paras); return TRUE;
		case 'totp':	rg_ssh_totp($db, $ip, $uid, $paras); return TRUE;
		case '':
			echo "Available commmands:\n"
				. " status - show some status about the user\n"
				. " repos  - list repos and information about them\n"
				. " repo   - list info about a repo\n"
				. " totp   - two-factor authentication commands\n";
			return TRUE;
	}

	return FALSE; // = continue execution
}

?>
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