chiark / gitweb /
git-daemon.pl: second iteration based on feedback from Ian.
authorTony Finch <dot@dotat.at>
Fri, 26 Mar 2010 18:00:46 +0000 (18:00 +0000)
committerIan Jackson <ian@liberator.relativity.greenend.org.uk>
Sat, 22 May 2010 14:54:40 +0000 (15:54 +0100)
Instead of invoking the standard git-daemon via userv, do what
the standard git-daemon does and invoke git-upload-pack.

Log errors to syslog. Obtain the client IP address and port.

Use a configuration file to work out the user from the
virtual host name and tilde part of the URL.
Put the syntax check regexes in the configuration file.

Pass parameters to userv git-upload-pack service with
user-defined variables instead of command line arguments.

git-daemon/git-daemon-vhosts.pl
git-daemon/git-daemon.pl [changed mode: 0644->0755]

index 4c6fb62..2046f9f 100644 (file)
@@ -8,6 +8,10 @@
 #      git://HOSTNAME/REPO.git
 #      git://HOSTNAME/~TILDE/REPO.git
 
+$HOSTNAME = qr{[-.0-9a-z]+};
+$TILDE    = qr{[0-9a-z]+};
+$REPO     = qr{[-+._0-9A-Za-z]+};
+
 # The vhost_default_user hash specifies what user handles git requests
 # for each virtual host, if the URL does not have a tilde part, or if
 # the virtual host does not appear in the vhost_user_from_tilde hash.
old mode 100644 (file)
new mode 100755 (executable)
index f18f6b9..df753e1
@@ -1,10 +1,10 @@
 #!/usr/bin/perl
 #
-# A very simple userv git-daemon wrapper.
+# A git daemon with an added userv security boundary.
 #
 # This reads the first packet-line of the protocol, checks the syntax
-# of the user, pathname, and hostname, then uses userv to invoke the
-# real git daemon as the target user with safe arguments.
+# of the pathname and hostname, then uses userv to invoke the
+# git-upload-pack as the target user with safe arguments.
 #
 # This was written by Tony Finch <dot@dotat.at>
 # You may do anything with it, at your own risk.
@@ -14,49 +14,74 @@ use strict;
 use warnings;
 
 use POSIX;
+use Socket;
+use Sys::Syslog;
 
-my $USER = qr{[0-9a-z]+};
-my $PATH = qr{[-+,._/0-9A-Za-z]+};
-my $HOST = qr{[-.0-9A-Za-z]+};
+use vars qw{ %vhost_default_user %vhost_user_from_tilde
+            $TILDE $REPO $HOSTNAME };
+
+use lib '/etc/userv';
+require 'git-daemon-vhosts.pl';
+
+my $peer = getpeername STDIN;
+my ($port,$addr);
+if (defined $peer) {
+    ($port,$addr) = sockaddr_in $peer;
+    $addr = inet_ntoa $addr;
+    $peer = "[$addr]:$port";
+} else {
+    $peer = "[?.?.?.?]:?";
+    undef $!;
+}
+
+openlog 'userv-git-daemon', 'pid', 'daemon';
+
+sub fail {
+    syslog 'err', "$peer @_";
+    exit;
+}
 
 sub xread {
     my $length = shift;
     my $buffer = "";
     my $count = 0;
+    # simply die if the client takes too long
+    alarm 30;
     while ($length > length $buffer) {
-       my $data;
-       my $ret = sysread STDIN, $data, $len
-         while not defined $ret and ($! == EINTR or $! == EAGAIN);
-       die "read" unless defined $ret;
-       die "short read: expected $length bytes, got $count\n" if $ret == 0;
-       $buffer .= $data;
-       $count += $ret;
+        my ($data,$ret);
+       do {
+           $ret = sysread STDIN, $data, $length
+       } while not defined $ret and ($! == EINTR or $! == EAGAIN);
+        fail "read: $!" unless defined $ret;
+        fail "short read: expected $length bytes, got $count" if $ret == 0;
+        $buffer .= $data;
+        $count += $ret;
     }
+    alarm 0;
     return $buffer;
 }
 
 my $len_hex = xread 4;
-die "bad packet length" unless $len_hex =~ m{^[0-9a-zA-Z]{4}$};
-my $len = hex $len;
+fail "non-hexadecimal packet length" unless $len_hex =~ m{^[0-9a-zA-Z]{4}$};
+my $len = hex $len_hex;
 
 my $line = xread $len;
-$line =~ m{^git-upload-pack ~($USER)/($PATH[.]git)\0host=($HOST)\0$};
-my ($user,$path,$host) = ($1,$2,$3);
-
-# child's output will go directly to inetd
-open CHILD, '-|', 'userv', $user,
-  qw(git daemon --inetd --strict-paths
-     --user-path=public-git --forbid-override=receive-pack)
-  or die "open pipe to userv: $!\n";
-
-# proxy command line to child
-syswrite CHILD, $len_hex.$line
-  or die "write to userv: $!\n";
-
-# relay stdin to child
-open STDOUT, ">&CHILD"
-  or die "dup: $!\n";
-exec 'cat'
-  or die "exec: $!\n";
-
-die
+unless ($line =~ m{^git-upload-pack (?:~($TILDE)/)?($REPO[.]git)\0host=($HOSTNAME)\0$}) {
+    $line =~ s/[^ -~]+/ /g;
+    fail "could not parse \"$line\""
+}
+my ($tilde,$repo,$host) = ($1,$2,$3);
+my $url = $tilde ? "git://$host/~$tilde/$repo" : "git://$host/$repo";
+
+my $user = $vhost_user_from_tilde{$host} ? $tilde : $vhost_default_user{$host};
+fail "no user configuration for $url" unless defined $user;
+
+syslog 'info', "$peer $user $url";
+
+my @opts = ("-DCLIENT=$addr", "-DHOST=$host", "-DREPO=$repo");
+push @opts, "-DTILDE=$tilde" if defined $tilde;
+
+exec 'userv', @opts, $user, 'git-upload-pack'
+    or fail "exec userv: $!";
+
+# end