chiark / gitweb /
Merge branch 'zealot'
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 20 Aug 2011 16:24:02 +0000 (17:24 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 20 Aug 2011 16:24:02 +0000 (17:24 +0100)
Conflicts:
.gitignore
ipif/Makefile

19 files changed:
.gitignore
Makefile
README
debian/changelog
debian/control
debian/rules
debian/userv-git-daemon/postinst [new file with mode: 0755]
git-daemon/Makefile [new file with mode: 0644]
git-daemon/README [new file with mode: 0644]
git-daemon/git-daemon.in [new file with mode: 0755]
git-daemon/git-service.in [new file with mode: 0755]
git-daemon/git-upload-pack.in [new file with mode: 0644]
git-daemon/git-urlmap [new file with mode: 0644]
git-daemon/inetd.conf.in [new file with mode: 0644]
git-daemon/logrotate.in [new file with mode: 0644]
git-daemon/read-urlmap [new file with mode: 0644]
ipif/Makefile
ipif/service.c
settings.make

index fc62bd474870cfcbcc2ef6c419c8a45b84c54ce3..0b6440f49a61a1e1ad145f6c1f51b04aaa3f3fa3 100644 (file)
@@ -1,5 +1,14 @@
+*.o
+*~
+*.new
+
 dist_tmp
 userv-utils-*.tar.gz
 build
 
-*.o
+git-daemon/git-upload-pack
+git-daemon/inetd.conf
+git-daemon/git-daemon
+git-daemon/git-service
+git-daemon/sedscript
+git-daemon/logrotate
index eecb31a61cca318a08c7276ebe6cc3133fd5bdfa..ad795cb7b6b41e638ac775a0fff205363b15c22a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -21,7 +21,7 @@ VERSION=0.2.99.0.1
 all:
        @echo >&2 'See README.  This is not a unified package.'
 
-SUBDIRS_DISTCLEAN=     www-cgi ipif
+SUBDIRS_DISTCLEAN=     www-cgi ipif git-daemon
 
 distclean:
        find . \( -name '*~' -o -name '#*#' -o -name '*.o' -o -name core \
diff --git a/README b/README
index 806e8bff46d7536828d6fcd5d9d9fcd7073dcf33..a968a8ee92560a59c997c931cbfa5814275a0ae4 100644 (file)
--- a/README
+++ b/README
@@ -25,6 +25,7 @@ ipif          Y Y A   UC create IP interfaces/VPNs (Linux-specific)
 www-cgi                Y B A   UC provide CGIs which run as themselves
 misc/mailq     Y S S   UC list mail queue even if sendmail forbids
 misc/ndc-reload        Y S S   UC reload named after editing own zone files
+git-daemon     Y Y Y   UC safely publish git repositories on port 9418
 newsrc-lg      X B X   Acquire list of subscribed groups from .newsrcs
 
 Key to the Status:
@@ -47,10 +48,11 @@ Key to the Status:
    S   Too small to need any significant documentation.
 
 userv-utils are
-Copyright (C)1996-2000,2003 Ian Jackson <ian@davenant.greenend.org.uk>.
+Copyright (C)1996-2010 Ian Jackson <ian@davenant.greenend.org.uk>.
 Copyright (C)1998 David Damerell <damerell@chiark.greenend.org.uk>
 Copyright (C)1999,2003
    Chancellor Masters and Scholars of the University of Cambridge
+Copyright (C)2010 Tony Finch <fanf@dotat.at>
 
 All the utilities here are free software.  You can redistribute it
 and/or modify them under the terms of the GNU General Public License
index 17009bc9b3e606e4d07f0455ff208a3fbb6f9fcb..72b0964745bddc5128b31a2157f60eb031afbe99 100644 (file)
@@ -1,3 +1,24 @@
+userv-utils (0.4~beta2) unstable; urgency=low
+
+  ipif:
+  * Now uses tun, not slip.  All modern Linux kernels have tun support,
+    and we weren't portable to non-Linux anyway.  slattach has sometimes
+    been implicated in kernel problems.  Interfaces are now called
+    "userv%d" (ie, userv0, userv1, etc.).  Only "slip" is now supported.
+
+ --
+
+userv-utils (0.4~beta1) unstable; urgency=low
+
+  git-daemon:
+  * New userv-git-daemon service.
+
+  package admin:
+  * Now in git.
+  * Fixed up some portability problems.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sat, 22 May 2010 19:50:57 +0100
+
 userv-utils (0.3) unstable; urgency=medium
 
   dyndns:
index 3247d90ca4cfa47227a59960082a49d7b8179e66..4f11db81dfbcc32346de077f47d42eb30ec2e405 100644 (file)
@@ -65,6 +65,20 @@ Description: user-controlled group membership
  The default configuration allows users to create and manage a few
  groups, but is reasonably conservative.
 
+Package: userv-git-daemon
+Architecture: all
+Depends: userv, git-core
+Description: per-user git daemon service
+ userv-git-daemon allows users to publish git repositories which will
+ be published via the git protocol on 9418.  This is a bit like
+ git-daemon except that the actual reading of each user's repositories
+ is done as that user.
+ .
+ The default configuration does nothing: you must (a) manually copy
+ the line from /usr/share/doc/examples/userv-git-daemon.inetd into
+ /etc/inetd.conf and (b) specifically list hostnames and target
+ directories in /etc/userv/git-urlmap.
+
 Package: userv-misc
 Architecture: all
 Depends: userv
index 49772399a7142cf391b227b0816b637568be3bf8..b7353f3718106fef500e59e0a335b2468af6398a 100755 (executable)
@@ -1,9 +1,9 @@
 #!/usr/bin/make -f
 
-subdirs_build= ipif www-cgi
+subdirs_build= ipif www-cgi git-daemon
 subdirs_nobuild=dyndns groupmanage misc
 package=       userv-utils
-packages_indep=        userv-dyndns userv-groupmanage userv-misc
+packages_indep=        userv-dyndns userv-groupmanage userv-misc userv-git-daemon
 packages_arch= userv-ipif userv-cgi
 packages=      $(packages_indep) $(packages_arch)
 
@@ -34,7 +34,8 @@ binary-prep:
                $(MAKE) -C $$s install install-docs install-examples \
                        prefix=$t/userv-$$s/usr \
                        etcdir=$t/userv-$$s/etc \
-                       varlib=$t/userv-$$s/var/lib; \
+                       vardir=$t/userv-$$s/var \
+                       gituser=root; \
        done
        #
        mv debian/tmp/userv-www-cgi debian/tmp/userv-cgi
@@ -71,6 +72,7 @@ binary-hook-userv-groupmanage:
 binary-hook-userv-cgi:
 binary-hook-userv-dyndns:
 binary-hook-userv-ipif:
+binary-hook-userv-git-daemon:
 
 binary-one:
        set -e; for f in preinst postinst prerm postrm conffiles; do \
diff --git a/debian/userv-git-daemon/postinst b/debian/userv-git-daemon/postinst
new file mode 100755 (executable)
index 0000000..8609c73
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+set -e
+
+# Copyright (C) 2010 Ian Jackson
+#
+# This file is part of userv-git-daemon, part of userv-utils
+#
+# This is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with userv-utils; if not, write to the Free Software
+# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+GITDUSER=git
+
+defaults=/etc/default/userv-git-daemon
+if test -f $defaults; then . $defaults; fi
+
+if [ "$GITDUSER" ]; then
+       if id $GITDUSER >/dev/null 2>&1; then exit 0; fi
+
+       adduser --system --group --gecos 'userv git daemon' \
+               --home /etc/userv $GITDUSER
+fi
diff --git a/git-daemon/Makefile b/git-daemon/Makefile
new file mode 100644 (file)
index 0000000..ea76975
--- /dev/null
@@ -0,0 +1,60 @@
+# Makefile for userv-git-daemon
+#
+# 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
+
+gituser=       git
+varloggit=     $(varlog)/git
+
+TARGETS=       git-upload-pack inetd.conf git-daemon git-service logrotate
+
+SUBSTVARS=     libuserv etcuserv varloggit gituser
+
+CONFIGS=       $(services)/git-upload-pack \
+               $(etcuserv)/git-urlmap \
+               $(etcdir)/logrotate.d/userv-git-daemon
+
+all:           $(TARGETS)
+
+sedscript:     Makefile read-urlmap
+               echo >$@.new '$(foreach f, $(SUBSTVARS), s,@$f@,$($f),g; )'
+               echo >>$@.new '/@@READ_URLMAP@@/c\'
+               perl >>$@.new -pe 's/\\/\\\\/g; 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) \
+                       $(etcdir)/logrotate.d
+               install -d -o $(gituser) -g adm $(varloggit)
+               cp git-daemon git-service $(libuserv)
+               cp git-upload-pack $(services)/git-upload-pack:new
+               cp git-urlmap $(etcuserv)/git-urlmap:new
+               cp logrotate $(etcdir)/logrotate.d/userv-git-daemon:new
+               set -e; for f in $(CONFIGS); do \
+                       if test -f $$f; then continue; fi; \
+                       mv $$f:new $$f; \
+               done
+
+mkdocdir:
+               mkdir -p $(docdir)/userv-git-daemon
+
+install-docs:  mkdocdir
+               cp README $(docdir)/userv-git-daemon/README
+
+install-examples: all mkdocdir
+               cp inetd.conf $(docdir)/userv-git-daemon/inetd.conf
+
+distclean clean:
+               rm -f $(TARGETS) *~
+
+# end
diff --git a/git-daemon/README b/git-daemon/README
new file mode 100644 (file)
index 0000000..76d9946
--- /dev/null
@@ -0,0 +1,68 @@
+userv-git-daemon is a replacement for the standard git daemon,
+which provides anonymous remote access to git repositories.
+
+It uses userv to invoke the service requested by the client, and users
+can configure it to map git:// URLs to repositories and enable and
+disable services as they see fit, without intervention from the system
+administrator.
+
+
+To install:
+-----------
+
+Adjust the paths in ../settings.make as necessary.
+userv-git-daemon uses $(libuserv), $(etcuserv), and $(services).
+
+Type make install.
+
+Create a "git" user that will run the outer part of the git-daemon.
+Ensure your /etc/services contains a line like "git 9418/tcp".
+
+Insert the inetd.conf fragment into your /etc/inetd.conf
+and tell inetd to reload.
+
+As a test user, create a 'public-git' directory, and copy a bare git
+repository into it, e.g.
+       git clone --bare git://dotat.at/unifdef.git public-git/unifdef.git
+
+This repository should now be visible:
+       git ls-remote git://localhost/~test/unifdef.git
+
+
+Operation:
+----------
+
+The userv-git-daemon is invoked by inetd which also tells it where to
+find its global git-urlmap config.
+
+The git-daemon parses the request from the network and uses the global
+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
+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 configs to the
+git-service script to determine where in the filesyetem the requested
+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 "git-urlmap" for syntax description and an example.
+
+
+----------------------------------------------
+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/
diff --git a/git-daemon/git-daemon.in b/git-daemon/git-daemon.in
new file mode 100755 (executable)
index 0000000..1b1b540
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/bin/perl
+#
+# A git daemon with an added userv security boundary.
+#
+# 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;
+use warnings;
+
+use POSIX;
+use Socket;
+use Sys::Syslog;
+
+BEGIN {
+    if ($ARGV[0] =~ s/^-L//) {
+       my $logfile= shift @ARGV;
+       open STDERR, ">> $logfile" or die $!;
+    }
+}
+
+sub ntoa {
+    my $sockaddr = shift;
+    return ('(local)') unless defined $sockaddr;
+    my ($port,$addr) = sockaddr_in $sockaddr;
+    $addr = inet_ntoa $addr;
+    return ("[$addr]:$port",$addr,$port);
+}
+our ($client,$client_addr,$client_port) = ntoa getpeername STDIN;
+our ($server,$server_addr,$server_port) = ntoa getsockname STDIN;
+our ($service,$specpath,$spechost);
+
+printf STDERR "%s [$$] %s %s\n",
+    strftime("%Y-%m-%d %H:%M:%S %Z", localtime), $server, $client;
+
+openlog 'userv-git-daemon', 'pid', 'daemon';
+sub fail { syslog 'err', "$client @_"; exit }
+
+$SIG{ALRM} = sub { fail "timeout" };
+alarm 30;
+
+sub xread {
+    my $length = shift;
+    my $buffer = "";
+    while ($length > length $buffer) {
+        my $ret = sysread STDIN, $buffer, $length, length $buffer;
+        fail "Expected $length bytes, got ".length $buffer
+                            if defined $ret and $ret == 0;
+        fail "read: $!" if not defined $ret and $! != EINTR and $! != EAGAIN;
+    }
+    return $buffer;
+}
+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,$specpath,$spechost) = $line =~
+        m|^(git-[a-z-]+) /*([!-~]+)\0host=([!-~]+)\0$|) {
+    $line =~ s|[^ -~]+| |g;
+    fail "Could not parse \"$line\""
+}
+
+@@READ_URLMAP@@
+
+fail "No global mapping for $uri" unless defined $serve_user;
+
+my ($hn,$ha,$at,$naddrs,@addrs) = gethostbyname $spechost;
+fail "hostname/address mismatch ($spechost $server_addr)" unless grep {
+    $server_addr eq inet_ntoa $_
+    } @addrs;
+
+our @opts;
+
+push @opts, "-D$_=${$::{$_}}"
+    for qw(service specpath spechost
+          client client_addr client_port
+          server server_addr server_port);
+
+fail "no user $serve_user" unless getpwnam($serve_user);
+
+syslog 'notice', "$client $service $uri $serve_user";
+
+my @cmd = ('userv', '-t300', @opts, $serve_user, $service);
+no warnings; # suppress errors to stderr
+exec @cmd or fail "exec userv: $!";
+
+# end
diff --git a/git-daemon/git-service.in b/git-daemon/git-service.in
new file mode 100755 (executable)
index 0000000..2b8aff3
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/bin/perl
+#
+# userv-git-daemon service script
+#
+# 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;
+use warnings;
+
+use POSIX;
+use Sys::Syslog;
+
+our ($client,$service,$specpath,$spechost,@opts);
+
+${$::{$_}} = $ENV{"USERV_U_$_"}
+       for qw(service specpath spechost client);
+
+openlog "userv-$service:$ENV{USER}", 'pid', 'daemon';
+sub fail { syslog 'err', "$client @_"; exit }
+
+@@READ_URLMAP@@
+
+fail "No user $ENV{USER} mapping for $uri" unless defined $serve_user;
+
+$serve_dir = "$ENV{HOME}/$serve_dir" unless $serve_dir =~ m|^/|;
+
+if (length $serve_repo) {
+    my $inspect= $serve_repo;
+    $inspect =~ s,^/,,;
+    fail "Bad subdirectory $serve_repo" unless $inspect =~ m/$repo_regexp/o;
+    fail "bad config - repo-regexp does not capture" unless defined $1;
+    $serve_repo= "/$1";
+}
+
+my $dir = $serve_dir.$serve_repo;
+
+my $path = $check_export ? "$dir/git-daemon-export-ok" : $dir;
+fail "$! $path" unless -e $path;
+
+syslog 'notice', "$client $uri $dir";
+
+@opts = qw( --strict )
+   if @opts == 0 and $service eq 'git-upload-pack';
+
+my @cmd = ($service =~ m|^(git)-(.*)$|, @opts, $dir);
+no warnings; # suppress errors to stderr
+exec @cmd or fail "exec $service: $!";
+
+# end
diff --git a/git-daemon/git-upload-pack.in b/git-daemon/git-upload-pack.in
new file mode 100644 (file)
index 0000000..bb4e847
--- /dev/null
@@ -0,0 +1,16 @@
+# userv configuration for git-daemon git-upload-pack service
+#
+# 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 ( grep service-user-shell /etc/shells
+   & glob service git-upload-pack
+   & glob calling-user @gituser@
+   )
+        reset
+       errors-to-syslog daemon error
+       execute @libuserv@/git-service @etcuserv@/git-urlmap .userv/git-urlmap
+fi
+
+# end
diff --git a/git-daemon/git-urlmap b/git-daemon/git-urlmap
new file mode 100644 (file)
index 0000000..80a1f3c
--- /dev/null
@@ -0,0 +1,35 @@
+# Each line is one of:
+# 
+#     single-user <vhost>[/<vsubpath>] <user> [<directory>]
+#           matching requests will be handled by <user>
+#           and unless overridden by <user> handled by
+#           serving subdirectories of <directory>
+# 
+#     multi-user <vhost>[/<vsubpath>] <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:
+#     repo-regexp ^(w[-+._0-9A-Za-z]*/?\.git)$
+# 
+#     [no-]require-git-daemon-export-ok
+#           For per-user service.  Default is no-.
+# 
+# Last match, or last setting, wins.
+# <vsubpath>s may start with ~
+
+# here is an example, taken from chiark:
+#
+# single-user dotat.at                   fanf      dotat-git
+# single-user git.chiark.greenend.org.uk webmaster /u2/git-repos
+#
+# multi-user       cabal.greenend.org.uk           cabal-git
+# multi-user  git.chiark.greenend.org.uk           public-git
diff --git a/git-daemon/inetd.conf.in b/git-daemon/inetd.conf.in
new file mode 100644 (file)
index 0000000..f316fcc
--- /dev/null
@@ -0,0 +1,2 @@
+# Example inetd.conf line for the userv git daemon.
+git stream tcp nowait @gituser@ /usr/sbin/tcpd @libuserv@/git-daemon -L@varloggit@/userv-git-daemon.log @etcuserv@/git-urlmap
diff --git a/git-daemon/logrotate.in b/git-daemon/logrotate.in
new file mode 100644 (file)
index 0000000..06e0ef2
--- /dev/null
@@ -0,0 +1,6 @@
+@varloggit@/userv-git-daemon.log {
+       rotate 7
+       daily
+       missingok
+       delaycompress
+}
diff --git a/git-daemon/read-urlmap b/git-daemon/read-urlmap
new file mode 100644 (file)
index 0000000..696ee65
--- /dev/null
@@ -0,0 +1,92 @@
+# -*- 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
+
+sub remain_path ($) {
+    # return value matches {( / [^/]+ )+}x
+    my ($vsubpath) = @_;
+    syslog 'debug', sprintf "DEBUG remain_path %s $specpath",
+                              (defined $vsubpath ? $vsubpath : '<undef>');
+    return "/$specpath" if !defined $vsubpath;
+    return "" if $vsubpath eq $specpath;
+    return substr($specpath,length($vsubpath))
+       if substr($specpath,0,length($vsubpath)+1) eq "$vsubpath/";
+    return undef;
+}
+
+fail "no config ??" unless @ARGV;
+fail "no specpath ??" unless length $specpath;
+
+our $uri = "git://$spechost/$specpath";
+
+our $repo_regexp= '^(\\w[-+._0-9A-Za-z]*/?\.git)$';  # stupid emacs ';
+our $check_export= 0;
+
+our ($serve_user, $serve_dir, $serve_repo);
+
+sub fexists ($) {
+    my ($f) = @_;
+    if (stat $f) {
+       -f _ or fail "bad config $_ - not a file";
+       return 1;
+    } else {
+       $!==&ENOENT or fail "bad config $_ - could not stat: $!";
+       return 0;
+    }
+}
+
+@ARGV = grep { fexists($_) } @ARGV;
+
+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,$v,$u,$d) = ($1,$2,$3,$4);
+       next unless $h eq $spechost;
+       $serve_repo= remain_path($v);
+       next unless defined $serve_repo;
+       $serve_user= $u;
+       $serve_dir= $d;
+        syslog 'debug', "DEBUG $ARGV:$. match".
+            " $serve_user $serve_dir $serve_repo";
+    } elsif (m{^ multi-user \s+ (\S+?) (/\S*)? \s+ (\S+) $ }x) {
+       my ($h,$v,$d) = ($1,$2,$3);
+       next unless $1 eq $spechost;
+       $serve_repo= remain_path($v);
+       next unless defined $serve_repo;
+        syslog 'debug', "DEBUG $ARGV:$. perhaps $serve_repo";
+       next unless $serve_repo =~ s{ ^/\~( [a-z][-+_0-9a-z]* )/ }{/}xi;
+       $serve_user= $1;
+       $serve_dir= $d;
+        syslog 'debug', "DEBUG $ARGV:$. match".
+            " $serve_user $serve_dir $serve_repo";
+    } elsif (m{^ repo-regexp \s+ (\S.*) $ }x) {
+       $repo_regexp= $1;
+    } elsif (m{^ (no-)?require-git-daemon-export-ok $ }x) {
+       $check_export= !defined $1;
+    } else {
+       fail "config syntax error at $ARGV:$.";
+    }
+}
+
+# end
index 77910581153ee1f5d261b99c081044a13f2051e5..a34a85ff0d41407b0238b3dc23f3de715ac52415 100644 (file)
@@ -53,12 +53,12 @@ install-examples:
                cp *.example $(etcvpn)/.
 
 udptunnel-reconf:      udptunnel-reconf.pl Makefile
-               set -x; perl -p \
--e '           print ' \
--e '   "\$$shareuserv= \"$(shareuserv)\";\n".' \
--e '   "\$$etcvpn= \"$(etcvpn)\";\n".' \
--e '   "\$$varlibvpn= \"$(varlibvpn)\";\n" if m#^\# \@\@\@\-#; ' \
--e '           $$_="" if m/^\# \@\@\@\-/ .. m/^\# \-\@\@\@/;   ' \
+               perl -p                                                   \
+       -e '    print "                                                 ' \
+       -e '\$$shareuserv= \"$(shareuserv)\";\n                         ' \
+       -e '\$$etcvpn= \"$(etcvpn)\";\n                                 ' \
+       -e '\$$varlibvpn= \"$(varlibvpn)\";\n" if m#^\# \@\@\@\-#;      ' \
+       -e '    $$_="" if m/^\# \@\@\@\-/ .. m/^\# \-\@\@\@/;           ' \
                        <$< >$@.new
                chmod +x $@.new
                mv -f $@.new $@
index 08b0ed9f61a40859d37f531231940433db77d76c..670b44706be087221e882eb69b9c61cf5e02ecef 100644 (file)
 #include <limits.h>
 #include <signal.h>
 #include <unistd.h>
+#include <stdint.h>
+#include <poll.h>
 
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <linux/if.h>
+#include <linux/if_tun.h>
+
 #define NARGS 4
 #define MAXEXROUTES 50
 #define ATXTLEN 16
 
 static const unsigned long gidmaxval= (unsigned long)((gid_t)-2);
-static const char *const protos_ok[]= { "slip", "cslip", "adaptive", 0 };
+static const char *const protos_ok[]= { "slip", 0 };
 static const int signals[]= { SIGHUP, SIGINT, SIGTERM, 0 };
 
 static const char *configstr, *proto;
@@ -155,53 +167,11 @@ static struct pplace {
 } *cpplace;
 
 
-static int slpipe[2], ptmaster, undoslattach;
-static const char *ifname;
-static const char *ptyname;
-
-#define NPIDS 4
-
-static union {
-  struct { pid_t sl, cout, cin, task; } byname;
-  pid_t bynumber[NPIDS];
-} pids;
-sigset_t emptyset, fullset;
+static int tunfd;
+static char *ifname;
 
 
-static int cleantask(void) {
-  pid_t pid;
-
-  pid= fork();
-  if (!pid) return 1;
-  if (pid == (pid_t)-1)
-    perror("userv-ipif: fork for undo slattach failed - cannot clean up properly");
-  return 0;
-}
-
 static void terminate(int estatus) {
-  int i, status;
-  pid_t pid;
-  
-  for (i=0; i<NPIDS; i++)
-    if (pids.bynumber[i]) kill(pids.bynumber[i], SIGTERM);
-
-  if (undoslattach) {
-    if (cleantask()) {
-      execlp("slattach", "slattach", "-p", "tty", ptyname, (char*)0);
-      perror("userv-ipif: exec slattach for undo slattach failed");
-      exit(-1);
-    }
-    if (ifname && cleantask()) {
-      execlp("ifconfig", "ifconfig", ifname, "down", (char*)0);
-      perror("userv-ipif: exec ifconfig for undo ifconfig failed");
-      exit(-1);
-    }
-  }
-
-  for (;;) {
-    pid= waitpid(-1,&status,0);
-    if (pid == (pid_t)-1) break;
-  }
   exit(estatus);
 }
 
@@ -656,205 +626,82 @@ static void dumpdebug(void) {
 }
 
 
-static void setsigmask(const sigset_t *ss) {
-  int r;
-  
-  r= sigprocmask(SIG_SETMASK, ss, 0);
-  if (r) sysfatal("[un]block signals");
-}  
-
-static void setsignals(void (*handler)(int), struct sigaction *sa, int chldflags) {
-  const int *signalp;
-  int r, sig;
-  
-  sa->sa_handler= handler;
-  sa->sa_flags= 0; 
-  for (signalp=signals; (sig=*signalp); signalp++) {
-    r= sigaction(sig, sa, 0);  if (r) sysfatal("uncatch signal");
-  }
-  sa->sa_flags= chldflags;
-  r= sigaction(SIGCHLD, sa, 0);  if (r) sysfatal("uncatch children");
-}
-
-static void infork(void) {
-  struct sigaction sa;
-
-  memset(&pids,0,sizeof(pids));
-  sigemptyset(&sa.sa_mask);
-  setsignals(SIG_DFL,&sa,0);
-  setsigmask(&emptyset);
-  undoslattach= 0;
-}
-
-static pid_t makesubproc(void (*entry)(void)) {
-  pid_t pid;
-
-  pid= fork();  if (pid == (pid_t)-1) sysfatal("fork for subprocess");
-  if (pid) return pid;
-
-  infork();
-  entry();
-  abort();
-}
-
-static int task(void) {
-  pid_t pid;
+static int task(const char *desc) {
+  pid_t pid, pidr;
+  int status;
 
   pid= fork();
   if (pid == (pid_t)-1) sysfatal("fork for task");
-  if (!pid) { infork(); return 1; }
-
-  pids.byname.task= pid;
-  while (pids.byname.task) sigsuspend(&emptyset);
-  return 0;
-}
-
-static void mdup2(int fd1, int fd2, const char *what) {
-  int r;
+  if (!pid) return 1;
 
   for (;;) {
-    r= dup2(fd1,fd2); if (r==fd2) return;
-    if (r!=-1) fatal("dup2 in %s gave wrong answer %d instead of %d",what,r,fd2);
-    if (errno != EINTR) sysfatal("dup2 failed in %s",what);
+    pidr= waitpid(pid,&status,0);
+    if (pidr!=(pid_t)-1) break;
+    if (errno==EINTR) continue;
+    sysfatal("waitpid for task");
   }
-}
-
-static void sl_entry(void) {
-  mdup2(slpipe[1],1,"slattach child");
-  execlp("slattach", "slattach", "-v", "-L", "-p",proto, ptyname, (char*)0);
-  sysfatal("cannot exec slattach");
-}
-
-static void cin_entry(void) {
-  mdup2(ptmaster,1,"cat input child");
-  execlp("cat", "cat", (char*)0);
-  sysfatal("cannot exec cat input");
-}
-
-static void cout_entry(void) {
-  mdup2(ptmaster,0,"cat output child");
-  execlp("cat", "cat", (char*)0);
-  sysfatal("cannot exec cat output");
-}
-
-static void sighandler(int signum) {
-  pid_t pid;
-  int estatus, status;
-  const char *taskfail;
-
-  estatus= 4;
-  
-  if (signum == SIGCHLD) {
-    for (;;) {
-      pid= waitpid(-1,&status,WNOHANG);
-      if (!pid || pid == (pid_t)-1) return;
-
-      if (pid == pids.byname.task) {
-       pids.byname.task= 0;
-       if (!status) return;
-       taskfail= "task";
-      } else if (pid == pids.byname.cin) {
-       pids.byname.cin= 0;
-       if (status) {
-         taskfail= "input cat";
-       } else {
-         taskfail= 0;
-         estatus= 0;
-       }
-      } else if (pid == pids.byname.cout) {
-       pids.byname.cout= 0;
-       taskfail= "output cat";
-      } else if (pid == pids.byname.sl) {
-       pids.byname.sl= 0;
-       taskfail= "slattach";
-      } else {
-       continue;
-      }
-      break;
-    }
-    if (taskfail) {
-      if (WIFEXITED(status)) {
-       fprintf(stderr,
-               "userv-ipif service: %s unexpectedly exited with exit status %d\n",
-               taskfail, WEXITSTATUS(status));
-      } else if (WIFSIGNALED(status)) {
-       fprintf(stderr,
-               "userv-ipif service: %s unexpectedly killed by signal %s%s\n",
-               taskfail, strsignal(WTERMSIG(status)),
-               WCOREDUMP(status) ? " (core dumped)" : "");
-      } else {
-       fprintf(stderr, "userv-ipif service: %s unexpectedly terminated"
-               " with unknown status code %d\n", taskfail, status);
-      }
-    }
+  assert(pidr==pid);
+
+  if (WIFEXITED(status)) {
+    if (WEXITSTATUS(status))
+      fatal("userv-ipif service: %s exited with error exit status %d\n",
+           desc, WEXITSTATUS(status));
+  } else if (WIFSIGNALED(status)) {
+    fatal("userv-ipif service: %s died due to signal %s%s\n",
+         desc, strsignal(WTERMSIG(status)),
+         WCOREDUMP(status) ? " (core dumped)" : "");
   } else {
-    fprintf(stderr,
-           "userv-ipif service: received signal %d, terminating\n",
-           signum);
+    fatal("userv-ipif service: %s unexpectedly terminated"
+         " with unknown status code %d\n", desc, status);
   }
 
-  terminate(estatus);
+  return 0;
 }
 
-static void startup(void) {
+static void createif(void) {
+  static const char ifnamepat[]= "userv%d";
+  struct ifreq ifr;
   int r;
-  struct sigaction sa;
-  
-  sigfillset(&fullset);
-  sigemptyset(&emptyset);
-
-  ptmaster= getpt();  if (ptmaster==-1) sysfatal("allocate pty master");
-  r= grantpt(ptmaster);  if (r) sysfatal("grab/grant pty slave");
-  ptyname= ptsname(ptmaster);  if (!ptyname) sysfatal("get pty slave name");
-  r= chmod(ptyname,0600);  if (r) sysfatal("chmod pty slave");
-  r= unlockpt(ptmaster);  if (r) sysfatal("unlock pty");
-
-  sigfillset(&sa.sa_mask);
-  setsignals(sighandler,&sa,SA_NOCLDSTOP);
-  setsigmask(&fullset);
-}
 
-static void startslattach(void) {
-  static char ifnbuf[200];
+  memset(&ifr,0,sizeof(ifr));
+  ifr.ifr_flags= IFF_TUN | IFF_NO_PI;
 
-  FILE *piper;
-  int r, l, k;
+  assert(sizeof(ifr.ifr_name) >= sizeof(ifnamepat));
+  strcpy(ifr.ifr_name, ifnamepat);
 
-  r= pipe(slpipe);  if (r) sysfatal("create pipe");
-  piper= fdopen(slpipe[0],"r");  if (!piper) sysfatal("fdopen pipe");
+  tunfd= open("/dev/net/tun", O_RDWR);
+  if (!tunfd) sysfatal("open /dev/net/tun");
 
-  undoslattach= 1;
-  pids.byname.sl= makesubproc(sl_entry);
+  r= fcntl(tunfd, F_GETFD);
+  if (r==-1) sysfatal("fcntl(tunfd,F_GETFD)");
+  r= fcntl(tunfd, F_SETFD, r|FD_CLOEXEC);
+  if (r==-1) sysfatal("fcntl(tunfd,F_SETFD,|FD_CLOEXEC)");
 
-  close(slpipe[1]);
-  setsigmask(&emptyset);
-  if (!fgets(ifnbuf,sizeof(ifnbuf),piper)) {
-    if (ferror(piper)) sysfatal("cannot read ifname from slattach");
-    else fatal("cannot read ifname from slattach");
-  }
-  setsigmask(&fullset);
-  l= strlen(ifnbuf);
-  if (l<=0 || ifnbuf[l-1] != '\n') fatal("slattach gave strange output `%s'",ifnbuf);
-  ifnbuf[l-1]= 0;
-  for (k=l; k>0 && ifnbuf[k-1]!=' '; k--);
-  ifname= ifnbuf+k;
+  r= ioctl(tunfd, TUNSETIFF, (void*)&ifr);
+  if (r) sysfatal("ioctl TUNSETIFF");
+
+  /* ifr.ifr_name might not be null-terminated.  crazy abi. */
+  ifname= malloc(sizeof(ifr.ifr_name)+1);
+  if (!ifname) sysfatal("malloc for interface name");
+  memcpy(ifname, ifr.ifr_name, sizeof(ifr.ifr_name));
+  ifname[sizeof(ifr.ifr_name)]= 0;
 }
 
 static void netconfigure(void) {
   char mtutxt[100];
   int i;
 
-  if (task()) {
+  if (task("ifconfig")) {
     sprintf(mtutxt,"%lu",mtu);
   
     execlp("ifconfig", "ifconfig", ifname, localtxt,
-          "netmask","255.255.255.255", "-broadcast", "pointopoint",peertxt,
+          "netmask","255.255.255.255", "pointopoint",peertxt, "-broadcast",
           "mtu",mtutxt, "up", (char*)0);
     sysfatal("cannot exec ifconfig");
   }
 
   for (i=0; i<nexroutes; i++) {
-    if (task()) {
+    if (task("route")) {
       execlp("route","route", "add", "-net",exroutes[i].prefixtxt,
             "netmask",exroutes[i].masktxt,
             "gw",peertxt, "dev",ifname, (char*)0);
@@ -863,18 +710,182 @@ static void netconfigure(void) {
   }
 }
 
-static void copydata(void) __attribute__((noreturn));
-static void copydata(void) {
+static void setnonblock(int fd) {
   int r;
+  r= fcntl(fd,F_GETFL); 
+  if (r==-1) sysfatal("fcntl F_GETFL");
+  r= fcntl(fd,F_SETFL, r|O_NONBLOCK);
+  if (r==-1) sysfatal("fcntl F_SETFL O_NONBLOCK");
+}
+
+static void rx_packet(const uint8_t *packet, int len) {
+  for (;;) {
+    int r= write(tunfd, packet, len);
+    if (r<0) {
+      if (errno==EINTR) continue;
+      if (errno==EAGAIN) return; /* oh well */
+      sysfatal("error writing packet to tun (transmitting)");
+    }
+    assert(r==len);
+    return;
+  }
+}
+
+static int output_waiting, input_waiting;
+
+#define SLIP_END     0300
+#define SLIP_ESC     0333
+#define SLIP_ESC_END 0334
+#define SLIP_ESC_ESC 0335
+
+static void more_rx_data(uint8_t *input_buf, uint8_t *output_buf) {
+  /* we make slip_data never contain continuation of a packet */
+  /* input_buf is passed as a parameter since it's in copydata's stack frame */
+  static int scanned;
+  static int output_len;
+
+  uint8_t *op= output_buf + output_len;
+  const uint8_t *ip= input_buf + scanned;
+  const uint8_t *ip_end= input_buf + input_waiting;
+  int eaten= 0;
   
-  pids.byname.cin= makesubproc(cin_entry);
   for (;;) {
-    r= write(1, "\300", 1); if (r==1) break;
-    assert(r==-1);  if (errno != EINTR) sysfatal("send initial delim to confirm");
+    if (ip>=ip_end) break;
+    uint8_t c= *ip++;
+    if (c==SLIP_END) {
+      rx_packet(output_buf, op-output_buf);
+      op= output_buf;
+      eaten= ip - input_buf;
+      continue;
+    }
+    if (c==SLIP_ESC) {
+      if (ip>=ip_end) { /* rescan this when there's more */ ip--; break; }
+      c= *ip++;
+      if (c==SLIP_ESC_END) c=SLIP_END;
+      else if (c==SLIP_ESC_ESC) c=SLIP_ESC;
+      else fatal("unexpected byte 0%o after SLIP_ESC",c);
+    }
+    if (op == output_buf+mtu)
+      fatal("SLIP packet exceeds mtu");
+    *op++= c;
+  }
+
+  output_len= op - output_buf;
+  scanned= ip - input_buf;
+
+  input_waiting -= eaten;
+  memmove(input_buf, input_buf+eaten, input_waiting);
+  scanned -= eaten;
+}
+
+static void tx_packet(uint8_t *output_buf, const uint8_t *ip, int inlen) {
+  /* output_buf is passed as a parameter since it's in copydata's stack frame */
+  assert(!output_waiting);
+  uint8_t *op= output_buf;
+
+  *op++= SLIP_END;
+  while (inlen-- >0) {
+    uint8_t c= *ip++;
+    if (c==SLIP_END) { *op++= SLIP_ESC; *op++= SLIP_ESC_END; }
+    else if (c==SLIP_ESC) { *op++= SLIP_ESC; *op++= SLIP_ESC_ESC; }
+    else *op++= c;
   }
-  pids.byname.cout= makesubproc(cout_entry);
+  *op++= SLIP_END;
+  assert(op <= output_buf + mtu*2+2);
+
+  output_waiting= op - output_buf;
+}
+
+static void copydata(void) __attribute__((noreturn));
+static void copydata(void) {
+  uint8_t output_buf[mtu*2+2];
+  uint8_t input_buf[mtu*2+2];
+  uint8_t rx_packet_buf[mtu];
 
-  for (;;) sigsuspend(&emptyset);
+  int r, i;
+
+  struct pollfd polls[3];
+  memset(polls, 0, sizeof(polls));
+  
+  polls[0].fd= 0;      polls[0].events= POLLIN;
+  polls[1].fd= 1;
+  polls[2].fd= tunfd;
+
+  /* We don't do flow control on input packets; instead, we just throw
+   * away ones which the kernel doesn't accept.  So we always poll for
+   * those.
+   *
+   * Output packets we buffer, so we poll only as appropriate for those.
+   */
+
+  /* Start by transmitting one END byte to say we're ready. */
+  output_buf[0]= SLIP_END;
+  output_waiting= 1;
+
+  for (;;) {
+    if (output_waiting) {
+      r= write(1, output_buf, output_waiting);
+      if (r<0) {
+       if (errno==EINTR) continue;
+       if (errno!=EAGAIN)
+         sysfatal("error writing SLIP output (packets being received)");
+      } else {
+       assert(r>0);
+       output_waiting -= r;
+       memmove(output_buf, output_buf+r, output_waiting);
+      }
+    }
+    if (output_waiting) {
+      polls[1].events |= POLLOUT;
+      polls[2].events &= ~POLLIN;
+    } else {
+      polls[1].events &= ~POLLOUT;
+      polls[2].events |= POLLIN;
+    }
+    r= poll(polls,3,-1);
+
+    if (r<0) {
+      if (errno==EINTR) continue;
+      sysfatal("poll() failed");
+    }
+    assert(r>0); /* we used an infinite timeout */
+
+    for (i=0; i<sizeof(polls)/sizeof(polls[0]); i++)
+      if (polls[i].revents & ~polls[i].events)
+       fatal("unexpected revents 0x%x for fd=%d",
+             polls[i].revents, polls[i].fd);
+
+    if (polls[0].events & POLLIN) {
+      int want= sizeof(input_buf) - input_waiting;
+      if (want<0) fatal("incoming packet necessarily exceeds MTU");
+      r= read(0, input_buf + input_waiting, want);
+      if (r>0) {
+       input_waiting += r;
+       assert(r < sizeof(input_buf));
+       more_rx_data(input_buf, rx_packet_buf);
+      } else if (r==0) {
+       terminate(0);
+      } else {
+       if (!(errno==EINTR || errno==EAGAIN))
+         sysfatal("error reading input SLIP data (packets to transmit)");
+      }
+    }
+
+    /* We handle what would be (polls[1].events & POLLOUT) above,
+     * unconditionally.  That eliminates the need to poll in the usual case */
+      
+    if (polls[2].events & POLLIN) {
+      uint8_t packet_buf[mtu];
+      r= read(tunfd, packet_buf, mtu);
+      if (r>0) {
+       tx_packet(output_buf, packet_buf, r);
+      } else {
+       assert(r<0);
+       if (!(errno==EAGAIN || errno==EWOULDBLOCK))
+         sysfatal("error reading packet (being transmitted) from tun");
+      }
+    }
+  } 
 }
 
 int main(int argc, const char *const *argv) {
@@ -883,8 +894,10 @@ int main(int argc, const char *const *argv) {
   checkpermit();
   if (!proto) dumpdebug();
 
-  startup();
-  startslattach();
+  createif();
   netconfigure();
+  setnonblock(tunfd);
+  setnonblock(0);
+  setnonblock(1);
   copydata();
 }
index bd9313c6d56526c282b77589c08b6d15bd9ecbcf..2fa37e7ecbb48bf87c3853a66cad47b22e2874c4 100644 (file)
@@ -30,6 +30,7 @@ docdir=               $(sharedir)/doc
 
 libuserv=      $(libdir)/userv
 shareuserv=    $(sharedir)/userv
+varlog=                $(vardir)/log
 varlib=                $(vardir)/lib
 varlibuserv=   $(varlib)/userv
 
@@ -37,7 +38,7 @@ etcuserv=     $(etcdir)/userv
 services=      $(etcuserv)/services.d
 
 CFLAGS=        -Wall -Wwrite-strings -Wmissing-prototypes -Wstrict-prototypes \
-       -Wpointer-arith -D_GNU_SOURCE \
+       -Wpointer-arith -D_GNU_SOURCE -Wno-pointer-sign \
        $(OPTIMISE) $(DEBUG) $(SUBDIR_CFLAGS)
 LDFLAGS= $(SUBDIR_LDFLAGS)