<?php // This is called by a remote client that does push or fetch 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 . "/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 . "/ver.php"); rg_prof_start("remote.php"); rg_log_set_file($rg_log_dir . "/remote.log"); function info($str) { rg_log("Sending: " . $str); $str2 = "RocketGit: " . $str . "\n"; if (isset($_SERVER['SSH_CONNECTION'])) { // ssh fwrite(STDERR, $str2); } else { // git - seems is not working as ssh $str2 = "\n" . $str2; $len = 4 + strlen($str2); echo sprintf("%04x", $len) . $str2; } } function fatal($str) { info("FATAL ERROR: $str"); exit(1); } umask(0022); rg_log("Start ($rocketgit_version)..."); rg_log("_SERVER: " . rg_array2string($_SERVER)); // DEBUG SELinux $label = @file_get_contents("/proc/self/attr/current"); rg_log("SELINUX: " . $label); rg_sql_app("rg-remote"); $db = rg_sql_open($rg_sql); if ($db === FALSE) fatal("Internal error (db)!"); if (rg_sql_struct_update_needed($db) !== 0) fatal("We are in a short maintenance window. Try again later."); if (rg_fixes_needed($db) !== 0) fatal("We are in a short maintenance window. Try again later."); if (isset($_SERVER['SSH_CONNECTION'])) { rg_log("SSH connection: " . $_SERVER['SSH_CONNECTION']); // 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; if ($login_uid == 0) fatal("uid not provided!"); rg_log("\tuid is $login_uid."); // second parameter must be the ssh key id $key_id = isset($_SERVER['argv'][2]) ? $_SERVER['argv'][2] : 0; if ($key_id == 0) fatal("key_id not provided!"); rg_log("\tkey_id is $key_id."); if (!isset($_SERVER['SSH_ORIGINAL_COMMAND'])) $cmd_repo = ""; else $cmd_repo = trim($_SERVER['SSH_ORIGINAL_COMMAND']); rg_ssh_dispatch($db, $login_uid, $cmd_repo); $ssh_client = getenv("SSH_CLIENT"); $_t = explode(" ", $ssh_client); $ip = $_t[0]; // TODO: This should be put in a queue for performance reasons $_r = rg_keys_update_use($db, $key_id, $ip); if ($_r !== TRUE) rg_internal_error("Cannot update key last_use!"); } else { rg_log("git-daemon connection..."); // we have no client info $login_uid = 0; $key_id = 0; $f = @fopen("php://stdin", "r"); if ($f === FALSE) fatal("\tCannot open stdin!"); $line = @fread($f, 8000); if ($line === FALSE) fatal("\tCannot read!"); fclose($f); $line_len = strlen($line); rg_log("line=[$line]"); if ($line_len < 4) fatal("\tLine is too short!"); $len = @hexdec(substr($line, 0, 4)); if ($line_len < $len) fatal("Too less data ($line_len/$len) received!"); // parse something like: 002bgit-upload-pack /aa.git[0x00]host=localhost $line = substr($line, 4); $v = explode("\0", $line); $cmd_repo = trim($v[0]); $host = isset($v[1]) ? trim(substr($v[1], 5)) : ""; $ip = getenv("REMOTE_HOST"); } // Extracts command and computes permissions if (strncasecmp($cmd_repo, "git-upload-pack", 15) == 0) { $cmd = "git-upload-pack"; $needed_rights = "F"; $push = 0; } else if (strncasecmp($cmd_repo, "git-receive-pack", 16) == 0) { $cmd = "git-receive-pack"; // We need push or anonymous push $needed_rights = "P|H"; $push = 1; } else { fatal("Unknown command [$cmd_repo]!"); } // extract repository name $_t = substr($cmd_repo, strlen($cmd)); $_t = trim($_t, "' "); $_t = ltrim($_t, "/"); $_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("host=[$host] cmd=[$cmd] prefix=[$prefix] user=[$user] repo=[$repo]."); // TODO: if $host does not match $rg_git_host, give a warning to the user to // update the config. // validity/security checks // Load info about the owner if (rg_user_ok($user) !== TRUE) fatal("User [$user] is invalid (" . rg_user_error() . ")!"); $owner_ui = rg_user_info($db, 0, $user, ""); if ($owner_ui['ok'] != 1) fatal("Internal problems. Try again later, please."); if ($owner_ui['exists'] != 1) fatal("User does not exists (repo)."); // Load info about the connecting user, if known if ($login_uid > 0) { $conn_ui = rg_user_info($db, $login_uid, "", ""); if ($conn_ui['exists'] != 1) fatal("User does not exists (conn)."); } else { $conn_ui = array('uid' => 0, 'username' => ''); } // Loading info about the repository if (rg_repo_ok($repo) !== TRUE) fatal("Repo is invalid (" . rg_repo_error() . ")"); $ri = rg_repo_info($db, 0, $owner_ui['uid'], $repo); if ($ri['ok'] != 1) fatal("Internal problems. Try again later, please."); if ($ri['exists'] != 1) fatal("Repo does not exists."); if ($ri['deleted'] == 1) fatal("Repo was deleted!"); $repo_path = rg_repo_path_by_id($owner_ui['uid'], $ri['repo_id']); rg_log("repo_path=$repo_path."); // TODO: signal user that the repo moved and provide a hint how to follow $x = array(); $x['obj_id'] = $ri['repo_id']; $x['type'] = 'repo_refs'; $x['owner'] = $ri['uid']; $x['uid'] = $conn_ui['uid']; $x['username'] = $conn_ui['username']; $x['needed_rights'] = $needed_rights; $x['ip'] = $ip; $x['misc'] = ''; $ret = rg_rights_allow($db, $x); if ($ret !== TRUE) fatal("You have no rights to access this repo!"); // TODO: limit per connection // TODO: limit time and/or cpu // TODO: limit cpuset // TODO: limit io // TODO: put process in a cgroup? if (($push == 1) && rg_user_over_limit($db, $owner_ui, $max)) fatal("Cannot push: user is over limit" . " (" . $owner_ui['disk_used_mb']. "MiB >= " . $max . "MiB)"); // Put in environment all we need putenv("ROCKETGIT_LOGIN_UID=" . $login_uid); putenv("ROCKETGIT_LOGIN_USERNAME=" . $conn_ui['username']); putenv("ROCKETGIT_KEY_ID=" . $key_id); putenv("ROCKETGIT_REPO_ID=" . $ri['repo_id']); putenv("ROCKETGIT_REPO_PATH=" . $repo_path); putenv("ROCKETGIT_REPO_UID=" . $ri['uid']); putenv("ROCKETGIT_IP=$ip"); putenv("ROCKETGIT_ITIME=" . microtime(TRUE)); if ($push == 1) { $namespace = "rg_" . rg_id(8); rg_log("namespace is $namespace."); putenv("GIT_NAMESPACE=" . $namespace); // Prepare refs to avoid the following message: // "No refs in common and none specified; doing nothing. // Perhaps you should specify a branch such as 'master'." $dst = $repo_path . "/refs/namespaces/" . $namespace . "/refs/heads"; $ret = @mkdir($dst, 0755, TRUE); if ($ret === FALSE) fatal("Internal error (namespace dir)"); // copy refs $r = rg_copy_tree($repo_path . "/refs/heads", $dst . "/", 0755); if ($r !== TRUE) fatal("Internal error (cannot copy refs)"); } $run = "git-shell -c \"" . $cmd . " " . escapeshellarg($repo_path) . "\""; rg_log("Running [$run]..."); rg_prof_start("git-shell"); passthru($run, $ret); rg_prof_end("git-shell"); rg_log("[$run] returned $ret."); rg_prof_end("remote.php"); rg_prof_log(); ?>