Upstream kup provided this patch, that was apparently written by the Gitolite author. --- standard/kup-server 2017-03-28 13:01:24.000000000 -0400 +++ gitolite/kup-server 2018-03-26 15:01:20.000000000 -0400 @@ -1,4 +1,4 @@ -#!/usr/bin/perl -T +#!/usr/bin/perl ## ----------------------------------------------------------------------- ## ## Copyright 2011 Intel Corporation; author: H. Peter Anvin @@ -68,12 +68,20 @@ use Digest::SHA; -my $VERSION = '0.3.6'; - -# Scrub the environment completely -%ENV = ('PATH' => '/bin:/usr/bin', - 'LANG' => 'C', - 'SHELL' => '/bin/false'); # Nothing in this program should shell out +use lib $ENV{GL_LIBDIR}; +use Gitolite::Easy; +use Gitolite::Conf::Load; + +my $VERSION = '0.3.6 (gitolite integrated)'; + +# Scrub the environment completely, except gitolite variables and HOME +{ + my %env = %ENV; + %ENV = ('PATH' => '/bin:/usr/bin', + 'LANG' => 'C', + 'SHELL' => '/bin/false'); # Nothing in this program should shell out + $ENV{$_} = $env{$_} for ('HOME', grep(/^GL_/, keys %env)); +} # The standard function to call on bail sub fatal($) { @@ -88,16 +96,7 @@ } sub my_username() { - my $whoami = getuid(); - my ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell,$expire) = getpwuid($whoami); - - if (!defined($name) || $whoami != $uid) { - # We haven't called openlog() yet so we need to do it here - openlog("kup-server($whoami)", 'ndelay,pid', LOG_LOCAL5); - fatal("You don't exist, go away!"); - } - - return (defined($name) && $whoami == $uid) ? $name : $whoami; + return $ENV{GL_USER}; } my $user_name = my_username(); @@ -106,7 +105,7 @@ # Get config values from kup-server.cfg -my $cfg_file = '/etc/kup/kup-server.cfg'; +my $cfg_file = '/var/lib/gitolite3/.gitolite/local-code/configs/kup-server.cfg'; my $cfg = new Config::Simple($cfg_file); @@ -371,6 +370,51 @@ return 1; } +# kup-server may "read" files from the kup data_path, or repos. If a repo is +# supplied, we assume it's a gitolite repo and check access accordingly (while +# remembering that kup seems to add a leading slash). If a repo is *not* +# supplied, we assume we're talking about the kup data_path, which means we +# make gitolite access rules from the "fake" repo called "@kup-server" +sub read_allowed +{ + Gitolite::Common::trace( 1, 'read_allowed', @_ ); + my $repo = shift || '@kup-server'; + + # gitolite expects a "normalised" repo name; no leading slash, no trailing ".git" + $repo =~ s(^/)(); $repo =~ s/\.git$//; + + return can_read($repo); +} + +# kup-server does not write to normal repos, it only writes to files in the +# kup data_path. So we don't have to worry about any repo other than +# "@kup-server", which is therefore hardcoded in here. +sub write_allowed +{ + Gitolite::Common::trace( 1, 'write_allowed', @_ ); + my($path, $perm) = @_; + + # other values for perm are + (rm) and C (mkdir), analogous to gitolite's + # "+ means delete or rewind branch, C means create branch" + $perm ||= 'W'; + + my $repo = '@kup-server'; + + # the paths that gitolite expects start with "refs/heads/", since we are + # simply re-using the existing ACL for this. (But remember $path, in + # kup-land, already starts with a "/".) + $path = "refs/heads" . $path; + + return can_write($repo, $perm, $path) || + can_write($repo, $perm, "$path/"); + # the second check is because, when specifying a permission on a directory + # in gitolite, you end with a "/", say "RW+C foo/ = user". To exercise + # that right, the user runs "kup mkdir foo" or "kup rm foo". This fails, + # because the regex "foo/" won't match. (In a *git* repo it doesn't + # matter, because git doesn't allow empty directories, so it never + # happens). +} + # Return a percentage, valid even if the denominator is zero sub percentage($$) { @@ -526,6 +570,10 @@ fatal("Invalid pathname in TAR command"); } + if (!read_allowed($tree)) { + fatal("Read access denied"); + } + if (!is_clean_string($prefix)) { fatal("Invalid prefix string"); } @@ -569,6 +617,10 @@ fatal("Invalid pathname in DIFF command"); } + if (!read_allowed($tree)) { + fatal("Read access denied"); + } + if ($tree !~ /\.git$/ || ! -d $git_path.$tree || ! -d $git_path.$tree.'/objects') { fatal("No such git tree"); @@ -788,8 +840,13 @@ or fatal("dup error"); close($devnull); + my $gpgvbin = '/opt/gnupg22/bin/gpgv'; + if ( ! -x $gpgvbin) { + $gpgvbin = '/usr/bin/gpgv'; + } + my $status = - system('/usr/bin/gpgv', + system($gpgvbin, '--quiet', '--homedir', $tmpdir, '--keyring', $pgp_path."/${user_name}.gpg", @@ -839,6 +896,10 @@ fatal("Invalid filename in PUT command"); } + if (!write_allowed($file)) { + fatal("Write access denied"); + } + my @install_ext; my @conflic_ext; my $stem; @@ -917,6 +978,10 @@ fatal("Invalid filename in MKDIR command"); } + if (!write_allowed($file, 'C')) { + fatal("MKDIR access denied"); + } + my @badext = ('.sign', keys(%zformats)); foreach my $e (@badext) { @@ -991,6 +1056,16 @@ fatal("Invalid filename in $cmd command"); } + if ($cmd eq 'MOVE') { + if (!write_allowed($from, '+')) { + fatal("Delete (as part of MOVE) access denied"); + } + } + + if (!write_allowed($to)) { + fatal("Write access denied"); + } + if ($from =~ /\.gz$/) { if ($to !~ /\.gz$/) { fatal("$cmd of .gz file must itself end in .gz"); @@ -1093,6 +1168,10 @@ fatal("Invalid pathname in DELETE command"); } + if (!write_allowed($file, "+")) { + fatal("Delete access denied"); + } + if ($file !~ /\.gz$/ && has_extension($file, '.sign', keys(%zformats))) { fatal("DELETE of auxiliary files not supported"); @@ -1222,6 +1301,10 @@ my($dir) = @args; + if (!read_allowed()) { + fatal("Read access denied"); + } + # DIR / is permitted unlike any other command $dir =~ s:/$::g; if ($dir ne '' && !is_valid_filename($dir)) { @@ -1261,7 +1344,25 @@ sub do_info() { - print "kup-server $VERSION\n"; + print "kup-server $VERSION\n\n"; + + my %xlat = ( + R => 'ls', + RW => 'put', + 'RW+' => 'put/rm/mv', + 'RWC' => 'put/mkdir', + 'RW+C' => 'put/rm/mv/mkdir', + '-' => '(denied)', + ); + Gitolite::Conf::Load::load('@kup-server'); + my @rules = Gitolite::Conf::Load::rules('@kup-server', $ENV{GL_USER}); + for my $r (@rules) { + my ($dummy, $perm, $ref) = @$r; + $ref =~ s(^refs/heads/)(); + $ref =~ s(/USER/)(/$ENV{GL_USER}/); + $ref = ($ref eq 'refs/.*') ? '/*' : '/' . $ref . '*'; + printf "%-24s %s\n", ($xlat{$perm} || $perm), $ref; + } } sub get_command()