chiark / gitweb /
WIP entirely new git approach with config parsers
authorIan Jackson <ian@liberator.relativity.greenend.org.uk>
Sat, 22 May 2010 16:25:07 +0000 (17:25 +0100)
committerIan Jackson <ian@liberator.relativity.greenend.org.uk>
Sat, 22 May 2010 16:25:07 +0000 (17:25 +0100)
.gitignore [new file with mode: 0644]
git-daemon/Makefile
git-daemon/README
git-daemon/chiark-urlmap
git-daemon/git-daemon.in [moved from git-daemon/git-daemon with 68% similarity]
git-daemon/git-service.in [moved from git-daemon/git-service with 53% similarity]
git-daemon/git-urlmap [deleted file]
git-daemon/read-urlmap [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..59c56ee
--- /dev/null
@@ -0,0 +1,11 @@
+*~
+
+dist_tmp
+userv-utils-*.tar.gz
+build
+
+git-daemon/git-upload-pack
+git-daemon/inetd.conf
+git-daemon/git-daemon
+git-daemon/git-service
+git-daemon/sedscript
index 07969cd..2d24274 100644 (file)
@@ -1,20 +1,29 @@
 # Makefile for userv-git-daemon
 #
-# This was written by Tony Finch <dot@dotat.at>
+# This was written by Tony Finch <dot@dotat.at> and subsequently
+# heavily modified by Ian Jackson <ijackson@chiark.greenend.org.uk>
 # You may do anything with it, at your own risk.
 # http://creativecommons.org/publicdomain/zero/1.0/
 
 include ../settings.make
 
-TARGETS=       git-upload-pack inetd.conf
+TARGETS=       git-upload-pack inetd.conf git-daemon git-service
 
 SUBSTVARS=     libuserv etcuserv
-SEDSCRIPT=     '$(foreach f, $(SUBSTVARS), s,@$f@,$($f),g; )'
 
 all:           $(TARGETS)
 
-%:             %.in
-               sed $(SEDSCRIPT) <$< >$@.new && mv -f $@.new $@
+sedscript:     Makefile
+               echo >$@.new '$(foreach f, $(SUBSTVARS), s,@$f@,$($f),g; )'
+               echo >>$@.new '/@@READ_URLMAP@@/c\'
+               perl >>$@.new -pe 's/$$/\\/' <read-urlmap
+               mv -f $@.new $@
+
+%:             %.in sedscript
+               set -e; \
+               sed -f sedscript <$< >$@.new; \
+               if test -x $<; then chmod +x $@.new; fi; \
+               mv -f $@.new $@
 
 install:       all
                mkdir -p $(libuserv) $(etcuserv) $(services)
index d8e6a7a..116a6b3 100644 (file)
@@ -33,31 +33,64 @@ Operation:
 ----------
 
 The userv-git-daemon is invoked by inetd which also tells it where to
-find its global git-urlmap script.
+find its global git-urlmap config.
 
 The git-daemon parses the request from the network and uses the global
-git-urlmap script to determine which user will run the requested
-service. It invokes userv for the request to be performed. The most
+git-urlmap config to determine which user will run the requested
+service.  It invokes userv for the request to be performed.  The most
 common service is git-upload-pack, which is confusingly named: it
 uploads from the repository to the network; other services supported
 by git are git-upload-archive and git-receive-pack.
 
-The git-daemon will pass any service beginning git- to userv. The
+The git-daemon will pass any service beginning git- to userv.  The
 userv configuration determines which services may be requested. This
 package includes example git-upload-pack service configurations.
 
 The service configuration uses the git-service script to run the
-service. It passes the global and per-user git-urlmap scripts to the
+service.  It passes the global and per-user git-urlmap configs to the
 git-service script to determine where in the filesyetem the requested
-repository is. Later urlmap scripts override the choices made by
-earlier ones. See the sample git-urlmap script for details of the
-variables they can examine and set.
+repository is.  Later urlmap entries override the choices made by
+earlier ones.
 
 If a repository is located, the git-service script runs the requested
 service, which is simply the git program with the same name.
 
 
+Configuration:
+--------------
+
+See "chiark-urlmap" for an example.
+
+Each line is one of:
+
+    single-user <vhost>[/<subpath>] <user> [<directory>]
+          matching requests will be handled by <user>
+          and unless overridden by <user> handled by
+          serving subdirectories of <directory>
+
+    multi-user <vhost>[/<subpath>] <directory>
+          matching requests are only those those next
+          path element starts with ~<user>.  The
+          request will be handled by <user> and unless
+          overridden by <user> will be handled by
+          serving subdirectories of <directory>
+          (<directory> must be a relative path)
+
+    repo-regexp <regexp>
+          For per-user service.  Subrepos must match this
+          regexp, which must contain a single matching
+          group which is the filesystem pathname inside
+          the <directory>.  The default is (Tcl syntax):
+    repo-regexp {^(\w[-+._0-9A-Za-z]*)$}
+
+    [no-]require-git-daemon-export-ok
+          For per-user service.  Default is no-.
+
+Last match, or last setting, wins.
+<subpath>s may start with ~
+
+
 ----------------------------------------------
-This was written by Tony Finch <dot@dotat.at>
-You may do anything with it, at your own risk.
+This was written by Tony Finch <dot@dotat.at> and subsequently
+heavily modified by Ian Jackson <ijackson@chiark.greenend.org.uk>
 http://creativecommons.org/publicdomain/zero/1.0/
index 499f21c..09e2a11 100644 (file)
@@ -1,29 +1,5 @@
-# chiark's configuration for the userv git daemon.
-#
-# This was written by Tony Finch <dot@dotat.at>
-# You may do anything with it, at your own risk.
-# http://creativecommons.org/publicdomain/zero/1.0/
 
-if ($host eq 'git.chiark.greenend.org.uk') {
-    if ($path =~ m{^~([^/]*)/(.*)}) {
-        $user = $1;
-        $dir = '.userv/public-git';
-        $repo = $2;
-    } else {
-        $user = 'webmaster';
-        $dir = '/u2/git-repos';
-        $repo = $path;
-    }
-} elsif ($server_addr eq '172.31.80.8' and
-         $host eq 'cabal.greenend.org.uk' and
-        $path =~ m|^~([^/]*)/(.*)$|) {
-    $user = $1;
-    $dir = 'cabal-git';
-    $repo = $2;
-} elsif ($host eq 'dotat.at') {
-    $user = 'fanf';
-    $dir = 'public-git';
-    $repo = $path;
-}
-
-# end
+single-user dotat.at                   fanf      dotat-git
+multi-user  git.chiark.greenend.org.uk           public-git
+multi-user       cabal.greenend.org.uk           cabal-git
+single-user git.chiark.greenend.org.uk webmaster /u2/git-repos
similarity index 68%
rename from git-daemon/git-daemon
rename to git-daemon/git-daemon.in
index 645cbc5..8fe8ca1 100755 (executable)
@@ -2,8 +2,8 @@
 #
 # A git daemon with an added userv security boundary.
 #
-# This was written by Tony Finch <dot@dotat.at>
-# You may do anything with it, at your own risk.
+# This was written by Tony Finch <dot@dotat.at> and subsequently
+# heavily modified by Ian Jackson <ijackson@chiark.greenend.org.uk>
 # http://creativecommons.org/publicdomain/zero/1.0/
 
 use strict;
@@ -22,7 +22,7 @@ sub ntoa {
 }
 our ($client,$client_addr,$client_port) = ntoa getpeername STDIN;
 our ($server,$server_addr,$server_port) = ntoa getsockname STDIN;
-our ($service,$path,$host,$user);
+our ($service,$specpath,$spechost);
 
 openlog 'userv-git-daemon', 'pid', 'daemon';
 sub fail { syslog 'err', "$client @_"; exit }
@@ -44,21 +44,28 @@ sub xread {
 my $hex_len = xread 4;
 fail "Bad hex in packet length" unless $hex_len =~ m|^[0-9a-fA-F]{4}$|;
 my $line = xread -4 + hex $hex_len;
-unless (($service,$path,$host) = $line =~
+unless (($service,$specpath,$spechost) = $line =~
         m|^(git-[a-z-]+) /*([!-~]+)\0host=([!-~]+)\0$|) {
     $line =~ s|[^ -~]+| |g;
     fail "Could not parse \"$line\""
 }
-our $uri = $_ = "git://$host/$path";
-for my $cf (@ARGV) { do $cf }
 
-fail "No user for $uri" unless defined $user;
-syslog 'notice', "$client $service $uri";
+@@READ_URLMAP@@
+
+fail "No mapping for $uri" unless defined $serve_user;
+syslog 'notice', "$client $service $serve_user $uri";
+
+my ($hn,$ha,$at,$naddrs,@addrs) = gethostbyname $host;
+die "hostname/address mismatch ($spechost $server_addr)" unless grep {
+    $server_addr eq inet_ntoa $_
+    } @addrs;
 
 my @opts = map "-D$_=${$::{$_}}",
-        grep defined ${$::{$_}} && /^[a-z_]+$/, keys %::;
+    qw(service path host
+       client client_addr client_port
+       server server_addr server_port);
 
-my @cmd = ('userv', @opts, $user, $service);
+my @cmd = ('userv', '-t300', @opts, $user, $service);
 no warnings; # suppress errors to stderr
 exec @cmd or fail "exec userv: $!";
 
similarity index 53%
rename from git-daemon/git-service
rename to git-daemon/git-service.in
index aeb4e0e..43ff340 100755 (executable)
@@ -2,8 +2,8 @@
 #
 # userv-git-daemon service script
 #
-# This was written by Tony Finch <dot@dotat.at>
-# You may do anything with it, at your own risk.
+# This was written by Tony Finch <dot@dotat.at> and subsequently
+# heavily modified by Ian Jackson <ijackson@chiark.greenend.org.uk>
 # http://creativecommons.org/publicdomain/zero/1.0/
 
 use strict;
@@ -11,29 +11,30 @@ use warnings;
 
 use Sys::Syslog;
 
-${$::{$_}} = $ENV{"USERV_U_$_"}
-       for grep s|^USERV_U_([a-z_]+)$|$1|, keys %ENV;
+our ($client,$service,$specpath,$spechost,@opts);
 
-our ($client,$service,$path,$host,@opts);
+${$::{$_}} = $ENV{"USERV_U_$_"}
+       for qw(service path host)
 
 openlog "userv-$service", 'pid', 'daemon';
 sub fail { syslog 'err', "$client @_"; exit }
 
-our ($check_repo,$check_export,$dir,$repo) = (1,0);
-our $uri = $_ = "git://$host/$path";
-for my $cf (@ARGV) { do $cf }
+@@READ_URLMAP@@
+
+fail "No mapping for $uri ($ENV{USERV_USER})" unless defined $serve_user;
+
+$1 = undef;
+fail "Bad subdirectory $serve_dir" unless $serve_dir =~ m/$repo_regexp/o;
+our $dir = $1;
+
+$dir = "$ENV{HOME}/$dir" unless $dir =~ m|^/|;
 
-my $home = (getpwuid $<)[7];
-$dir = "$home/$dir" if $dir =~ m|^[^/]|
-                    or $dir =~ s|^~/||;
-fail "Bad filename $repo" if $check_repo
-               and $repo !~ m|^\w[\w.=+-]*\.git$|;
 $dir = "$dir/$repo" if defined $repo;
 $path = $check_export ? "$dir/git-daemon-export-ok" : $dir;
 fail "$! $path" unless -e $path;
 syslog 'notice', "$client $dir";
 
-@opts = qw( --strict --timeout-30 )
+@opts = qw( --strict )
    if @opts == 0 and $service eq 'git-upload-pack';
 
 my @cmd = ($service =~ m|^(git)-(.*)$|, @opts, $dir);
diff --git a/git-daemon/git-urlmap b/git-daemon/git-urlmap
deleted file mode 100644 (file)
index a72b48c..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-# Configuration file for the userv git daemon.
-#
-# This was written by Tony Finch <dot@dotat.at>
-# You may do anything with it, at your own risk.
-# http://creativecommons.org/publicdomain/zero/1.0/
-#
-# This is a perl script which is expected to turn the information
-# found in the request variables into the location of the
-# corresponding repository, which it stores in the output variables.
-# Some information about the network connection is also available.
-#
-# REQUEST VARIABLES
-#  $service - normally "git-upload-pack"
-#    `git daemon --help` describes other possible services
-#  $host
-#  $path
-#  $uri     = git://$host/$path
-#  $_       = $uri
-#
-# OUTPUT VARIABLES
-#  $user - who runs the service
-#  $dir  - may be:
-#    * an absolute path
-#    * a path relative to the user's home directory
-#    * equivalently, a path starting ~/
-#  $repo - if set, the repository's path is $dir/$repo
-#          otherwise, the repository's path is just $dir
-#  $check_export - whether to check for a git-daemon-export-ok file
-#                  (default false)
-#  $check_repo   - whether to restrict the syntax of $repo
-#                  (default true)
-#  @opts - options for the service command
-#
-# CONNECTION VARIABLES
-#  $client_addr
-#  $client_port
-#  $client      = [$client_addr]:$client_port
-#  $server_addr
-#  $server_port
-#  $server      = [$server_addr]:$server_port
-
-if ($path =~ m{^~([^/]*)/(.*)}) {
-    $user = $1;
-    $dir = 'public-git';
-    $repo = $2;
-}
-
-# end
diff --git a/git-daemon/read-urlmap b/git-daemon/read-urlmap
new file mode 100644 (file)
index 0000000..6ae3237
--- /dev/null
@@ -0,0 +1,69 @@
+# -*- perl -*-
+
+# uses:
+#     $specpath    whole path from caller, minus any leading /s
+#     $spechost    host from caller
+#
+# sets:
+#
+#  always:
+#     $uri
+#
+#  if match found for this host and path:
+#     $serve_user   username, or undef if no match (then other serve_* invalid)
+#     $serve_dir    directory as specified in config
+#     $serve_repo   subpath under $serve_dir _including_ leading /
+#
+#  for use by user's service program
+#     $repo_regexp
+#     $require_exportok
+
+die "no config" unless @ARGV;
+
+sub remain_path ($) {
+    # return value matches {( / [^/]+ )+}x
+    my ($p) = @_;
+    return "/$specpath" if !defined $p;
+    return "" if $p eq $specpath;
+    return substr($specpath,length($p))
+       if substr($specpath,0,length($p)+1) eq "$p/";
+    return undef;
+}
+
+die unless length $specpath;
+
+our $uri = "git://$spechost/$specpath";
+our $repo_regexp= '^/*(\\w[-+._0-9A-Za-z]*)$';
+our $check_export= 0;
+
+while (<>) {
+    s/^\s*//;
+    s/\s+$//;
+    next unless m/\S/;
+    next if m/^\#/;
+
+    if (m{^ single-user \s+ (\S+?) (/\S*)? \s+ (\S+) (?: \s+ (\S+) )? $ }x) {
+       my ($h,$p,$u,$d) = ($1,$2,$3,$4);
+       next unless $h ne $host;
+       $serve_repo= remain_path($p);
+       next unless defined $serve_repo;
+       $serve_user= $u;
+       $serve_dir= $d;
+    } elsif (m{^ multi-user \s+ (\S+?) (/\S*)? \s+ (\S+) $ }x) {
+       my ($h,$p,$d) = ($1,$2,$3);
+       next unless $1 ne $host;
+       $serve_repo= remain_path($p);
+       next unless defined $serv_repo;
+       next unless $serve_repo =~ s{ ^/\~( [a-z][-+_0-9a-z] )/$ }{ / }xi;
+       $serve_user= $u;
+       $serve_dir= $d;
+    } elsif (m{^ repo-regexp \s+ (\S.*) $ }x) {
+       $repo_regexp= $1;
+    } elsif (m{^ (no-)?require-git-daemon-export-ok $ }x) {
+       $check_export= !defined $1;
+    } else {
+       die "bad config";
+    }
+}
+
+# end