From 692552d14692295b801c51f4b2d911cea5ba94ad Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 15 Jan 2011 21:09:56 +0530 Subject: [PATCH] gitolite v2.0rc1 -- please see new developer-notes doc --- README.mkd | 3 + conf/example.gitolite.rc | 23 +- contrib/adc/fork | 4 +- contrib/adc/get-rights-and-owner.in-perl | 8 +- contrib/adc/gl-reflog | 8 +- contrib/adc/rm | 4 +- contrib/autotoc | 4 +- contrib/gitweb/gitweb.conf | 37 +- doc/admin-defined-commands.mkd | 2 +- doc/developer-notes.mkd | 213 +++++++ doc/gitolite.rc.mkd | 8 +- doc/mirroring.mkd | 10 +- doc/shell-games.mkd | 4 +- hooks/common/update | 64 +- hooks/gitolite-admin/post-update | 6 +- src/gitolite.pm | 762 +++++++++++------------ src/gitolite_env.pm | 157 +++++ src/gitolite_rc.pm | 68 ++ src/gl-auth-command | 223 ++----- src/gl-compile-conf | 141 ++--- src/gl-dont-panic | 17 +- src/gl-easy-install | 12 +- src/gl-install | 31 +- src/gl-mirror-shell | 13 +- src/gl-mirror-sync | 2 +- src/gl-query-rc | 23 + src/gl-setup | 33 +- src/gl-setup-authkeys | 58 +- src/gl-time | 30 +- src/gl-tool | 9 +- src/sshkeys-lint | 6 +- t/README.mkd | 9 + t/t65-rsync | 20 +- t/test-driver.sh | 75 +-- 34 files changed, 1224 insertions(+), 863 deletions(-) create mode 100644 doc/developer-notes.mkd create mode 100644 src/gitolite_env.pm create mode 100644 src/gitolite_rc.pm create mode 100755 src/gl-query-rc diff --git a/README.mkd b/README.mkd index f3355a1..49d3ada 100644 --- a/README.mkd +++ b/README.mkd @@ -1,5 +1,8 @@ +***IMPORTANT NOTE: v2.0rc1 is out; all development will be on that now. +Please see new doc/developer-notes.mkd*** + # gitolite Gitolite is an access control layer on top of git, which allows access control diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index 9ee7840..e99efc0 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -3,19 +3,21 @@ # PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS # ( http://github.com/sitaramc/gitolite/blob/pu/doc/gitolite.rc.mkd ) -# this file is meant to be pulled into a perl program using "do" or "require". -# You do NOT need to know perl to edit the paths; it should be fairly -# self-explanatory and easy to maintain perl syntax :-) +# this file is in perl syntax. However, you do NOT need to know perl to edit +# it; it should be fairly self-explanatory and easy to maintain # ------------------------------------------------------------------------------ -# VARIABLES THAT SHOULD NOT BE TOUCHED AT ALL. EVER. +# DO NOT TOUCH THIS SECTION! # ------------------------------------------------------------------------------ + $GL_ADMINDIR=$ENV{HOME} . "/.gitolite"; $GL_CONF="$GL_ADMINDIR/conf/gitolite.conf"; $GL_KEYDIR="$GL_ADMINDIR/keydir"; $GL_CONF_COMPILED="$GL_ADMINDIR/conf/gitolite.conf-compiled.pm"; -# DO NOT CHANGE THE NEXT TWO LINES UNLESS YOU REALLY KNOW WHAT YOU'RE DOING. + +# DO NOT CHANGE THE NEXT FOUR LINES UNLESS YOU REALLY KNOW WHAT YOU'RE DOING. # These variables are set automatically by the install method you choose. +# (PACKAGE MAINTAINERS: PLEASE READ doc/packaging.mkd) # $GL_PACKAGE_CONF = ""; # $GL_PACKAGE_HOOKS = ""; @@ -27,13 +29,14 @@ $PROJECTS_LIST = $ENV{HOME} . "/projects.list"; $REPO_UMASK = 0077; # ------------------------------------------------------------------------------ -# variables with an efficiency impact +# variables with an efficiency/performance impact # ------------------------------------------------------------------------------ $GL_BIG_CONFIG = 0; $GL_NO_DAEMON_NO_GITWEB = 0; +# $GL_NICE_VALUE = 0; # ------------------------------------------------------------------------------ -# VARIABLES WITH A SECURITY IMPACT. READ DOC WELL BEFORE CHANGING THESE. +# VARIABLES WITH A SECURITY IMPACT. READ DOCS BEFORE CHANGING THESE! # http://github.com/sitaramc/gitolite/blob/pu/doc/gitolite.rc.mkd#_variables_with_a_security_impact # ------------------------------------------------------------------------------ # $GL_ALL_READ_ALL = 0; @@ -59,19 +62,23 @@ $SVNSERVE = ""; # $ENV{GL_SLAVES} = 'gitolite@server2 gitolite@server3'; # PLEASE USE SINGLE QUOTES ABOVE, NOT DOUBLE QUOTES $GL_WILDREPOS_PERM_CATS = "READERS WRITERS"; +# $GL_SITE_INFO = "XYZ.COM DEVELOPERS: PLEASE SEE http://xyz.com/gitolite/help first"; # ------------------------------------------------------------------------------ # rarely changed variables # ------------------------------------------------------------------------------ $GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y-%m.log"; # $GL_PERFLOGT="$GL_ADMINDIR/logs/perf-gitolite-%y-%m.log"; -# $GL_SITE_INFO = "XYZ.COM DEVELOPERS: PLEASE SEE http://xyz.com/gitolite/help first"; # ------------------------------------------------------------------------------ # variables that should NOT be changed after the install step completes # ------------------------------------------------------------------------------ $REPO_BASE="repositories"; +# ------------------------------------------------------------------------------ +# DO NOT TOUCH ANY THING AFTER THIS LINE +# ------------------------------------------------------------------------------ + # ------------------------------------------------------------------------------ # per perl rules, this should be the last line in such a file: 1; diff --git a/contrib/adc/fork b/contrib/adc/fork index 22d451f..63a66b8 100755 --- a/contrib/adc/fork +++ b/contrib/adc/fork @@ -2,6 +2,8 @@ . $(dirname $0)/adc.common-functions +[ -z "$GL_RC" ] && die "ENV GL_RC not set" + # get_rights_and_owner now also sets $repo; see comments in common functions get_rights_and_owner $1; from=$repo [ -z "$perm_read" ] && die "no read permissions on $from" @@ -17,7 +19,7 @@ git clone --bare -l $GL_REPO_BASE_ABS/$from.git $GL_REPO_BASE_ABS/$to.git cd $GL_REPO_BASE_ABS/$to.git echo $GL_USER > gl-creater git config gitweb.owner "$GL_USER" -( cd $HOME;perl -le 'do ".gitolite.rc"; print $GL_WILDREPOS_DEFPERMS' ) | +( $GL_BINDIR/gl-query-rc GL_WILDREPOS_DEFPERMS ) | SSH_ORIGINAL_COMMAND="setperms $to" $GL_BINDIR/gl-auth-command $GL_USER cp -R $GL_REPO_BASE_ABS/$from.git/hooks/* $GL_REPO_BASE_ABS/$to.git/hooks diff --git a/contrib/adc/get-rights-and-owner.in-perl b/contrib/adc/get-rights-and-owner.in-perl index d848cfb..defc360 100755 --- a/contrib/adc/get-rights-and-owner.in-perl +++ b/contrib/adc/get-rights-and-owner.in-perl @@ -3,8 +3,12 @@ use strict; use warnings; +die "ENV GL_RC not set\n" unless $ENV{GL_RC}; +die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR}; + unshift @INC, $ENV{GL_BINDIR}; require gitolite or die "parse gitolite.pm failed\n"; +gitolite->import; # get the repo name my $repo = shift; @@ -15,7 +19,7 @@ $repo =~ s/\.git$//; # to do a "level 1" check (repo level -- not branch level), do this: -my ($perm, $creator) = &check_access($repo); +my ($perm, $creator) = check_access($repo); # you can pass in any repo name you wish instead of the active repo # the first return value looks like one of these, so you can just check for @@ -30,7 +34,7 @@ my ($perm, $creator) = &check_access($repo); # to do a "level 2" check (branches), do something like this -my $ret = &check_access($repo, 'refs/heads/foo', 'W', 1); +my $ret = check_access($repo, 'refs/heads/foo', 'W', 1); # the 2nd argument must be a *full* refname (i.e., not "master", but # "refs/heads/master"). The 3rd argument is one of W, +, C, or D. The 4th # argument should be any non-false perl value, like 1. diff --git a/contrib/adc/gl-reflog b/contrib/adc/gl-reflog index b211317..d9a9905 100755 --- a/contrib/adc/gl-reflog +++ b/contrib/adc/gl-reflog @@ -3,6 +3,9 @@ use strict; use warnings; +die "ENV GL_RC not set\n" unless $ENV{GL_RC}; +die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR}; + # - show fake "reflog" from gitolite server # - recover deleted branches # - recover from bad force pushes @@ -42,8 +45,9 @@ $limit ||= 10; unshift @INC, $ENV{GL_BINDIR}; require gitolite or die "parse gitolite.pm failed\n"; +gitolite->import; -my ($perm, $creator, $wild) = &repo_rights($repo); +my ($perm, $creator, $wild) = repo_rights($repo); die "you don't have read access to $repo\n" unless $perm =~ /R/; my @logfiles = sort glob("$ENV{GL_ADMINDIR}/logs/*"); @@ -93,5 +97,5 @@ if ( $cmd eq 'recover' ) { $newsha2 = '' if $newsha =~ /^0+$/; system("git", "update-ref", $ref, $oldsha, $newsha2) and die "repo $repo, update-ref $ref $oldsha $newsha failed...\n"; - &log_it("", "+\t$newsha\t$oldsha\t$repo\t$ref"); + log_it("", "+\t$newsha\t$oldsha\t$repo\t$ref"); } diff --git a/contrib/adc/rm b/contrib/adc/rm index d259b1c..7223a6c 100755 --- a/contrib/adc/rm +++ b/contrib/adc/rm @@ -2,6 +2,8 @@ . $(dirname $0)/adc.common-functions +[ -z "$GL_RC" ] && die "ENV GL_RC not set" + # options settable in adc.common-functions are # ARE_YOU_SURE -- prompts "are you sure?" # USE_LOCK_UNLOCK -- allows delete only if repo is "unlock"ed @@ -32,6 +34,6 @@ rm -rf $repo.git echo "$repo is now GONE!" cd $HOME -PROJECTS_LIST=$(perl -e 'do ".gitolite.rc"; print $PROJECTS_LIST') +PROJECTS_LIST=$($GL_BINDIR/gl-query-rc PROJECTS_LIST) export repo perl -ni -e 'print unless /^\Q$ENV{repo}.git\E$/' $PROJECTS_LIST diff --git a/contrib/autotoc b/contrib/autotoc index 9ddbe7e..964baa9 100755 --- a/contrib/autotoc +++ b/contrib/autotoc @@ -20,10 +20,10 @@ $doc =~ s/^<\/a>\n\n//mg; $doc =~ s/^<\/a>\n\n//mg; my @toc = $doc =~ /^###+ .*/mg; -$doc =~ s/^(###+) (.*)/"<\/a>\n\n$1 $2"/mge; +$doc =~ s/^(###+) (.*)/"<\/a>\n\n$1 $2"/mge; for (@toc) { - s/^(###+) (.*)/' ' x (length($1)-3) . ' * $2<\/a>"/e; + s/^(###+) (.*)/' ' x (length($1)-3) . ' * $2<\/a>"/e; } my $toc = "In this document:\n\n"; diff --git a/contrib/gitweb/gitweb.conf b/contrib/gitweb/gitweb.conf index 283879f..5ed8dbc 100644 --- a/contrib/gitweb/gitweb.conf +++ b/contrib/gitweb/gitweb.conf @@ -6,29 +6,36 @@ # HOME of the gitolite user my $gl_home = "/home/git"; -# environment variables needed by gitolite.pm +# the following variables are needed by gitolite; please edit before using + +# this should normally not be anything else $ENV{GL_RC} = "$gl_home/.gitolite.rc"; +# this can have different values depending on how you installed. + +# If you installed using the 'from-client' method it will be this: +$ENV{GL_BINDIR} = "$gl_home/.gitolite/src"; +# if you used RPM/DEB or "root" methods it **might** be this: +$ENV{GL_BINDIR} = "/usr/local/bin"; +# if you used the "non-root" method it **might** be this: +$ENV{GL_BINDIR} = "$gl_home/bin"; +# If in doubt take a look at ~/.ssh/authorized_keys; at least one of the lines +# might contain something like: +# command="/home/git/.gitolite/src/gl-auth-command +# and you should use whatever directory the gl-auth-command is in (in this +# example /home/git/.gitolite.src) + +# finally the user name $ENV{GL_USER} = $cgi->remote_user || "gitweb"; -# variables from the RC file -our ($REPO_BASE, $GL_ADMINDIR); - -# set HOME temporarily for RC parsing -my $orig_home = $ENV{HOME}; -$ENV{HOME} = $gl_home; -do $ENV{GL_RC} - or die_error(500, "Failed to parse $ENV{GL_RC}: " . ($! or $@)); -$ENV{HOME} = $orig_home; +# now get gitolite stuff in... +use lib $ENV{GL_BINDIR}; +use gitolite_rc; +use gitolite; # set project root etc. absolute paths $ENV{GL_REPO_BASE_ABS} = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$gl_home/$REPO_BASE" ); $projects_list = $projectroot = $ENV{GL_REPO_BASE_ABS}; -# load gitolite helper routines -unshift @INC, "$GL_ADMINDIR/src"; -require gitolite - or die_error(500, "Failed to parse gitolite.pm: " . ($! or $@)); - $export_auth_hook = sub { my $repo = shift; # gitweb passes us the full repo path; so we strip the beginning diff --git a/doc/admin-defined-commands.mkd b/doc/admin-defined-commands.mkd index 32fe267..06de555 100644 --- a/doc/admin-defined-commands.mkd +++ b/doc/admin-defined-commands.mkd @@ -119,7 +119,7 @@ Finally, you can call gitolite to query ownership and permissions for the current user (which may not necessarily be the owner). This is done loosely as follows (don't use this exact code yet though): - perl -I$HOME/.gitolite/src -Mgitolite -e "cli_repo_rights('reponame')" + perl -I$GL_BINDIR -Mgitolite -e "cli_repo_rights('reponame')" which will print two space-separated words, something like `_____R__W u1` or maybe `____@R_@W `. (The former is the response for a wildcard repo diff --git a/doc/developer-notes.mkd b/doc/developer-notes.mkd new file mode 100644 index 0000000..b59850f --- /dev/null +++ b/doc/developer-notes.mkd @@ -0,0 +1,213 @@ +# developer/patch maintainer notes + +In this document: + + * current development version + * testing status of gitolite v2.0rc1 + * migration to gitolite v2.x + * general stuff + * the rc file + * modules + * that 'bindir' thing + * from perl + * from shell + * OUTLIER! + * special types of setups + * Fedora + +---- + + + +### current development version + +The current gitolite development version is v2.0rc1. Unless there is a +serious security problem, *or* one of my large users [i.e., anyone whose name +is in doc/who-uses-it.mkd (grin!)] needs it, all future changes will now +happen here. + +The commit looks *huge*, but it's mostly just large chunks of code moving +around; there's not a whole lot of new code. However, I do apologise if +anyone has their local changes conflicted when merging or rebasing against +this version, and I promise to help as much as I can. + + + +#### testing status of gitolite v2.0rc1 + +Pretty much all the major features have been properly tested using the test +suite. The following exceptions exist: + + * basic, manual, testing only + * most admin defined commands + * not yet tested + * smart http + * mirroring + * mob branches + * things which I have no easy way to test + * controlling gitweb authentication using gitolite (as described [here][gw]) + * SVN passthru + + + +### migration to gitolite v2.x + +In general, the procedure for migrating described in the install document +should suffice. Even the rc file hasn't really changed much from the latest +versions in v1.x, except that if you add a new variable to it you must also +add it to the @EXPORT list in `src/gitolite_rc.pm`. + + + +### general stuff + + * all scripts and libraries must be in the same directory. However, RPM/DEB + packagers can put the libraries where they want, as long as they can be + found in perl's default `@INC`. + + * gl-auth-command **requires** an actual `~/.gitolite.rc` (except if your + initials are "JK" or "DG", in which case `/etc/gitolite/gitolite.rc` also + works!) It knows how to look around and set env vars etc correctly + + * all programs except gl-auth-command **require** the environment variables + `GL_RC` and `GL_BINDIR` set properly. Your best bet is to run them *via* + gl-auth-command, like so: + + path/to/gl-auth-command -e other_program other_program_arguments + + In any case none of these programs are meant to be run manually -- pretty + much all of them are run via gl-auth-command or from something that was + forked from it so the variables *will* exist during normal operation. + + + +### the rc file + +The 'rc' file has one major change from v1: any new values in the rc file need +to be added to the @EXPORT list in `src/gitolite_rc.pm`. + + + +### modules + +There are 3 "modules" (`gitolite_rc`, `gitolite_env`, and `gitolite` itself). +Their purposes should be fairly obvious. + + + +### that 'bindir' thing + +The importance of `GL_BINDIR` is that the command= argument in +`~/.ssh/authorized_keys` must be a full path, ideally, and the compile script +gets this from `GL_BINDIR`. + + + +#### from perl + + * for frequently run perl programs, I prefer my method + + * gl-auth-command -- this is invoked with a full path + * gl-time -- same as above + + * "their" ideal is "FindBin". I will use it only on manually or + infrequently run programs + + * gl-setup-authkeys (external shim to compile keys separately from PTA) + + + +#### from shell + + * a perl program called gl-query-rc finds its own BINDIR (using my perl + method, not FindBin). This is suitable for calling from shell scripts + as `${0%/*}/gl-query-rc GL_BINDIR` + + * gl-mirror-shell (frequent use) + * gl-setup + * gl-tool + + + +#### OUTLIER! + + * gl-dont-panic is an outlier. For some silly reason I have the notion that + even if it runs from /tmp it should get the right values, so it is the + only one that interrogates `~/.ssh/authorized_keys` to get the actual + BINDIR in use! + + + +### special types of setups + + + +#### Fedora + +Fedora has a very special setup, as follows: + + * each user has his own userid and login + * his/her ~/.ssh/authkeys file (containing only his/her key) has a + "command=" clause invoking gl-auth-command + * trusted users have "gl-auth-command -s" meaning they can get a shell if + they want to + + * actual git repos are under "git" (or some such), and include the chmod g+s + (git init --shared) unix perms tricks for shared access + + * but since they're coming through gl-auth, branch-level acls are in effect + + * the gitolite config file is generated from some database and compiled (all + via cron) + + * the keydir/ is empty; in fact they probably don't use the admin repo at + all, AFAIK + +The most important implication of this setup is that **the RC file is no +longer is `$HOME` of the 'git' user**. They keep it in +`/etc/gitolite/gitolite.rc`. This means that a properly setup rc file must +already be present in `/etc/gitolite/gitolite.rc` before doing any such +installs. + +---- + +# **Why v2?** + +I went onto `#perl` to ask some question about setpriority() and got yelled at +for writing "horrible code". And that was one of the kinder comments; my +rather fragile ego is trying to forget the rest ;-) + +They also gave me a link to a PDF book, "Modern Perl" by 'chromatic'. Nice +book; one of the first things you learn from it is that you should not go to +`#perl` for general help. + +Anyway, the summary of the collective angst of `#perl` (well 2 people anyway) +was: use Getopt::Long, FindBin, 'use lib', a library for HTTP stuff, stop +prefixing subs with '&', and get rid of the huge number of 'our' declarations. + +That last item is the only one I totally agree with, because it was on my long +term todo list anyway. And 'use lib' sorta goes with it, so that's fine too. +And as soon as I found that vim colors the sub names differently if you take +out the '&' I decided I'd do that too :-) [But honestly, if `&sub` is so bad +shouldn't "man perlsub" at least say something negative about it, other than +"disables prototype checking", which doesn't matter here since I'm not using +prototypes?] + +As for the rest, FindBin brings in a good 1000+ lines for something that I do +in a line or two (since I don't care about all the pathological edge cases). +Getopt::Long is 2649 lines to replace the code below [note that there *is* +only one possible option to this command, and it is *never* run manually +either, so I don't need any fancy features]: + + my $shell_allowed = 0; + if (@ARGV and $ARGV[0] eq '-s') { + $shell_allowed = 1; + shift; + } + +Apparently TMTOWTDI has given way to TOOWTDI. + +Anyway, I spent a few hours refactoring it. And I do thank them for pushing +me to stop being lazy on the "our" business. + +[gw]: https://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#_easier_to_link_gitweb_authorisation_with_gitolite diff --git a/doc/gitolite.rc.mkd b/doc/gitolite.rc.mkd index e354ff0..83e86d7 100644 --- a/doc/gitolite.rc.mkd +++ b/doc/gitolite.rc.mkd @@ -61,7 +61,7 @@ things happen if you change them. -### variables with an efficiency impact +### variables with an efficiency/performance impact * `$GL_BIG_CONFIG`, boolean, default 0 @@ -79,6 +79,12 @@ things happen if you change them. blocks repo config settings. Please read [doc/big-config.mkd][bc] for more details. + * `$GL_NICE_VALUE`, boolean, default undef + + The nice value to run under. Applicable only if it is greater than 0. + Please do NOT set it if your bits/resource.h does not define PRIO_PROCESS + is 0. For Linux this is true... + ### variables with a security impact diff --git a/doc/mirroring.mkd b/doc/mirroring.mkd index 7b18e01..8392923 100644 --- a/doc/mirroring.mkd +++ b/doc/mirroring.mkd @@ -113,17 +113,21 @@ bar.pub and baz.pub, etc. #### setup the mirror-shell on each server +XXX review this document after testing mirroring... + If you installed gitolite using the from client method, run the following: # on foo - export GL_ADMINDIR=` cd $HOME;perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR'` + export GL_BINDIR=$HOME/.gitolite/src cat bar.pub baz.pub | - sed -e 's,^,command="'$GL_ADMINDIR'/src/gl-mirror-shell" ,' >> ~/.ssh/authorized_keys + sed -e 's,^,command="'$GL_BINDIR'/gl-mirror-shell" ,' >> ~/.ssh/authorized_keys If you installed using any of the other 3 methods do this: + # on foo + export GL_BINDIR=`gl-query-rc GL_BINDIR` cat bar.pub baz.pub | - sed -e 's,^,command="'$(which gl-mirror-shell)'" ,' >> ~/.ssh/authorized_keys + sed -e 's,^,command="'$GL_BINDIR'/gl-mirror-shell" ,' >> ~/.ssh/authorized_keys Also do the same thing on the other machines. diff --git a/doc/shell-games.mkd b/doc/shell-games.mkd index 8f8d621..c9a1e74 100644 --- a/doc/shell-games.mkd +++ b/doc/shell-games.mkd @@ -36,7 +36,9 @@ clone's `hooks/common` directory, containing the following code: #!/bin/bash - GL_ADMINDIR=` cd;perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR'` + [ -z "$GL_RC" ] && { echo "ENV GL_RC not set"; exit 1; } + + GL_ADMINDIR=`$GL_BINDIR/gl-query-rc GL_ADMINDIR` cp $GL_ADMINDIR/local/gitolite.rc $HOME/.gitolite.rc cp -a $GL_ADMINDIR/local/hooks/* $GL_ADMINDIR/hooks/common diff --git a/hooks/common/update b/hooks/common/update index d7296ae..9ed1ea8 100755 --- a/hooks/common/update +++ b/hooks/common/update @@ -6,50 +6,32 @@ use warnings; # === update === # this is gitolite's update hook -# part of the gitolite (GL) suite - -# how run: via git, being copied as .git/hooks/update in every repo -# when: every push -# input: -# - see man githooks for STDIN -# - uses the compiled config file to get permissions info -# output: based on permissions etc., exit 0 or 1 -# security: -# - none - -# robustness: - -# other notes: - # ---------------------------------------------------------------------------- -# common definitions +# find the rc file, then pull the libraries # ---------------------------------------------------------------------------- -our ($GL_CONF_COMPILED, $UPDATE_CHAINS_TO, $GL_PERFLOGT); -our %repos; +BEGIN { + die "ENV GL_RC not set\n" unless $ENV{GL_RC}; + die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR}; +} -# people with shell access should be allowed to bypass the update hook, simply -# by setting an env var that the ssh "front door" will never set -exit 0 if exists $ENV{GL_BYPASS_UPDATE_HOOK}; - -# we should already have the GL_RC env var set when we enter this hook -die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; - -unshift @INC, $ENV{GL_BINDIR}; -require gitolite or die "parse gitolite.pm failed\n"; - -my ($perm, $creator, $wild) = &repo_rights($ENV{GL_REPO}); -my $reported_repo = $ENV{GL_REPO} . ( $ENV{GL_REPOPATT} ? " ($ENV{GL_REPOPATT})" : "" ); +use lib $ENV{GL_BINDIR}; +use gitolite_rc; +use gitolite qw(:DEFAULT %repos); # ---------------------------------------------------------------------------- # start... # ---------------------------------------------------------------------------- -my @saved_ARGV = @ARGV; # for chaining to another update hook at the end +# people with shell access should be allowed to bypass the update hook, simply +# by setting an env var that the ssh "front door" will never set +exit 0 if exists $ENV{GL_BYPASS_UPDATE_HOOK}; -my $ref = shift; -my $oldsha = shift; -my $newsha = shift; +my ($perm, $creator, $wild) = repo_rights($ENV{GL_REPO}); +my $reported_repo = $ENV{GL_REPO} . ( $wild ? " ($wild)" : "" ); + +# arguments are as supplied to an update hook by git; man githooks +my ($ref, $oldsha, $newsha) = @ARGV; my $merge_base = '0' x 40; # compute a merge-base if both SHAs are non-0, else leave it as '0'x40 # (i.e., for branch create or delete, merge_base == '0'x40) @@ -97,21 +79,21 @@ if (exists $repos{$ENV{GL_REPO}}{NAME_LIMITS}) { push @refs, map { chomp; s/^/NAME\//; $_; } `git diff --name-only $oldtree $newtree`; } -# and in this version, we have many "refs" to check. The one we print in the -# log is the *first* one (which is a *real* ref, like refs/heads/master), -# while all the rest (if they exist) are like NAME/something. So we do the -# first one separately to capture it, then run the rest (if any) +# we potentially have many "refs" to check. The one we print in the log is +# the *first* one (which is a *real* ref, like refs/heads/master), while all +# the rest (if they exist) are like NAME/something. So we do the first one +# separately to capture it, then run the rest (if any) my $log_refex = check_ref(\@allowed_refs, $ENV{GL_REPO}, (shift @refs), $att_acc); -&check_ref (\@allowed_refs, $ENV{GL_REPO}, $_ , $att_acc) for @refs; +check_ref (\@allowed_refs, $ENV{GL_REPO}, $_ , $att_acc) for @refs; # if we returned at all, all the checks succeeded, so we log the action and exit 0 -&log_it("", "$att_acc\t" . substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) . +log_it("", "$att_acc\t" . substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) . "\t$reported_repo\t$ref\t$log_refex"); # now chain to the local admin defined update hook, if present $UPDATE_CHAINS_TO ||= 'hooks/update.secondary'; -exec $UPDATE_CHAINS_TO, @saved_ARGV +exec $UPDATE_CHAINS_TO, @ARGV if -f $UPDATE_CHAINS_TO or -l $UPDATE_CHAINS_TO; exit 0; diff --git a/hooks/gitolite-admin/post-update b/hooks/gitolite-admin/post-update index b390827..4293c1f 100755 --- a/hooks/gitolite-admin/post-update +++ b/hooks/gitolite-admin/post-update @@ -1,5 +1,9 @@ #!/bin/sh +[ -z "$GL_RC" ] && die "ENV GL_RC not set" +[ -z "$GL_BINDIR" ] && die "ENV GL_BINDIR not set" +[ -z "$GL_ADMINDIR" ] && die "ENV GL_ADMINDIR not set" + # ensure that the admin is not sneaking in src/ and hooks/ :) GIT_WORK_TREE=$GL_ADMINDIR git ls-tree --name-only master | perl -lne 'exit 1 if /^(src|hooks)$/' || { @@ -19,7 +23,7 @@ $GL_BINDIR/gl-compile-conf cd $od -ADMIN_POST_UPDATE_CHAINS_TO=` cd $HOME;perl -e 'do ".gitolite.rc"; print $ADMIN_POST_UPDATE_CHAINS_TO'` +ADMIN_POST_UPDATE_CHAINS_TO=`$GL_BINDIR/gl-query-rc ADMIN_POST_UPDATE_CHAINS_TO` [ -n "$ADMIN_POST_UPDATE_CHAINS_TO" ] || ADMIN_POST_UPDATE_CHAINS_TO=hooks/post-update.secondary if [ -f $ADMIN_POST_UPDATE_CHAINS_TO ] || [ -L $ADMIN_POST_UPDATE_CHAINS_TO ] diff --git a/src/gitolite.pm b/src/gitolite.pm index 53368e9..9bdf456 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -1,56 +1,71 @@ +# lots of common routines + +package gitolite; +use Exporter 'import'; +@EXPORT = qw( + can_read + check_access + check_ref + check_repo_write_enabled + cli_repo_rights + dbg + list_phy_repos + ln_sf + log_it + new_repo + new_wild_repo + repo_rights + run_custom_command + setup_authkeys + setup_daemon_access + setup_git_configs + setup_gitweb_access + shell_out + special_cmd + try_adc + wrap_chdir + wrap_open + wrap_print +); +@EXPORT_OK = qw( + %repos + %groups + %git_configs + %split_conf +); + use strict; +use warnings; + use Data::Dumper; $Data::Dumper::Deepcopy = 1; $|++; -# this file is commonly used using "require". It is not required to use "use" -# (because it doesn't live in a different package) - -# warning: preceding para requires 4th attribute of a programmer after -# laziness, impatience, and hubris: sense of humour :-) - -# WARNING -# ------- -# the name of this file will change as soon as its function/feature set -# stabilises enough ;-) - -# right now all it does is -# - define a function that tells you where to find the rc file -# - define a function that creates a new repo and give it our update hook - # ---------------------------------------------------------------------------- -# common definitions +# find the rc file, then pull the libraries # ---------------------------------------------------------------------------- -our $ABRT = "\n\t\t***** ABORTING *****\n "; -our $WARN = "\n\t\t***** WARNING *****\n "; +BEGIN { + die "ENV GL_RC not set\n" unless $ENV{GL_RC}; + die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR}; +} -# commands we're expecting -our $R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/; -our $W_COMMANDS=qr/^git[ -]receive-pack$/; +use lib $ENV{GL_BINDIR}; +use gitolite_rc; -# note that REPONAME_PATT allows "/", while USERNAME_PATT does not -# also, the reason REPONAME_PATT is a superset of USERNAME_PATT is (duh!) -# because in this version, a repo can have "CREATOR" in the name (see docs) -our $REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$); # very simple pattern -our $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); # very simple pattern -# same as REPONAME, but used for wildcard repos, allows some common regex metas -our $REPOPATT_PATT=qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/-]*$); -# ADC commands and arguments must match this pattern -our $ADC_CMD_ARGS_PATT=qr(^[0-9a-zA-Z._\@/+:-]*$); +# ---------------------------------------------------------------------------- +# the big data structures we care about +# ---------------------------------------------------------------------------- -# these come from the RC file -our ($REPO_UMASK, $GL_WILDREPOS, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS, $REPO_BASE, $GL_CONF_COMPILED, $GL_BIG_CONFIG, $GL_PERFLOGT, $PROJECTS_LIST, $GL_ALL_INCLUDES_SPECIAL, $GL_SITE_INFO, $GL_GET_MEMBERSHIPS_PGM, $GL_WILDREPOS_PERM_CATS); our %repos; our %groups; our %git_configs; -our %split_conf;; +our %split_conf; our $data_version; -our $current_data_version = '1.7'; # the following are read in from individual repo's gl-conf files, if present -our %one_repo; -our %one_git_config; +our %one_repo; # corresponds to what goes into %repos +our %one_git_config; # ditto for %git_configs # ---------------------------------------------------------------------------- # convenience subs @@ -94,45 +109,12 @@ sub add_del_line { } sub dbg { + use Data::Dumper; for my $i (@_) { - print STDERR "DBG: $i\n"; + print STDERR "DBG: " . Dumper($i); } } -my $http_headers_printed = 0; -sub print_http_headers { - my($code, $text) = @_; - - return if $http_headers_printed++; - $code ||= 200; - $text ||= "OK - gitolite"; - - $|++; - print "Status: $code $text\r\n"; - print "Expires: Fri, 01 Jan 1980 00:00:00 GMT\r\n"; - print "Pragma: no-cache\r\n"; - print "Cache-Control: no-cache, max-age=0, must-revalidate\r\n"; - print "\r\n"; -} - -sub get_logfilename { - # this sub has a wee little side-effect; it sets $ENV{GL_TS} - my($template) = shift; - - my ($s, $min, $h, $d, $m, $y) = (localtime)[0..5]; - $y += 1900; $m++; # usual adjustments - for ($s, $min, $h, $d, $m) { - $_ = "0$_" if $_ < 10; - } - $ENV{GL_TS} = "$y-$m-$d.$h:$min:$s"; - - # substitute template parameters and set the logfile name - $template =~ s/%y/$y/g; - $template =~ s/%m/$m/g; - $template =~ s/%d/$d/g; - return ($template); -} - sub log_it { my ($ip, $logmsg); open my $log_fh, ">>", $ENV{GL_LOG} or die "open log failed: $!\n"; @@ -147,33 +129,6 @@ sub log_it { close $log_fh or die "close log failed: $!\n"; } -# check one ref -sub check_ref { - - # normally, the $ref will be whatever ref the commit is trying to update - # (like refs/heads/master or whatever). At least one of the refexes that - # pertain to this user must match this ref **and** the corresponding - # permission must also match the action (W or +) being attempted. If none - # of them match, the access is denied. - - # Notice that the function DIES unless a non-false 5th argument is present - - my ($allowed_refs, $repo, $ref, $perm, $dry_run) = @_; - my @allowed_refs = sort { $a->[0] <=> $b->[0] } @{$allowed_refs}; - for my $ar (@allowed_refs) { - my $refex = $ar->[1]; - # refex? sure -- a regex to match a ref against :) - next unless $ref =~ /^$refex/; - return "DENIED by $refex" if $ar->[2] eq '-' and $dry_run; - die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->[2] eq '-'; - - # as far as *this* ref is concerned we're ok - return $refex if ($ar->[2] =~ /\Q$perm/); - } - return "DENIED by fallthru" if $dry_run; - die "$perm $ref $repo $ENV{GL_USER} DENIED by fallthru\n"; -} - # ln -sf :-) sub ln_sf { @@ -200,71 +155,35 @@ sub list_phy_repos return @phy_repos; } - # ---------------------------------------------------------------------------- -# birth and death registration ;-) +# serious logic subs (as opposed to just "convenience" subs) # ---------------------------------------------------------------------------- -# background +# check one ref +sub check_ref { -# till now, the rc file was in one fixed place: .gitolite.rc in $HOME of the -# user hosting the gitolite repos. This was fine, because gitolite is all -# about empowering non-root users :-) + # normally, the $ref will be whatever ref the commit is trying to update + # (like refs/heads/master or whatever). At least one of the refexes that + # pertain to this user must match this ref **and** the corresponding + # permission must also match the action (W/+, or C/D if used) being + # attempted. If none of them match, the access is denied. -# but in smart http mode, running under "apache", you should actually use -# $GITOLITE_HTTP_HOME instead of $HOME (in fact $HOME may not even be -# defined). However, the dependency on $HOME is so pervasive that we'd best -# just set it here and be done. We also set $ENV{GL_RC} to point to the rc -# file + # NOTE: the function DIES when access is denied, unless arg 5 is true -# every gitolite program ends up calling this anyway, so that's birth + my ($allowed_refs, $repo, $ref, $perm, $dry_run) = @_; + my @allowed_refs = sort { $a->[0] <=> $b->[0] } @{$allowed_refs}; + for my $ar (@allowed_refs) { + my $refex = $ar->[1]; + # refex? sure -- a regex to match a ref against :) + next unless $ref =~ /^$refex/; + return "DENIED by $refex" if $ar->[2] eq '-' and $dry_run; + die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->[2] eq '-'; -# the second thing we need to do is handle death a little better. A plain -# "die" was fine for ssh but http has all that extra gunk it needs. So we -# need to, in effect, create a "death handler". - -# the name of the sub, however, is a holdover from when that was the sole -# purpose. I suck at function names anyway... - -sub where_is_rc -{ - die "I need either HOME or GITOLITE_HTTP_HOME env vars set\n" unless $ENV{GITOLITE_HTTP_HOME} or $ENV{HOME}; - if ($ENV{GITOLITE_HTTP_HOME}) { - # smart http mode; GITOLITE_HTTP_HOME becomes our HOME - $ENV{HOME} = $ENV{GITOLITE_HTTP_HOME}; - - $SIG{__DIE__} = sub { - my $service = ($ENV{SSH_ORIGINAL_COMMAND} =~ /git-receive-pack/ ? 'git-receive-pack' : 'git-upload-pack'); - my $message = shift; chomp($message); - print STDERR "$message\n"; - - # format the service response, then the message. With initial - # help from Ilari and then a more detailed email from Shawn... - $service = "# service=$service\n"; $message = "ERR $message\n"; - $service = sprintf("%04X", length($service)+4) . "$service"; # no CRLF on this one - $message = sprintf("%04X", length($message)+4) . "$message"; - - &print_http_headers(); - print $service; - print "0000"; # flush-pkt, apparently - print $message; - print STDERR $service; - print STDERR $message; - exit 0; # if it's ok for die_webcgi in git.git/http-backend.c, it's ok for me ;-) - } - } - - return if $ENV{GL_RC}; - - # Fedora doesn't actually have a "hosting user" at all (yeah -- bet you - # didn't know gitolite was *that* flexible!), so there's no fixed $HOME, - # and they prefer to keep their RC file in /etc/gitolite. - for my $glrc ( $ENV{HOME} . "/.gitolite.rc", "/etc/gitolite/gitolite.rc" ) { - if (-f $glrc) { - $ENV{GL_RC} = $glrc; - last; - } + # as far as *this* ref is concerned we're ok + return $refex if ($ar->[2] =~ /\Q$perm/); } + return "DENIED by fallthru" if $dry_run; + die "$perm $ref $repo $ENV{GL_USER} DENIED by fallthru\n"; } # ---------------------------------------------------------------------------- @@ -302,8 +221,21 @@ sub new_repo system("env GL_REPO='$repo' hooks/gl-post-init") if -x "hooks/gl-post-init"; } +sub new_wild_repo { + my ($repo, $user) = @_; + + wrap_chdir("$ENV{GL_REPO_BASE_ABS}"); + new_repo($repo, "$GL_ADMINDIR/hooks/common", $user); + # note pwd is now the bare "repo.git"; new_repo does that... + wrap_print("gl-perms", "$GL_WILDREPOS_DEFPERMS\n") if $GL_WILDREPOS_DEFPERMS; + setup_git_configs($repo, \%git_configs); + setup_daemon_access($repo); + add_del_line ("$repo.git", $PROJECTS_LIST, setup_gitweb_access($repo, '', '')); + wrap_chdir($ENV{HOME}); +} + # ---------------------------------------------------------------------------- -# metaphysics (like, "is there a god?", "who created me?", etc) +# wild_repo_rights # ---------------------------------------------------------------------------- { @@ -372,7 +304,7 @@ sub new_repo # "DOG bar foo baz", you add DOG => foo to the hash. And # since specific perms must override @all, we do @all first. $perm_cats{$1} = '@all' while ($perms =~ /^[ \t]*(\S+)(?=[ \t]).*[ \t]\@all([ \t]|$)/mg); - $perm_cats{$1} = $user while ($perms =~ /^[ \t]*(\S+)(?=[ \t]).*[ \t]$user([ \t]|$)/mg); + $perm_cats{$1} = $user while ($perms =~ /^[ \t]*(\S+)(?=[ \t]).*[ \t]$user([ \t]|$)/mg); # validate the categories being sent back for (sort keys %perm_cats) { die "invalid permission category $_\n" unless $GL_WILDREPOS_PERM_CATS =~ /(^|\s)$_(\s|$)/; @@ -393,7 +325,7 @@ sub get_set_perms my($repo, $verb, $user) = @_; # set default categories $GL_WILDREPOS_PERM_CATS ||= "READERS WRITERS"; - my ($creator, $dummy, $dummy2) = &wild_repo_rights($repo, ""); + my ($creator, $dummy, $dummy2) = wild_repo_rights($repo, ""); die "$repo doesnt exist or is not yours\n" unless $user eq $creator; wrap_chdir("$ENV{GL_REPO_BASE_ABS}"); wrap_chdir("$repo.git"); @@ -419,7 +351,7 @@ sub get_set_perms # gitweb and daemon setup_daemon_access($repo); # add or delete line (arg1) from file (arg2) depending on arg3 - &add_del_line ("$repo.git", $PROJECTS_LIST, &setup_gitweb_access($repo, '', '')); + add_del_line ("$repo.git", $PROJECTS_LIST, setup_gitweb_access($repo, '', '')); } } @@ -430,7 +362,7 @@ sub get_set_perms sub get_set_desc { my($repo, $verb, $user) = @_; - my ($creator, $dummy, $dummy2) = &wild_repo_rights($repo, ""); + my ($creator, $dummy, $dummy2) = wild_repo_rights($repo, ""); die "$repo doesnt exist or is not yours\n" unless $user eq $creator; wrap_chdir("$ENV{GL_REPO_BASE_ABS}"); wrap_chdir("$repo.git"); @@ -475,7 +407,7 @@ sub setup_daemon_access { my $repo = shift; - if (&can_read($repo, 'daemon')) { + if (can_read($repo, 'daemon')) { system("touch $export_ok"); } else { unlink($export_ok); @@ -517,10 +449,111 @@ sub setup_gitweb_access system("git config --remove-section gitweb 2>/dev/null"); } - return ($desc or &can_read($repo, 'gitweb')); + return ($desc or can_read($repo, 'gitweb')); # this return value is used by the caller to write to projects.list } +# ---------------------------------------------------------------------------- +# print a report of $user's basic permissions +# ---------------------------------------------------------------------------- + +sub report_version { + my($user) = @_; + print "hello $user, the gitolite version here is "; + system("cat", ($GL_PACKAGE_CONF || "$GL_ADMINDIR/conf") . "/VERSION"); +} + +sub perm_code { + # print the permission code + my($all, $super, $user, $x) = @_; + return " " unless $all or $super or $user; + return " $x " unless $all or $super; # only $user (explicit access) was given + my $ret; + $ret = " \@$x" if $all; # prefix @ if repo allows access for @all users + $ret = " \#$x" if $super; # prefix # if user has access to @all repos (sort of like a super user) + $ret = " \&$x" if $all and $super; # prefix & if both the above + $ret .= ($user ? " " : "_" ); # suffix _ if no explicit access else + return $ret; +} + +# basic means wildcards will be shown as wildcards; this is pretty much what +# got parsed by the compile script +sub report_basic +{ + my($repo, $user) = @_; + + # XXX The correct way is actually to give parse_acl another argument + # (defaulting to $ENV{GL_USER}, the value being used now). But for now + # this will do, even though it's a bit of a kludge to get the basic access + # rights for some other user this way + local $ENV{GL_USER} = $user; + + parse_acl("", "CREATOR"); + # all we need is for 'keys %repos' to come up with all the names, so: + @repos{ keys %split_conf } = values %split_conf if %split_conf; + + # send back some useful info if no command was given + report_version($user); + print "\rthe gitolite config gives you the following access:\r\n"; + my $count = 0; + for my $r (sort keys %repos) { + next unless $r =~ /$repo/i; + # if $GL_BIG_CONFIG is on, limit the number of output lines to 20 + next if $GL_BIG_CONFIG and $count++ >= 20; + if ($r =~ $REPONAME_PATT and $r !~ /\bCREAT[EO]R\b/) { + parse_acl($r, "NOBODY"); + } else { + $r =~ s/\bCREAT[EO]R\b/$user/g; + parse_acl($r, $ENV{GL_USER}); + } + # @all repos; meaning of read/write flags: + # @R => @all users are allowed access to this repo + # #R => you're a super user and can see @all repos + # R => normal access + my $perm .= ( $repos{$r}{C}{'@all'} ? ' @C' : ( $repos{$r}{C}{$user} ? ' C' : ' ' ) ); + $perm .= perm_code( $repos{$r}{R}{'@all'}, $repos{'@all'}{R}{$user}, $repos{$r}{R}{$user}, 'R'); + $perm .= perm_code( $repos{$r}{W}{'@all'}, $repos{'@all'}{W}{$user}, $repos{$r}{W}{$user}, 'W'); + print "$perm\t$r\r\n" if $perm =~ /\S/; + } + print "only 20 out of $count candidate repos examined\r\nplease use a partial reponame or regex pattern to limit output\r\n" if $GL_BIG_CONFIG and $count > 20; + print "$GL_SITE_INFO\n" if $GL_SITE_INFO; +} + +# ---------------------------------------------------------------------------- +# print a report of $user's expanded permissions +# ---------------------------------------------------------------------------- + +sub expand_wild +{ + my($repo, $user) = @_; + + report_version($user); + print "\ryou have access to the following repos on the server:\r\n"; + # this is for convenience; he can copy-paste the output of the basic + # access report instead of having to manually change CREATOR to his name + $repo =~ s/\bCREAT[EO]R\b/$user/g; + + # display matching repos (from *all* the repos in the system) that $user + # has at least "R" access to + + chdir("$ENV{GL_REPO_BASE_ABS}") or die "chdir $ENV{GL_REPO_BASE_ABS} failed: $!\n"; + my $count = 0; + for my $actual_repo (`find . -type d -name "*.git"|sort`) { + chomp ($actual_repo); + $actual_repo =~ s/^\.\///; + $actual_repo =~ s/\.git$//; + # actual_repo has to match the pattern being expanded + next unless $actual_repo =~ /$repo/i; + next if $GL_BIG_CONFIG and $count++ >= 20; + + my($perm, $creator, $wild) = repo_rights($actual_repo); + next unless $perm =~ /\S/; + print "$perm\t$creator\t$actual_repo\n"; + } + print "only 20 out of $count candidate repos examined\nplease use a partial reponame or regex pattern to limit output\n" if $GL_BIG_CONFIG and $count > 20; + print "$GL_SITE_INFO\n" if $GL_SITE_INFO; +} + # ---------------------------------------------------------------------------- # parse the compiled acl # ---------------------------------------------------------------------------- @@ -530,8 +563,8 @@ sub parse_acl # IMPLEMENTATION NOTE: a wee bit of this is duplicated in the update hook; # please update that also if the interface or the env vars change - my ($GL_CONF_COMPILED, $repo, $c, %perm_cats) = @_; - my $perm_cats_sig = ''; + my ($repo, $c, %perm_cats) = @_; + my $perm_cats_sig = ''; # a "signature" of the perm_cats hash map { $perm_cats_sig .= "$_.$perm_cats{$_}," } sort keys %perm_cats; $c = "NOBODY" unless $GL_WILDREPOS; @@ -572,8 +605,8 @@ sub parse_acl my ($wild, @repo_plus, @user_plus); # expand $repo and $gl_user into all possible matching values - ($wild, @repo_plus) = &get_memberships($repo, 1); - ( @user_plus) = &get_memberships($gl_user, 0); + ($wild, @repo_plus) = get_memberships($repo, 1); + ( @user_plus) = get_memberships($gl_user, 0); # the old "convenience copy" thing. Now on steroids :) @@ -598,8 +631,6 @@ sub parse_acl } } - $ENV{GL_REPOPATT} = ""; - $ENV{GL_REPOPATT} = $wild if $wild and $GL_WILDREPOS; return ($wild); } @@ -613,108 +644,10 @@ sub add_repo_conf $git_configs{$repo} = $one_git_config{$repo}; } - # ---------------------------------------------------------------------------- -# print a report of $user's basic permissions +# repo_rights # ---------------------------------------------------------------------------- -sub report_version { - my($GL_ADMINDIR, $user) = @_; - print "hello $user, the gitolite version here is "; - system("cat", ($GL_PACKAGE_CONF || "$GL_ADMINDIR/conf") . "/VERSION"); -} - -sub perm_code { - # print the permission code - my($all, $super, $user, $x) = @_; - return " " unless $all or $super or $user; - return " $x " unless $all or $super; # only $user (explicit access) was given - my $ret; - $ret = " \@$x" if $all; # prefix @ if repo allows access for @all users - $ret = " \#$x" if $super; # prefix # if user has access to @all repos (sort of like a super user) - $ret = " \&$x" if $all and $super; # prefix & if both the above - $ret .= ($user ? " " : "_" ); # suffix _ if no explicit access else - return $ret; -} - -# basic means wildcards will be shown as wildcards; this is pretty much what -# got parsed by the compile script -sub report_basic -{ - my($GL_ADMINDIR, $GL_CONF_COMPILED, $repo, $user) = @_; - - # XXX The correct way is actually to give parse_acl another argument - # (defaulting to $ENV{GL_USER}, the value being used now). But for now - # this will do, even though it's a bit of a kludge to get the basic access - # rights for some other user this way - local $ENV{GL_USER} = $user; - - &parse_acl($GL_CONF_COMPILED, "", "CREATOR"); - # all we need is for 'keys %repos' to come up with all the names, so: - @repos{ keys %split_conf } = values %split_conf if %split_conf; - - # send back some useful info if no command was given - &report_version($GL_ADMINDIR, $user); - print "\rthe gitolite config gives you the following access:\r\n"; - my $count = 0; - for my $r (sort keys %repos) { - next unless $r =~ /$repo/i; - # if $GL_BIG_CONFIG is on, limit the number of output lines to 20 - next if $GL_BIG_CONFIG and $count++ >= 20; - if ($r =~ $REPONAME_PATT and $r !~ /\bCREAT[EO]R\b/) { - &parse_acl($GL_CONF_COMPILED, $r, "NOBODY"); - } else { - $r =~ s/\bCREAT[EO]R\b/$user/g; - &parse_acl($GL_CONF_COMPILED, $r, $ENV{GL_USER}); - } - # @all repos; meaning of read/write flags: - # @R => @all users are allowed access to this repo - # #R => you're a super user and can see @all repos - # R => normal access - my $perm .= ( $repos{$r}{C}{'@all'} ? ' @C' : ( $repos{$r}{C}{$user} ? ' C' : ' ' ) ); - $perm .= &perm_code( $repos{$r}{R}{'@all'}, $repos{'@all'}{R}{$user}, $repos{$r}{R}{$user}, 'R'); - $perm .= &perm_code( $repos{$r}{W}{'@all'}, $repos{'@all'}{W}{$user}, $repos{$r}{W}{$user}, 'W'); - print "$perm\t$r\r\n" if $perm =~ /\S/; - } - print "only 20 out of $count candidate repos examined\r\nplease use a partial reponame or regex pattern to limit output\r\n" if $GL_BIG_CONFIG and $count > 20; - print "$GL_SITE_INFO\n" if $GL_SITE_INFO; -} - -# ---------------------------------------------------------------------------- -# print a report of $user's basic permissions -# ---------------------------------------------------------------------------- - -sub expand_wild -{ - my($GL_ADMINDIR, $GL_CONF_COMPILED, $repo, $user) = @_; - - &report_version($GL_ADMINDIR, $user); - print "\ryou have access to the following repos on the server:\r\n"; - # this is for convenience; he can copy-paste the output of the basic - # access report instead of having to manually change CREATOR to his name - $repo =~ s/\bCREAT[EO]R\b/$user/g; - - # display matching repos (from *all* the repos in the system) that $user - # has at least "R" access to - - chdir("$ENV{GL_REPO_BASE_ABS}") or die "chdir $ENV{GL_REPO_BASE_ABS} failed: $!\n"; - my $count = 0; - for my $actual_repo (`find . -type d -name "*.git"|sort`) { - chomp ($actual_repo); - $actual_repo =~ s/^\.\///; - $actual_repo =~ s/\.git$//; - # actual_repo has to match the pattern being expanded - next unless $actual_repo =~ /$repo/i; - next if $GL_BIG_CONFIG and $count++ >= 20; - - my($perm, $creator, $wild) = &repo_rights($actual_repo); - next unless $perm =~ /\S/; - print "$perm\t$creator\t$actual_repo\n"; - } - print "only 20 out of $count candidate repos examined\nplease use a partial reponame or regex pattern to limit output\n" if $GL_BIG_CONFIG and $count > 20; - print "$GL_SITE_INFO\n" if $GL_SITE_INFO; -} - # there will be multiple calls to repo_rights; better to use a closure. We # might even be called from outside (see the admin-defined-commands docs for # how/why). Regardless of how we're called, we assume $ENV{GL_USER} is @@ -733,7 +666,7 @@ sub expand_wild unless ($REPO_BASE) { # means we've been called from outside; see doc/admin-defined-commands.mkd - &where_is_rc(); + where_is_rc(); die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; } @@ -749,11 +682,11 @@ sub expand_wild # "wild_repo_rights" sub for nuances. my (%perm_cats); # these will be empty if it's not a wildcard repo anyway - ($creator, %perm_cats) = &wild_repo_rights($repo, $ENV{GL_USER}); + ($creator, %perm_cats) = wild_repo_rights($repo, $ENV{GL_USER}); # get access list with these substitutions - $wild = &parse_acl($GL_CONF_COMPILED, $repo, $creator || "NOBODY", %perm_cats); + $wild = parse_acl($repo, $creator || "NOBODY", %perm_cats); } else { - $wild = &parse_acl($GL_CONF_COMPILED, $repo, $ENV{GL_USER}); + $wild = parse_acl($repo, $ENV{GL_USER}); } if ($exists) { @@ -774,8 +707,8 @@ sub expand_wild delete $repos{$repo} if $perm !~ /C/ and $wild; $creator = ""; } - $perm .= &perm_code( $repos{$repo}{R}{'@all'}, $repos{'@all'}{R}{$ENV{GL_USER}}, $repos{$repo}{R}{$ENV{GL_USER}}, 'R' ); - $perm .= &perm_code( $repos{$repo}{W}{'@all'}, $repos{'@all'}{W}{$ENV{GL_USER}}, $repos{$repo}{W}{$ENV{GL_USER}}, 'W' ); + $perm .= perm_code( $repos{$repo}{R}{'@all'}, $repos{'@all'}{R}{$ENV{GL_USER}}, $repos{$repo}{R}{$ENV{GL_USER}}, 'R' ); + $perm .= perm_code( $repos{$repo}{W}{'@all'}, $repos{'@all'}{W}{$ENV{GL_USER}}, $repos{$repo}{W}{$ENV{GL_USER}}, 'W' ); # set up for caching %repos $last_repo = $repo; @@ -784,18 +717,22 @@ sub expand_wild } } +# ---------------------------------------------------------------------------- +# helpers... +# ---------------------------------------------------------------------------- + # helper/convenience routine to get rights and ownership from a shell command sub cli_repo_rights { # check_access does a lot more, so just call it. Since it returns perms # and creator separately, just space-join them and print it. - print join(" ", &check_access($_[0])), "\n"; + print join(" ", check_access($_[0])), "\n"; } sub can_read { my $repo = shift; my $user = shift || $ENV{GL_USER}; local $ENV{GL_USER} = $user; - my ($perm, $creator, $wild) = &repo_rights($repo); + my ($perm, $creator, $wild) = repo_rights($repo); return ( ($GL_ALL_INCLUDES_SPECIAL || $user !~ /^(gitweb|daemon)$/) ? $perm =~ /R/ : $perm =~ /R / @@ -812,6 +749,117 @@ sub check_repo_write_enabled { } } +# ---------------------------------------------------------------------------- +# get memberships +# ---------------------------------------------------------------------------- + +# given a plain reponame or username, return: +# - the name itself if it's a user +# - the name itself if it's a repo and the repo exists in the config +# plus, if $GL_BIG_CONFIG is set: +# - all the groups the name belongs to +# plus, for repos: +# - all the wildcards matching it +# plus, if $GL_BIG_CONFIG is set: +# - all the groups those wildcards belong to + +# A name can normally appear (repo example) (user example) +# - directly (repo foo) (RW = bob) +# - (only for repos) as a direct wildcard (repo foo/.*) +# but if $GL_BIG_CONFIG is set, it can also appear: +# - indirectly (@g = foo; repo @g) (@ug = bob; RW = @ug)) +# - (only for repos) as an indirect wildcard (@g = foo/.*; repo @g). +# note: the wildcard stuff does not apply to username memberships + +our %extgroups_cache; +sub get_memberships { + my $base = shift; # reponame or username + my $is_repo = shift; # some true value means a repo name has been passed + + my $wild = ''; # will be a space-sep list of matching patterns + my @ret; # list of matching groups/patterns + + # direct + push @ret, $base if not $is_repo or exists $repos{$base}; + if ($is_repo and $GL_WILDREPOS) { + for my $i (sort keys %repos) { + next if $i eq $base; # "direct" name already done; skip + # direct wildcard + if ($base =~ /^$i$/) { + push @ret, $i; + $wild = ($wild ? "$wild $i" : $i); + } + } + } + + if ($GL_BIG_CONFIG) { + for my $g (sort keys %groups) { + for my $i (sort keys %{ $groups{$g} }) { + if ($base eq $i) { + # indirect + push @ret, $g; + } elsif ($is_repo and $GL_WILDREPOS and $base =~ /^$i$/) { + # indirect wildcard + push @ret, $g; + $wild = ($wild ? "$wild $i" : $i); + } + } + } + } + + # deal with returning user info first + unless ($is_repo) { + # bring in group membership info stored externally, by running + # $GL_GET_MEMBERSHIPS_PGM if it is defined + + if ($extgroups_cache{$base}) { + push @ret, @{ $extgroups_cache{$base} }; + } elsif ($GL_GET_MEMBERSHIPS_PGM) { + my @extgroups = map { s/^/@/; $_; } split ' ', `$GL_GET_MEMBERSHIPS_PGM $base`; + $extgroups_cache{$base} = \@extgroups; + push @ret, @extgroups; + } + + return (@ret); + } + + # note that there is an extra return value when called for repos (as + # opposed to being called for usernames) + return ($wild, @ret); +} + +# ---------------------------------------------------------------------------- +# generic check access routine +# ---------------------------------------------------------------------------- + +sub check_access +{ + my ($repo, $ref, $aa, $dry_run) = @_; + # aa = attempted access + + my ($perm, $creator, $wild) = repo_rights($repo); + $perm =~ s/ /_/g; + $creator =~ s/^\(|\)$//g; + return ($perm, $creator) unless $ref; + + # until I do some major refactoring (which will bloat the update hook a + # bit, sadly), this code duplicates stuff in the current update hook. + + my @allowed_refs; + # user+repo specific perms override everything else, so they come first. + # Then perms given to specific user for @all repos, and finally perms + # given to @all users for specific repo + push @allowed_refs, @ { $repos{$repo}{$ENV{GL_USER}} || [] }; + push @allowed_refs, @ { $repos{'@all'}{$ENV{GL_USER}} || [] }; + push @allowed_refs, @ { $repos{$repo}{'@all'} || [] }; + + if ($dry_run) { + return check_ref(\@allowed_refs, $repo, $ref, $aa, $dry_run); + } else { + check_ref(\@allowed_refs, $repo, $ref, $aa); + } +} + # ---------------------------------------------------------------------------- # setup the ~/.ssh/authorized_keys file # ---------------------------------------------------------------------------- @@ -820,16 +868,16 @@ sub setup_authkeys { # ARGUMENTS - my($bindir, $GL_KEYDIR, $user_list_p) = @_; + my($GL_KEYDIR, $user_list_p) = @_; # calling from outside the normal compile script may mean that argument 2 # may not be passed; so make sure it's a valid hashref, even if empty $user_list_p = {} unless $user_list_p; - # CONSTANTS + # LOCAL CONSTANTS # command and options for authorized_keys - my $AUTH_COMMAND="$bindir/gl-auth-command"; - $AUTH_COMMAND="$bindir/gl-time $bindir/gl-auth-command" if $GL_PERFLOGT; + my $AUTH_COMMAND="$ENV{GL_BINDIR}/gl-auth-command"; + $AUTH_COMMAND="$ENV{GL_BINDIR}/gl-time $ENV{GL_BINDIR}/gl-auth-command" if $GL_PERFLOGT; my $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; # START @@ -931,14 +979,14 @@ sub setup_authkeys sub special_cmd { - my ($GL_ADMINDIR, $GL_CONF_COMPILED, $shell_allowed, $RSYNC_BASE, $HTPASSWD_FILE, $SVNSERVE) = @_; + my ($shell_allowed) = @_; my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; my $user = $ENV{GL_USER}; # check each special command we know about and call it if enabled if ($cmd eq 'info') { - &report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, '^', $user); + report_basic('^', $user); print "you also have shell access\r\n" if $shell_allowed; } elsif ($cmd =~ /^info\s+(.+)$/) { my @otherusers = split ' ', $1; @@ -951,139 +999,69 @@ sub special_cmd # set up the list of users being queried; it's either a list passed in # (allowed only for admin pushers) or just $user if (@otherusers) { - my($perm, $creator, $wild) = &repo_rights('gitolite-admin'); + my($perm, $creator, $wild) = repo_rights('gitolite-admin'); die "you can't ask for others' permissions\n" unless $perm =~ /W/; } push @otherusers, $user unless @otherusers; - &parse_acl($GL_CONF_COMPILED); + parse_acl(); for my $otheruser (@otherusers) { warn("ignoring illegal username $otheruser\n"), next unless $otheruser =~ $USERNAME_PATT; - &report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $repo, $otheruser); + report_basic($repo, $otheruser); } } elsif ($HTPASSWD_FILE and $cmd eq 'htpasswd') { - &ext_cmd_htpasswd($HTPASSWD_FILE); + ext_cmd_htpasswd($HTPASSWD_FILE); } elsif ($RSYNC_BASE and $cmd =~ /^rsync /) { - &ext_cmd_rsync($GL_CONF_COMPILED, $RSYNC_BASE, $cmd); + ext_cmd_rsync($GL_CONF_COMPILED, $RSYNC_BASE, $cmd); } elsif ($SVNSERVE and $cmd eq 'svnserve -t') { - &ext_cmd_svnserve($SVNSERVE); + ext_cmd_svnserve($SVNSERVE); } else { # if the user is allowed a shell, just run the command - &log_it(); + log_it(); exec $ENV{SHELL}, "-c", $cmd if $shell_allowed; die "bad command: $cmd\n"; } } -# ---------------------------------------------------------------------------- -# get memberships -# ---------------------------------------------------------------------------- +sub run_custom_command { + my $user = shift; -# given a plain reponame or username, return: -# - the name itself if it's a user -# - the name itself if it's a repo and the repo exists in the config -# plus, if $GL_BIG_CONFIG is set: -# - all the groups the name belongs to -# plus, for repos: -# - all the wildcards matching it -# plus, if $GL_BIG_CONFIG is set: -# - all the groups those wildcards belong to - -# A name can normally appear (repo example) (user example) -# - directly (repo foo) (RW = bob) -# - (only for repos) as a direct wildcard (repo foo/.*) -# but if $GL_BIG_CONFIG is set, it can also appear: -# - indirectly (@g = foo; repo @g) (@ug = bob; RW = @ug)) -# - (only for repos) as an indirect wildcard (@g = foo/.*; repo @g). -# note: the wildcard stuff does not apply to username memberships - -our %extgroups_cache; -sub get_memberships { - my $base = shift; # reponame or username - my $is_repo = shift; # some true value means a repo name has been passed - - my $wild = ''; # will be a space-sep list of matching patterns - my @ret; # list of matching groups/patterns - - # direct - push @ret, $base if not $is_repo or exists $repos{$base}; - if ($is_repo and $GL_WILDREPOS) { - for my $i (sort keys %repos) { - next if $i eq $base; # "direct" name already done; skip - # direct wildcard - if ($base =~ /^$i$/) { - push @ret, $i; - $wild = ($wild ? "$wild $i" : $i); - } - } + my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; + my ($verb, $repo) = ($cmd =~ /^\s*(\S+)(?:\s+'?\/?(.*?)(?:\.git)?'?)?$/); + # deal with "no argument" cases + $verb eq 'expand' ? $repo = '^' : die "$verb needs an argument\n" unless $repo; + if ($repo =~ $REPONAME_PATT and $verb =~ /getperms|setperms/) { + # with an actual reponame, you can "getperms" or "setperms" + get_set_perms($repo, $verb, $user); } - - if ($GL_BIG_CONFIG) { - for my $g (sort keys %groups) { - for my $i (sort keys %{ $groups{$g} }) { - if ($base eq $i) { - # indirect - push @ret, $g; - } elsif ($is_repo and $GL_WILDREPOS and $base =~ /^$i$/) { - # indirect wildcard - push @ret, $g; - $wild = ($wild ? "$wild $i" : $i); - } - } - } + elsif ($repo =~ $REPONAME_PATT and $verb =~ /(get|set)desc/) { + # with an actual reponame, you can "getdesc" or "setdesc" + get_set_desc($repo, $verb, $user); } - - # deal with returning user info first - unless ($is_repo) { - # bring in group membership info stored externally, by running - # $GL_GET_MEMBERSHIPS_PGM if it is defined - - if ($extgroups_cache{$base}) { - push @ret, @{ $extgroups_cache{$base} }; - } elsif ($GL_GET_MEMBERSHIPS_PGM) { - my @extgroups = map { s/^/@/; $_; } split ' ', `$GL_GET_MEMBERSHIPS_PGM $base`; - $extgroups_cache{$base} = \@extgroups; - push @ret, @extgroups; - } - - return (@ret); + elsif ($verb eq 'expand') { + # with a wildcard, you can "expand" it to see what repos actually match + die "$repo has invalid characters" unless "x$repo" =~ $REPOPATT_PATT; + expand_wild($repo, $user); + } else { + die "$cmd doesn't make sense to me\n"; } - - # note that there is an extra return value when called for repos (as - # opposed to being called for usernames) - return ($wild, @ret); } -# ---------------------------------------------------------------------------- -# generic check access routine -# ---------------------------------------------------------------------------- +sub shell_out { + my $shell = $ENV{SHELL}; + $shell =~ s/.*\//-/; # change "/bin/bash" to "-bash" + log_it($shell); + exec { $ENV{SHELL} } $shell; +} -sub check_access -{ - my ($repo, $ref, $aa, $dry_run) = @_; - # aa = attempted access - - my ($perm, $creator, $wild) = &repo_rights($repo); - $perm =~ s/ /_/g; - $creator =~ s/^\(|\)$//g; - return ($perm, $creator) unless $ref; - - # until I do some major refactoring (which will bloat the update hook a - # bit, sadly), this code duplicates stuff in the current update hook. - - my @allowed_refs; - # user+repo specific perms override everything else, so they come first. - # Then perms given to specific user for @all repos, and finally perms - # given to @all users for specific repo - push @allowed_refs, @ { $repos{$repo}{$ENV{GL_USER}} || [] }; - push @allowed_refs, @ { $repos{'@all'}{$ENV{GL_USER}} || [] }; - push @allowed_refs, @ { $repos{$repo}{'@all'} || [] }; - - if ($dry_run) { - return &check_ref(\@allowed_refs, $repo, $ref, $aa, $dry_run); - } else { - &check_ref(\@allowed_refs, $repo, $ref, $aa); +sub try_adc { + my ($cmd, @args) = split ' ', $ENV{SSH_ORIGINAL_COMMAND}; + if (-x "$GL_ADC_PATH/$cmd") { + # yes this is rather strict, sorry. + do { die "I don't like $_\n" unless $_ =~ $ADC_CMD_ARGS_PATT } for ($cmd, @args); + log_it("$GL_ADC_PATH/$ENV{SSH_ORIGINAL_COMMAND}"); + exec("$GL_ADC_PATH/$cmd", @args); } } @@ -1114,11 +1092,11 @@ sub ext_cmd_rsync # ok now check if we're permitted to execute a $perm action on $path # (taken as a refex) using rsync. - &check_access('EXTCMD/rsync', "NAME/$path", $perm); + check_access('EXTCMD/rsync', "NAME/$path", $perm); # that should "die" if there's a problem wrap_chdir($RSYNC_BASE); - &log_it(); + log_it(); exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND}; } @@ -1162,4 +1140,6 @@ sub ext_cmd_svnserve die "svnserve exec failed\n"; } +# ------------------------------------------------------------------------------ +# per perl rules, this should be the last line in such a file: 1; diff --git a/src/gitolite_env.pm b/src/gitolite_env.pm new file mode 100644 index 0000000..81b8168 --- /dev/null +++ b/src/gitolite_env.pm @@ -0,0 +1,157 @@ +# stuff that detects or sets up the runtime environment + +package gitolite_env; +use Exporter 'import'; +@EXPORT = qw( + setup_environment + simulate_ssh_connection + get_logfilename +); + +use strict; +use warnings; + +# ---------------------------------------------------------------------------- +# find the rc file, then pull the libraries +# ---------------------------------------------------------------------------- + +BEGIN { + die "ENV GL_RC not set\n" unless $ENV{GL_RC}; + die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR}; +} + +use lib $ENV{GL_BINDIR}; +use gitolite_rc; +use gitolite; + +# ---------------------------------------------------------------------------- +# start +# ---------------------------------------------------------------------------- + +# firstly, the following function, 'setup_environment', is not only about env +# vars; it does other stuff too (like umask, nice...) + +# a lot of stuff gets carried around in env vars primarily for 2 reasons. One +# is that git calls the hooks, so they're not in the same 'process' as the +# 'gl-auth-command' that probably started things off. + +# Granted; we could write the same 'discovery' within the hook code, but +# that's needless code duplication, plus in some cases a good amount of +# inefficiency. + +# Even more important, we do *not* want to burden the ADCs (admin defined +# commands) with all this discovery, because those are written by the users +# themselves (my 'user' == some gitolite 'admin' somewhere; I don't mean +# 'gitolite user') + +# think of it OS-supported memo-ization :-) +sub setup_environment { + $ENV{GL_ADMINDIR} = $GL_ADMINDIR; + $ENV{GL_LOG} = get_logfilename($GL_LOGT); + $ENV{PATH} = "$GIT_PATH:$ENV{PATH}" if $GIT_PATH; + # set default permission of wildcard repositories + $ENV{GL_WILDREPOS_DEFPERMS} = $GL_WILDREPOS_DEFPERMS if $GL_WILDREPOS_DEFPERMS; + # this is used in so many places, inside and outside gitolite by external + # hooks and ADCs, it isn't even funny... + $ENV{GL_REPO_BASE_ABS} = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); + + # be nice if asked. If you want me to pull in BSD::Resource to get rid of + # the first '0', feel free to send me a patch that does everything needed + # from within my own installer, does not require internet access (don't + # ask!), and doesn't require a C compiler or the perl-devel (or eqvt + # named) packages. Heck in some cases it's not even Linux... + setpriority(0, 0, $GL_NICE_VALUE) if $GL_NICE_VALUE and $GL_NICE_VALUE > 0; + + umask($REPO_UMASK); + + set_up_http_death() if $ENV{GITOLITE_HTTP_HOME}; +} + +sub simulate_ssh_connection { + # these patterns indicate normal git usage; see "services[]" in + # http-backend.c for how I got that. Also note that "info" is overloaded; + # git uses "info/refs...", while gitolite uses "info" or "info?...". So + # there's a "/" after info in the list below + if ($ENV{PATH_INFO} =~ m(^/(.*)/(HEAD$|info/refs$|objects/|git-(?:upload|receive)-pack$))) { + my $repo = $1; + my $verb = ($ENV{REQUEST_URI} =~ /git-receive-pack/) ? 'git-receive-pack' : 'git-upload-pack'; + $ENV{SSH_ORIGINAL_COMMAND} = "$verb '$repo'"; + } else { + # this is one of our custom commands; could be anything really, + # because of the adc feature + my ($verb) = ($ENV{PATH_INFO} =~ m(^/(\S+))); + my $args = $ENV{QUERY_STRING}; + $args =~ s/\+/ /g; + $ENV{SSH_ORIGINAL_COMMAND} = $verb; + $ENV{SSH_ORIGINAL_COMMAND} .= " $args" if $args; + print_http_headers(); # in preparation for the eventual output! + } + $ENV{SSH_CONNECTION} = "$ENV{REMOTE_ADDR} $ENV{REMOTE_PORT} $ENV{SERVER_ADDR} $ENV{SERVER_PORT}"; +} + +# a plain "die" was fine for ssh but http has all that extra gunk it needs. +# So we need to, in effect, create a "death handler". +sub set_up_http_death +{ + $SIG{__DIE__} = sub { + my $service = ($ENV{SSH_ORIGINAL_COMMAND} =~ /git-receive-pack/ ? 'git-receive-pack' : 'git-upload-pack'); + my $message = shift; chomp($message); + print STDERR "$message\n"; + + # format the service response, then the message. With initial + # help from Ilari and then a more detailed email from Shawn... + $service = "# service=$service\n"; $message = "ERR $message\n"; + $service = sprintf("%04X", length($service)+4) . "$service"; # no CRLF on this one + $message = sprintf("%04X", length($message)+4) . "$message"; + + print_http_headers(); + print $service; + print "0000"; # flush-pkt, apparently + print $message; + print STDERR $service; + print STDERR $message; + exit 0; # if it's ok for die_webcgi in git.git/http-backend.c, it's ok for me ;-) + } +} + +# ---------------------------------------------------------------------------- +# helpers +# ---------------------------------------------------------------------------- + +my $http_headers_printed = 0; +sub print_http_headers { + my($code, $text) = @_; + + return if $http_headers_printed++; + $code ||= 200; + $text ||= "OK - gitolite"; + + $|++; + print "Status: $code $text\r\n"; + print "Expires: Fri, 01 Jan 1980 00:00:00 GMT\r\n"; + print "Pragma: no-cache\r\n"; + print "Cache-Control: no-cache, max-age=0, must-revalidate\r\n"; + print "\r\n"; +} + +sub get_logfilename { + # this sub has a wee little side-effect; it sets $ENV{GL_TS} + my($template) = shift; + + my ($s, $min, $h, $d, $m, $y) = (localtime)[0..5]; + $y += 1900; $m++; # usual adjustments + for ($s, $min, $h, $d, $m) { + $_ = "0$_" if $_ < 10; + } + $ENV{GL_TS} = "$y-$m-$d.$h:$min:$s"; + + # substitute template parameters and set the logfile name + $template =~ s/%y/$y/g; + $template =~ s/%m/$m/g; + $template =~ s/%d/$d/g; + return ($template); +} + +# ------------------------------------------------------------------------------ +# per perl rules, this should be the last line in such a file: +1; diff --git a/src/gitolite_rc.pm b/src/gitolite_rc.pm new file mode 100644 index 0000000..90fbe50 --- /dev/null +++ b/src/gitolite_rc.pm @@ -0,0 +1,68 @@ +# stuff to help pull in the rc file, plus various constants + +package gitolite_rc; +use Exporter 'import'; + +# the first set (before the blank line) are constants defined right here in +# this program. The second set are from the 'rc'; We're clubbing all in +# because they're all "constants" in a programmatic sense +@EXPORT = qw( + $ABRT $WARN + $R_COMMANDS $W_COMMANDS + $REPONAME_PATT $USERNAME_PATT $REPOPATT_PATT + $ADC_CMD_ARGS_PATT + $current_data_version + + $ADMIN_POST_UPDATE_CHAINS_TO $ENV $GITOLITE_BASE $GITOLITE_PATH $GIT_PATH + $GL_ADC_PATH $GL_ADMINDIR $GL_ALL_INCLUDES_SPECIAL $GL_ALL_READ_ALL + $GL_BIG_CONFIG $GL_CONF $GL_CONF_COMPILED $GL_GET_MEMBERSHIPS_PGM + $GL_GITCONFIG_KEYS $GL_GITCONFIG_WILD $GL_KEYDIR $GL_LOGT $GL_NICE_VALUE + $GL_NO_CREATE_REPOS $GL_NO_DAEMON_NO_GITWEB $GL_NO_SETUP_AUTHKEYS + $GL_PACKAGE_CONF $GL_PACKAGE_HOOKS $GL_PERFLOGT $GL_SITE_INFO + $GL_SLAVE_MODE $GL_WILDREPOS $GL_WILDREPOS_DEFPERMS + $GL_WILDREPOS_PERM_CATS $HTPASSWD_FILE $PROJECTS_LIST $REPO_BASE + $REPO_UMASK $RSYNC_BASE $SVNSERVE $UPDATE_CHAINS_TO +); + +# ------------------------------------------------------------------------------ +# bring in the rc vars and allow querying them +# ------------------------------------------------------------------------------ + +# in case we're running under Apache using smart http +$ENV{HOME} = $ENV{GITOLITE_HTTP_HOME} if $ENV{GITOLITE_HTTP_HOME}; + +# we also need to "bring in" the rc variables. The rc can only be in one of +# these two places; the first one we find, wins +for ("$ENV{HOME}/.gitolite.rc", "/etc/gitolite/gitolite.rc") { + $ENV{GL_RC} ||= $_ if -f; +} +die "no rc file found\n" unless $ENV{GL_RC}; +do $ENV{GL_RC} or die "error parsing $ENV{GL_RC}\n"; + +# ------------------------------------------------------------------------------ +# real constants +# ------------------------------------------------------------------------------ + +$current_data_version = '1.7'; + +$ABRT = "\n\t\t***** ABORTING *****\n "; +$WARN = "\n\t\t***** WARNING *****\n "; + +# commands we're expecting +$R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/; +$W_COMMANDS=qr/^git[ -]receive-pack$/; + +# note that REPONAME_PATT allows "/", while USERNAME_PATT does not +# also, the reason REPONAME_PATT is a superset of USERNAME_PATT is (duh!) +# because a repo can have "CREATOR" in the name +$REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$); +$USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); +# same as REPONAME, but used for wildcard repos, allows some common regex metas +$REPOPATT_PATT=qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/-]*$); + +# ADC commands and arguments must match this pattern +$ADC_CMD_ARGS_PATT=qr(^[0-9a-zA-Z._\@/+:-]*$); + +# ------------------------------------------------------------------------------ +# per perl rules, this should be the last line in such a file: +1; diff --git a/src/gl-auth-command b/src/gl-auth-command index 1af4232..55091d6 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -1,17 +1,9 @@ #!/usr/bin/perl -use strict; -use warnings; - # ---------------------------------------------------------------------------- -# you: what's the invocation? -# me: Hail, O Lord Ganesha, destroyer of obsta... -# you: err hmm not *that* sort of invocation... I meant how does this program -# get invoked? -# me: oh hehe , ok here we go... -# # ssh mode # - started by sshd +# - one optional flag, "-s", for "shell allowed" people # - one argument, the "user" name # - one env var, SSH_ORIGINAL_COMMAND, containing the command # - command typically: git-(receive|upload)-pack 'reponame(.git)?' @@ -27,180 +19,101 @@ use warnings; # - no special processing commands currently handled # ---------------------------------------------------------------------------- +use strict; +use warnings; + # ---------------------------------------------------------------------------- -# common definitions +# find the rc file, then pull the libraries in # ---------------------------------------------------------------------------- -# these are set by the "rc" file -our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR, $RSYNC_BASE, $HTPASSWD_FILE, $GL_WILDREPOS, $GL_WILDREPOS_DEFPERMS, $GL_ADC_PATH, $SVNSERVE, $PROJECTS_LIST, $GL_SLAVE_MODE, $GL_PERFLOGT, $GL_ALL_READ_ALL); -# and these are set by gitolite.pm -our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT, $REPOPATT_PATT, $ADC_CMD_ARGS_PATT); -our %repos; -our %groups; -our %git_configs; -our %split_conf;; +# this (gl-auth-command) is one of the two valid starting points for all of +# gitolite for normal operations (the other being gl-time). All other +# programs are invoked either from this, or from something else (typically +# git-*-pack) in between). They thus get the benefit of the environment +# variables that this code sets up. +BEGIN { + # find and set bin dir + $0 =~ m|^(/)?(.*)/| and $ENV{GL_BINDIR} = ($1 || "$ENV{PWD}/") . $2; +} -# the common setup module is in the same directory as this running program is -my $bindir = $0; -$bindir =~ s/\/[^\/]+$//; -$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//; -unshift @INC, $bindir; -require gitolite or die "parse gitolite.pm failed\n"; +# our libraries are either in the same place the scripts are, or, as with +# RPM/DEB install, in some 'system' location that is already in perl's @INC +# anyway +use lib $ENV{GL_BINDIR}; -# ask where the rc file is, get it, and "do" it -&where_is_rc(); -die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; - -# we need to pass GL_ADMINDIR and the bindir to the child hooks -$ENV{GL_ADMINDIR} = $GL_ADMINDIR; -$ENV{GL_BINDIR} = $bindir; - -# add a custom path for git binaries, if specified -$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; - -# set default permission of wildcard repositories -$ENV{GL_WILDREPOS_DEFPERMS} = $GL_WILDREPOS_DEFPERMS if $GL_WILDREPOS_DEFPERMS; - -# set the umask before creating any files -umask($REPO_UMASK); - -$ENV{GL_REPO_BASE_ABS} = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); +use gitolite_rc; # this does a "do" of the rc file +use gitolite_env; +use gitolite; # ---------------------------------------------------------------------------- # start... # ---------------------------------------------------------------------------- -# if the first argument is a "-s", this user is allowed to get a shell using -# this key -my $shell_allowed = 0; -if (@ARGV and $ARGV[0] eq '-s') { - $shell_allowed = 1; - shift; -} +# these two options are mutually exclusive. And this program is not supposed +# to be called manually anyway +my $shell_allowed = (@ARGV and $ARGV[0] eq '-s' and shift); +my $program = (@ARGV and $ARGV[0] eq '-e' and shift); + +# setup the environment for the kids so they don't need to embark on the +# voyage of self-discovery above ;-) [environment also means things like +# nice, umask, etc., not just the environment *variables*] +setup_environment(); + +# if one of the other programs is being invoked (see doc/hacking.mkd), exec it +exec(@ARGV) if $program; # ---------------------------------------------------------------------------- -# set up SSH_ORIGINAL_COMMAND and SSH_CONNECTION in http mode +# set up GL_USER and (if reqd) SSH_ORIGINAL_COMMAND and SSH_CONNECTION # ---------------------------------------------------------------------------- -# fake out SSH_ORIGINAL_COMMAND and SSH_CONNECTION so the rest of the code -# stays the same (except the exec at the end). - my $user; if ($ENV{REQUEST_URI}) { die "fallback to DAV not supported\n" if $ENV{REQUEST_METHOD} eq 'PROPFIND'; - # these patterns indicate normal git usage; see "services[]" in - # http-backend.c for how I got that. Also note that "info" is overloaded; - # git uses "info/refs...", while gitolite uses "info" or "info?...". So - # there's a "/" after info in the list below - if ($ENV{PATH_INFO} =~ m(^/(.*)/(HEAD$|info/refs$|objects/|git-(?:upload|receive)-pack$))) { - my $repo = $1; - my $verb = ($ENV{REQUEST_URI} =~ /git-receive-pack/) ? 'git-receive-pack' : 'git-upload-pack'; - $ENV{SSH_ORIGINAL_COMMAND} = "$verb '$repo'"; - } else { - # this is one of our custom commands; could be anything really, - # because of the adc feature - my ($verb) = ($ENV{PATH_INFO} =~ m(^/(\S+))); - my $args = $ENV{QUERY_STRING}; - $args =~ s/\+/ /g; - $ENV{SSH_ORIGINAL_COMMAND} = $verb; - $ENV{SSH_ORIGINAL_COMMAND} .= " $args" if $args; - &print_http_headers(); # in preparation for the eventual output! - } - $ENV{SSH_CONNECTION} = "$ENV{REMOTE_ADDR} $ENV{REMOTE_PORT} $ENV{SERVER_ADDR} $ENV{SERVER_PORT}"; + # fake out SSH_ORIGINAL_COMMAND and SSH_CONNECTION when called via http, + # so the rest of the code stays the same (except the exec at the end). + simulate_ssh_connection(); + $user = $ENV{GL_USER} = $ENV{REMOTE_USER}; } else { # no (more) arguments given in ssh mode? default user is $USER # (fedorahosted works like this, and it is harmless for others) @ARGV = ($ENV{USER}) unless @ARGV; - $user=$ENV{GL_USER}=shift; + $user = $ENV{GL_USER} = shift; } # ---------------------------------------------------------------------------- -# logging, timestamp env vars +# SSH_ORIGINAL_COMMAND # ---------------------------------------------------------------------------- -$ENV{GL_LOG} = &get_logfilename($GL_LOGT); - -# ---------------------------------------------------------------------------- -# sanity checks on SSH_ORIGINAL_COMMAND -# ---------------------------------------------------------------------------- - -# no SSH_ORIGINAL_COMMAND given... +# no SSH_ORIGINAL_COMMAND given: shell out or default to 'info' unless ($ENV{SSH_ORIGINAL_COMMAND}) { - # if the user is allowed to use a shell, give him one - if ($shell_allowed) { - my $shell = $ENV{SHELL}; - $shell =~ s/.*\//-/; # change "/bin/bash" to "-bash" - &log_it($shell); - exec { $ENV{SHELL} } $shell; - } - # otherwise, pretend he typed in "info" and carry on... + shell_out() if $shell_allowed; # doesn't return ('exec's out) $ENV{SSH_ORIGINAL_COMMAND} = 'info'; } -# ---------------------------------------------------------------------------- -# slave mode should not do much -# ---------------------------------------------------------------------------- - +# slave mode should not do much die "server is in slave mode; you can only fetch\n" if ($GL_SLAVE_MODE and $ENV{SSH_ORIGINAL_COMMAND} !~ /^(info|expand|get|git-upload-)/); -# ---------------------------------------------------------------------------- -# admin defined commands -# ---------------------------------------------------------------------------- - -# please see doc/admin-defined-commands.mkd for details +# admin defined commands; please see doc/admin-defined-commands.mkd if ($GL_ADC_PATH and -d $GL_ADC_PATH) { - my ($cmd, @args) = split ' ', $ENV{SSH_ORIGINAL_COMMAND}; - if (-x "$GL_ADC_PATH/$cmd") { - # yes this is rather strict, sorry. - do { die "I don't like $_\n" unless $_ =~ $ADC_CMD_ARGS_PATT } for ($cmd, @args); - &log_it("$GL_ADC_PATH/$ENV{SSH_ORIGINAL_COMMAND}"); - exec("$GL_ADC_PATH/$cmd", @args); - } + try_adc(); # if it succeeds, this also 'exec's out } -# ---------------------------------------------------------------------------- -# get and set perms for actual repo created by wildcard-autoviv -# ---------------------------------------------------------------------------- - +# get/set perms/desc for wild repos; also the 'expand' command my $CUSTOM_COMMANDS=qr/^\s*(expand|(get|set)(perms|desc))\b/; - # note that all the subs called here chdir somewhere else and do not come # back; they all blithely take advantage of the fact that processing custom # commands is sort of a dead end for normal (git) processing - if ($ENV{SSH_ORIGINAL_COMMAND} =~ $CUSTOM_COMMANDS) { die "wildrepos disabled, sorry\n" unless $GL_WILDREPOS; - my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; - my ($verb, $repo) = ($cmd =~ /^\s*(\S+)(?:\s+'?\/?(.*?)(?:\.git)?'?)?$/); - # deal with "no argument" cases - $verb eq 'expand' ? $repo = '^' : die "$verb needs an argument\n" unless $repo; - if ($repo =~ $REPONAME_PATT and $verb =~ /getperms|setperms/) { - # with an actual reponame, you can "getperms" or "setperms" - get_set_perms($repo, $verb, $user); - } - elsif ($repo =~ $REPONAME_PATT and $verb =~ /(get|set)desc/) { - # with an actual reponame, you can "getdesc" or "setdesc" - get_set_desc($repo, $verb, $user); - } - elsif ($verb eq 'expand') { - # with a wildcard, you can "expand" it to see what repos actually match - die "$repo has invalid characters" unless "x$repo" =~ $REPOPATT_PATT; - expand_wild($GL_ADMINDIR, $GL_CONF_COMPILED, $repo, $user); - } else { - die "$cmd doesn't make sense to me\n"; - } + run_custom_command($user); exit 0; } -# ---------------------------------------------------------------------------- -# non-git commands -# ---------------------------------------------------------------------------- - -# if the command does NOT fit the pattern of a normal git command, send it off -# somewhere else... +# non-git commands: if the command does NOT fit the pattern of a normal git +# command, send it off somewhere else... # side notes on detecting a normal git command: the pattern we check allows # old style as well as new style ("git-subcommand arg" or "git subcommand @@ -210,63 +123,53 @@ if ($ENV{SSH_ORIGINAL_COMMAND} =~ $CUSTOM_COMMANDS) { my ($verb, $repo) = ($ENV{SSH_ORIGINAL_COMMAND} =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:\.git)?'/); unless ( $verb and ( $verb eq 'git-init' or $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) and $repo and $repo =~ $REPONAME_PATT ) { - # ok, it's not a normal git command; call the special command helper - &special_cmd ($GL_ADMINDIR, $GL_CONF_COMPILED, $shell_allowed, $RSYNC_BASE, $HTPASSWD_FILE, $SVNSERVE); - exit; + special_cmd ($shell_allowed); + exit 0; } + +# some final sanity checks die "$repo ends with a slash; I don't like that\n" if $repo =~ /\/$/; die "$repo has two consecutive periods; I don't like that\n" if $repo =~ /\.\./; -# reponame +# save the reponame; too many things need this $ENV{GL_REPO}=$repo; # ---------------------------------------------------------------------------- # the real git commands (git-receive-pack, etc...) # ---------------------------------------------------------------------------- -# ---------------------------------------------------------------------------- -# first level permissions check -# ---------------------------------------------------------------------------- +# first level permissions check my ($perm, $creator, $wild); if ( $GL_ALL_READ_ALL and $verb =~ $R_COMMANDS and -d "$ENV{GL_REPO_BASE_ABS}/$repo.git") { $perm = 'R'; } else { - ($perm, $creator, $wild) = &repo_rights($repo); -} -if ($perm =~ /C/) { - # it was missing, and you have create perms - wrap_chdir("$ENV{GL_REPO_BASE_ABS}"); - new_repo($repo, "$GL_ADMINDIR/hooks/common", $user); - # note pwd is now the bare "repo.git"; new_repo does that... - wrap_print("gl-perms", "$GL_WILDREPOS_DEFPERMS\n") if $GL_WILDREPOS_DEFPERMS; - &setup_git_configs($repo, \%git_configs); - &setup_daemon_access($repo); - &add_del_line ("$repo.git", $PROJECTS_LIST, &setup_gitweb_access($repo, '', '')); - wrap_chdir($ENV{HOME}); + ($perm, $creator, $wild) = repo_rights($repo); } +# it was missing, and you have create perms, so create it +new_wild_repo($repo, $user) if ($perm =~ /C/); -# we know the user and repo; we just need to know what perm he's trying -# aa == attempted access +# we know the user and repo; we just need to know what perm he's trying for +# (aa == attempted access) my $aa = ($verb =~ $R_COMMANDS ? 'R' : 'W'); die "$aa access for $repo DENIED to $user (Or there may be no repository at the given path. Did you spell it correctly?)\n" unless $perm =~ /$aa/; # check if repo is write-enabled -&check_repo_write_enabled($repo) if $aa eq 'W'; +check_repo_write_enabled($repo) if $aa eq 'W'; # ---------------------------------------------------------------------------- # over to git now # ---------------------------------------------------------------------------- if ($ENV{REQUEST_URI}) { - &log_it($ENV{REQUEST_URI}); + log_it($ENV{REQUEST_URI}); exec $ENV{GIT_HTTP_BACKEND}; # the GIT_HTTP_BACKEND env var should be set either by the rc file, or as # a SetEnv in the apache config somewhere } -&log_it(); +log_it(); $repo = "'$REPO_BASE/$repo.git'"; exec("git", "shell", "-c", "$verb $repo") unless $verb eq 'git-init'; diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 3930b38..3c1e251 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -6,70 +6,24 @@ use Data::Dumper; $Data::Dumper::Indent = 1; $Data::Dumper::Sortkeys = 1; +# ---------------------------------------------------------------------------- +# find the rc file, then pull the libraries +# ---------------------------------------------------------------------------- + +BEGIN { + die "ENV GL_RC not set\n" unless $ENV{GL_RC}; + die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR}; +} + +use lib $ENV{GL_BINDIR}; +use gitolite_rc; +use gitolite qw(:DEFAULT %repos %groups %git_configs %split_conf); + # === add-auth-keys === -# part of the gitolite (GL) suite - -# (1) - "compiles" ~/.ssh/authorized_keys from the list of pub-keys -# (2) - also "compiles" the user-friendly GL conf file into something easier -# to parse. We're doing this because both the gl-auth-command and the -# (gl-)update hook need this, and it seems easier to do this than -# replicate the parsing code in both those places. As a bonus, it's -# probably more efficient. -# (3) - finally does what I have resisted doing all along -- handle gitweb and -# git-daemon access. It won't *setup* gitweb/daemon for you -- you have -# to that yourself. What this does is make sure that "repo.git" -# contains the file "git-daemon-export-ok" (for daemon case) and the -# line "repo.git" exists in the "projects.list" file (for gitweb case). - -# how run: manual, by GL admin -# when: -# - anytime a pubkey is added/deleted -# - anytime gitolite.conf is changed -# input: -# - GL_CONF (default: ~/.gitolite/conf/gitolite.conf) -# - GL_KEYDIR (default: ~/.gitolite/keydir) -# output: -# - ~/.ssh/authorized_keys (dictated by sshd) -# - GL_CONF_COMPILED (default: ~/.gitolite/conf/gitolite.conf-compiled.pm) -# security: -# - touches a very critical system file that manages the restrictions on -# incoming users. Be sure to audit AUTH_COMMAND and AUTH_OPTIONS (see -# below) on any change to this script -# - no security checks within program. The GL admin runs this manually - -# warnings: -# - if the "start" line exists, but the "end" line does not, you lose the -# rest of the existing authkey file. In general, "don't do that (TM)", -# but we do have a "vim -d" popping up so you can see the changes being -# made, just in case... - -# ---------------------------------------------------------------------------- -# common definitions -# ---------------------------------------------------------------------------- - # setup quiet mode if asked; please do not use this when running manually open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); -# these are set by the "rc" file -our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $GL_WILDREPOS, $GL_GITCONFIG_KEYS, $GL_GITCONFIG_WILD, $GL_PACKAGE_HOOKS, $GL_BIG_CONFIG, $GL_NO_DAEMON_NO_GITWEB, $GL_NO_CREATE_REPOS, $GL_NO_SETUP_AUTHKEYS, $GL_PERFLOGT); -# and these are set by gitolite.pm -our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $ABRT, $WARN); - -# the common setup module is in the same directory as this running program is -my $bindir = $0; -$bindir =~ s/\/[^\/]+$//; -$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//; -unshift @INC, $bindir; -require gitolite or die "parse gitolite.pm failed\n"; - -# ask where the rc file is, get it, and "do" it -&where_is_rc(); -die "$ABRT parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; - -# add a custom path for git binaries, if specified -$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; - # ---------------------------------------------------------------------------- # definitions specific to this program # ---------------------------------------------------------------------------- @@ -78,7 +32,7 @@ $ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; # $groups{group}{member} = "master" (or name of fragment file in which the # group is defined). -our %groups = (); +# our %groups = (); # moved to gitolite.pm now # %repos has two functions. @@ -90,10 +44,10 @@ our %groups = (); # level 2 check. In order to allow "exclude" rules, the order of rules now # matters, so what used to be entirely "hash of hash of hash" now has a list # in between :) -my %repos = (); +# copy above desc to lite.pm -- my %repos = (); -# repos whose ACLs don't make it into the main compiled config file -my %split_conf = (); +# names of repos whose ACLs don't make it into the main compiled config file +# copy above desc to lite.pm -- my %split_conf = (); # rule sequence number my $rule_seq = 0; @@ -103,21 +57,16 @@ my $rule_seq = 0; # multiple times for the same repo+user. So... my %rurp_seen = (); -our $current_data_version; # this comes from gitolite.pm - # catch usernames<->pubkeys mismatches; search for "lint" below my %user_list = (); # repo specific 'git config' stuff -our %git_configs = (); +# our %git_configs = (); # moved to gitolite.pm now # gitweb descriptions and owners; plain text, keyed by "$repo.git" my %desc = (); my %owner = (); -# set the umask before creating any files -umask($REPO_UMASK); - # ---------------------------------------------------------------------------- # subroutines # ---------------------------------------------------------------------------- @@ -401,28 +350,6 @@ for my $fragment_file (glob("conf/fragments/*.conf")) parse_conf_file($fragment_file, $fragment); } -sub write_compiled_conf -{ - my $compiled_fh = wrap_open( ">", "$GL_CONF_COMPILED.new" ); - my $data_version = $current_data_version; - print $compiled_fh Data::Dumper->Dump([$data_version], [qw(*data_version)]); - my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]); - $dumped_data .= Data::Dumper->Dump([\%git_configs], [qw(*git_configs)]) if %git_configs; - # the dump uses single quotes, but we convert any strings containing $creator - # and $gl_user to double quoted strings. A bit sneaky, but not too much... - $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g; - print $compiled_fh $dumped_data; - if (%groups) { - $dumped_data = Data::Dumper->Dump([\%groups], [qw(*groups)]); - $dumped_data =~ s/\bCREAT[EO]R\b/\$creator/g; - $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g; - print $compiled_fh $dumped_data; - } - print $compiled_fh Data::Dumper->Dump([\%split_conf], [qw(*split_conf)]) if %split_conf; - close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n"; - rename "$GL_CONF_COMPILED.new", "$GL_CONF_COMPILED"; -} - # ---------------------------------------------------------------------------- # (that ends the config file compiler, though we postpone the writing # for now to deal with the latest GL_BIG_CONFIG innovation!) @@ -539,12 +466,34 @@ sub write_1_compiled_conf $split_conf{$repo} = 1; } +sub write_compiled_conf +{ + my $compiled_fh = wrap_open( ">", "$GL_CONF_COMPILED.new" ); + my $data_version = $current_data_version; + print $compiled_fh Data::Dumper->Dump([$data_version], [qw(*data_version)]); + my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]); + $dumped_data .= Data::Dumper->Dump([\%git_configs], [qw(*git_configs)]) if %git_configs; + # the dump uses single quotes, but we convert any strings containing $creator + # and $gl_user to double quoted strings. A bit sneaky, but not too much... + $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g; + print $compiled_fh $dumped_data; + if (%groups) { + $dumped_data = Data::Dumper->Dump([\%groups], [qw(*groups)]); + $dumped_data =~ s/\bCREAT[EO]R\b/\$creator/g; + $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g; + print $compiled_fh $dumped_data; + } + print $compiled_fh Data::Dumper->Dump([\%split_conf], [qw(*split_conf)]) if %split_conf; + close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n"; + rename "$GL_CONF_COMPILED.new", "$GL_CONF_COMPILED"; +} + # ---------------------------------------------------------------------------- # get a list of physical repos for later # ---------------------------------------------------------------------------- my @phy_repos = (); -@phy_repos = &list_phy_repos() unless $GL_NO_DAEMON_NO_GITWEB; +@phy_repos = list_phy_repos() unless $GL_NO_DAEMON_NO_GITWEB; # NOTE: we're overloading GL_NO_DAEMON_NO_GITWEB to mean "no git config" also. # In fact anything that requires trawling through the existing repos doing @@ -573,7 +522,7 @@ my %projlist = (); for my $repo (@phy_repos) { wrap_chdir("$ENV{GL_REPO_BASE_ABS}/$repo.git"); # daemon is easy - &setup_daemon_access($repo); + setup_daemon_access($repo); } for my $repo (@phy_repos) { @@ -588,7 +537,7 @@ for my $repo (@phy_repos) { # into the "repo foo" section; they're essentialy independent. # Anyway, I believe it doesn't make sense to have all wild repos # (for some pattern) to have the same description and owner. - $projlist{"$repo.git"} = 1 if &setup_gitweb_access($repo, $desc{"$repo.git"} || '', $owner{"$repo.git"} || ''); + $projlist{"$repo.git"} = 1 if setup_gitweb_access($repo, $desc{"$repo.git"} || '', $owner{"$repo.git"} || ''); # git config # implementation note: this must happen *after* one of the previous 2 @@ -597,7 +546,7 @@ for my $repo (@phy_repos) { # set for the *current* repo, which in turn stores translated values for # $creator in the git_configs hash, which, (phew!) is needed for a match # that eventually gets you a valid $git_configs{} below - &setup_git_configs($repo, \%git_configs) if $git_configs{$repo}; + setup_git_configs($repo, \%git_configs) if $git_configs{$repo}; } # write out the project list @@ -612,5 +561,5 @@ close $projlist_fh; # ---------------------------------------------------------------------------- unless ($GL_NO_SETUP_AUTHKEYS) { - &setup_authkeys($bindir, $GL_KEYDIR, \%user_list); + setup_authkeys($GL_KEYDIR, \%user_list); } diff --git a/src/gl-dont-panic b/src/gl-dont-panic index c0f491e..b30a743 100755 --- a/src/gl-dont-panic +++ b/src/gl-dont-panic @@ -62,10 +62,19 @@ else fi # ------------------------------------------------------------------------ -# setup stuff -REPO_BASE=$( cd $HOME; perl -e 'do ".gitolite.rc"; print $REPO_BASE' ) -GL_BINDIR=$( cd $HOME; perl -ne 'print($1), exit if /^command="(.*?)\/gl-auth-command /' < $HOME/.ssh/authorized_keys) -GL_ADMINDIR=$(cd $HOME; perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR') +# setup stuff. Note that for *this* program, we don't want to rely on $0 +# telling us bindir; the user should be allowed to run it from anywhere and +# still have it work. Luckily, by the time you feel the need to run this +# program, authkeys is already populated, and anyway that's the only +# *reliable* place to get this info. However, when running in HTTP mode or +# Fedora mode, you have *no* keys in the authkeys file. In those cases you +# have to manually set GL_BINDIR externally before running this program +[ -z "$GL_BINDIR" ] && + GL_BINDIR=$( perl -ne 'print($1), exit if /^command="(.+?)\/gl-(time|auth-command) /' < $HOME/.ssh/authorized_keys) +GL_RC=$( $GL_BINDIR/gl-query-rc GL_RC) +REPO_BASE=$( $GL_BINDIR/gl-query-rc REPO_BASE) +GL_ADMINDIR=$($GL_BINDIR/gl-query-rc GL_ADMINDIR) +export GL_RC export REPO_BASE export GL_BINDIR export GL_ADMINDIR diff --git a/src/gl-easy-install b/src/gl-easy-install index 11b14e2..f0723c3 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -48,7 +48,7 @@ main() { [[ $upgrade == 0 ]] && initial_conf_key # MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf" - ssh $p_port $user@$host "cd $GL_ADMINDIR; \$PWD/src/gl-compile-conf $quiet" + ssh $p_port $user@$host "cd $GL_ADMINDIR; src/gl-auth-command -e src/gl-compile-conf $quiet" setup_pta @@ -361,9 +361,9 @@ run_install() { prompt "installing/upgrading..." "$v_ignore_stuff" # extract the GL_ADMINDIR, REPO_BASE and GIT_PATH locations - GL_ADMINDIR=$(ssh $p_port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GL_ADMINDIR'") - REPO_BASE=$( ssh $p_port $user@$host "perl -e 'do \".gitolite.rc\"; print \$REPO_BASE'") - GIT_PATH=$( ssh $p_port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GIT_PATH'") + GL_ADMINDIR=$(ssh $p_port $user@$host gitolite-install/src/gl-query-rc GL_ADMINDIR) + REPO_BASE=$( ssh $p_port $user@$host gitolite-install/src/gl-query-rc REPO_BASE) + GIT_PATH=$( ssh $p_port $user@$host gitolite-install/src/gl-query-rc GIT_PATH) # determine if this is an upgrade; we decide based on whether a file # called $GL_ADMINDIR/conf/gitolite.conf exists on the remote side. We @@ -382,7 +382,7 @@ run_install() { # MANUAL: still in the "gitolite-install" directory? Good. Run # "src/gl-install" - ssh $p_port $user@$host "cd gitolite-install; src/gl-install $quiet" + ssh $p_port $user@$host "cd gitolite-install; src/gl-auth-command -e src/gl-install $quiet" # MANUAL: if you're upgrading, run "src/gl-compile-conf" and you're done! # -- ignore the rest of this file for the purposes of an upgrade @@ -443,7 +443,7 @@ GIT_WORK_TREE=$GL_ADMINDIR git diff --cached --quiet 2>/dev/null || GIT_WORK_TRE # properly. The install program does this. So cd back to the # "gitolite-install" directory and run "src/gl-install" - ssh $p_port $user@$host "cd gitolite-install; src/gl-install $quiet" + ssh $p_port $user@$host "cd gitolite-install; src/gl-auth-command -e src/gl-install $quiet" # MANUAL: you're done! Log out of the server, come back to your # workstation, and clone the admin repo using "git clone diff --git a/src/gl-install b/src/gl-install index 844b2c9..e3734fc 100755 --- a/src/gl-install +++ b/src/gl-install @@ -5,7 +5,22 @@ use strict; use warnings; -our ($REPO_BASE, $GL_ADMINDIR, $GL_CONF, $GIT_PATH, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS, $GL_PERFLOGT, $REPO_UMASK); +# ---------------------------------------------------------------------------- +# find the rc file, then pull the libraries +# ---------------------------------------------------------------------------- + +BEGIN { + die "ENV GL_RC not set\n" unless $ENV{GL_RC}; + die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR}; +} + +use lib $ENV{GL_BINDIR}; +use gitolite_rc; +use gitolite; + +# ---------------------------------------------------------------------------- +# start... +# ---------------------------------------------------------------------------- # setup quiet mode if asked; please do not use this when running manually open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); @@ -25,21 +40,13 @@ sub wrap_mkdir print "created $dir\n"; } -# the common setup module is in the same directory as this running program is -my $bindir = $0; -$bindir =~ s/\/[^\/]+$//; -unshift @INC, $bindir; -require gitolite or die "parse gitolite.pm failed\n"; - -# ask where the rc file is, get it, and "do" it -&where_is_rc(); unless ($ENV{GL_RC}) { # doesn't exist. Copy it across, tell user to edit it and come back my $glrc = $ENV{HOME} . "/.gitolite.rc"; if ($GL_PACKAGE_CONF) { system("cp $GL_PACKAGE_CONF/example.gitolite.rc $glrc"); } else { - system("cp $bindir/../conf/example.gitolite.rc $glrc"); + system("cp $ENV{GL_BINDIR}/../conf/example.gitolite.rc $glrc"); } print "created $glrc\n"; print "please edit it, change the paths if you wish to, and RERUN THIS SCRIPT\n"; @@ -69,8 +76,8 @@ for my $dir qw(conf doc keydir logs src hooks hooks/common hooks/gitolite-admin) if ($GL_PACKAGE_HOOKS) { system("cp -R -p $GL_PACKAGE_HOOKS $GL_ADMINDIR"); } else { - system("cp -R -p $bindir/../src $bindir/../doc $bindir/../hooks $GL_ADMINDIR"); - system("cp $bindir/../conf/VERSION $GL_ADMINDIR/conf"); + system("cp -R -p $ENV{GL_BINDIR}/../src $ENV{GL_BINDIR}/../doc $ENV{GL_BINDIR}/../hooks $GL_ADMINDIR"); + system("cp $ENV{GL_BINDIR}/../conf/VERSION $GL_ADMINDIR/conf"); } unless (-f $GL_CONF or $GL_PACKAGE_CONF) { diff --git a/src/gl-mirror-shell b/src/gl-mirror-shell index a18899a..4b4bde5 100755 --- a/src/gl-mirror-shell +++ b/src/gl-mirror-shell @@ -3,8 +3,13 @@ export GL_BYPASS_UPDATE_HOOK GL_BYPASS_UPDATE_HOOK=1 -export REPO_BASE=`cd $HOME;perl -e 'do ".gitolite.rc"; print $REPO_BASE' ` -export REPO_UMASK=`cd $HOME;perl -e 'do ".gitolite.rc"; print $REPO_UMASK' ` +get_rc_val() { + ${0%/*}/gl-query-rc $1 +} + +REPO_BASE=$( get_rc_val REPO_BASE) +REPO_UMASK=$(get_rc_val REPO_UMASK) + umask $REPO_UMASK if echo $SSH_ORIGINAL_COMMAND | egrep git-upload\|git-receive >/dev/null @@ -13,8 +18,8 @@ then # the (special) admin post-update hook needs these, so we cheat export GL_ADMINDIR export GL_BINDIR - GL_ADMINDIR=` cd $HOME;perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR'` - GL_BINDIR=`echo $0 | perl -lpe 's/^/$ENV{PWD}\// unless /^\//; s/\/[^\/]+$//;'` + GL_ADMINDIR=$(get_rc_val GL_ADMINDIR) + GL_BINDIR=$( get_rc_val GL_BINDIR) SSH_ORIGINAL_COMMAND=`echo $SSH_ORIGINAL_COMMAND | sed -e "s:':'$REPO_BASE/:"` exec git shell -c "$SSH_ORIGINAL_COMMAND" diff --git a/src/gl-mirror-sync b/src/gl-mirror-sync index 9b6d195..60458a8 100755 --- a/src/gl-mirror-sync +++ b/src/gl-mirror-sync @@ -6,7 +6,7 @@ ssh -o PasswordAuthentication=no $mirror echo hello-there | grep hello-there >/d { echo I cant ssh to $mirror; exit 1; } cd $HOME -REPO_BASE=` cd $HOME;perl -e 'do ".gitolite.rc"; print $REPO_BASE'` +REPO_BASE=`${0%/*}/gl-query-rc REPO_BASE` cd $REPO_BASE ssh $mirror cat \$HOME/.gitolite.rc | expand | egrep '^ *\$GL_SLAVE_MODE *= *1; *$' >/dev/null || { diff --git a/src/gl-query-rc b/src/gl-query-rc new file mode 100755 index 0000000..4233403 --- /dev/null +++ b/src/gl-query-rc @@ -0,0 +1,23 @@ +#!/usr/bin/perl + +# let shell scripts query rc values +# prints out a tab delimited list of all queried values +# just run "gl-query-rc REPO_BASE GL_ADMINDIR" (for example) + +use strict; +no strict 'refs'; +use warnings; + +# find the rc file, then pull the libraries +BEGIN { + # find and set bin dir; same code as in gl-auth-command + $0 =~ m|^(/)?(.*)/| and $ENV{GL_BINDIR} = ($1 || "$ENV{PWD}/") . $2; +} +use lib $ENV{GL_BINDIR}; +require gitolite_rc; +gitolite_rc->import; + +our $GL_RC=$ENV{GL_RC}; +our $GL_BINDIR=$ENV{GL_BINDIR}; + +print join("\t", map { $$_ } grep { $$_ } @ARGV) . "\n" if @ARGV; diff --git a/src/gl-setup b/src/gl-setup index fb20fbb..124ae80 100755 --- a/src/gl-setup +++ b/src/gl-setup @@ -20,6 +20,10 @@ GL_PACKAGE_CONF=/tmp/share/gitolite/conf die() { echo "$@"; exit 1; } >&2 +get_rc_val() { + ${0%/*}/gl-query-rc $1 +} + TEMPDIR=$(mktemp -d -t tmp.XXXXXXXXXX) export TEMPDIR trap "/bin/rm -rf $TEMPDIR" 0 @@ -40,20 +44,24 @@ else fi fi -if [ -f $HOME/.gitolite.rc ] +export GL_RC +GL_RC=$(get_rc_val GL_RC 2>/dev/null) +[ -z "$GL_RC" ] && GL_RC=$HOME/.gitolite.rc + +if [ -f $GL_RC ] then print_rc_vars() { perl -ne 's/^\s+//; s/[\s=].*//; print if /^\$/;' < $1 | sort } print_rc_vars $GL_PACKAGE_CONF/example.gitolite.rc > $TEMPDIR/.newvars - print_rc_vars $HOME/.gitolite.rc > $TEMPDIR/.oldvars + print_rc_vars $GL_RC > $TEMPDIR/.oldvars comm -23 $TEMPDIR/.newvars $TEMPDIR/.oldvars > $TEMPDIR/.diffvars if [ -s $TEMPDIR/.diffvars ] then cp $GL_PACKAGE_CONF/example.gitolite.rc $HOME/.gitolite.rc.new echo new version of the rc file saved in $HOME/.gitolite.rc.new echo - echo please update $HOME/.gitolite.rc manually if you need features + echo please update $GL_RC manually if you need features echo controlled by any of the following variables: echo ---- sed -e 's/^/ /' < $TEMPDIR/.diffvars @@ -63,11 +71,11 @@ else [ -n "$GITOLITE_HTTP_HOME" ] || [ -n "$pubkey_file" ] || die "looks like first run -- I need a pubkey file" [ -z "$GITOLITE_HTTP_HOME" ] || [ -n "$admin_name" ] || die "looks like first run -- I need an admin name" - cp $GL_PACKAGE_CONF/example.gitolite.rc $HOME/.gitolite.rc - printf "The default settings in the "rc" file ($HOME/.gitolite.rc) are fine for most\n" + cp $GL_PACKAGE_CONF/example.gitolite.rc $GL_RC + printf "The default settings in the "rc" file ($GL_RC) are fine for most\n" printf "people but if you wish to make any changes, you can do so now.\n\nhit enter..." read i - ${EDITOR:-vi} $HOME/.gitolite.rc + ${EDITOR:-vi} $GL_RC fi # setup ssh stuff. We break our normal rule that we will not fiddle with @@ -80,16 +88,17 @@ fi chmod go-w . .ssh .ssh/authorized_keys ) +export GL_BINDIR +export REPO_BASE +export GL_ADMINDIR +GL_BINDIR=$( get_rc_val GL_BINDIR ) +REPO_BASE=$( get_rc_val REPO_BASE ) +GL_ADMINDIR=$(get_rc_val GL_ADMINDIR) + # now we get to gitolite itself gl-install -q -get_rc_val() { - perl -e "do '$HOME/.gitolite.rc'; print $1" -} -GL_ADMINDIR=$(get_rc_val '$GL_ADMINDIR') -REPO_BASE=$( get_rc_val '$REPO_BASE' ) - [ -f $GL_ADMINDIR/conf/gitolite.conf ] || { cat < $GL_ADMINDIR/conf/gitolite.conf repo gitolite-admin diff --git a/src/gl-setup-authkeys b/src/gl-setup-authkeys index c431cc5..86c47b4 100755 --- a/src/gl-setup-authkeys +++ b/src/gl-setup-authkeys @@ -1,43 +1,51 @@ #!/usr/bin/perl -w -# shim program +# documentation for this program is right here, please read -# arg-1: keydir +# IMPORTANT NOTES: + +# - this program MUST be placed in the same directory as the rest of the +# programs that come with gitolite + +# - this program MUST be run by supplying its full path! + +# BACKGROUND/PURPOSE: # - an external program populates "keydir" with *all* keys and then -# calls us, giving "keydir" as arg-1 +# calls this program, giving "keydir" as arg-1 # - we then call gitolite.pm's "setup_authkeys" function to do its thing -# IMPLEMENTATION NOTE: make sure this is in the same directory as -# "gitolite.pm" and all the rest of "src/". +# arg-1: keydir # DISCUSSION: # # For now, we will assume *all* the keys are in the keydir passed. The -# setup_authkeys routine factored out from the old gl-compile-conf is -# not setup to take a partial set of keys and create the -# ~/.ssh/authorized_keys file. +# setup_authkeys routine factored out from the old gl-compile-conf is not +# setup to take a partial set of keys and create the ~/.ssh/authorized_keys +# file. # -# Also, there are issues to do with *deleted* keys that need to be taken -# care of. +# Also, there are issues to do with *deleted* keys that need to be taken care +# of. # -# All in all, unless it is shown to be quite inefficient, I'd much -# prefer processing *all* keys each time there is a change. +# All in all, unless it is shown to be quite inefficient, I'd much prefer +# processing *all* keys each time there is a change. -our ($GL_PERFLOGT); +use strict; +use warnings; -# setup -my $bindir = $0; -$bindir =~ s/\/[^\/]+$//; -$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//; -unshift @INC, $bindir; -require gitolite or die "parse gitolite.pm failed\n"; +use FindBin; +BEGIN { $ENV{GL_BINDIR} = $FindBin::Bin; } -# prevent newbie from running it accidentally and clobbering his authkeys -# file! -if (@ARGV and $ARGV[0] eq '-batch') { - shift; -} else { +use lib $ENV{GL_BINDIR}; +use gitolite_rc; +use gitolite; + +use Getopt::Long; +my $batch = 0; +GetOptions('batch' => \$batch); + +# prevent newbie from running it accidentally and clobbering his authkeys file! +unless ($batch) { print STDERR " This is a cronnable, batchable, program to rewrite ~/.ssh/authorized_keys using public keys in a given directory. @@ -54,4 +62,4 @@ if (@ARGV and $ARGV[0] eq '-batch') { my $keydir = shift or die "I need a directory name\n"; -d $keydir or die "$keydir should be a directory\n"; -&setup_authkeys($bindir, $keydir); +setup_authkeys($keydir); diff --git a/src/gl-time b/src/gl-time index 26102f9..b7b15ce 100755 --- a/src/gl-time +++ b/src/gl-time @@ -9,22 +9,32 @@ use strict; use warnings; +# ---------------------------------------------------------------------------- +# find the rc file, then pull the libraries +# ---------------------------------------------------------------------------- + +# see notes on this code in gl-auth-command +BEGIN { + # find and set bin dir + $0 =~ m|^(/)?(.*)/| and $ENV{GL_BINDIR} = ($1 || "$ENV{PWD}/") . $2; +} + +use lib $ENV{GL_BINDIR}; +use gitolite_rc; +use gitolite_env; +use gitolite qw(log_it); + use Time::HiRes qw(gettimeofday tv_interval); -our ($GL_PERFLOGT); +# ---------------------------------------------------------------------------- +# start... +# ---------------------------------------------------------------------------- # rc file do "$ENV{HOME}/.gitolite.rc"; # this file is always in a fixed place; code in the main gitolite that # seems to indicate it is not, is obsolete and needs to be fixed. -# the common setup module is in the same directory as this running program is -my $bindir = $0; -$bindir =~ s/\/[^\/]+$//; -$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//; -unshift @INC, $bindir; -require gitolite or die "parse gitolite.pm failed\n"; - # --------------------------------------------------------------- my $starttime = [gettimeofday]; @@ -36,6 +46,6 @@ $ENV{GL_USER} = shift; my $elapsedtime = tv_interval($starttime); -$ENV{GL_LOG} = &get_logfilename($GL_PERFLOGT); +$ENV{GL_LOG} = get_logfilename($GL_PERFLOGT); # log_it logs to $ENV{GL_LOG} -&log_it("", "$elapsedtime\trc=$returncode"); +log_it("", "$elapsedtime\trc=$returncode"); diff --git a/src/gl-tool b/src/gl-tool index 8f52e72..923eb03 100755 --- a/src/gl-tool +++ b/src/gl-tool @@ -14,8 +14,9 @@ # current sub-commands: # (1) REPLACE THE OLD $SHELL_USERS MECHANISM - -# $0 shell-add foo.pub +# +# $0 shell-add foo.pub +# # adds the pubkey in foo.pub into the authkeys file with "-s" argument (shell # access) and user "foo". The line will be added *before* the "# gitolite # start" section, so that a gitolite-admin push will not affect it. @@ -45,12 +46,12 @@ then # side, it's not likely to change anytime soon! AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding" - bindir=`echo $0 | perl -lpe 's/^/$ENV{PWD}\// unless /^\//; s/\/[^\/]+$//;'` + GL_BINDIR=`${0%/*}/gl-query-rc GL_BINDIR` pubkey_file=$2 user=`basename $pubkey_file .pub` - authline="command=\"$bindir/gl-auth-command -s $user\",$AUTH_OPTIONS `cat $pubkey_file`"; + authline="command=\"$GL_BINDIR/gl-auth-command -s $user\",$AUTH_OPTIONS `cat $pubkey_file`"; authkeys=$HOME/.ssh/authorized_keys diff --git a/src/sshkeys-lint b/src/sshkeys-lint index a283bfe..2e638fd 100755 --- a/src/sshkeys-lint +++ b/src/sshkeys-lint @@ -6,8 +6,8 @@ our (%users, %linenos); my $thisbin = $0; $thisbin = "$ENV{PWD}/$thisbin" unless $thisbin =~ /^\//; -&usage unless $ARGV[0] and -f $ARGV[0]; -my @authlines = &filelines($ARGV[0]); +usage() unless $ARGV[0] and -f $ARGV[0]; +my @authlines = filelines($ARGV[0]); my $lineno = 0; for (@authlines) { @@ -36,7 +36,7 @@ print "\n"; my @pubkeys = glob("*.pub"); die "no *.pub files here\n" unless @pubkeys; for my $pub (@pubkeys) { - my @lines = &filelines($pub); + my @lines = filelines($pub); die "$pub has more than one line\n" if @lines > 1; die "$pub does not start with ssh-rsa or ssh-dss\n" unless $lines[0] =~ /^(?:ssh-rsa|ssh-dss) (\S+)/; my $key = $1; diff --git a/t/README.mkd b/t/README.mkd index c90d016..78b2ef4 100644 --- a/t/README.mkd +++ b/t/README.mkd @@ -71,6 +71,15 @@ In this document: # or ./test-driver.sh t51 + * you can also run them through "prove", although to make it work easier + with prove, I ended up making the "subtest" numbers be the actual test + numbers, making it look like I have over 2000 tests, when in reality I + have about 600: + + prove ./test-driver.sh + # or + prove ./test-driver.sh :: t51 + ### instructions for adding new tests diff --git a/t/t65-rsync b/t/t65-rsync index aed4f30..0efb8fd 100644 --- a/t/t65-rsync +++ b/t/t65-rsync @@ -26,8 +26,8 @@ name "u1 rsync to frob" cd ~/gitolite-admin runlocal rsync -avP conf u1:frob expect conf/gitolite.conf -expect 386.*100% -expect "total size is 386" +expect 100% +expect "total size is" runlocal find /tmp/rsyncbase -type f expect /tmp/rsyncbase/frob/conf/gitolite.conf @@ -42,8 +42,8 @@ name "u2 rsync to nitz" cd ~/gitolite-admin runlocal rsync -avP conf u2:nitz expect conf/gitolite.conf -expect 386.*100% -expect "total size is 386" +expect 100% +expect "total size is" runlocal find /tmp/rsyncbase -type f expect /tmp/rsyncbase/nitz/conf/gitolite.conf @@ -55,8 +55,8 @@ expect "W NAME/spl u2 DENIED by NAME/spl" name "u1 rsync to spl" cd ~/gitolite-admin runlocal rsync -avP conf u1:spl -expect 386.*100% -expect "total size is 386" +expect 100% +expect "total size is" name "u2 rsync from spl" cd ~/td @@ -66,14 +66,14 @@ expect "R NAME/spl u2 DENIED by NAME/spl" name "u1 rsync from spl" cd ~/td runlocal rsync -avP u1:spl splhere -expect 386.*100% -expect "total size is 386" +expect 100% +expect "total size is" name "u3 rsync to foo" cd ~/gitolite-admin runlocal rsync -avP conf u3:foo/ -expect 386.*100% -expect "total size is 386" +expect 100% +expect "total size is" name "u3 rsync to bar" cd ~/gitolite-admin diff --git a/t/test-driver.sh b/t/test-driver.sh index 78ba786..27ce7a1 100755 --- a/t/test-driver.sh +++ b/t/test-driver.sh @@ -4,7 +4,6 @@ # documentation testnum=0 -subtests=0 # remote local command runlocal() { "$@" > ~/1 2> ~/2; } @@ -28,22 +27,22 @@ taillog() { ssh gitolite-test@localhost tail $1 .gitolite/logs/gitolite-????-??. hl() { # highlight function normal=`tput sgr0` red=`tput sgr0; tput setaf 1; tput bold` + echo >&2 if [[ -n $1 ]] then - echo $red"$@"$normal + echo $red"$@"$normal >&2 else - echo $red + echo $red >&2 cat - echo $normal + echo $normal >&2 fi } -pause() { echo pausing, "$@"\; hit enter or ctrl-c...; read; } capture() { cf=$1; shift; "$@" >& $TESTDIR/$cf; } editrc() { scp gitolite-test@localhost:.gitolite.rc ~/junk >/dev/null - perl -pi -e "print STDERR if not /^#/ and /$1\b/ and s/=.*/= $2;/" ~/junk + perl -pi -e "print STDERR if not /^#/ and /$1\b/ and s/=.*/= $2;/" ~/junk 2> >(sed -e 's/^/# /') scp ~/junk gitolite-test@localhost:.gitolite.rc >/dev/null } @@ -80,80 +79,64 @@ mdc() ) >~/1 2>~/2 } -# flush result of last test when next one comes along -testdone() { - [[ $subtests > 1 ]] && TESTNAME="($subtests) $TESTNAME" - echo -e $testnum\\t$TESTNAME -} - # set test name/desc name() { - if [[ -n $TESTNAME ]] - then - if [[ $TESTNAME != INTERNAL ]] - then - (( testnum++ )) - testdone - fi - subtests=0 - fi export TESTNAME="$*" + if [[ $TESTNAME != INTERNAL ]] + then + echo '#' "$*" + fi } +ok() { + (( testnum++ )) + echo 'ok' "($testnum) $*" +} + + notok() { - echo ---------- - head -999 ~/1 ~/2 | sed -e 's/^/ /' + (( testnum++ )) + echo 'not ok' "($testnum) $*" } expect_filesame() { if cmp ~/1 "$1" then - (( subtests++ )) + ok else - echo files ~/1 and "$1" are different - echo '*** ABORTING ***' - exit 1 + notok files ~/1 and "$1" are different fi } die() { - echo '***** AAAAARRRGGH! *****' - echo ${BASH_LINENO[1]} ${BASH_SOURCE[2]} - read - cd $TESTDIR - vim +${BASH_LINENO[1]} '+r !head ~/1 ~/2 /dev/null' ${BASH_SOURCE[2]} + echo '***** AAAAARRRGGH! *****' >&2 + echo ${BASH_LINENO[1]} ${BASH_SOURCE[2]} >&2 + echo "vim +${BASH_LINENO[1]} \'+r !head ~/1 ~/2 /dev/null\' ${BASH_SOURCE[2]}" >&2 exit 1 } expect() { if cat ~/1 ~/2 | grep "$1" >/dev/null then - (( subtests++ )) + ok else - notok - echo ---------- - echo " expecting: $1" - echo ---------- - die $TESTNAME - exit 1 + notok "expecting: $1, got:" + cat ~/1 ~/2|sed -e 's/^/# /' fi } notexpect() { if cat ~/1 ~/2 | grep "$1" >/dev/null then - notok - echo "NOT expecting: $1" - echo ---------- - die $TESTNAME - exit 1 + notok "NOT expecting: $1, got:" + cat ~/1 ~/2|sed -e 's/^/# /' else - (( subtests++ )) + ok fi } print_summary() { - echo -e "==========\n$testnum tests succeeded" + echo 1..$testnum } expect_push_ok() {