3 # gitolite shell, invoked from ~/.ssh/authorized_keys
4 # ----------------------------------------------------------------------
8 BEGIN { $ENV{GL_BINDIR
} = $FindBin::RealBin
; }
9 BEGIN { $ENV{GL_LIBDIR
} = "$ENV{GL_BINDIR}/lib"; }
10 use lib
$ENV{GL_LIBDIR
};
13 BEGIN { $ENV{HOME
} = $ENV{GITOLITE_HTTP_HOME
} if $ENV{GITOLITE_HTTP_HOME
}; }
17 use Gitolite
::Conf
::Load
;
22 # the main() sub expects ssh-ish things; set them up...
24 if ( exists $ENV{G3T_USER
} ) {
25 $id = in_file
(); # file:// masquerading as ssh:// for easy testing
26 } elsif ( exists $ENV{SSH_CONNECTION
} ) {
28 } elsif ( exists $ENV{REQUEST_URI
} ) {
31 _die
"who the *heck* are you?";
35 my $soc = $ENV{SSH_ORIGINAL_COMMAND
};
36 $soc =~ s/[\n\r]+/<<newline>>/g;
37 _die
"I don't like newlines in the command: '$soc'\n" if $ENV{SSH_ORIGINAL_COMMAND
} ne $soc;
39 # allow gitolite-shell to be used as "$SHELL". Experts only; no support, no docs
40 if (@ARGV and $ARGV[0] eq '-c') {
42 $ARGV[0] =~ s/^$0 // or _die
"unknown git/gitolite command: '$ARGV[0]'";
45 # the INPUT trigger massages @ARGV and $ENV{SSH_ORIGINAL_COMMAND} as needed
50 gl_log
('END') if $$ == $ENV{GL_TID
};
54 # ----------------------------------------------------------------------
57 gl_log
( 'file', "ARGV=" . join( ",", @ARGV ), "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
59 if ( $ENV{SSH_ORIGINAL_COMMAND
} =~ /git-\w+-pack/ ) {
60 print STDERR
"TRACE: gsh(", join( ")(", @ARGV ), ")\n";
61 print STDERR
"TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n";
67 http_setup_die_handler
();
69 _die
"GITOLITE_HTTP_HOME not set" unless $ENV{GITOLITE_HTTP_HOME
};
71 _die
"fallback to DAV not supported" if $ENV{REQUEST_METHOD
} eq 'PROPFIND';
73 # fake out SSH_ORIGINAL_COMMAND and SSH_CONNECTION when called via http,
74 # so the rest of the code stays the same (except the exec at the end).
75 http_simulate_ssh_connection
();
76 $ENV{SSH_ORIGINAL_COMMAND
} ||= '';
78 $ENV{REMOTE_USER
} ||= $rc{HTTP_ANON_USER
};
79 @ARGV = ( $ENV{REMOTE_USER
} );
82 ( $ip = $ENV{SSH_CONNECTION
} || '(no-IP)' ) =~ s/ .*//;
84 gl_log
( 'http', "ARGV=" . join( ",", @ARGV ), "SOC=" . ( $ENV{SSH_ORIGINAL_COMMAND
} || '' ), "FROM=$ip" );
91 ( $ip = $ENV{SSH_CONNECTION
} || '(no-IP)' ) =~ s/ .*//;
93 gl_log
( 'ssh', "ARGV=" . join( ",", @ARGV ), "SOC=" . ( $ENV{SSH_ORIGINAL_COMMAND
} || '' ), "FROM=$ip" );
95 $ENV{SSH_ORIGINAL_COMMAND
} ||= '';
100 # ----------------------------------------------------------------------
102 # call this once you are sure arg-1 is the username and SSH_ORIGINAL_COMMAND
103 # has been setup (even if it's not actually coming via ssh).
108 my $user = $ENV{GL_USER
} = shift @ARGV;
110 # set up the repo and the attempted access
111 my ( $verb, $repo ) = parse_soc
(); # returns only for git commands
112 Gitolite
::Conf
::Load
::sanity
($repo, $REPONAME_PATT);
113 $ENV{GL_REPO
} = $repo;
114 my $aa = ( $verb =~ 'upload' ?
'R' : 'W' );
116 # set up env vars from options set for this repo
120 if ( repo_missing
($repo) and access
( $repo, $user, '^C', 'any' ) !~ /DENIED/ ) {
121 require Gitolite
::Conf
::Store
;
122 Gitolite
::Conf
::Store
->import;
123 new_wild_repo
( $repo, $user, $aa );
124 gl_log
( 'create', $repo, $user, $aa );
127 # a ref of 'any' signifies that this is a pre-git check, where we don't
128 # yet know the ref that will be eventually pushed (and even that won't
129 # apply if it's a read operation). See the matching code in access() for
131 unless ( $ENV{GL_BYPASS_ACCESS_CHECKS
} ) {
132 my $ret = access
( $repo, $user, $aa, 'any' );
133 trigger
( 'ACCESS_1', $repo, $user, $aa, 'any', $ret );
134 _die
$ret . "\n(or you mis-spelled the reponame)" if $ret =~ /DENIED/;
136 gl_log
( "pre_git", $repo, $user, $aa, 'any', $ret );
139 trigger
( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
140 if ( $ENV{REQUEST_URI
} ) {
141 _system
( "git", "http-backend" );
143 my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";
144 _system
( "git", "shell", "-c", "$verb $repodir" );
146 trigger
( 'POST_GIT', $repo, $user, $aa, 'any', $verb );
149 # ----------------------------------------------------------------------
152 my $soc = $ENV{SSH_ORIGINAL_COMMAND
};
155 my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
156 # simplify the regex; we'll handle all the reponame nuances later
157 if ( $soc =~ m
(^($git_commands) '?/?(.*?)'?
$) ) {
158 my ( $verb, $repo ) = ( $1, $2 );
159 trace
( 2, "git command", $soc );
161 # clean up the repo name; first extract the trace level if supplied
162 # (and no, you can't have a trace level *and* a trailing slash).
163 $ENV{D
} = $1 if $repo =~ s/\.git(\d)$//;
164 # and then the git-daemon-compatibility trailers
166 $repo =~ s
(\
.git
$)();
168 _die
"invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT;
169 return ( $verb, $repo );
172 # after this we should not return; caller expects us to handle it all here
175 my @words = split ' ', $soc;
176 if ( $rc{COMMANDS
}{ $words[0] } ) {
177 if ( $rc{COMMANDS
}{ $words[0] } ne 'ua' ) {
178 _die
"suspicious characters loitering about '$soc'" if $soc !~ $REMOTE_COMMAND_PATT;
179 _die
"no relative paths allowed anywhere!" if $soc =~ m
(\
.\
./);
181 trace
( 2, "gitolite command", $soc );
182 _system
( "gitolite", @words );
186 _die
"unknown git/gitolite command: '$soc'";
189 # ----------------------------------------------------------------------
190 # helper functions for "in_http"
192 sub http_setup_die_handler
{
194 $SIG{__DIE__
} = sub {
195 my $service = ( $ENV{SSH_ORIGINAL_COMMAND
} =~ /git-receive-pack/ ?
'git-receive-pack' : 'git-upload-pack' );
196 my $message = shift; chomp($message);
197 print STDERR
"$message\n";
199 http_print_headers
($service);
201 # format the service response, then the message. With initial
202 # help from Ilari and then a more detailed email from Shawn...
203 $service = "# service=$service\n"; $message = "ERR $message\n";
204 $service = sprintf( "%04X", length($service) + 4 ) . "$service"; # no CRLF on this one
205 $message = sprintf( "%04X", length($message) + 4 ) . "$message";
208 print "0000"; # flush-pkt, apparently
210 print STDERR
$service;
211 print STDERR
$message;
212 exit 0; # if it's ok for die_webcgi in git.git/http-backend.c, it's ok for me ;-)
216 sub http_simulate_ssh_connection
{
217 # these patterns indicate normal git usage; see "services[]" in
218 # http-backend.c for how I got that. Also note that "info" is overloaded;
219 # git uses "info/refs...", while gitolite uses "info" or "info?...". So
220 # there's a "/" after info in the list below
221 if ( $ENV{PATH_INFO
} =~ m
(^/(.*)/(HEAD
$|info
/refs$|objects/|git
-(?
:upload
|receive
)-pack$)) ) {
223 my $verb = ( $ENV{REQUEST_URI
} =~ /git-receive-pack/ ) ?
'git-receive-pack' : 'git-upload-pack';
224 $ENV{SSH_ORIGINAL_COMMAND
} = "$verb '$repo'";
226 # this is one of our custom commands; could be anything really,
227 # because of the adc feature
228 my ($verb) = ( $ENV{PATH_INFO
} =~ m
(^/(\S
+)) );
229 my $args = $ENV{QUERY_STRING
};
231 $args =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
232 $ENV{SSH_ORIGINAL_COMMAND
} = $verb;
233 $ENV{SSH_ORIGINAL_COMMAND
} .= " $args" if $args;
234 http_print_headers
(); # in preparation for the eventual output!
236 # we also need to pipe STDERR out via STDOUT, else the user doesn't see those messages!
237 open(STDERR
, ">&STDOUT") or _die
"Can't dup STDOUT: $!";
239 $ENV{SSH_CONNECTION
} = "$ENV{REMOTE_ADDR} $ENV{REMOTE_PORT} $ENV{SERVER_ADDR} $ENV{SERVER_PORT}";
242 my $http_headers_printed = 0;
244 sub http_print_headers
{
245 my ( $service, $code, $text ) = @_;
247 return if $http_headers_printed++;
249 $text ||= "OK - gitolite";
252 print "Status: $code $text\r\n";
253 print "Expires: Fri, 01 Jan 1980 00:00:00 GMT\r\n";
254 print "Pragma: no-cache\r\n";
255 print "Cache-Control: no-cache, max-age=0, must-revalidate\r\n";
257 print "Content-Type: application/x-$service-advertisement\r\n";
259 print "Content-Type: text/plain\r\n";