modules/gitolite/include/git.php
68db2f2f
 <?php
c208bd90
 /*
 This file belongs to the Webinterface of schokokeks.org Hosting
 
cf54502a
 Written 2008-2018 by schokokeks.org Hosting, namely
c208bd90
   Bernd Wurst <bernd@schokokeks.org>
   Hanno Böck <hanno@schokokeks.org>
 
 To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
 
2626dd47
 You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see
c208bd90
 http://creativecommons.org/publicdomain/zero/1.0/
 
 Nevertheless, in case you use a significant part of this code, we ask (but not require, see the license) that you keep the authors' names in place and return your changes to the public. We would be especially happy if you tell us what you're going to do with this code.
 */
 
68db2f2f
 require_role(ROLE_SYSTEMUSER);
 
1eafec53
 $data_dir = realpath(dirname(__FILE__).'/../../../../gitolite-data/');
68db2f2f
 $config_file = $data_dir.'/gitolite-admin/conf/webinterface.conf';
 $config_dir = $data_dir.'/gitolite-admin/conf/webinterface';
 $key_dir = $data_dir.'/gitolite-admin/keydir';
 DEBUG("gitolite-data_dir: ".$data_dir);
1eafec53
 $git_wrapper = realpath(dirname(__FILE__).'/../scripts/git-wrapper.sh');
68db2f2f
 
 
637cd934
 
2626dd47
 function check_env()
68db2f2f
 {
2626dd47
     global $git_wrapper, $data_dir, $config_file, $config_dir, $key_dir;
     if (!is_executable($git_wrapper)) {
         system_failure("git_wrapper.sh is not executable: {$git_wrapper}");
     }
     if (! (is_file($data_dir.'/sshkey') && is_file($data_dir.'/sshkey.pub'))) {
         system_failure("SSH-key not found. Please setup the gitolite-module correctly. Run ./data/initialize.sh");
     }
     if (! is_dir($data_dir.'/gitolite-admin')) {
         system_failure("Repository gitolite-admin ot found. Initial checkout must be made manually. Run ./data/initialize.sh");
     }
     if (! is_dir($config_dir)) {
         system_failure("gitolite-admin repository is not prepared.");
     }
     if (! (is_dir($key_dir) && is_writeable($config_file))) {
         system_failure("Repository gitolite-admin is corrupted or webinterface.conf is not writeable.");
     }
68db2f2f
 }
 
 
2626dd47
 function validate_name($name)
 {
     return (preg_match('/^[[:alnum:]][[:alnum:]._-]*$/', $name));
97b6f432
 }
 
2626dd47
 function get_git_url($repo)
 {
     $remote = git_wrapper('remote --verbose');
     DEBUG('gitolite-admin repo: '.$remote[0]);
     $url = preg_replace('#^.*\s+(\S+):gitolite-admin.*#', '$1', $remote[0]);
     DEBUG('URL: '.$url);
     return $url.':'.$repo;
5c30f40b
 }
 
97b6f432
 
68db2f2f
 function git_wrapper($commandline)
 {
2626dd47
     global $git_wrapper, $data_dir;
 
     $command = $git_wrapper.' '.$commandline;
     $output = array();
     $retval = 0;
     DEBUG($command);
     exec($command, $output, $retval);
     DEBUG($output);
     DEBUG($retval);
     if ($retval > 0) {
         system_failure('Interner Fehler!');
         // FIXME: Hier sollte auf jeden Fall ein Logging angeworfen werden!
     }
     return $output;
68db2f2f
 }
 
2626dd47
 function refresh_gitolite()
68db2f2f
 {
2626dd47
     check_env();
     git_wrapper('pull');
68db2f2f
 }
 
 
 
2626dd47
 function list_repos()
68db2f2f
 {
2626dd47
     global $config_file, $config_dir;
     $username = $_SESSION['userinfo']['username'];
     $userconfig = $config_dir . '/' . $username . '.conf';
     DEBUG("using config file ".$userconfig);
     if (! is_file($userconfig)) {
         DEBUG("user-config does not exist");
         return array();
     }
 
     $repos = array();
     $lines = file($userconfig);
     $current_repo = null;
     $current_repo_users = array();
     foreach ($lines as $line) {
         DEBUG("LINE: ".$line);
         $m = array();
         if (preg_match('/^(\S+) "[^"]+" = "([^"]+)"$/', $line, $m) != 0) {
             if (!array_key_exists($m[1], $repos)) {
                 $repos[$m[1]] = array('users' => null, 'description' => '');
             }
             DEBUG("found description: {$m[1]} = \"{$m[2]}\"");
             $repos[$m[1]]['description'] = $m[2];
         } elseif (preg_match('_^\s*repo (\S+)\s*$_', $line, $m) != 0) {
             if (!array_key_exists($m[1], $repos)) {
                 $repos[$m[1]] = array('users' => null, 'description' => '');
             }
             if ($current_repo) {
                 $repos[$current_repo]['users'] = $current_repo_users;
             }
             DEBUG("found repo ".$m[1]);
             $current_repo = chop($m[1]);
             $current_repo_users = array();
         } elseif (preg_match('/^\s*(R|RW|RW\+)\s*=\s*([[:alnum:]][[:alnum:]._-]*)\s*$/', $line, $m) != 0) {
             DEBUG("found access rule: ".$m[1]." for ".$m[2]);
             $current_repo_users[chop($m[2])] = chop($m[1]);
         }
     }
     if ($current_repo) {
ff794853
         $repos[$current_repo]['users'] = $current_repo_users;
2626dd47
     }
     ksort($repos);
     DEBUG($repos);
     return $repos;
68db2f2f
 }
 
 
2626dd47
 function list_users()
 {
     global $config_file, $config_dir;
     $username = $_SESSION['userinfo']['username'];
     $userconfig = $config_dir . '/' . $username . '.conf';
     DEBUG("using config file ".$userconfig);
     if (! is_file($userconfig)) {
         DEBUG("user-config does not exist");
         return array();
     }
9086c9ad
 
2626dd47
     $lines = file($userconfig);
     $users = array();
     foreach ($lines as $line) {
         $m = array();
         if (preg_match('_# user ([^]]+)_', $line, $m) != 0) {
             $users[] = chop($m[1]);
         }
         if (preg_match('_^\s*repo .*_', $line) != 0) {
             break;
         }
     }
     sort($users);
     DEBUG($users);
     return $users;
97b6f432
 }
68db2f2f
 
2626dd47
 function list_foreign_users()
 {
     global $config_file, $config_dir;
     $username = $_SESSION['userinfo']['username'];
     $userconfig = $config_dir . '/' . $username . '.conf';
     DEBUG("using config file ".$userconfig);
     if (! is_file($userconfig)) {
         DEBUG("user-config does not exist");
         return array();
     }
9086c9ad
 
2626dd47
     $lines = file($userconfig);
     $users = array();
     foreach ($lines as $line) {
         $m = array();
         if (preg_match('_# foreign user ([^]]+)_', $line, $m) != 0) {
             $users[] = chop($m[1]);
         }
         if (preg_match('_^\s*repo .*_', $line) != 0) {
             break;
         }
     }
     sort($users);
     DEBUG($users);
     return $users;
f7030eb6
 }
 
2626dd47
 function get_pubkey($handle)
 {
     global $key_dir;
     if (! validate_name($handle)) {
         return '';
     }
     $keyfile = $key_dir.'/'.$handle.'.pub';
     if (! file_exists($keyfile)) {
         return '';
     }
     return file_get_contents($keyfile);
97b6f432
 }
 
 
 
2626dd47
 function new_foreign_user($handle)
f7030eb6
 {
2626dd47
     global $key_dir, $config_dir;
     $username = $_SESSION['userinfo']['username'];
 
     if (! validate_name($handle)) {
         system_failure("Der eingegebene Name enthält ungültige Zeichen. Bitte nur Buchstaben, Zahlen, Unterstrich, Binderstrich und Punkt benutzen.");
     }
 
     if (in_array($handle, list_users())) {
         system_failure('Dieser GIT-Benutzer gehört zu diesem Kundenaccount.');
     }
 
     $keyfile = $key_dir.'/'.$handle.'.pub';
     if (! file_exists($keyfile)) {
         system_failure('Diesen GIT-Benutzer gibt es nicht');
     }
 
     $userconfig = $config_dir . '/' . $username . '.conf';
     DEBUG("using config file ".$userconfig);
     if (! is_file($userconfig)) {
         DEBUG("user-config does not exist, creating new one");
         file_put_contents($userconfig, '# user '.$handle."\n");
         set_user_include();
     } elseif (in_array($handle, list_foreign_users())) {
         # user ist schon eingetragen
     } else {
         $content = file_get_contents($userconfig);
         file_put_contents($userconfig, "# foreign user {$handle}\n".$content);
     }
     git_wrapper('add '.$userconfig);
 
     git_wrapper('commit --allow-empty -m "added new key for '.$handle.'"');
     git_wrapper('push');
f7030eb6
 }
 
 function delete_foreign_user($handle)
 {
2626dd47
     global $config_dir;
     $username = $_SESSION['userinfo']['username'];
 
     $userconfig = $config_dir . '/' . $username . '.conf';
     DEBUG("using config file ".$userconfig);
     if (! is_file($userconfig)) {
         DEBUG("user-config does not exist, wtf?");
         system_failure("Es gibt für diesen Benutzer noch keine Konfiguration. Das sollte nicht sein!");
     } else {
         $content = file($userconfig);
         DEBUG("Old file:");
         DEBUG($content);
         $newcontent = array();
         foreach ($content as $line) {
             if (preg_match('/^# foreign user '.$handle.'$/', $line)) {
                 DEBUG("delete1: ".$line);
                 continue;
             }
             if (preg_match('/^\s*(R|RW|RW+)\s*=\s*'.$handle.'\s*$/', $line)) {
                 DEBUG("delete2: ".$line);
                 continue;
             }
             $newcontent[] = $line;
         }
         DEBUG("Modified file:");
         DEBUG($newcontent);
         file_put_contents($userconfig, implode('', $newcontent));
     }
     git_wrapper('add '.$userconfig);
 
     git_wrapper('commit -m "deleted foreign user '.$handle.' for '.$username.'"');
     git_wrapper('push');
f7030eb6
 }
 
 
97b6f432
 function newkey($pubkey, $handle)
68db2f2f
 {
2626dd47
     global $key_dir, $config_dir;
     $username = $_SESSION['userinfo']['username'];
9086c9ad
 
2626dd47
     $handle = $username.'-'.$handle;
     if (! validate_name($handle) || (str_replace(".", "x", $handle) != $handle)) {
         system_failure("Der eingegebene Name enthält ungültige Zeichen. Bitte nur Buchstaben, Zahlen, Unterstrich und Bindestrich benutzen.");
     }
97b6f432
 
2626dd47
     $keyfile = $key_dir.'/'.$handle.'.pub';
     file_put_contents($keyfile, $pubkey);
9086c9ad
 
2626dd47
     DEBUG("checking public key $keyfile");
     $proc = popen("/usr/bin/ssh-keygen -l -f '{$keyfile}' 2>&1", 'r');
     $output = fread($proc, 512);
     DEBUG($output);
     pclose($proc);
     if (preg_match('/.* is not a public key file.*/', $output)) {
         unlink($keyfile);
         system_failure('Der angegebene SSH-Key scheint ungültig zu sein.');
     }
9086c9ad
 
04f386f1
 
2626dd47
     git_wrapper('add '.$keyfile);
 
     $userconfig = $config_dir . '/' . $username . '.conf';
     DEBUG("using config file ".$userconfig);
     if (! is_file($userconfig)) {
         DEBUG("user-config does not exist, creating new one");
         file_put_contents($userconfig, '# user '.$handle."\n");
         set_user_include();
     } elseif (in_array($handle, list_users())) {
         # user ist schon eingetragen, nur neuer Key
     } else {
         $content = file_get_contents($userconfig);
         file_put_contents($userconfig, "# user {$handle}\n".$content);
     }
     git_wrapper('add '.$userconfig);
9086c9ad
 
2626dd47
     git_wrapper('commit --allow-empty -m "added new key for '.$handle.'"');
     git_wrapper('push');
68db2f2f
 }
 
 
97b6f432
 function delete_key($handle)
 {
2626dd47
     global $key_dir, $config_dir;
     $username = $_SESSION['userinfo']['username'];
97b6f432
 
2626dd47
     if (! validate_name($handle)) {
         system_failure("Der eingegebene Name enthält ungültige Zeichen. Bitte nur Buchstaben, Zahlen, Unterstrich, Binderstrich und Punkt benutzen.");
     }
     if (!in_array($handle, list_users())) {
         DEBUG("key {$handle} not in");
         DEBUG(list_users());
         system_failure("Den angegebenen Key scheint es nicht zu geben");
     }
97b6f432
 
2626dd47
     $keyfile = $key_dir.'/'.$handle.'.pub';
     if (! file_exists($keyfile)) {
         system_failure("Der angegebene Schlüssel scheint nicht mehr vorhanden zu sein. Bitte manuelle Korrektur anfordern!");
     }
     git_wrapper('rm '.$keyfile);
 
 
     $userconfig = $config_dir . '/' . $username . '.conf';
     DEBUG("using config file ".$userconfig);
     if (! is_file($userconfig)) {
         DEBUG("user-config does not exist, wtf?");
         system_failure("Es gibt für diesen Benutzer noch keine Konfiguration. Das sollte nicht sein!");
     } else {
         $content = file($userconfig);
         DEBUG("Old file:");
         DEBUG($content);
         $newcontent = array();
         foreach ($content as $line) {
             if (preg_match('/^# user '.$handle.'$/', $line)) {
                 DEBUG("delete1: ".$line);
                 continue;
             }
             if (preg_match('/^\s*(R|RW|RW+)\s*=\s*'.$handle.'\s*$/', $line)) {
                 DEBUG("delete2: ".$line);
                 continue;
             }
             $newcontent[] = $line;
         }
         DEBUG("Modified file:");
         DEBUG($newcontent);
         file_put_contents($userconfig, implode('', $newcontent));
     }
     git_wrapper('add '.$userconfig);
9086c9ad
 
2626dd47
     git_wrapper('commit -m "deleted key for '.$handle.'"');
     git_wrapper('push');
97b6f432
 }
637cd934
 
 
2626dd47
 function remove_repo_from_array($data, $repo)
 {
     DEBUG("Request to remove repo »{$repo}«...");
     $inside = false;
     $outdata = array();
     $blank = true;
     foreach ($data as $line) {
         if ($blank && chop($line) == '') {
             continue;
         }
         $blank = (chop($line) == '');
         $m = array();
         if (preg_match('_^\s*repo (\S+)\s*$_', $line, $m) != 0) {
             $inside = ($m[1] == $repo);
         }
         if (! $inside && ! preg_match('/^'.$repo.'\s.*/', $line)) {
             $outdata[] = $line;
         }
637cd934
     }
2626dd47
     DEBUG($outdata);
     return $outdata;
637cd934
 }
 
 
2626dd47
 function repo_exists_globally($repo)
637cd934
 {
2626dd47
     global $config_dir;
     $files = scandir($config_dir);
     foreach ($files as $f) {
         if (is_file(realpath($config_dir.'/'.$f))) {
             $data = file(realpath($config_dir.'/'.$f));
             foreach ($data as $line) {
                 if (preg_match('/^\s*repo '.$repo.'\s*$/', $line) != 0) {
                     return true;
                 }
             }
637cd934
         }
     }
2626dd47
     return false;
637cd934
 }
 
 
2626dd47
 function delete_repo($repo)
637cd934
 {
2626dd47
     $repos = list_repos();
     if (!array_key_exists($repo, $repos)) {
         system_failure("Ein solches Repository existiert nicht!");
     }
9086c9ad
 
2626dd47
     global $config_dir;
     $username = $_SESSION['userinfo']['username'];
     $userconfig = $config_dir . '/' . $username . '.conf';
     DEBUG("using config file ".$userconfig);
     $data = file($userconfig);
     $data = remove_repo_from_array($data, $repo);
     file_put_contents($userconfig, implode('', $data));
     git_wrapper('add '.$userconfig);
9086c9ad
 
2626dd47
     git_wrapper('commit --allow-empty -m "deleted repo '.$repo.'"');
     git_wrapper('push');
637cd934
 }
 
3fc07ceb
 
 function set_user_include()
 {
2626dd47
     global $config_file, $userconfig;
     $username = $_SESSION['userinfo']['username'];
     if (!file_exists($userconfig)) {
         // Erzeuge eine leere Konfiguration damit das Include auf jeden Fall funktionieren kann
         file_put_contents($userconfig, '');
         git_wrapper('add '.$userconfig);
     }
     $found = false;
     $data = file($config_file);
     foreach ($data as $line) {
         if (preg_match('#webinterface/'.$username.'\.conf#', $line)) {
             $found = true;
         }
     }
     if (!$found) {
         $includeline = 'include  "webinterface/'.$username.'.conf"';
         $data = chop(file_get_contents($config_file));
         $data = $data."\n".$includeline."\n";
         file_put_contents($config_file, $data);
         git_wrapper('add '.$config_file);
     }
3fc07ceb
 }
 
 
2626dd47
 function save_repo($repo, $permissions, $description)
637cd934
 {
2626dd47
     if (!validate_name($repo)) {
         system_failure("Der gewählte name entspricht nicht den Konventionen!");
     }
     if (!array_key_exists($repo, list_repos()) && repo_exists_globally($repo)) {
         system_failure("Der gewählte Name existiert bereits auf diesem Server. Bitte wählen Sie einen spezifischeren Namen.");
     }
     global $config_dir;
     $username = $_SESSION['userinfo']['username'];
     $userconfig = $config_dir . '/' . $username . '.conf';
     DEBUG("using config file ".$userconfig);
     $data = array();
     if (! is_file($userconfig)) {
         DEBUG("user-config does not exist, creating new one");
         set_user_include();
     } else {
         $data = file($userconfig);
     }
637cd934
 
2626dd47
     $repos = list_repos();
     if (array_key_exists($repo, $repos)) {
         $data = remove_repo_from_array($data, $repo);
     }
 
     $data[] = "\n";
     if ($description) {
         $description = preg_replace('/\[\'"\r\n/', '', $description);
         $realname = $_SESSION['userinfo']['name'];
         $data[] = "{$repo} \"{$realname}\" = \"{$description}\"\n";
     }
     $data[] = 'repo '.$repo."\n";
     foreach ($permissions as $user => $perm) {
         $data[] = '  '.$perm.' = '.$user."\n";
     }
     file_put_contents($userconfig, implode('', $data));
     git_wrapper('add '.$userconfig);
9086c9ad
 
2626dd47
     git_wrapper('commit --allow-empty -m "written repo '.$repo.'"');
     git_wrapper('push');
637cd934
 }