<?php
//
// Description: This file deals with commands accepted by ssh
//
require_once(__DIR__ . '/sql.inc.php');
require_once(__DIR__ . '/state.inc.php');
require_once(__DIR__ . '/prof.inc.php');
require_once(__DIR__ . '/repo.inc.php');
require_once(__DIR__ . '/totp.inc.php');
require_once(__DIR__ . '/api.inc.php');
require_once(__DIR__ . '/stats.inc.php');
$rg_ssh_error = '';
function rg_ssh_set_error($str)
{
global $rg_ssh_error;
$rg_ssh_error = $str;
rg_log('ssh_set_error: ' . $str);
}
function rg_ssh_error()
{
global $rg_ssh_error;
return $rg_ssh_error;
}
function rg_ssh_status($db)
{
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)
{
rg_log('ssh_repos');
$ui_login = rg_ui_login();
$params = array('uid' => $ui_login['uid']);
$sql = 'SELECT * FROM repos'
. ' WHERE uid = @@uid@@'
. ' AND deleted = 0'
. ' ORDER BY name, itime';
$res = rg_sql_query_params($db, $sql, $params);
$rows = rg_sql_num_rows($res);
if ($rows > 0) {
echo 'Repositories (creation (UTC), disk used, name):' . "\n";
while (($row = rg_sql_fetch_array($res))) {
$d = rg_1024($row['disk_used_mb'] * 1024 * 1024);
echo gmdate('Y-m-d', $row['itime'])
. ' ' . str_pad($d, 15)
. ' ' . $row['name']
. "\n";
}
} else {
echo 'You have no repository.' . "\n";
}
rg_sql_free_result($res);
}
/*
* Info about a repo
*/
function rg_ssh_repo($db, $paras)
{
rg_log('ssh_repo: ' . rg_array2string($paras));
if (empty($paras)) {
fwrite(STDERR, 'Please specify the repo name.' . "\n");
exit(0);
}
$ui_login = rg_ui_login();
$repo_name = trim($paras[0]);
$ri = rg_repo_info($db, 0, $ui_login['uid'], $repo_name);
if ($ri['exists'] != 1) {
fwrite(STDERR, '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 {
fwrite(STDERR, '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'] !== 1) {
fwrite(STDERR, 'Error: ' . rg_totp_error() . ".\n");
break;
}
if ($r['enrolled'] == 0) {
echo 'Info: You are not enrolled.' . "\n";
break;
}
if (empty($r['ip_list'])) {
fwrite(STDERR, 'Error: ' . rg_totp_error() . ".\n");
break;
}
$ret = $r['ip_list'];
break;
}
return $ret;
}
/*
* Deal with TOTP stuff
*/
function rg_ssh_totp($db, $paras)
{
global $rg_ssh_host;
rg_prof_start('ssh_totp');
rg_log_enter('ssh_totp paras=' . rg_array2string($paras));
$ui_login = rg_ui_login();
$cmd = empty($paras) ? '' : 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, $ui_login['uid'], 'SSH',
$secret, rg_ip(), 'f');
if ($r !== TRUE) {
fwrite(STDERR, '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 rocketgit@' . $rg_ssh_host
. ' totp enroll <6_digits_code_generated_by_device>' . "\n";
break;
}
$token = empty($paras) ? '' : array_shift($paras);
$v = rg_totp_device_verify($db, $ui_login['uid'], $token);
if ($v['token_valid'] != 1) {
fwrite(STDERR, 'Error: ' . rg_totp_error() . ".\n");
break;
}
echo 'Success!' . "\n";
break;
case 'val':
$token = empty($paras) ? '' : array_shift($paras);
$days = 0;
$hours = 0;
$minutes = 0;
if (empty($paras)) {
$hours = 1;
} else {
$s_expire = empty($paras) ? '' : array_shift($paras);
$val = intval($s_expire);
if (stristr($s_expire, 'y'))
$days = 366 * $val;
else 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, $ui_login['uid'], $token);
if ($v['token_valid'] != 1) {
fwrite(STDERR, '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, $ui_login['uid'], $v['id'],
rg_ip(), $expire_ts);
if ($r === FALSE) {
fwrite(STDERR, 'Error: ' . rg_totp_error() . ".\n");
break;
}
echo 'Success! IP ' . rg_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, $ui_login['uid'], rg_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 = empty($paras) ? '' : array_shift($paras);
$v = rg_totp_verify_any($db, $ui_login['uid'], $token);
if ($v['token_valid'] != 1) {
fwrite(STDERR, 'Error: ' . rg_totp_error() . ".\n");
break;
}
$r = rg_totp_unenroll($db, $ui_login['uid']);
if ($r !== TRUE) {
fwrite(STDERR, 'Error: ' . rg_totp_error() . ".\n");
break;
}
echo 'You are now unenrolled.' . "\n";
break;
case 'remove-device':
$token = empty($paras) ? '' : array_shift($paras);
$v = rg_totp_device_verify($db, $ui_login['uid'], $token);
if ($v['token_valid'] != 1) {
fwrite(STDERR, 'Error: ' . rg_totp_error() . ".\n");
break;
}
$a = array($v['id'] => '');
$r = rg_totp_remove($db, $ui_login['uid'], $a);
if ($r !== TRUE) {
fwrite(STDERR, 'Error: ' . rg_totp_error() . ".\n");
break;
}
echo 'Success!' . "\n";
break;
case 'inval':
if (empty($paras)) {
fwrite(STDERR, 'Error: Please specify the IP address'
. ' or \'all\'.' . "\n");
break;
}
$del_ip = empty($paras) ? '' : array_shift($paras);
if (rg_ssh_totp_verify_ip($db, $ui_login['uid'], rg_ip()) === FALSE)
break;
$r = rg_totp_del_ip($db, $ui_login['uid'], $del_ip);
if ($r['found'] != 1) {
fwrite(STDERR, 'Error: ' . rg_totp_error() . ".\n");
break;
}
echo 'Success!' . "\n";
break;
default:
fwrite(STDERR, "\n"
. 'Posible TOTP commands:' . "\n"
. ' enroll <token> - adds a new device in the system' . "\n"
. ' val <token> [X(y|w|d|h|m|s)] - adds your IP to the allow list for X time' . "\n"
. ' the default is 1 hour; X is a number (defauls is \'minutes\')' . "\n"
. ' y=years, w=weeks, d=days, h=hours, m=minutes, and s=seconds' . "\n"
. ' list-val - lists the already validated IPs' . "\n"
. ' inval ip|all - invalidates IP address(es)' . "\n"
. ' remove-device <token> - removes a device from TOTP system' . "\n"
. ' unenroll <token> - removes all devices and scratch codes from TOTP system' . "\n"
. "\n"
. 'Notes:' . "\n"
. ' - <token> means a code generated by mobile device or a scratch code' . "\n"
);
break;
}
rg_log_exit();
rg_prof_end('ssh_totp');
}
/*
* Deal with API
*/
function rg_ssh_api($db, $paras)
{
rg_prof_start('ssh_api');
rg_log_enter('ssh_api paras=' . rg_array2string($paras));
$cmd = empty($paras) ? '' : array_shift($paras);
rg_stats_conns_set('type', 'api-over-ssh');
$ret = array();
while (1) {
if (empty($cmd)) {
$ret['error'] = 'missing API command';
break;
}
$a = array();
foreach ($paras as $t) {
$v = explode('=', $t, 2);
if (count($v) != 2) {
$ret['error'] = 'invalid para (no =)';
break;
}
$k = trim($v[0]);
$a[$k] = trim($v[1]);
}
if (isset($ret['error']))
break;
$a['cmd'] = $cmd;
$x = array();
$x['ip'] = rg_ip();
$ret = array_merge($ret, rg_api($db, $x, $a));
break;
}
rg_log_debug('ret: ' . print_r($ret, TRUE));
rg_log_exit();
rg_prof_end('ssh_api');
echo json_encode($ret, JSON_PRETTY_PRINT) . "\n";
}
/*
* Returns TRUE if we need to stop execution
*/
function rg_ssh_dispatch($db, $orig_cmd)
{
rg_log('ssh_dispatch orig_cmd=[' . $orig_cmd . ']');
$paras = explode(' ', $orig_cmd);
$cmd = empty($paras) ? '' : 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:
$ui_login = rg_ui_login();
$r = rg_totp_verify_ip($db, $ui_login['uid'], rg_ip());
if (($r['ok'] !== 1)
|| ($r['enrolled'] && empty($r['ip_list']))) {
fwrite(STDERR, '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); return TRUE;
case 'repos': rg_ssh_repos($db); return TRUE;
case 'repo': rg_ssh_repo($db, $paras); return TRUE;
case 'totp': rg_ssh_totp($db, $paras); return TRUE;
case 'api': rg_ssh_api($db, $paras); return TRUE;
case '':
fwrite(STDERR, "\n"
. '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"
. ' api - API calls' . "\n");
return TRUE;
}
return FALSE; // = continue execution
}
/*
* Loads data for graphs
* @unit - interval on which a sum is made
*/
function rg_ssh_data($db, $type, $start, $end, $unit, $mode)
{
$params = array('start' => $start, 'end' => $end);
switch ($type) {
case 'create_key':
$q = 'SELECT itime FROM keys'
. ' WHERE itime >= @@start@@ AND itime <= @@end@@';
break;
default:
rg_internal_error('invalid data');
return FALSE;
}
$ret = rg_graph_query($db, $start, $end, $unit, $mode, $q, $params, '');
if ($ret === FALSE)
rg_ssh_set_error(rg_graph_error());
return $ret;
}