gitolite/src/gl-auth-command
Sitaram Chamarty 68b45e1616 (new mirroring) bulk of the changes are here:
- post-receive now just calls mirror-push
  - mirror-push is a medium complex shell script (all that backgrounding
    etc., can't be done so easily in God's first language!)
  - mirror-shell is now a perl program that does a few different things
    (receive mirror-pushes, command line re-sync, re-sync requests from a
    slave, etc)
  - auth-command changes to reject/redirect non-native pushes
2011-08-12 22:51:44 +05:30

192 lines
7.7 KiB
Perl
Executable file

#!/usr/bin/perl
# ----------------------------------------------------------------------------
# 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)?'
# - special gitolite commands: info, expand, (get|set)(perms|desc)
# - special non-gitolite commands: rsync, svnserve, htpasswd
# - other commands: anything in $GL_ADC_PATH if defined (see rc file)
#
# (smart) http mode
# - started by apache (httpd)
# - no arguments
# - REQUEST_URI contains verb and repo, REMOTE_USER contains username
# - REQUEST_URI looks like /path/reponame.git/(info/refs\?service=)?git-(receive|upload)-pack
# - no special processing commands currently handled
# ----------------------------------------------------------------------------
use strict;
use warnings;
# ----------------------------------------------------------------------------
# find the rc file, then pull the libraries in
# ----------------------------------------------------------------------------
# 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;
}
# 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};
use gitolite_rc; # this does a "do" of the rc file
use gitolite_env;
use gitolite;
# ----------------------------------------------------------------------------
# start...
# ----------------------------------------------------------------------------
# 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 GL_USER and (if reqd) SSH_ORIGINAL_COMMAND and SSH_CONNECTION
# ----------------------------------------------------------------------------
my $user;
if ($ENV{REQUEST_URI}) {
die "fallback to DAV not supported\n" if $ENV{REQUEST_METHOD} eq 'PROPFIND';
# 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();
$ENV{REMOTE_USER} ||= $GL_HTTP_ANON_USER; # see doc/http-backend.mkd
$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;
}
# ----------------------------------------------------------------------------
# SSH_ORIGINAL_COMMAND
# ----------------------------------------------------------------------------
# no SSH_ORIGINAL_COMMAND given: shell out or default to 'info'
unless ($ENV{SSH_ORIGINAL_COMMAND}) {
shell_out() if $shell_allowed; # doesn't return ('exec's out)
$ENV{SSH_ORIGINAL_COMMAND} = 'info';
}
# admin defined commands; please see doc/admin-defined-commands.mkd
if ($GL_ADC_PATH and -d $GL_ADC_PATH) {
try_adc(); # if it succeeds, this also 'exec's out
}
# 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;
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...
# 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
# arg"). Currently, this is how git sends across the command (including the
# single quotes):
# git-receive-pack 'reponame.git'
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 ) {
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 =~ /\.\./;
# save the reponame; too many things need this
$ENV{GL_REPO}=$repo;
# ----------------------------------------------------------------------------
# the real git commands (git-receive-pack, etc...)
# ----------------------------------------------------------------------------
# we know the user and repo; we just need to know what perm he's trying for
# (aa == attempted access; setting this makes some later logic simpler)
my $aa = ($verb =~ $R_COMMANDS ? 'R' : 'W');
# writes may get redirected under certain conditions
if ( $GL_HOSTNAME and $aa eq 'W' and mirror_mode($repo) =~ /^slave of (\S+)/ ) {
my $master = $1;
die "$ABRT $GL_HOSTNAME not the master, please push to $master\n" unless mirror_redirectOK($repo, $GL_HOSTNAME);
print STDERR "$GL_HOSTNAME ==== $user ($repo) ===> $master\n";
exec("ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}");
}
# first level permissions check
my ($perm, $creator, $wild);
if ( $GL_ALL_READ_ALL and $verb =~ $R_COMMANDS and -d "$REPO_BASE/$repo.git") {
$perm = 'R';
} else {
($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/);
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';
# run the pre-git hook if present (do this last, just before actually handing
# off to git). Force its output to go to STDERR so the git client does not
# get confused, in case the code in the pre-git hook forgot. To make it
# simple for the script, send in $aa (which will be 'R' or 'W') so now they
# have all three: GL_USER and GL_REPO in the env, and $aa as arg-1.
if (-x "$REPO_BASE/$repo.git/hooks/gl-pre-git") {
system("cd $REPO_BASE/$repo.git; hooks/gl-pre-git $aa >&2");
die "gl-pre-git hook failed ($?)\n" if $?;
}
# ----------------------------------------------------------------------------
# over to git now
# ----------------------------------------------------------------------------
if ($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();
$repo = "'$REPO_BASE/$repo.git'";
exec("git", "shell", "-c", "$verb $repo") unless $verb eq 'git-init';