diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:17:27 +0000 |
commit | aae1a14ea756102251351d96e2567b4986d30e2b (patch) | |
tree | a1af617672e26aee4c1031a3aa83e8ff08f6a0a5 /src/lib/Gitolite/Triggers | |
parent | Initial commit. (diff) | |
download | gitolite3-aae1a14ea756102251351d96e2567b4986d30e2b.tar.xz gitolite3-aae1a14ea756102251351d96e2567b4986d30e2b.zip |
Adding upstream version 3.6.12.upstream/3.6.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/Gitolite/Triggers')
-rw-r--r-- | src/lib/Gitolite/Triggers/Alias.pm | 128 | ||||
-rw-r--r-- | src/lib/Gitolite/Triggers/AutoCreate.pm | 24 | ||||
-rw-r--r-- | src/lib/Gitolite/Triggers/CpuTime.pm | 52 | ||||
-rwxr-xr-x | src/lib/Gitolite/Triggers/Kindergarten.pm | 99 | ||||
-rw-r--r-- | src/lib/Gitolite/Triggers/Mirroring.pm | 256 | ||||
-rw-r--r-- | src/lib/Gitolite/Triggers/Motd.pm | 29 | ||||
-rw-r--r-- | src/lib/Gitolite/Triggers/RefexExpr.pm | 80 | ||||
-rw-r--r-- | src/lib/Gitolite/Triggers/RepoUmask.pm | 62 | ||||
-rw-r--r-- | src/lib/Gitolite/Triggers/Shell.pm | 66 | ||||
-rw-r--r-- | src/lib/Gitolite/Triggers/TProxy.pm | 98 | ||||
-rw-r--r-- | src/lib/Gitolite/Triggers/Writable.pm | 17 |
11 files changed, 911 insertions, 0 deletions
diff --git a/src/lib/Gitolite/Triggers/Alias.pm b/src/lib/Gitolite/Triggers/Alias.pm new file mode 100644 index 0000000..adaceb5 --- /dev/null +++ b/src/lib/Gitolite/Triggers/Alias.pm @@ -0,0 +1,128 @@ +package Gitolite::Triggers::Alias; + +use Gitolite::Rc; +use Gitolite::Common; +use Gitolite::Conf::Load; + +use strict; +use warnings; + +# aliasing a repo to another +# ---------------------------------------------------------------------- + +=for usage + +Why: + + We had an existing repo "foo" that lots of people use. We wanted to + rename it to "foo/code", so that related repos "foo/upstream" and + "foo/docs" (both containing stuff we did not want to put in "foo") could + also be made and then the whole thing would be structured nicely. + + At the same time we did not want to *force* all the users to change the + name. At least git operations should still work with the old name, + although it is OK for "info" and other "commands" to display/require the + proper name (i.e., the new name). + +How: + + * uncomment the line "Alias" in the "user-visible behaviour" section in the + rc file + + * add a new variable REPO_ALIASES to the rc file, with entries like: + + REPO_ALIASES => + { + # if you need a more aggressive warning message than the default + WARNING => "Please change your URLs to use '%new'; '%old' will not work after XXXX-XX-XX", + + # prefix mapping section + PREFIX_MAPS => { + # note: NO leading slash in keys or values below + 'var/lib/git/' => '', + 'var/opt/git/' => 'opt/', + }, + + # individual repo mapping section + 'foo' => 'foo/code', + + # force users to change their URLs + 'bar' => '301/bar/code', + # a target repo starting with "301/" won't actually work; + # it will just produce an error message pointing the user + # to the new name. This allows admins to force users to + # fix their URLs. + }, + + If a prefix map is supplied, each key is checked (in *undefined* order), + and the *first* key which matches the prefix of the repo will be applied. + If more than one key matches (for example if you specify '/abc/def' as one + key, and '/abc' as another), it is undefined which will get picked up. + + The result of this, (or the original repo name if no map was found), will + then be subject to the individual repo mappings. Since these are full + repo names, there is no possibility of multiple matches. + +Notes: + + * only git operations (clone/fetch/push) are alias aware. Nothing else in + gitolite, such as all the gitolite commands etc., are alias-aware and will + always use/require the proper repo name. + + * http mode has not been tested and will not be. If someone has the time to + test it and make it work please let me know. + + * funnily enough, this even works with mirroring! That is, a master can + push a repo "foo" to a copy per its configuration, while the copy thinks + it is getting repo "bar" from the master per its configuration. + + Just make sure to put the Alias::input line *before* the Mirroring::input + line in the rc file on the copy. + + However, it will probably not work with redirected pushes unless you setup + the opposite alias ("bar" -> "foo") on master. +=cut + +sub input { + my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive"; + my $user = $ARGV[0] || '@all'; # user name is undocumented for now + + if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /(?:$git_commands) '\/?(\S+)'$/ ) { + my $repo = $1; + ( my $norm = $repo ) =~ s/\.git$//; # normalised repo name + + my $target = $norm; + + # prefix maps first + my $pm = $rc{REPO_ALIASES}{PREFIX_MAPS} || {}; + while (my($k, $v) = each %$pm) { + last if $target =~ s/^$k/$v/; + # no /i, /g, etc. by design + } + + # individual repo map next + $target = $rc{REPO_ALIASES}{$target} || $target; + + # undocumented; don't use without discussing on mailing list + $target = $target->{$user} if ref($target) eq 'HASH'; + + # if the repo name finally maps to empty, we bail, with no changes + return unless $target; + + # we're done. Did we actually change anything? + return if $norm eq $target; + + # if the new name starts with "301/", inform and abort + _die "please use '$target' instead of '$norm'" if $target =~ s(^301/)(); + # otherwise print a warning and continue with the new name + my $wm = $rc{REPO_ALIASES}{WARNING} || "'%old' is an alias for '%new'"; + $wm =~ s/%new/$target/g; + $wm =~ s/%old/$norm/g; + _warn $wm; + + $ENV{SSH_ORIGINAL_COMMAND} =~ s/'\/?$repo'/'$target'/; + } + +} + +1; diff --git a/src/lib/Gitolite/Triggers/AutoCreate.pm b/src/lib/Gitolite/Triggers/AutoCreate.pm new file mode 100644 index 0000000..e1d977a --- /dev/null +++ b/src/lib/Gitolite/Triggers/AutoCreate.pm @@ -0,0 +1,24 @@ +package Gitolite::Triggers::AutoCreate; + +use strict; +use warnings; + +# perl trigger set for stuff to do with auto-creating repos +# ---------------------------------------------------------------------- + +# to deny auto-create on read access, uncomment 'no-create-on-read' in the +# ENABLE list in the rc file +sub deny_R { + die "autocreate denied\n" if $_[3] and $_[3] eq 'R'; + return; +} + +# to deny auto-create on read *and* write, uncomment 'no-auto-create' in the +# ENABLE list in the rc file. This means you can only create wild repos using +# the 'create' command, (which needs to be enabled in the ENABLE list). +sub deny_RW { + die "autocreate denied\n" if $_[3] and ( $_[3] eq 'R' or $_[3] eq 'W' ); + return; +} + +1; diff --git a/src/lib/Gitolite/Triggers/CpuTime.pm b/src/lib/Gitolite/Triggers/CpuTime.pm new file mode 100644 index 0000000..74b4217 --- /dev/null +++ b/src/lib/Gitolite/Triggers/CpuTime.pm @@ -0,0 +1,52 @@ +package Gitolite::Triggers::CpuTime; + +use Time::HiRes; + +use Gitolite::Rc; +use Gitolite::Common; + +use strict; +use warnings; + +# cpu and elapsed times for gitolite+git operations +# ---------------------------------------------------------------------- +# uncomment the appropriate lines in the rc file to enable this + +# Ideally, you will (a) write your own code with a different filename so later +# gitolite upgrades won't overwrite your copy, (b) add appropriate variables +# to the rc file, and (c) change your rc file to call your program instead. + +# ---------------------------------------------------------------------- +my $start_time; + +sub input { + _warn "something wrong with the invocation of CpuTime::input" if $ENV{GL_TID} ne $$; + $start_time = [ Time::HiRes::gettimeofday() ]; +} + +sub post_git { + _warn "something wrong with the invocation of CpuTime::post_git" if $ENV{GL_TID} ne $$; + + my ( $trigger, $repo, $user, $aa, $ref, $verb ) = @_; + my ( $utime, $stime, $cutime, $cstime ) = times(); + my $elapsed = Time::HiRes::tv_interval($start_time); + + gl_log( 'times', $utime, $stime, $cutime, $cstime, $elapsed ); + + # now do whatever you want with the data; the following is just an example. + + if ( my $limit = $rc{CPU_TIME_WARN_LIMIT} ) { + my $total = $utime + $cutime + $stime + $cstime; + # some code to send an email or whatever... + say2 "limit = $limit, actual = $total" if $total > $limit; + } + + if ( $rc{DISPLAY_CPU_TIME} ) { + say2 "perf stats for $verb on repo '$repo':"; + say2 " user CPU time: " . ( $utime + $cutime ); + say2 " sys CPU time: " . ( $stime + $cstime ); + say2 " elapsed time: " . $elapsed; + } +} + +1; diff --git a/src/lib/Gitolite/Triggers/Kindergarten.pm b/src/lib/Gitolite/Triggers/Kindergarten.pm new file mode 100755 index 0000000..6274c3d --- /dev/null +++ b/src/lib/Gitolite/Triggers/Kindergarten.pm @@ -0,0 +1,99 @@ +package Gitolite::Triggers::Kindergarten; + +# http://www.great-quotes.com/quote/424177 +# "Doctor, it hurts when I do this." +# "Then don't do that!" + +# Prevent various things that sensible people shouldn't be doing anyway. List +# of things it prevents is at the end of the program. + +# If you were forced to enable this module because someone is *constantly* +# doing things that need to be caught, consider getting rid of that person. +# Because, really, who knows what *else* he/she is doing that can't be caught +# with some clever bit of code? + +use Gitolite::Rc; +use Gitolite::Common; + +use strict; +use warnings; + +my %active; +sub active { + # in rc, you either see just 'Kindergarten' to activate all features, or + # 'Kindergarten U0 CREATOR' (i.e., a space sep list of features after the + # word Kindergarten) to activate only those named features. + + # no features specifically activated; implies all of them are active + return 1 if not %active; + # else check if this specific feature is active + return 1 if $active{ +shift }; + + return 0; +} + +my ( $verb, $repo, $cmd, $args ); +sub input { + # get the features to be activated, if supplied + while ( $_[0] ne 'INPUT' ) { + $active{ +shift } = 1; + } + + # generally fill up variables you might use later + my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive"; + if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /($git_commands) '\/?(\S+)'$/ ) { + $verb = $1; + $repo = $2; + } elsif ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^(\S+) (.*)$/ ) { + $cmd = $1; + $args = $2; + } + + prevent_CREATOR($repo) if active('CREATOR') and $verb; + prevent_0(@ARGV) if active('U0') and @ARGV; +} + +sub prevent_CREATOR { + my $repo = shift; + _die "'CREATOR' not allowed as part of reponame" if $repo =~ /\bCREATOR\b/; +} + +sub prevent_0 { + my $user = shift; + _die "'0' is not a valid username" if $user eq '0'; +} + +1; + +__END__ + +CREATOR + + prevent literal 'CREATOR' from being part of a repo name + + a quirk deep inside gitolite would let this config + + repo foo/CREATOR/..* + C = ... + + allow the creation of repos like "foo/CREATOR/bar", i.e., the word CREATOR is + literally used. + + I consider this a totally pathological situation to check for. The worst that + can happen is someone ends up cluttering the server with useless repos. + + One solution could be to prevent this only for wild repos, but I can't be + bothered to fine tune this, so this module prevents even normal repos from + having the literal CREATOR in them. + + See https://groups.google.com/forum/#!topic/gitolite/cS34Vxix0Us for more. + +U0 + + prevent a user from being called literal '0' + + Ideally we should prevent keydir/0.pub (or variants) from being created, + but for "Then don't do that" purposes it's enough to prevent the user from + logging in. + + See https://groups.google.com/forum/#!topic/gitolite/F1IBenuSTZo for more. diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm new file mode 100644 index 0000000..07b7f96 --- /dev/null +++ b/src/lib/Gitolite/Triggers/Mirroring.pm @@ -0,0 +1,256 @@ +package Gitolite::Triggers::Mirroring; + +use Gitolite::Rc; +use Gitolite::Common; +use Gitolite::Conf::Load; + +use strict; +use warnings; + +my $hn = $rc{HOSTNAME}; + +my ( $mode, $master, %copies, %trusted_copies ); + +# ---------------------------------------------------------------------- + +sub input { + unless ( $ARGV[0] =~ /^server-(\S+)$/ ) { + _die "'$ARGV[0]' is not a valid server name" if $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/; + return; + } + + # note: we treat %rc as our own internal "poor man's %ENV" + $rc{FROM_SERVER} = $1; + trace( 3, "from_server: $1" ); + my $sender = $rc{FROM_SERVER} || ''; + + # custom peer-to-peer commands. At present the only one is 'perms -c', + # sent from a mirror command + if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^CREATOR=(\S+) perms -c '(\S+)'$/ ) { + $ENV{GL_USER} = $1; + + my $repo = $2; + details($repo); + _die "$hn: '$repo' is local" if $mode eq 'local'; + _die "$hn: '$repo' is native" if $mode eq 'master'; + _die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender; + + $ENV{GL_BYPASS_CREATOR_CHECK} = option($repo, "bypass-creator-check"); + # this expects valid perms content on STDIN + _system("gitolite perms -c $repo"); + delete $ENV{GL_BYPASS_CREATOR_CHECK}; + + # we're done. Yes, really... + exit 0; + } + + if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/ ) { + # my ($user, $newsoc, $repo) = ($1, $2, $3); + $ENV{SSH_ORIGINAL_COMMAND} = $2; + @ARGV = ($1); + $rc{REDIRECTED_PUSH} = 1; + trace( 3, "redirected_push for user $1" ); + } else { + # master -> copy push, no access checks needed + $ENV{GL_BYPASS_ACCESS_CHECKS} = 1; + } +} + +# ---------------------------------------------------------------------- + +sub pre_git { + return unless $hn; + # nothing, and I mean NOTHING, happens if HOSTNAME is not set + trace( 3, "pre_git() on $hn" ); + + my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ]; + + my $sender = $rc{FROM_SERVER} || ''; + $user = '' if $sender and not exists $rc{REDIRECTED_PUSH}; + + # ------------------------------------------------------------------ + # now you know the repo, get its mirroring details + details($repo); + + # print mirror status if at least one copy status file is present + print_status( $repo ) if not $rc{HUSH_MIRROR_STATUS} and $mode ne 'local' and glob("$rc{GL_REPO_BASE}/$repo.git/gl-copy-*.status"); + + # we don't deal with any reads. Note that for pre-git this check must + # happen *after* getting details, to give mode() a chance to die on "known + # unknown" repos (repos that are in the config, but mirror settings + # exclude this host from both the master and copy lists) + return if $aa eq 'R'; + + trace( 1, "mirror", "pre_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) ); + + # ------------------------------------------------------------------ + # case 1: we're master or copy, normal user pushing to us + if ( $user and not $rc{REDIRECTED_PUSH} ) { + trace( 3, "case 1, user push" ); + return if $mode eq 'local' or $mode eq 'master'; + if ( $trusted_copies{$hn} ) { + trace( 1, "redirect to $master" ); + exec( "ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}" ); + } else { + _die "$hn: pushing '$repo' to copy '$hn' not allowed"; + } + } + + # ------------------------------------------------------------------ + # case 2: we're copy, master pushing to us + if ( $sender and not $rc{REDIRECTED_PUSH} ) { + trace( 3, "case 2, master push" ); + _die "$hn: '$repo' is local" if $mode eq 'local'; + _die "$hn: '$repo' is native" if $mode eq 'master'; + _die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender; + return; + } + + # ------------------------------------------------------------------ + # case 3: we're master, copy sending a redirected push to us + if ( $sender and $rc{REDIRECTED_PUSH} ) { + trace( 3, "case 2, copy redirect" ); + _die "$hn: '$repo' is local" if $mode eq 'local'; + _die "$hn: '$repo' is not native" if $mode eq 'copy'; + _die "$hn: '$sender' is not a valid copy for '$repo'" if not $copies{$sender}; + _die "$hn: redirection not allowed from '$sender'" if not $trusted_copies{$sender}; + return; + } + + _die "$hn: should not reach this line"; + +} + +# ---------------------------------------------------------------------- + +sub post_git { + return unless $hn; + # nothing, and I mean NOTHING, happens if HOSTNAME is not set + trace( 1, "post_git() on $hn" ); + + my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ]; + # we don't deal with any reads + return if $aa eq 'R'; + + my $sender = $rc{FROM_SERVER} || ''; + $user = '' if $sender; + + # ------------------------------------------------------------------ + # now you know the repo, get its mirroring details + details($repo); + + trace( 1, "mirror", "post_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) ); + + # ------------------------------------------------------------------ + # case 1: we're master or copy, normal user pushing to us + if ( $user and not $rc{REDIRECTED_PUSH} ) { + trace( 3, "case 1, user push" ); + return if $mode eq 'local'; + # copy was eliminated earlier anyway, so that leaves 'master' + + # find all copies and push to each of them + push_to_copies($repo); + + return; + } + + # ------------------------------------------------------------------ + # case 2: we're copy, master pushing to us + if ( $sender and not $rc{REDIRECTED_PUSH} ) { + trace( 3, "case 2, master push" ); + # nothing to do + return; + } + + # ------------------------------------------------------------------ + # case 3: we're master, copy sending a redirected push to us + if ( $sender and $rc{REDIRECTED_PUSH} ) { + trace( 3, "case 2, copy redirect" ); + + # find all copies and push to each of them + push_to_copies($repo); + + return; + } +} + +{ + my $lastrepo = ''; + + sub details { + my $repo = shift; + return if $lastrepo eq $repo; + + $master = master($repo); + %copies = copies($repo); + $mode = mode($repo); + %trusted_copies = trusted_copies($repo); + trace( 3, $master, $mode, join( ",", sort keys %copies ), join( ",", sort keys %trusted_copies ) ); + } + + sub master { + return option( +shift, 'mirror.master' ); + } + + sub copies { + my $repo = shift; + + my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.copies.*" ); + my %out = map { $_ => 'async' } map { split } values %$ref; + + $ref = git_config( $repo, "^gitolite-options\\.mirror\\.copies\\.sync.*" ); + map { $out{$_} = 'sync' } map { split } values %$ref; + + $ref = git_config( $repo, "^gitolite-options\\.mirror\\.copies\\.nosync.*" ); + map { $out{$_} = 'nosync' } map { split } values %$ref; + + return %out; + } + + sub trusted_copies { + my $ref = git_config( +shift, "^gitolite-options\\.mirror\\.redirectOK.*" ); + # the list of trusted copies (where we accept redirected pushes from) + # is either explicitly given... + my @out = map { split } values %$ref; + my %out = map { $_ => 1 } @out; + # ...or it's all the copies mentioned if the list is just a "all" + %out = %copies if ( @out == 1 and $out[0] eq 'all' ); + return %out; + } + + sub mode { + my $repo = shift; + return 'local' if not $hn; + return 'master' if $master eq $hn; + return 'copy' if $copies{$hn}; + return 'local' if not $master and not %copies; + _die "$hn: '$repo' is mirrored but not here"; + } +} + +sub push_to_copies { + my $repo = shift; + + my $u = $ENV{GL_USER}; + delete $ENV{GL_USER}; # why? see src/commands/mirror + + my $lb = "$ENV{GL_REPO_BASE}/$repo.git/.gl-mirror-lock"; + for my $s ( sort keys %copies ) { + trace( 1, "push_to_copies skipping self" ), next if $s eq $hn; + system("gitolite 1plus1 $lb.$s gitolite mirror push $s $repo </dev/null >/dev/null 2>&1 &") if $copies{$s} eq 'async'; + system("gitolite 1plus1 $lb.$s gitolite mirror push $s $repo </dev/null >/dev/null 2>&1") if $copies{$s} eq 'sync'; + _warn "manual mirror push pending for '$s'" if $copies{$s} eq 'nosync'; + } + + $ENV{GL_USER} = $u; +} + +sub print_status { + my $repo = shift; + my $u = $ENV{GL_USER}; + delete $ENV{GL_USER}; + system("gitolite mirror status all $repo >&2"); + $ENV{GL_USER} = $u; +} + +1; diff --git a/src/lib/Gitolite/Triggers/Motd.pm b/src/lib/Gitolite/Triggers/Motd.pm new file mode 100644 index 0000000..6de80a2 --- /dev/null +++ b/src/lib/Gitolite/Triggers/Motd.pm @@ -0,0 +1,29 @@ +package Gitolite::Triggers::Motd; + +use Gitolite::Rc; +use Gitolite::Common; + +use strict; +use warnings; + +# print a message of the day to STDERR +# ---------------------------------------------------------------------- + +my $file = "gl-motd"; + +sub input { + # at present, we print it for every single interaction with gitolite. We + # may want to change that later; if we do, get code from Kindergarten.pm + # to get the gitcmd+repo or cmd+args so you can filter on them + + my $f = "$rc{GL_ADMIN_BASE}/$file"; + print STDERR slurp($f) if -f $f; +} + +sub pre_git { + my $repo = $_[1]; + my $f = "$rc{GL_REPO_BASE}/$repo.git/$file"; + print STDERR slurp($f) if -f $f; +} + +1; diff --git a/src/lib/Gitolite/Triggers/RefexExpr.pm b/src/lib/Gitolite/Triggers/RefexExpr.pm new file mode 100644 index 0000000..e913665 --- /dev/null +++ b/src/lib/Gitolite/Triggers/RefexExpr.pm @@ -0,0 +1,80 @@ +package Gitolite::Triggers::RefexExpr; +use strict; +use warnings; + +# track refexes passed and evaluate expressions on them +# ---------------------------------------------------------------------- +# see src/VREF/refex-expr for instructions and WARNINGS! + +use Gitolite::Easy; + +my %passed; +my %rules; +my $init_done = 0; + +sub access_2 { + # get out quick for repos that don't have any rules + return if $init_done and not %rules; + + # but we don't really know that the first time, heh! + if ( not $init_done ) { + my $repo = $_[1]; + init($repo); + return unless %rules; + } + + my $refex = $_[5]; + return if $refex =~ /DENIED/; + + $passed{$refex}++; + + # evaluate the rules each time; it's not very expensive + for my $k ( sort keys %rules ) { + $ENV{ "GL_REFEX_EXPR_" . $k } = eval_rule( $rules{$k} ); + } +} + +sub eval_rule { + my $rule = shift; + + my $e; + $e = join " ", map { convert($_) } split ' ', $rule; + + my $ret = eval $e; + _die "eval '$e' -> '$@'" if $@; + Gitolite::Common::trace( 1, "RefexExpr", "'$rule' -> '$e' -> '$ret'" ); + + return "'$rule' -> '$e'" if $ret; +} + +my %constant; +%constant = map { $_ => $_ } qw(1 not and or xor + - ==); +$constant{'-lt'} = '<'; +$constant{'-gt'} = '>'; +$constant{'-eq'} = '=='; +$constant{'-le'} = '<='; +$constant{'-ge'} = '>='; +$constant{'-ne'} = '!='; + +sub convert { + my $i = shift; + return $i if $i =~ /^-?\d+$/; + return $constant{$i} || $passed{$i} || $passed{"refs/heads/$i"} || 0; +} + +# called only once +sub init { + $init_done = 1; + my $repo = shift; + + # find all the rule expressions + my %t = config( $repo, "^gitolite-options\\.refex-expr\\." ); + my ( $k, $v ); + # get rid of the cruft and store just the rule name as the key + while ( ( $k, $v ) = each %t ) { + $k =~ s/^gitolite-options\.refex-expr\.//; + $rules{$k} = $v; + } +} + +1; diff --git a/src/lib/Gitolite/Triggers/RepoUmask.pm b/src/lib/Gitolite/Triggers/RepoUmask.pm new file mode 100644 index 0000000..276cd01 --- /dev/null +++ b/src/lib/Gitolite/Triggers/RepoUmask.pm @@ -0,0 +1,62 @@ +package Gitolite::Triggers::RepoUmask; + +use Gitolite::Rc; +use Gitolite::Common; +use Gitolite::Conf::Load; + +use strict; +use warnings; + +# setting a repo specific umask +# ---------------------------------------------------------------------- +# this is for people who are too paranoid to trust e.g., gitweb's repo +# exclusion logic, but not paranoid enough to put it on a different server + +=for usage + + * In the rc file, add the line + 'RepoUmask', + somewhere in the ENABLE list + + * For each repo that is to get a different umask than the default, add a + line like this: + + option umask = 0027 + + * Anytime you add or change the value, if there are existing repos that + would be affected, you will need to do a manual "chmod" adjustment, + because umask only affects newly created files. + +=cut + +# sadly option/config values are not available at pre_create time for normal +# repos. So we have to do a one-time fixup in a post_create trigger. +sub post_create { + my $repo = $_[1]; + + my $umask = option( $repo, 'umask' ); + _chdir( $rc{GL_REPO_BASE} ); # because using option() moves us to ADMIN_BASE! + + return unless $umask; + + # unlike the one in the rc file, this is a string + $umask = oct($umask); + my $mode = "0" . sprintf( "%o", $umask ^ 0777 ); + + system("chmod -R $mode $repo.git >&2"); + system("find $repo.git -type f -exec chmod a-x '{}' \\;"); +} + +sub pre_git { + my $repo = $_[1]; + + my $umask = option( $repo, 'umask' ); + _chdir( $rc{GL_REPO_BASE} ); # because using option() moves us to ADMIN_BASE! + + return unless $umask; + + # unlike the one in the rc file, this is a string + umask oct($umask); +} + +1; diff --git a/src/lib/Gitolite/Triggers/Shell.pm b/src/lib/Gitolite/Triggers/Shell.pm new file mode 100644 index 0000000..a2c5c0d --- /dev/null +++ b/src/lib/Gitolite/Triggers/Shell.pm @@ -0,0 +1,66 @@ +package Gitolite::Triggers::Shell; + +# usage notes: uncomment 'Shell' in the ENABLE list in the rc file. + +# documentation is in the ssh troubleshooting and tips document, under the +# section "giving shell access to gitolite users" + +use Gitolite::Rc; +use Gitolite::Common; + +# fedora likes to do things that are a little off the beaten track, compared +# to typical gitolite usage: +# - every user has their own login +# - the forced command may not get the username as an argument. If it does +# not, the gitolite user name is $USER (the unix user name) +# - and finally, if the first argument to the forced command is '-s', and +# $SSH_ORIGINAL_COMMAND is empty or runs a non-git/gitolite command, then +# the user gets a shell + +sub input { + my $shell_allowed = 0; + if ( @ARGV and $ARGV[0] eq '-s' ) { + shift @ARGV; + $shell_allowed++; + } + + @ARGV = ( $ENV{USER} ) unless @ARGV; + + return unless $shell_allowed; + + # now determine if this was intended as a shell command or git/gitolite + # command + + my $soc = $ENV{SSH_ORIGINAL_COMMAND}; + + # no command, just 'ssh alice@host'; doesn't return ('exec's out) + shell_out() if $shell_allowed and not $soc; + + return if git_gitolite_command($soc); + + gl_log( 'shell', $ENV{SHELL}, "-c", $soc ); + exec $ENV{SHELL}, "-c", $soc; +} + +sub shell_out { + my $shell = $ENV{SHELL}; + $shell =~ s/.*\//-/; # change "/bin/bash" to "-bash" + gl_log( 'shell', $shell ); + exec { $ENV{SHELL} } $shell; +} + +# some duplication with gitolite-shell, factor it out later, if it works fine +# for fedora and they like it. +sub git_gitolite_command { + my $soc = shift; + + my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive"; + return 1 if $soc =~ /^($git_commands) /; + + my @words = split ' ', $soc; + return 1 if $rc{COMMANDS}{ $words[0] }; + + return 0; +} + +1; diff --git a/src/lib/Gitolite/Triggers/TProxy.pm b/src/lib/Gitolite/Triggers/TProxy.pm new file mode 100644 index 0000000..9c42918 --- /dev/null +++ b/src/lib/Gitolite/Triggers/TProxy.pm @@ -0,0 +1,98 @@ +package Gitolite::Triggers::TProxy; + +# ---------------------------------------------------------------------- +# transparent proxy for git repos, hosted on a gitolite server + +# ---------------------------------------------------------------------- +# WHAT + +# 1. user runs a git command (clone, fetch, push) against a gitolite +# server. +# 2. if that server has the repo, it will serve it up. Else it will +# *transparently* forward the git operation to a designated upstream +# server. The user does not have to do anything, and in fact may not +# even know this has happened. + +# can be combined with, but does not *require*, gitolite mirroring. + +# ---------------------------------------------------------------------- +# SECURITY +# +# 1. Most of the issues that apply to "redirected push" in mirroring.html +# also apply here. In particular, you had best make sure the two +# servers use the same authentication data (i.e., "alice" here should be +# "alice" there!) +# +# 2. Also, do not add keys for servers you don't trust! + +# ---------------------------------------------------------------------- +# HOW + +# on transparent proxy server (the one that is doing the redirect): +# 1. add +# INPUT => ['TProxy::input'], +# just before the ENABLE list in the rc file +# 2. add an RC variable to tell gitolite where to go; this is also just +# before the ENABLE list: +# TPROXY_FORWARDS_TO => 'git@upstream', + +# on upstream server (the one redirected TO): +# 1. add +# INPUT => ['TProxy::input'], +# just before the ENABLE list in the rc file +# 2. add the pubkey of the proxy server (the one that will be redirecting +# to us) to this server's gitolite-admin "keydir" as +# "server-<something>.pub", and push the change. + +# to use in combination with gitolite mirroring +# 1. just follow the same instructions as above. Server names and +# corresponding pub keys would already be set ok so step 2 in the +# upstream server setup (above) will not be needed. +# 2. needless to say, **don't** declare the repos you want to be +# transparently proxied in the gitolite.conf for the copy. + +use Gitolite::Rc; +use Gitolite::Common; +use Gitolite::Conf::Load; + +use strict; +use warnings; + +my $soc = $ENV{SSH_ORIGINAL_COMMAND}; + +# ---------------------------------------------------------------------- + +sub input { + # are we the upstream, getting something from a tproxy server? + my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive"; + if ( $ARGV[0] =~ /^server-/ and $soc =~ /^TPROXY_FOR=(\S+) SOC=(($git_commands) '\S+')$/ ) { + @ARGV = ($1); + # you better make sure you read the security warnings up there! + + $ENV{SSH_ORIGINAL_COMMAND} = $2; + delete $ENV{GL_BYPASS_ACCESS_CHECKS}; + # just in case we somehow end up running before Mirroring::input! + + return; + } + + # well we're not upstream; are we a tproxy? + return unless $rc{TPROXY_FORWARDS_TO}; + + # is it a normal git command? + return unless $ENV{SSH_ORIGINAL_COMMAND} =~ m(^($git_commands) '/?(.*?)(?:\.git(\d)?)?'$); + + # ...get the repo name from $ENV{SSH_ORIGINAL_COMMAND} + my ( $verb, $repo, $trace_level ) = ( $1, $2, $3 ); + $ENV{D} = $trace_level if $trace_level; + _die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT; + + # nothing to do if the repo exists locally + return if -d "$ENV{GL_REPO_BASE}/$repo.git"; + + my $user = shift @ARGV; + # redirect to upstream + exec( "ssh", $rc{TPROXY_FORWARDS_TO}, "TPROXY_FOR=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}" ); +} + +1; diff --git a/src/lib/Gitolite/Triggers/Writable.pm b/src/lib/Gitolite/Triggers/Writable.pm new file mode 100644 index 0000000..ed86e12 --- /dev/null +++ b/src/lib/Gitolite/Triggers/Writable.pm @@ -0,0 +1,17 @@ +package Gitolite::Triggers::Writable; + +use Gitolite::Rc; +use Gitolite::Common; + +sub access_1 { + my ( $repo, $aa, $result ) = @_[ 1, 3, 5 ]; + return if $aa eq 'R' or $result =~ /DENIED/; + + for my $f ( "$ENV{HOME}/.gitolite.down", "$rc{GL_REPO_BASE}/$repo.git/.gitolite.down" ) { + next unless -f $f; + _die slurp($f) if -s $f; + _die "sorry, writes are currently disabled (no more info available)\n"; + } +} + +1; |