<?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");
rg_prof_start("remote.php");
rg_log_set_file($rg_log_dir . "/remote.log");
function info($str)
{
global $access_type;
rg_log("Sending error: " . $str);
$str2 = "RocketGit: " . $str . "\n";
if ($access_type == 2) { // git
$str3 = "\n" . $str2;
$len = strlen($str3) + 4;
$str4 = sprintf("%04x", $len) . $str3;
fwrite(STDERR, $str4);
} else { // ssh
fwrite(STDERR, $str2);
}
}
function fatal($str)
{
info("FATAL ERROR: $str");
exit(1);
}
umask(0022);
rg_log("Start...");
rg_log("_SERVER: " . rg_array2string($_SERVER));
info("Welcome to RocketGit " . $more['rg_version'] . "!");
$db = rg_sql_open($rg_sql);
if ($db === FALSE)
fatal("Internal error (db)!");
$r = rg_sql_struct_update($db, 0);
if ($r !== TRUE)
fatal("We are in a short maintenance. Try again later.");
if (isset($_SERVER['SSH_CONNECTION'])) {
rg_log("SSH connection: " . $_SERVER['SSH_CONNECTION']);
$access_type = 1;
// we do not have host info
$host = "";
// first parameter must be uid of the user
$uid = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : 0;
if ($uid == 0)
fatal("uid not provided!");
rg_log("\tuid is $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, $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...");
$access_type = 2;
// we have no client info
$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");
}
// extract command and compute permissions
if (strncmp($cmd_repo, "git-upload-pack", 15) == 0) {
$cmd = "git-upload-pack";
$needed_rights = "F";
$push = 0;
} else if (strncmp($cmd_repo, "git-receive-pack", 16) == 0) {
$cmd = "git-receive-pack";
$needed_rights = "";
$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].");
// validity/security checks
if (rg_user_ok($user) !== TRUE)
fatal("User [$user] is invalid (" . rg_user_error() . ")!");
if (rg_repo_ok($repo) !== TRUE)
fatal("Repo [$repo] is invalid (" . rg_repo_error() . ")");
// Loading info about the repository
$rr = array("prefix" => $prefix, "user" => $user, "repo" => $repo);
$ri = rg_repo_info($db, $rr);
if ($ri['ok'] != 1)
fatal("Temporary error!");
if ($ri['exists'] != 1)
fatal("Repo does not exists!");
if ($ri['deleted'] == 1)
fatal("Repo was deleted!");
// We must not use here the rg_repo_allow function because we need
// $rights variable below.
$ret = rg_repo_rights_get($db, $ri, $uid, 0);
if ($ret['ok'] !== 1)
fatal("Internal error (rights_get)");
$rights = $ret['rights'];
if (rg_rights_allow($rights, $needed_rights) === FALSE)
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?
$repo_base = rg_repo_name2base($rr);
$repo_path = $repo_base . $repo . ".git";
rg_log("repo_path=$repo_path.");
if (($push == 1) && rg_repo_over_limit($ri))
fatal("Cannot push: repo is over limit"
. " (" . $ri['disk_used_mb']. "MiB >= "
. $ri['disk_quota_mb'] . "MiB)");
// Put in environment all we need
putenv("ROCKETGIT_UID=" . $uid);
putenv("ROCKETGIT_KEY_ID=" . $key_id);
putenv("ROCKETGIT_REPO_ID=" . $ri['repo_id']);
putenv("ROCKETGIT_REPO_RIGHTS=" . $rights);
putenv("ROCKETGIT_REPO_PATH=" . $repo_path);
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:
// "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]...");
passthru($run, $ret);
rg_log("[$run] returned $ret.");
info("Done. Thank you!");
rg_prof_end("remote.php");
rg_prof_log("rg_log");
?>
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