gitolite/src/gl-auth-command
Sitaram Chamarty 85da5572b2 some nice ADC changes... (warning: minor backward compat breakage)
- support for ADCs with unchecked arguments
  - rsync, htpasswd, and svnserve gone from core; turned into ADCs

Backward compat breakage and fix: Please see documentation for details,
but if you're using gitolite to control rsync you will now need to setup
ADCs (admin defined commands), and install at least the new "rsync" ADC.

----

Thanks to Joey Hess (see commit prior to this) for forcing me to stop
being lazy and get this out of my long term todo list.
2011-10-17 18:42:57 +05:30

196 lines
8 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)
# - 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';
}
# 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...
# 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 ( $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
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';