| 1 | #!/usr/bin/perl |
| 2 | # |
| 3 | # A git daemon with an added userv security boundary. |
| 4 | # |
| 5 | # This reads the first packet-line of the protocol, checks the syntax |
| 6 | # of the pathname and hostname, then uses userv to invoke the |
| 7 | # git-upload-pack as the target user with safe arguments. |
| 8 | # |
| 9 | # This was written by Tony Finch <dot@dotat.at> |
| 10 | # You may do anything with it, at your own risk. |
| 11 | # http://creativecommons.org/publicdomain/zero/1.0/ |
| 12 | |
| 13 | use strict; |
| 14 | use warnings; |
| 15 | |
| 16 | use POSIX; |
| 17 | use Socket; |
| 18 | use Sys::Syslog; |
| 19 | |
| 20 | use vars qw{ %vhost_default_user %vhost_user_from_tilde |
| 21 | $TILDE $REPO $HOSTNAME }; |
| 22 | |
| 23 | use lib '/etc/userv'; |
| 24 | require 'git-daemon-vhosts.pl'; |
| 25 | |
| 26 | my $peer = getpeername STDIN; |
| 27 | my ($port,$addr); |
| 28 | if (defined $peer) { |
| 29 | ($port,$addr) = sockaddr_in $peer; |
| 30 | $addr = inet_ntoa $addr; |
| 31 | $peer = "[$addr]:$port"; |
| 32 | } else { |
| 33 | $peer = "[?.?.?.?]:?"; |
| 34 | undef $!; |
| 35 | } |
| 36 | |
| 37 | openlog 'userv-git-daemon', 'pid', 'daemon'; |
| 38 | |
| 39 | sub fail { |
| 40 | syslog 'err', "$peer @_"; |
| 41 | exit; |
| 42 | } |
| 43 | |
| 44 | sub xread { |
| 45 | my $length = shift; |
| 46 | my $buffer = ""; |
| 47 | local $SIG{ALRM} = sub { fail "timeout" }; |
| 48 | alarm 30; |
| 49 | while ($length > length $buffer) { |
| 50 | my $ret = sysread STDIN, $buffer, $length, length $buffer; |
| 51 | fail "short read: expected $length bytes, got " . length $buffer |
| 52 | if defined $ret and $ret == 0; |
| 53 | fail "read: $!" if not defined $ret and $! != EINTR and $! != EAGAIN; |
| 54 | $ret = 0 if not defined $ret; |
| 55 | } |
| 56 | alarm 0; |
| 57 | return $buffer; |
| 58 | } |
| 59 | |
| 60 | my $len_hex = xread 4; |
| 61 | fail "non-hexadecimal packet length" unless $len_hex =~ m{^[0-9a-zA-Z]{4}$}; |
| 62 | my $line = xread hex $len_hex; |
| 63 | unless ($line =~ m{^git-upload-pack (?:~($TILDE)/)?($REPO[.]git)\0host=($HOSTNAME)\0$}) { |
| 64 | $line =~ s/[^ -~]+/ /g; |
| 65 | fail "could not parse \"$line\"" |
| 66 | } |
| 67 | my ($tilde,$repo,$host) = ($1,$2,$3); |
| 68 | my $url = $tilde ? "git://$host/~$tilde/$repo" : "git://$host/$repo"; |
| 69 | |
| 70 | my $user = $vhost_user_from_tilde{$host} ? $tilde : $vhost_default_user{$host}; |
| 71 | fail "no user configuration for $url" unless defined $user; |
| 72 | syslog 'info', "$peer $user $url"; |
| 73 | |
| 74 | my @opts = ("-DCLIENT=$addr", "-DHOST=$host", "-DREPO=$repo"); |
| 75 | push @opts, "-DTILDE=$tilde" if defined $tilde; |
| 76 | exec 'userv', @opts, $user, 'git-upload-pack' |
| 77 | or fail "exec userv: $!"; |
| 78 | |
| 79 | # end |