From 52e0ed34882ba22edd857bdefc0d57afe8a98bb2 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 5 Sep 2010 18:43:21 +0530 Subject: [PATCH] (http) auth: handle REQUEST_URI and friends TODO: if the verb doesn't actually contain "git-receive-pack", I am assuming it is some sort of read. The list in services[] in http-backend.c does not seem to look like any other verb is a "write"; need to check this with someone. For normal git commands: - PATH_INFO gives you the repo name - REQUEST_URI gives you the verb - we construct a fake SSH_ORIGINAL_COMMAND so the rest of the processing does not have to change For our special commands: - PATH_INFO is actually the verb - QUERY_STRING has the parameters - we again fake out the SSH_ORIGINAL_COMMAND - we print the extra HTTP headers in anticipation of the actual output Either way, we also fake out the SSH_CONNECTION so that the IP address can get logged ok And of course REMOTE_USER is now the incoming userid Finally, at the end, we exec GIT_HTTP_BACKEND instead of the normal one --- src/gitolite.pm | 16 +++++++++ src/gl-auth-command | 85 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 82 insertions(+), 19 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 78d166f..cbd6180 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -92,6 +92,22 @@ sub dbg { } } +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; diff --git a/src/gl-auth-command b/src/gl-auth-command index 958eb5b..a95fbc3 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -3,20 +3,29 @@ use strict; use warnings; -# === auth-command === -# the command that GL users actually run - -# part of the gitolite (GL) suite - -# how run: via sshd, being listed in "command=" in ssh authkeys -# when: every login by a GL user -# input: $1 is GL username, plus $SSH_ORIGINAL_COMMAND -# output: -# security: - -# robustness: - -# other notes: +# ---------------------------------------------------------------------------- +# 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 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 +# ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # common definitions @@ -67,12 +76,43 @@ if (@ARGV and $ARGV[0] eq '-s') { shift; } -# no (more) arguments given? default user is $USER (fedorahosted works like -# this, and it is harmless for others) -@ARGV = ($ENV{USER}) unless @ARGV; +# ---------------------------------------------------------------------------- +# set up SSH_ORIGINAL_COMMAND and SSH_CONNECTION in http mode +# ---------------------------------------------------------------------------- -# first, fix the biggest gripe I have with gitosis, a 1-line change -my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere! +# 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}) { + # 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) = ($ENV{PATH_INFO} =~ m(^/(.*)\.git(?:/|$))); + my ($verb) = ($ENV{REQUEST_URI} =~ m((git-(?:receive|upload)-pack))); + print STDERR "(gitolite) no verb found in $ENV{REQUEST_URI}\n" unless $verb; + $verb ||= '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}"; + $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; +} # if there are any more arguments, they're a list of group names that the user # is a member of @@ -212,6 +252,13 @@ die "$aa access for $repo DENIED to $user\n" unless $perm =~ /$aa/; # 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'";