gitolite/src/gl-auth-command

196 lines
8 KiB
Plaintext
Raw Normal View History

2009-08-25 05:14:46 +02:00
#!/usr/bin/perl
2009-08-23 14:54:37 +02:00
# ----------------------------------------------------------------------------
# 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)
# - 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
# ----------------------------------------------------------------------------
2009-08-23 14:54:37 +02:00
use strict;
use warnings;
2009-08-23 14:54:37 +02:00
# ----------------------------------------------------------------------------
# find the rc file, then pull the libraries in
2009-08-23 14:54:37 +02:00
# ----------------------------------------------------------------------------
# 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;
2009-08-23 14:54:37 +02:00
# ----------------------------------------------------------------------------
# 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;
}
2009-08-23 14:54:37 +02:00
2010-02-01 12:24:39 +01:00
# ----------------------------------------------------------------------------
# SSH_ORIGINAL_COMMAND
2010-02-01 12:24:39 +01:00
# ----------------------------------------------------------------------------
# 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';
}
# quick sanity check for newlines; could be used to create fake log entries.
# Not an access violation but possibly an audit/compliance reporting violation
die "I don't like newlines in the command: $ENV{SSH_ORIGINAL_COMMAND}\n" if $ENV{SSH_ORIGINAL_COMMAND} =~ /[\n\r]/;
# 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...
2009-08-23 14:54:37 +02:00
# 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
2010-05-21 18:02:33 +02:00
# arg"). Currently, this is how git sends across the command (including the
# single quotes):
# git-receive-pack 'reponame.git'
2009-08-23 14:54:37 +02:00
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 =~ /\.\./;
2009-08-23 14:54:37 +02:00
# save the reponame; too many things need this
$ENV{GL_REPO}=$repo;
2009-08-23 14:54:37 +02:00
# ----------------------------------------------------------------------------
# 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 ( $aa eq 'W' and mirror_mode($repo) =~ /^slave of (\S+)/ ) {
my $master = $1;
die "$ABRT GL_HOSTNAME not set; rejecting push to non-local repo\n" unless $GL_HOSTNAME;
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
2009-08-23 14:54:37 +02:00
2011-01-01 15:18:18 +01:00
my ($perm, $creator, $wild);
make REPO_BASE absolute early $ENV{GL_REPO_BASE_ABS} is meant to point to the same directory as $REPO_BASE, except it is meant to be passed to hooks, ADCs and other child programs. And since you can't be sure where the child program starts in, this became an absolute path. Gradually, however, I started using it wherever I needed an absolute path (mostly in code that jumps around various directories to do stuff). Which is silly, because there's no reason $REPO_BASE cannot also be made an absolute, even if the rc file has a relative path. So that's what I did now: made $REPO_BASE absolute very early on, and then systematically changed all uses of the longer form to the shorter form when appropriate. And so the only thing we now use the longer one for is to pass to child programs. (Implementation note: The actual change is not very big, but while I was about it I decided to make the test suite able to test with an absolute REPO_BASE also, which is why the commit seems so large.) ---- This all started with a complaint from Damien Regad. He had an extremely odd setup where his bashrc changed PWD to something other than $HOME before anything else ran. This caused those two variables to beceom inconsistent, and he had a 1-line fix he wanted me to apply. I generally don't like making special fixes for for non-standard setups, and anyway all he had to do was set the full path to REPO_BASE in the rc file to get around this. Which is what I told him and he very politely left it at that. However, this did get me thinking, and I soon realised I was needlessly conflating "relative versus absolute" with "able to be passed to child programs". Fixing that solved his problem also, as a side-effect. So I guess this is all thanks to Damien!
2011-03-18 06:29:52 +01:00
if ( $GL_ALL_READ_ALL and $verb =~ $R_COMMANDS and -d "$REPO_BASE/$repo.git") {
2011-01-01 15:18:18 +01:00
$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/;
2009-08-23 14:54:37 +02:00
# 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.
make REPO_BASE absolute early $ENV{GL_REPO_BASE_ABS} is meant to point to the same directory as $REPO_BASE, except it is meant to be passed to hooks, ADCs and other child programs. And since you can't be sure where the child program starts in, this became an absolute path. Gradually, however, I started using it wherever I needed an absolute path (mostly in code that jumps around various directories to do stuff). Which is silly, because there's no reason $REPO_BASE cannot also be made an absolute, even if the rc file has a relative path. So that's what I did now: made $REPO_BASE absolute very early on, and then systematically changed all uses of the longer form to the shorter form when appropriate. And so the only thing we now use the longer one for is to pass to child programs. (Implementation note: The actual change is not very big, but while I was about it I decided to make the test suite able to test with an absolute REPO_BASE also, which is why the commit seems so large.) ---- This all started with a complaint from Damien Regad. He had an extremely odd setup where his bashrc changed PWD to something other than $HOME before anything else ran. This caused those two variables to beceom inconsistent, and he had a 1-line fix he wanted me to apply. I generally don't like making special fixes for for non-standard setups, and anyway all he had to do was set the full path to REPO_BASE in the rc file to get around this. Which is what I told him and he very politely left it at that. However, this did get me thinking, and I soon realised I was needlessly conflating "relative versus absolute" with "able to be passed to child programs". Fixing that solved his problem also, as a side-effect. So I guess this is all thanks to Damien!
2011-03-18 06:29:52 +01:00
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 $?;
}
2009-08-23 14:54:37 +02:00
# ----------------------------------------------------------------------------
2010-02-01 12:24:39 +01:00
# over to git now
2009-08-23 14:54:37 +02:00
# ----------------------------------------------------------------------------
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();
2009-08-23 14:54:37 +02:00
$repo = "'$REPO_BASE/$repo.git'";
exec("git", "shell", "-c", "$verb $repo") unless $verb eq 'git-init';