#!/usr/bin/perl # gitolite shell, invoked from ~/.ssh/authorized_keys # ---------------------------------------------------------------------- use FindBin; BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; } use lib $ENV{GL_BINDIR}; use Gitolite::Rc; use Gitolite::Common; use Gitolite::Conf::Load; use strict; use warnings; # the main() sub expects ssh-ish things; set them up... my $id = ''; if ( exists $ENV{G3T_USER} ) { $id = in_local(); # file:// masquerading as ssh:// for easy testing } elsif ( exists $ENV{SSH_CONNECTION} ) { $id = in_ssh(); } elsif ( exists $ENV{REQUEST_URI} ) { $id = in_http(); } else { _die "who the *heck* are you?"; } main($id); gl_log( '==end==' ) if $$ == $ENV{GL_TID}; exit 0; # ---------------------------------------------------------------------- sub in_local { if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /git-\w+-pack/ ) { print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n"; print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n"; } return 'local'; } sub in_http { _die 'http not yet implemented...'; } sub in_ssh { $ENV{SSH_ORIGINAL_COMMAND} ||= ''; my $soc = $ENV{SSH_ORIGINAL_COMMAND}; $soc =~ s/[\n\r]+/<>/g; _die "I don't like newlines in the command: $soc\n" if $ENV{SSH_ORIGINAL_COMMAND} ne $soc; my $ip; ($ip = $ENV{SSH_CONNECTION} || '(no-IP)') =~ s/ .*//; return $ip; } # ---------------------------------------------------------------------- # call this once you are sure arg-1 is the username and SSH_ORIGINAL_COMMAND # has been setup (even if it's not actually coming via ssh). sub main { my $id = shift; gl_log( 'remote', $id, @ARGV, $ENV{SSH_ORIGINAL_COMMAND} ); umask $rc{UMASK}; # set up the user my $user = $ENV{GL_USER} = shift @ARGV; # set up the repo and the attempted access my ( $verb, $repo ) = parse_soc(); # returns only for git commands sanity($repo); $ENV{GL_REPO} = $repo; my $aa = ( $verb =~ 'upload' ? 'R' : 'W' ); # auto-create? if ( repo_missing($repo) and access( $repo, $user, '^C', 'any' ) !~ /DENIED/ ) { require Gitolite::Conf::Store; Gitolite::Conf::Store->import; new_wild_repo( $repo, $user ); gl_log( 'create', $repo, $user ); } # a ref of 'any' signifies that this is a pre-git check, where we don't # yet know the ref that will be eventually pushed (and even that won't # apply if it's a read operation). See the matching code in access() for # more information. my $ret = access( $repo, $user, $aa, 'any' ); trace( 1, "access($repo, $user, $aa, 'any')", "-> $ret" ); gl_log( 'check1', $repo, $user, $aa, 'any', '->', $ret ); trigger( 'ACCESS_1', $repo, $user, $aa, 'any', $ret ); _die $ret . "\n(or you mis-spelled the reponame)" if $ret =~ /DENIED/; check_repo_write_enabled($repo) if $aa eq 'W'; trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb ); my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'"; _system( "git", "shell", "-c", "$verb $repodir" ); trigger( 'POST_GIT', $repo, $user, $aa, 'any', $verb, times() ); } # ---------------------------------------------------------------------- sub parse_soc { my $soc = $ENV{SSH_ORIGINAL_COMMAND}; $soc ||= 'info'; my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive"; if ( $soc =~ m(^($git_commands) '/?(.*?)(?:\.git(\d)?)?'$) ) { my ( $verb, $repo, $trace_level ) = ( $1, $2, $3 ); $ENV{D} = $trace_level if $trace_level; _die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT; trace( 2, "git command", $soc ); return ( $verb, $repo ); } # after this we should not return; caller expects us to handle it all here # and exit out _die "suspicious characters loitering about '$soc'" if $soc !~ $REMOTE_COMMAND_PATT; my @words = split ' ', $soc; if ( $rc{COMMANDS}{ $words[0] } ) { trace( 2, "gitolite command", $soc ); _system( "gitolite", @words ); exit 0; } _die "unknown git/gitolite command: $soc"; } sub sanity { my $repo = shift; _die "'$repo' contains bad characters" if $repo !~ $REPONAME_PATT; _die "'$repo' ends with a '/'" if $repo =~ m(/$); _die "'$repo' contains '..'" if $repo =~ m(\.\.$); }