<?php // It is called by a remote client that does a push/fetch by git/ssh. error_reporting(E_ALL); ini_set("track_errors", "On"); require_once("/etc/rocketgit/config.php"); $INC = dirname(__FILE__) . "/../inc"; require_once($INC . "/init.inc.php"); require_once($INC . "/util.inc.php"); require_once($INC . "/log.inc.php"); require_once($INC . "/sql.inc.php"); require_once($INC . "/struct.inc.php"); require_once($INC . "/user.inc.php"); require_once($INC . "/repo.inc.php"); require_once($INC . "/prof.inc.php"); require_once($INC . "/ssh.inc.php"); require_once($INC . "/keys.inc.php"); require_once($INC . "/fixes.inc.php"); require_once($INC . "/plan.inc.php"); require_once($INC . '/stats.inc.php'); require_once($INC . "/ver.php"); rg_prof_start("remote.php"); rg_log_set_file($rg_log_dir . "/remote.log"); $rg = array(); $rg['start'] = microtime(TRUE); function info($str) { rg_log("Sending: " . $str); $str2 = "RocketGit: Info: " . $str . "\n"; if (isset($_SERVER['SSH_CONNECTION'])) { // ssh fwrite(STDERR, $str2); } else { // Keep in mind we did not negotiated yet the side-band feat. // So, seems we cannot send nice messages to the user. } } function fatal($str) { rg_log("Sending: " . $str); $str2 = "RocketGit: Error: " . $str . "\n"; if (isset($_SERVER['SSH_CONNECTION'])) { // ssh fwrite(STDERR, $str2); } else { // See above for 'info' function // But, keep in mind that at least git archive --remote // expects a 4 byte len! //echo "\n" . $str2; echo rg_git_pack('ERR ' . $str2); } exit(1); } @umask(0022); // Without next lines the STDOUT/STDERR are mixed @stream_set_write_buffer(STDOUT, 0); @stream_set_write_buffer(STDERR, 0); rg_log("Start ($rocketgit_version)..."); // DEBUG SELinux $label = @file_get_contents("/proc/self/attr/current"); if (!empty($label)) rg_log('SELINUX: ' . $label); rg_sql_app('rg-remote-' . $rg_log_sid); $db = rg_sql_open($rg_sql); if ($db === FALSE) fatal("Internal error (db)!"); // Force ste state loading, for sure we will need it rg_cache_get('state'); if (rg_struct_ok($db) === FALSE) fatal("We are in a short maintenance window. Try again later."); $rg['hostname'] = rg_state_get($db, 'hostname'); $rg['http_allow'] = rg_state_get($db, 'http_allow'); $rg['https_allow'] = rg_state_get($db, 'https_allow'); rg_base_url_build($rg['hostname'], $rg['http_allow'], $rg['https_allow']); $rg['base_url'] = rg_base_url(); rg_log('DEBUG: base_url=' . rg_base_url()); $rg['debug'] = rg_state_get($db, 'debug'); rg_stats_conns_set('start', $rg['start']); $login_ui = array('uid' => 0, 'username' => 'anonymous user', 'organization' => 0); if (isset($_SERVER['SSH_CONNECTION'])) { rg_log("SSH connection: " . $_SERVER['SSH_CONNECTION']); rg_stats_conns_set('type', 'ssh'); // we do not have host info $host = ''; // first parameter must be uid of the user $login_uid = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : 0; rg_log("uid is $login_uid."); // second parameter must be the ssh key id $key_id = isset($_SERVER['argv'][2]) ? $_SERVER['argv'][2] : 0; // TODO: because of build system, 0 may be valid. //if ($key_id == 0) // fatal("key_id not provided!"); rg_log("key_id is $key_id."); // Third para is 'flags' $flags = isset($_SERVER['argv'][3]) ? $_SERVER['argv'][3] : ''; rg_log('flags=' . $flags); if (!isset($_SERVER['SSH_ORIGINAL_COMMAND'])) $cmd_repo = ""; else $cmd_repo = trim($_SERVER['SSH_ORIGINAL_COMMAND']); $ssh_client = getenv("SSH_CLIENT"); $_t = explode(" ", $ssh_client); $rg['ip'] = rg_fix_ip($_t[0]); rg_stats_conns_set('ip', $rg['ip']); info('== Welcome to RocketGit! =='); info('you are connecting from IP ' . $rg['ip'] . ' by ssh.'); info('date/time: ' . gmdate('Y-m-d H:i:s') . ', debug id ' . $rg_log_sid . '.'); $must_exit = FALSE; if (strstr($flags, 'N')) { $login_ui = rg_user_info($db, $login_uid, '', ''); if ($login_ui['exists'] != 1) fatal("User does not exists (conn)."); info('you are connecting as user \'' . $login_ui['username'] . '\'.' . "\n"); putenv('ROCKETGIT_SHOW_INFO=0'); // We assume that the login user is the target user and no repo rg_stats_conns_set('uid', $login_ui['uid']); rg_stats_conns_set('repo_id', 0); rg_stats_conns_set('type', 'ssh'); // Only normal keys can execute some commands $must_exit = rg_ssh_dispatch($db, $rg['ip'], $login_ui, $cmd_repo); // Only normal keys can update stats // We do this operation after dispatch to not impact the latency. // TODO: This should be put in a queue for performance reasons // At the same time, update stats? events? $_r = rg_keys_update_use($db, $login_ui['uid'], $key_id, $rg['ip'], $cmd_repo); if ($_r !== TRUE) rg_internal_error("Cannot update key last_use!"); if ($must_exit) { rg_stats_conns_insert($db); rg_prof_end('remote.php'); rg_prof_log(); exit(0); } } rg_stats_conns_set('type', 'git-over-ssh'); } else { rg_log("git-daemon connection..."); rg_log_ml('_SERVER: ' . print_r($_SERVER, true)); rg_stats_conns_set('type', 'git'); // we have no client info $login_uid = 0; $key_id = 0; $flags = ''; $line = ''; $line_len = 0; $len = 0; while (1) { $r = @fread(STDIN, 8000); if ($r === FALSE) fatal('Error in receive: ' . rg_php_err()); if (empty($r)) fatal("Too less data ($line_len/$len) received!"); $line .= $r; $line_len += strlen($r); rg_log("line=[$line]"); if ($line_len < 4) fatal('Line is too short [' . $line . ']!'); $len = @hexdec(substr($line, 0, 4)); rg_log('Decoded len=' . $len . ' line_len=' . $line_len); if ($line_len >= $len) break; } // parse something like: 002bgit-upload-pack /aa.git[0x00]host=localhost:9418[0x00] $line = substr($line, 4); // skip length $v = explode("\0", $line); $cmd_repo = trim($v[0]); $host_port = isset($v[1]) ? trim(substr($v[1], 5)) : ''; $v = explode(':', $host_port); $host = $v[0]; $rg['ip'] = rg_fix_ip(getenv("REMOTE_HOST")); rg_stats_conns_set('ip', $rg['ip']); } if (strncasecmp($cmd_repo, 'git-upload-pack', 15) == 0) { $rg['cmd'] = 'git-upload-pack'; } else if (strncasecmp($cmd_repo, 'git-receive-pack', 16) == 0) { $rg['cmd'] = 'git-receive-pack'; } else { $rg['cmd'] = $cmd_repo; } rg_stats_conns_set('cmd', $rg['cmd']); // extract repository name $rg['url'] = substr($cmd_repo, strlen($rg['cmd'])); // skip cmd $rg['url'] = trim($rg['url'], "' "); rg_stats_conns_set('url', $rg['url']); $_t = ltrim($rg['url'], "/"); $_t = preg_replace('/\.git$/' , '', $_t); $_t = explode("/", $_t); if (strcmp($_t[0], "user") == 0) { $prefix = "/user"; $user = isset($_t[1]) ? $_t[1] : ""; $repo = isset($_t[2]) ? $_t[2] : ""; } else { $prefix = ""; $user = isset($_t[0]) ? $_t[0] : ""; $repo = isset($_t[1]) ? $_t[1] : ""; } rg_log('ip=[' . $rg['ip'] . '] host=[' . $host . '] cmd=[' . $rg['cmd'] . ']' . ' prefix=[' . $prefix . '] user=[' . $user . ']' . ' repo=[' . $repo . '].'); if (strstr($flags, 'W')) { // We are a worker, the command may be only for cloning! if (strcmp($rg['cmd'], 'git-upload-pack') != 0) fatal('A worker can only clone!'); } $helper_flags = ''; // bypass authentication because it is a global worker if (($login_uid == 0) && strstr($flags, 'W')) $helper_flags .= 'B'; $r = rg_repo_fetch_push_helper($db, $host, $rg['ip'], $login_ui, $prefix, $user, $repo, $rg['cmd'], $helper_flags); rg_log_ml('DEBUG: repo_fetch_push_helper: ' . print_r($r, TRUE)); if (($r['ok'] !== 1) || ($r['allow'] !== 1)) fatal($r['errmsg']); $run = "git-shell -c \"" . $rg['cmd'] . " " . escapeshellarg($r['repo_path']) . "\""; //$run = $rg['cmd'] . ' ' . escapeshellarg($r['repo_path']); rg_log("Running [$run]..."); rg_prof_start($rg['cmd']); // TODO: shouldn't we use rg_exec to capture stderr? passthru($run, $ret); rg_prof_end($rg['cmd']); rg_log("[$run] returned $ret."); if (!strstr($flags, 'W')) { $repo_id = 0; $uid = 0; $_ui = rg_user_info($db, 0, $user, ''); if ($_ui['exists'] == 1) $uid = $_ui['uid']; if ($uid > 0) { rg_stats_conns_set('uid', $uid); $_ri = rg_repo_info($db, 0, $uid, $repo); if ($_ri['exists'] == 1) $repo_id = $_ri['repo_id']; rg_stats_conns_set('repo_id', $repo_id); } rg_stats_conns_insert($db); } rg_prof_end('remote.php'); rg_prof_log();