From: Ian Jackson Date: Sat, 20 Aug 2011 16:24:02 +0000 (+0100) Subject: Merge branch 'zealot' X-Git-Url: https://git.distorted.org.uk/~mdw/userv-utils/commitdiff_plain/8acaed657eb4db47c448c8a582810bf301ff47ef?hp=0789c49b475067e89dcd39ffd2cf11871dcb7e80 Merge branch 'zealot' Conflicts: .gitignore ipif/Makefile --- diff --git a/.gitignore b/.gitignore index fc62bd4..0b6440f 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile b/Makefile index eecb31a..ad795cb 100644 --- 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 806e8bf..a968a8e 100644 --- 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 . +Copyright (C)1996-2010 Ian Jackson . Copyright (C)1998 David Damerell Copyright (C)1999,2003 Chancellor Masters and Scholars of the University of Cambridge +Copyright (C)2010 Tony Finch All the utilities here are free software. You can redistribute it and/or modify them under the terms of the GNU General Public License diff --git a/debian/changelog b/debian/changelog index 17009bc..72b0964 100644 --- a/debian/changelog +++ b/debian/changelog @@ -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 Sat, 22 May 2010 19:50:57 +0100 + userv-utils (0.3) unstable; urgency=medium dyndns: diff --git a/debian/control b/debian/control index 3247d90..4f11db8 100644 --- a/debian/control +++ b/debian/control @@ -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 diff --git a/debian/rules b/debian/rules index 4977239..b7353f3 100755 --- a/debian/rules +++ b/debian/rules @@ -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 index 0000000..8609c73 --- /dev/null +++ b/debian/userv-git-daemon/postinst @@ -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 index 0000000..ea76975 --- /dev/null +++ b/git-daemon/Makefile @@ -0,0 +1,60 @@ +# Makefile for userv-git-daemon +# +# This was written by Tony Finch and subsequently +# heavily modified by Ian Jackson +# 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/$$/\\/' $@.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 index 0000000..76d9946 --- /dev/null +++ b/git-daemon/README @@ -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 and subsequently +heavily modified by Ian Jackson +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 index 0000000..1b1b540 --- /dev/null +++ b/git-daemon/git-daemon.in @@ -0,0 +1,87 @@ +#!/usr/bin/perl +# +# A git daemon with an added userv security boundary. +# +# This was written by Tony Finch and subsequently +# heavily modified by Ian Jackson +# 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 index 0000000..2b8aff3 --- /dev/null +++ b/git-daemon/git-service.in @@ -0,0 +1,51 @@ +#!/usr/bin/perl +# +# userv-git-daemon service script +# +# This was written by Tony Finch and subsequently +# heavily modified by Ian Jackson +# 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 index 0000000..bb4e847 --- /dev/null +++ b/git-daemon/git-upload-pack.in @@ -0,0 +1,16 @@ +# userv configuration for git-daemon git-upload-pack service +# +# This was written by Tony Finch +# 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 index 0000000..80a1f3c --- /dev/null +++ b/git-daemon/git-urlmap @@ -0,0 +1,35 @@ +# Each line is one of: +# +# single-user [/] [] +# matching requests will be handled by +# and unless overridden by handled by +# serving subdirectories of +# +# multi-user [/] +# matching requests are only those those next +# path element starts with ~. The +# request will be handled by and unless +# overridden by will be handled by +# serving subdirectories of +# ( must be a relative path) +# +# repo-regexp +# For per-user service. Subrepos must match this +# regexp, which must contain a single matching +# group which is the filesystem pathname inside +# the . 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. +# 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 index 0000000..f316fcc --- /dev/null +++ b/git-daemon/inetd.conf.in @@ -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 index 0000000..06e0ef2 --- /dev/null +++ b/git-daemon/logrotate.in @@ -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 index 0000000..696ee65 --- /dev/null +++ b/git-daemon/read-urlmap @@ -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 : ''); + 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 diff --git a/ipif/Makefile b/ipif/Makefile index 7791058..a34a85f 100644 --- a/ipif/Makefile +++ b/ipif/Makefile @@ -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 $@ diff --git a/ipif/service.c b/ipif/service.c index 08b0ed9..670b447 100644 --- a/ipif/service.c +++ b/ipif/service.c @@ -121,17 +121,29 @@ #include #include #include +#include +#include #include #include #include +#include +#include +#include + +#include +#include + +#include +#include + #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; isa_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=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; i0) { + 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(); } diff --git a/settings.make b/settings.make index bd9313c..2fa37e7 100644 --- a/settings.make +++ b/settings.make @@ -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)