chiark / gitweb /
Internal code review complete.
authorian <ian>
Thu, 18 Sep 1997 01:58:49 +0000 (01:58 +0000)
committerian <ian>
Thu, 18 Sep 1997 01:58:49 +0000 (01:58 +0000)
18 files changed:
.cvsignore
INSTALL
Makefile.in
README [new file with mode: 0644]
acconfig.h
buildship
client.c
common.h
configure.in
daemon.h
lexer.l.m4
lib.c
lib.h
overlord.c
parser.c
process.c
servexec.c
spec.sgml.in

index a7787646aa1f5ffa6e4a24ad405a7f10f02b3e7e..564911f8bd63362119932338329f4d5a62c67d2c 100644 (file)
@@ -6,7 +6,9 @@ lexer.c
 tokens.h
 pcsum.h
 
-english.lp
+version.h
+spec.sgml
+
 *.sasp*
 *.lout*
 *.li
diff --git a/INSTALL b/INSTALL
index 8c5db28545b190e7ddf019230875dcf3fcccf802..baf762f9d8e797449bde81be112f698a83bbbd43 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -47,11 +47,15 @@ System interfaces:
 * Unix-domain (AF_UNIX) stream sockets, for use with:
   * BSD sockets - socket(), bind(), listen(), accept(), connect();
   * socketpair(2);
-* lstat(2) (though stat(2) will be safe on systems without symlinks).
+* lstat(2) (though stat(2) will be safe on systems without symlinks,
+  if you say -Dlstat=stat).
 * Pipes:
   * creating using pipe(2) and mkfifo(2);
   * proper interaction between open(O_RDWR), open(O_RDONLY),
-    open(O_WRONLY), close(), dup2, EPIPE, SIGPIPE, &c.;
+    open(O_WRONLY), close(), dup2, EPIPE, SIGPIPE, &c.
+    (ie, opening pipes with O_RDWR never blocks; EPIPE happens
+    if you write with no readers; EOF happens if you read with
+    no buffered data and writers)
 * POSIX signal handling - sigaction(2), sigprocmask(2), sigsuspend(2);
 
 To format the documentation:
index 4ef8e7dbbb08a81749ade47ed69b985693dd088f..08b06112b0268f5318723bdc227fa0483a6e63cc 100644 (file)
 #  along with userv; if not, write to the Free Software
 #  Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
+VERSION=0.55
+
 CC=@CC@
-CFLAGS=@CFLAGS@ $(XCFLAGS)
+CFLAGS=@CFLAGS@ $(XCFLAGS) -DVERSION='"$(VERSION)"'
 OPTIMISE=@OPTIMISE@
 CPPFLAGS=@DEBUGDEFS@ $(XCPPFLAGS)
 LDLIBS=@DEBUGLIBS@ $(XLDLIBS)
@@ -38,15 +40,15 @@ etcdir=/etc
 etcsubdir=$(etcdir)/userv
 
 SOURCES=       Makefile.in configure.in acconfig.h                     \
-               client.c common.h version.h                             \
+               client.c common.h                                       \
                overlord.c process.c servexec.c                         \
                daemon.h debug.c parser.c lib.c lib.h                   \
                language.i4 lexer.l.m4 tokens.h.m4
 ALSOSHIP=      system.default system.override \
-               spec.sgml overview.fig overview.ps \
+               spec.sgml.in overview.fig overview.ps \
                COPYING INSTALL buildship install-sh .cvsignore
 GENSHIP=       lexer.l lexer.c tokens.h configure config.h.in \
-               spec.html spec.ps overview.ps
+               spec.sgml spec.html spec.ps overview.ps
 
 SHIPTARGETS=   $(SOURCES) $(ALSOSHIP) $(GENSHIP)
 
@@ -66,6 +68,10 @@ daemon:              overlord.o process.o servexec.o parserlexer.o debug.o lib.o
 
 lexer.l:       language.i4
 
+spec.sgml:     spec.sgml.in Makefile
+               sed -e '/<version><\/version>/ s/>/&$(VERSION)/' \
+                       spec.sgml.in >$@.new && mv -f $@.new $@
+
 client.o:      config.h common.h pcsum.h version.h
 
 process.o:     config.h common.h pcsum.h daemon.h lib.h tokens.h
@@ -74,7 +80,7 @@ overlord.o:   config.h common.h pcsum.h daemon.h
 
 servexec.o:    config.h common.h pcsum.h daemon.h lib.h version.h
 
-lib.o:         config.h lib.h
+lib.o:         config.h common.h lib.h
 
 debug.o:       config.h common.h pcsum.h daemon.h lib.h tokens.h
 
@@ -82,11 +88,14 @@ parserlexer.o:      lexer.c parser.c config.h common.h pcsum.h daemon.h lib.h tokens.
 # lexer.c #include's parser.c at the end.  Blame flex.
                $(CC) -c $(CPPFLAGS) $(CFLAGS) lexer.c -o $@
 
-pcsum.h:       common.h version.h config.h config.status Makefile
+pcsum.h:       common.h config.h config.status Makefile
                cat $^ | md5sum | sed -e 's/../0x&,/g; s/,$$//;' >pcsum.h.new
                cmp pcsum.h.new pcsum.h || mv -f pcsum.h.new pcsum.h
                @rm -f pcsum.h.new
 
+version.h:     Makefile
+               echo '#define VERSION "$(VERSION)"' >$@.new && mv -f $@.new $@
+
 tokens.h:      language.i4
 
 autoconf configure:
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..c327d7a
--- /dev/null
+++ b/README
@@ -0,0 +1,29 @@
+This is userv, a trust boundary management service for Unix.
+
+Using userv, one program can call another without either of them
+having to trust the other completely.  This enables applications such
+as mail delivery, cron, user-provided CGI scripts and so forth to
+operate without needing to be given root privilege.
+
+THIS VERSION IS ALPHA AND PROVIDED FOR REVIEW.
+
+For installation requirements and instructions please see INSTALL.
+For usage documentation and discussion see the manual in spec.*
+(various formats via Debiandoc-SGML in spec.sgml).
+
+userv is Copyright (C)1996-7 Ian Jackson <ian@chiark.greenend.org.uk>.
+
+userv 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 as the file COPYING; if not, email me at the address
+above or write to the Free Software Foundation, 59 Temple Place -
+Suite 330, Boston, MA 02111-1307, USA.
index 998b8e39c796d32067df71cdc718d2e9da02a5bf..160a27c9240e4f3cbc8dacf01f1a85cfb7168f6f 100644 (file)
@@ -22,8 +22,8 @@
 /* Define if function attributes a la GCC 2.5 and higher are available.  */
 #undef HAVE_GNUC25_ATTRIB
 
-/* Define if constant functions a la GCC 2.5 and higher are available.  */
-#undef HAVE_GNUC25_CONST
+/* Define if unused functions a la GCC 2.5 and higher are available.  */
+#undef HAVE_GNUC25_UNUSED
 
 /* Define if nonreturning functions a la GCC 2.5 and higher are available.  */
 #undef HAVE_GNUC25_NORETURN
 #define NONRETURNPRINTFFORMAT(si,tc) FUNCATTR((ATTRPRINTF(si,tc),ATTRNORETURN))
 #endif
 
-/* GNU C constant functions, or null. */
-#ifndef ATTRCONST
-#ifdef HAVE_GNUC25_CONST
-#define ATTRCONST const
+/* GNU C unused functions, or null. */
+#ifndef ATTRUNUSED
+#ifdef HAVE_GNUC25_UNUSED
+#define ATTRUNUSED unused
 #else
-#define ATTRCONST
+#define ATTRUNUSED
 #endif
 #endif
-#ifndef CONSTANT
-#define CONSTANT FUNCATTR((ATTRCONST))
+#ifndef UNUSED
+#define UNUSED FUNCATTR((ATTRUNUSED))
 #endif
index 759c30e716e37f47f6a5c81cac3028398b2e8185..b6d13f3e9edc55499a26026da86c978953f6aac9 100755 (executable)
--- a/buildship
+++ b/buildship
@@ -2,7 +2,7 @@
 # to release, check out a fresh copy and then run this
 
 set -e
-version=`sed -n 's/^#define VERSION \"\(.*\)\" *$/\1/p' version.h`
+version=`sed -n 's/^VERSION=\(.*\) *$/\1/p' Makefile`
 targz=userv-$version.tar.gz
 tag=`echo release-$version | sed -e 's/\./-/g'`
 
index 02d77a9340057e8da024a92aa777c39e6c0fe44e..3e9f08fa835da9dfa5fa921d5f345a8e87887a2b 100644 (file)
--- a/client.c
+++ b/client.c
  * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
+/*
+ * Here too, we do some horrible asynchronous stuff with signals.
+ *
+ * The following objects &c. are used in signal handlers and so
+ * must be protected by calls to blocksignals if they are used in
+ * the main program:
+ *  stderr
+ *  swfile
+ *
+ * The following objects are used in the main program unprotected
+ * and so must not be used in signal handlers:
+ *  srfile
+ *  fdsetup[].copyfd
+ *  fdsetup[].pipefd
+ *
+ * The following objects/functions are not modified/called after the
+ * asynchronicity starts:
+ *  malloc
+ *  fdsetupsize
+ *  fdsetup[].mods
+ *  fdsetup[].filename
+ *  fdsetup[].oflags
+ *  results of argument parsing
+ *
+ * systemerror, swfile, fdsetup[].catpid and fdsetup[].killed are used
+ * for communication between the main thread and the signal handlers.
+ *
+ * All the signal handlers save errno so that is OK too.
+ */
+
 #include <fcntl.h>
 #include <stdarg.h>
 #include <errno.h>
 #include "common.h"
 #include "version.h"
 
-struct optioninfo;
-
-typedef void optionfunction(const struct optioninfo*, const char *value, char *key);
-
-struct optioninfo {
-  int abbrev;
-  const char *full;
-  int values; /* 0: no value; 1: single value; 2: key and value */
-  optionfunction *fn;
-};
-
 enum fdmodifiervalues {
   fdm_read=       00001,
   fdm_write=      00002,
@@ -76,60 +95,11 @@ struct fdmodifierinfo {
   int oflags;
 };
 
-const struct fdmodifierinfo fdmodifierinfos[]= {
-  { "read",      fdm_read,
-                 fdm_write,
-                 O_RDONLY                                                         },
-  { "write",     fdm_write,
-                 fdm_read,
-                 O_WRONLY                                                         },
-  { "overwrite", fdm_write|fdm_create|fdm_truncate,
-                 fdm_read|fdm_fd|fdm_exclusive,
-                 O_WRONLY|O_CREAT|O_TRUNC                                         },
-  { "create",    fdm_write|fdm_create,
-                 fdm_read|fdm_fd,
-                 O_WRONLY|O_CREAT                                                 },
-  { "creat",     fdm_write|fdm_create,
-                 fdm_read|fdm_fd,
-                 O_WRONLY|O_CREAT                                                 },
-  { "exclusive", fdm_write|fdm_create|fdm_exclusive,
-                 fdm_read|fdm_fd|fdm_truncate,
-                 O_WRONLY|O_CREAT|O_EXCL                                          },
-  { "excl",      fdm_write|fdm_create|fdm_exclusive,
-                 fdm_read|fdm_fd|fdm_truncate,
-                 O_WRONLY|O_CREAT|O_EXCL                                          },
-  { "truncate",  fdm_write|fdm_truncate,
-                 fdm_read|fdm_fd|fdm_exclusive,
-                 O_WRONLY|O_CREAT|O_EXCL                                          },
-  { "trunc",     fdm_write|fdm_truncate,
-                 fdm_read|fdm_fd|fdm_exclusive,
-                 O_WRONLY|O_CREAT|O_EXCL                                          },
-  { "append",    fdm_write|fdm_append,
-                 fdm_read|fdm_fd,
-                 O_WRONLY|O_CREAT|O_APPEND                                        },
-  { "sync",      fdm_write|fdm_sync,
-                 fdm_read|fdm_fd,
-                 O_WRONLY|O_CREAT|O_SYNC                                          },
-  { "wait",      fdm_wait,
-                 fdm_nowait|fdm_close,
-                 0                                                                },
-  { "nowait",    fdm_nowait,
-                 fdm_wait|fdm_close,
-                 0                                                                },
-  { "close",     fdm_close,
-                 fdm_wait|fdm_nowait,
-                 0                                                                },
-  { "fd",        fdm_fd,
-                 fdm_create|fdm_exclusive|fdm_truncate|fdm_append|fdm_sync,
-                 0                                                                },
-  {  0                                                                            }
-};
-
 struct fdsetupstate {
-  const char *filename;
-  int copyfd;
-  int mods, oflags, pipefd, killed;
-  pid_t catpid;
+  const char *filename; /* non-null iff this fd has been specified */
+  int copyfd; /* fd to copy, -1 unless mods & fdm_fd */
+  int mods, oflags, pipefd, killed; /* 0,0,-1,0 unless otherwise set */
+  pid_t catpid; /* -1 indicates no cat process */
 };
 
 enum signalsexitspecials { se_number=-100, se_numbernocore, se_highbit, se_stdout };
@@ -137,8 +107,9 @@ enum overridetypes { ot_none, ot_string, ot_file, ot_builtin };
 
 struct constkeyvaluepair { const char *key, *value; };
 
+/* Variables from command-line arguments */
 static const char *serviceuser;
-static uid_t serviceuid, myuid;
+static uid_t serviceuid;
 static struct fdsetupstate *fdsetup;
 static int fdsetupsize;
 static struct constkeyvaluepair *defvararray;
@@ -149,7 +120,18 @@ static int sigpipeok, hidecwd;
 static int overridetype= ot_none;
 static const char *overridevalue, *spoofuser=0;
 
+/* Other state variables */
 static FILE *srfile, *swfile;
+static pid_t mypid;
+static uid_t myuid, spoofuid;
+static gid_t mygid, spoofgid, *gidarray;
+static int ngids;
+static struct opening_msg opening_mbuf;
+static const char *logname;
+static char *cwdbuf;
+static size_t cwdbufsize;
+static char *ovbuf;
+static int ovused, systemerror;
 
 static void blocksignals(int how) {
   sigset_t set;
@@ -168,6 +150,12 @@ static void blocksignals(int how) {
   }
 }
 
+/* Functions which may be called either from signal handlers or from
+ * the main thread.  They block signals in case they are on the main
+ * thread, and may only use signal handler objects.  None of them
+ * return.  If they did they'd have to restore the signal mask.
+ */
+
 static void NONRETURNPRINTFFORMAT(1,2) miscerror(const char *fmt, ...) {
   va_list al;
 
@@ -217,6 +205,33 @@ static void NONRETURNPRINTFFORMAT(1,2) protoerror(const char *fmt, ...) {
   exit(-1);
 }
 
+/*
+ * General-purpose functions; these do nothing special about signals,
+ * except that they can call error-handlers which may block them
+ * to print error messages.
+ */
+
+static void xfread(void *p, size_t sz, FILE *file) {
+  size_t nr;
+  nr= fread(p,1,sz,file);
+  if (nr != sz) protoreaderror(file,"in data");
+}
+
+static void xfwrite(const void *p, size_t sz, FILE *file) {
+  size_t nr;
+  nr= fwrite(p,1,sz,file); if (nr == sz) return;
+  syscallerror("writing to server");
+}
+
+static void xfflush(FILE *file) {
+  if (fflush(file)) syscallerror("flush server socket");
+}
+
+/* Functions which may be called only from the main thread.  These may
+ * use main-thread objects and must block signals before using signal
+ * handler objects.
+ */
+
 #ifdef DEBUG
 static void priv_suspend(void) { }
 static void priv_resume(void) { }
@@ -238,6 +253,51 @@ static void priv_permanentlyrevokesuspended(void) {
 }
 #endif
 
+static void checkmagic(unsigned long was, unsigned long should, const char *when) {
+  if (was != should)
+    protoerror("magic number %s was %08lx, expected %08lx",when,was,should);
+}
+
+static void getprogress(struct progress_msg *progress_r, FILE *file) {
+  int i, c;
+  unsigned long ul;
+
+  for (;;) {
+    xfread(progress_r,sizeof(struct progress_msg),file);
+    checkmagic(progress_r->magic,PROGRESS_MAGIC,"in progress message");
+    switch (progress_r->type) {
+    case pt_failed:
+      blocksignals(SIG_BLOCK);
+      fputs("userv: uservd reports that service failed\n",stderr);
+      exit(-1);
+    case pt_errmsg:
+      blocksignals(SIG_BLOCK);
+      fputs("uservd: ",stderr);
+      if (progress_r->data.errmsg.messagelen>MAX_ERRMSG_STRING)
+       protoerror("stderr message length %d is far too long",
+                  progress_r->data.errmsg.messagelen);
+      for (i=0; i<progress_r->data.errmsg.messagelen; i++) {
+       c= getc(file); if (c==EOF) protoreaderror(file,"in error message");
+       if (isprint(c)) putc(c,stderr);
+       else fprintf(stderr,"\\x%02x",(unsigned char)c);
+      }
+      putc('\n',stderr);
+      if (ferror(stderr)) syscallerror("printing error message");
+      xfread(&ul,sizeof(ul),file);
+      checkmagic(ul,PROGRESS_ERRMSG_END_MAGIC,"after error message");
+      blocksignals(SIG_UNBLOCK);
+      break;
+    default:
+      return;
+    }
+  }
+}
+
+/*
+ * Functions which are called only during setup, before
+ * the signal asynchronicity starts.  They can do anything they like.
+ */
+
 static void *xmalloc(size_t s) {
   void *p;
   p= malloc(s?s:1);
@@ -251,18 +311,6 @@ static void *xrealloc(void *p, size_t s) {
   return p;
 }
 
-static void xfread(void *p, size_t sz, FILE *file) {
-  size_t nr;
-  nr= fread(p,1,sz,file);
-  if (nr != sz) protoreaderror(file,"in data");
-}
-
-static void xfwrite(const void *p, size_t sz, FILE *file) {
-  size_t nr;
-  nr= fwrite(p,1,sz,file); if (nr == sz) return;
-  syscallerror("writing to server");
-}
-
 static void xfwritestring(const char *s, FILE *file) {
   int l;
   l= strlen(s);
@@ -271,12 +319,108 @@ static void xfwritestring(const char *s, FILE *file) {
   xfwrite(s,sizeof(*s)*l,file);
 }
 
-static void xfflush(FILE *file) {
-  if (fflush(file)) syscallerror("flush server socket");
+static void xfwritefds(int modifier, int expected, FILE *file) {
+  int i, fdcount;
+
+  for (i=0, fdcount=0; i<fdsetupsize; i++) {
+    if (!(fdsetup[i].filename && (fdsetup[i].mods & modifier)))
+      continue;
+    xfwrite(&i,sizeof(int),file); fdcount++;
+  }
+  assert(fdcount == expected);
 }
 
+/* Functions which may be called from signal handlers.  These
+ * may use signal-handler objects.  The main program may only
+ * call them with signals blocked, and they may not use any
+ * main-thread objects.
+ */
+
+static void disconnect(void) /* DOES return, unlike in daemon */ {
+  struct event_msg event_mbuf;
+  int r;
+
+  if (swfile) {
+    memset(&event_mbuf,0,sizeof(event_mbuf));
+    event_mbuf.magic= EVENT_MAGIC;
+    event_mbuf.type= et_disconnect;
+    r= fwrite(&event_mbuf,1,sizeof(event_mbuf),swfile);
+    if ((r != sizeof(event_mbuf) || fflush(swfile)) && errno != EPIPE)
+      syscallerror("write to server when disconnecting\n");
+  }
+  systemerror= 1;
+}
+
+static void sighandler_alrm(int ignored) /* DOES return, unlike in daemon */ {
+  int es;
+  es= errno;
+  fputs("userv: timeout\n",stderr);
+  disconnect();
+  errno= es;
+}
+
+static void sighandler_chld(int ignored) /* DOES return, unlike in daemon */ {
+  struct event_msg event_mbuf;
+  pid_t child;
+  int status, fd, r, es;
+
+  es= errno;
+  for (;;) {
+    child= wait3(&status,WNOHANG,0);
+    if (child == 0 || (child == -1 && errno == ECHILD)) break;
+    if (child == -1) syscallerror("wait for child process (in sigchld handler)");
+    for (fd=0; fd<fdsetupsize && fdsetup[fd].catpid != child; fd++);
+    if (fd>=fdsetupsize) continue; /* perhaps the caller gave us children */
+    if ((WIFEXITED(status) && WEXITSTATUS(status)==0) ||
+       (WIFSIGNALED(status) && WTERMSIG(status)==SIGPIPE) ||
+       (fdsetup[fd].killed && WIFSIGNALED(status) && WTERMSIG(status)==SIGKILL)) {
+      if (swfile && fdsetup[fd].mods & fdm_read) {
+       memset(&event_mbuf,0,sizeof(event_mbuf));
+       event_mbuf.magic= EVENT_MAGIC;
+       event_mbuf.type= et_closereadfd;
+       r= fwrite(&event_mbuf,1,sizeof(event_mbuf),swfile);
+       if (r != sizeof(event_mbuf) || fflush(swfile))
+         if (errno != EPIPE) syscallerror("inform service of closed read fd");
+      }
+    } else {
+      if (WIFEXITED(status))
+       fprintf(stderr,"userv: cat for fd %d exited with error exit status %d\n",
+               fd,WEXITSTATUS(status));
+      else if (WIFSIGNALED(status))
+       if (WCOREDUMP(status))
+         fprintf(stderr,"userv: cat for fd %d dumped core due to signal %s (%d)\n",
+                 fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
+       else
+         fprintf(stderr,"userv: cat for fd %d terminated by signal %s (%d)\n",
+                 fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
+      else
+       fprintf(stderr,"userv: cat for fd %d gave unknown wait status %d\n",
+               fd,status);
+      disconnect();
+    }
+    fdsetup[fd].catpid= -1;
+  }
+  errno= es;
+}
+
+/*
+ * Argument parsing.  These functions which are called only during
+ * setup, before the signal asynchronicity starts.
+ */
+
+struct optioninfo;
+
+typedef void optionfunction(const struct optioninfo*, const char *value, char *key);
+
+struct optioninfo {
+  int abbrev;
+  const char *full;
+  int values; /* 0: no value; 1: single value; 2: key and value */
+  optionfunction *fn;
+};
+
 static void usage(void) {
-  if (fprintf(stderr,
+  if (fputs(
     "usage: userv <options> [--] <service-user> <service-name> [<argument> ...]\n"
     "usage: userv <options> -B|--builtin [--] <builtin-service> [<info-argument> ...]\n"
     "options: -f|--file <fd>[<fdmodifiers>]=<filename>\n"
@@ -291,21 +435,71 @@ static void usage(void) {
     "fdmodifiers:            read    write  overwrite    trunc[ate]\n"
     "(separate with commas)  append  sync   excl[usive]  creat[e]  fd\n\n"
     "userv and uservd version " VERSION "; copyright (C)1996-1997 Ian Jackson.\n"
-    "there is NO WARRANTY; type `userv --copyright' for details.\n")
-      == EOF) syscallerror("write usage to stderr");
+    "there is NO WARRANTY; type `userv --copyright' for details.\n",
+            stderr) < 0)
+    syscallerror("write usage to stderr");
 }
 
 static void NONRETURNPRINTFFORMAT(1,2) usageerror(const char *fmt, ...) {
   va_list al;
   va_start(al,fmt);
-  fprintf(stderr,"userv: ");
+  fputs("userv: ",stderr);
   vfprintf(stderr,fmt,al);
-  fprintf(stderr,"\n\n");
+  fputs("\n\n",stderr);
   usage();
   exit(-1);
 }
 
-static void addfdmodifier(struct fdsetupstate *fdsus, int fd, const char *key) {
+static const struct fdmodifierinfo fdmodifierinfos[]= {
+  { "read",      fdm_read,
+                 fdm_write,
+                 O_RDONLY                                                         },
+  { "write",     fdm_write,
+                 fdm_read,
+                 O_WRONLY                                                         },
+  { "overwrite", fdm_write|fdm_create|fdm_truncate,
+                 fdm_read|fdm_fd|fdm_exclusive,
+                 O_WRONLY|O_CREAT|O_TRUNC                                         },
+  { "create",    fdm_write|fdm_create,
+                 fdm_read|fdm_fd,
+                 O_WRONLY|O_CREAT                                                 },
+  { "creat",     fdm_write|fdm_create,
+                 fdm_read|fdm_fd,
+                 O_WRONLY|O_CREAT                                                 },
+  { "exclusive", fdm_write|fdm_create|fdm_exclusive,
+                 fdm_read|fdm_fd|fdm_truncate,
+                 O_WRONLY|O_CREAT|O_EXCL                                          },
+  { "excl",      fdm_write|fdm_create|fdm_exclusive,
+                 fdm_read|fdm_fd|fdm_truncate,
+                 O_WRONLY|O_CREAT|O_EXCL                                          },
+  { "truncate",  fdm_write|fdm_truncate,
+                 fdm_read|fdm_fd|fdm_exclusive,
+                 O_WRONLY|O_CREAT|O_EXCL                                          },
+  { "trunc",     fdm_write|fdm_truncate,
+                 fdm_read|fdm_fd|fdm_exclusive,
+                 O_WRONLY|O_CREAT|O_EXCL                                          },
+  { "append",    fdm_write|fdm_append,
+                 fdm_read|fdm_fd,
+                 O_WRONLY|O_CREAT|O_APPEND                                        },
+  { "sync",      fdm_write|fdm_sync,
+                 fdm_read|fdm_fd,
+                 O_WRONLY|O_CREAT|O_SYNC                                          },
+  { "wait",      fdm_wait,
+                 fdm_nowait|fdm_close,
+                 0                                                                },
+  { "nowait",    fdm_nowait,
+                 fdm_wait|fdm_close,
+                 0                                                                },
+  { "close",     fdm_close,
+                 fdm_wait|fdm_nowait,
+                 0                                                                },
+  { "fd",        fdm_fd,
+                 fdm_create|fdm_exclusive|fdm_truncate|fdm_append|fdm_sync,
+                 0                                                                },
+  {  0                                                                            }
+};
+
+static void addfdmodifier(int fd, const char *key) {
   const struct fdmodifierinfo *fdmip;
   
   if (!*key) return;
@@ -348,11 +542,13 @@ static void of_file(const struct optioninfo *oip, const char *value, char *key)
     fdsetup= xrealloc(fdsetup,sizeof(struct fdsetupstate)*fdsetupsize);
     while (oldarraysize < fdsetupsize) {
       fdsetup[oldarraysize].filename= 0;
+      fdsetup[oldarraysize].pipefd= -1;
       fdsetup[oldarraysize].copyfd= -1;
       fdsetup[oldarraysize].mods= 0;
+      fdsetup[oldarraysize].oflags= 0;
       fdsetup[oldarraysize].catpid= -1;
       fdsetup[oldarraysize].killed= 0;
-      fdsetup[oldarraysize++].filename= 0;
+      fdsetup[oldarraysize].filename= 0;
       oldarraysize++;
     }
   }
@@ -364,15 +560,15 @@ static void of_file(const struct optioninfo *oip, const char *value, char *key)
     key= delim;
     delim= strchr(key,',');
     if (delim) *delim++= 0;
-    addfdmodifier(&fdsetup[fd],fd,key);
+    addfdmodifier(fd,key);
   }
   if (!(fdsetup[fd].mods & (fdm_read|fdm_write))) {
-    if (fd != 1 && fd != 2) {
-      addfdmodifier(&fdsetup[fd],fd,"read");
+    if (fd == 0) {
+      addfdmodifier(fd,"read");
     } else if (fdsetup[fd].mods & fdm_fd) {
-      addfdmodifier(&fdsetup[fd],fd,"write");
+      addfdmodifier(fd,"write");
     } else {
-      addfdmodifier(&fdsetup[fd],fd,"overwrite");
+      addfdmodifier(fd,"overwrite");
     }
   }
   if (fdsetup[fd].mods & fdm_fd) {
@@ -381,13 +577,13 @@ static void of_file(const struct optioninfo *oip, const char *value, char *key)
       copyfd= strtoul(value,&delim,0);
       if (*delim)
        usageerror("value part of argument to --file with fd modifier must be "
-                  "numeric or fd name- `%s' is not recognised",value);
+                  "numeric or fd name - `%s' is not recognised",value);
       else if (copyfd > MAX_ALLOW_FD)
        usageerror("file descriptor %lu named as target of file descriptor redirection"
                   " (for file descriptor %lu) is larger than maximum allowed (%d)",
                   copyfd,fd,MAX_ALLOW_FD);
     }
-    do { r= fstat(copyfd,&stab); } while (r && errno==EINTR);
+    r= fstat(copyfd,&stab);
     if (r) {
       if (oip) syscallerror("check filedescriptor %lu (named as target of file "
                            "descriptor redirection for %lu)",copyfd,fd);
@@ -408,7 +604,7 @@ static void of_fdwait(const struct optioninfo *oip, const char *value, char *key
     ul= strtoul(key,&delim,0);
     if (*delim) usageerror("first part of argument to --fdwait must be "
                           "numeric or fd name - `%s' is not recognised",key);
-    if (ul>INT_MAX) usageerror("first part of argument to --fdwait is far too large");
+    if (ul>MAX_ALLOW_FD) usageerror("first part of argument to --fdwait is too large");
     fd= ul;
   }
   if (fd >= fdsetupsize || !fdsetup[fd].filename)
@@ -442,14 +638,18 @@ static void of_defvar(const struct optioninfo *oip, const char *value, char *key
 
 static void of_timeout(const struct optioninfo *oip, const char *value, char *key) {
   char *endp;
-  timeout= strtoul(value,&endp,0);
+  unsigned long ul;
+  
+  ul= strtoul(value,&endp,0);
   if (*endp) usageerror("timeout value `%s' must be a plain decimal string",value);
-  if (timeout>INT_MAX) usageerror("timeout value %lu too large",timeout);
+  if (ul>INT_MAX) usageerror("timeout value %lu too large",ul);
+  timeout= ul;
 }
 
 static void of_signals(const struct optioninfo *oip, const char *value, char *key) {
   unsigned long numvalue;
   char *endp;
+  
   numvalue= strtoul(value,&endp,0);
   if (*endp) {
     if (!strcmp(value,"number")) signalsexit= se_number;
@@ -478,7 +678,7 @@ static void of_help(const struct optioninfo *oip, const char *value, char *key)
 }
 
 static void of_copyright(const struct optioninfo *oip, const char *value, char *key) {
-  if (fprintf(stdout,
+  if (fputs(
 " userv - user service daemon and client; copyright (C)1996-1997 Ian Jackson\n\n"
 " This is free software; you can redistribute it and/or modify it under the\n"
 " terms of the GNU General Public License as published by the Free Software\n"
@@ -491,8 +691,8 @@ static void of_copyright(const struct optioninfo *oip, const char *value, char *
 " You should have received a copy of the GNU General Public License along\n"
 " with userv; if not, write to Ian Jackson <ian@chiark.greenend.org.uk> or\n"
 " to the Free Software Foundation, 59 Temple Place - Suite 330, Boston,\n"
-" MA 02111-1307, USA.\n"
-             ) == EOF) syscallerror("write usage to stderr");
+" MA 02111-1307, USA.\n",
+           stdout) < 0) syscallerror("write usage to stderr");
   exit(0);
 }
 
@@ -551,180 +751,41 @@ static void callvalueoption(const struct optioninfo *oip, char *arg) {
   }
 }
 
-static void checkmagic(unsigned long was, unsigned long should, const char *when) {
-  if (was != should)
-    protoerror("magic number %s was %08lx, expected %08lx",when,was,should);
-}
-
-static void getprogress(struct progress_msg *progress_r, FILE *file) {
-  int i, c;
-  unsigned long ul;
-
-  for (;;) {
-    xfread(progress_r,sizeof(struct progress_msg),file);
-    switch (progress_r->type) {
-    case pt_failed:
-      blocksignals(SIG_BLOCK);
-      fputs("userv: uservd reports that service failed\n",stderr);
-      exit(-1);
-    case pt_errmsg:
-      blocksignals(SIG_BLOCK);
-      fputs("uservd: ",stderr);
-      if (progress_r->data.errmsg.messagelen>4096)
-       protoerror("stderr message length %d is far too long",
-                  progress_r->data.errmsg.messagelen);
-      for (i=0; i<progress_r->data.errmsg.messagelen; i++) {
-       c= getc(file); if (c==EOF) protoreaderror(file,"in error message");
-       if (isprint(c)) putc(c,stderr);
-       else fprintf(stderr,"\\x%02x",(unsigned char)c);
-      }
-      putc('\n',stderr);
-      if (ferror(stderr)) syscallerror("printing error message");
-      xfread(&ul,sizeof(ul),file);
-      checkmagic(ul,PROGRESS_ERRMSG_END_MAGIC,"after error message");
-      blocksignals(SIG_UNBLOCK);
-      break;
-    default:
-      return;
-    }
-  }
-}
-
-static void xfwritefds(int modifier, int expected, FILE *file) {
-  int i, fdcount;
-
-  for (i=0, fdcount=0; i<fdsetupsize; i++) {
-    if (!(fdsetup[i].filename && (fdsetup[i].mods & modifier)))
-      continue;
-    xfwrite(&i,sizeof(int),file); fdcount++;
-  }
-  assert(fdcount == expected);
-}
-
-static void disconnect(void) /* DOES return, unlike in daemon */ {
-  struct event_msg event_mbuf;
-
-  if (!swfile) {
-    fputs("userv: failed, after service program terminated\n",stderr);
-    _exit(255);
-  }
-  memset(&event_mbuf,0,sizeof(event_mbuf));
-  event_mbuf.magic= EVENT_MAGIC;
-  event_mbuf.type= et_disconnect;
-  xfwrite(&event_mbuf,sizeof(event_mbuf),swfile);
-  xfflush(swfile);
-}
-
-static void sighandler_alrm(int ignored) /* DOES return, unlike in daemon */ {
-  int es;
-  es= errno;
-  fputs("userv: timeout\n",stderr);
-  disconnect();
-  errno= es;
-}
-
-static void sighandler_chld(int ignored) /* DOES return, unlike in daemon */ {
-  struct event_msg event_mbuf;
-  pid_t child;
-  int status, fd, r, es;
+/*
+ * Main thread main processing functions - in order of execution.
+ */
 
-  es= errno;
-  for (;;) {
-    child= wait3(&status,WNOHANG,0);
-    if (child == 0 || (child == -1 && errno == ECHILD)) break;
-    if (child == -1) syscallerror("wait for child process (in sigchld handler)");
-    for (fd=0; fd<fdsetupsize && fdsetup[fd].catpid != child; fd++);
-    if (fd>=fdsetupsize) continue; /* perhaps the invoker gave us children */
-    if ((WIFEXITED(status) && WEXITSTATUS(status)==0) ||
-       (WIFSIGNALED(status) && WTERMSIG(status)==SIGPIPE) ||
-       (fdsetup[fd].killed && WIFSIGNALED(status) && WTERMSIG(status)==SIGKILL)) {
-      if (swfile && fdsetup[fd].mods & fdm_read) {
-       memset(&event_mbuf,0,sizeof(event_mbuf));
-       event_mbuf.magic= EVENT_MAGIC;
-       event_mbuf.type= et_closereadfd;
-       r= fwrite(&event_mbuf,1,sizeof(event_mbuf),swfile);
-       if (r != sizeof(event_mbuf) || fflush(swfile))
-         if (errno != EPIPE) syscallerror("inform service of closed read fd");
-      }
-    } else {
-      if (WIFEXITED(status))
-       fprintf(stderr,"userv: cat for fd %d exited with error exit status %d\n",
-               fd,WEXITSTATUS(status));
-      else if (WIFSIGNALED(status))
-       if (WCOREDUMP(status))
-         fprintf(stderr,"userv: cat for fd %d dumped core due to signal %s (%d)\n",
-                 fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
-       else
-         fprintf(stderr,"userv: cat for fd %d terminated by signal %s (%d)\n",
-                 fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
-      else
-       fprintf(stderr,"userv: cat for fd %d gave unknown wait status %d\n",
-               fd,status);
-      disconnect();
-    }
-    fdsetup[fd].catpid= -1;
-  }
-  errno= es;
-}
+static void security_init(void) {
+  /* May not open any file descriptors. */
+  
+  mypid= getpid(); if (mypid == (pid_t)-1) syscallerror("getpid");
+  myuid= getuid(); if (myuid == (uid_t)-1) syscallerror("getuid");
+  mygid= getgid(); if (mygid == (gid_t)-1) syscallerror("getgid");
+  ngids= getgroups(0,0); if (ngids == (gid_t)-1) syscallerror("getgroups(0,0)");
+  gidarray= xmalloc(sizeof(gid_t)*ngids);
+  if (getgroups(ngids,gidarray) != ngids) syscallerror("getgroups(ngids,)");
+  
+  priv_suspend();
 
-static void catdup(const char *which, int from, int to) {
-  if (dup2(from,to)<0) {
-    blocksignals(SIG_BLOCK);
-    fprintf(stderr,"userv: %s: cannot dup for %s: %s\n",which,
-           to?"stdout":"stdin", strerror(errno));
-    exit(-1);
-  }
+  if (ngids > MAX_GIDS) miscerror("caller is in far too many gids");
 }
 
-int main(int argc, char *const *argv) {
+static void parse_arguments(int *argcp, char *const **argvp) {
   static char fd0key[]= "stdin,fd,read";
   static char fd1key[]= "stdout,fd,write";
   static char fd2key[]= "stderr,fd,write";
-  static char stderrbuf[BUFSIZ], stdoutbuf[1024];
-  
+
   char *const *argpp;
   char *argp;
   const struct optioninfo *oip;
-  struct sockaddr_un ssockname;
-  int sfd, ngids, i, tempfd, l, c, reading, fd, r, status, ngidssize;
-  sigset_t sset;
-  unsigned long ul;
-  size_t cwdbufsize;
-  char *cwdbuf, **mem;
-  struct opening_msg opening_mbuf;
-  struct request_msg request_mbuf;
-  struct progress_msg progress_mbuf;
-  struct event_msg event_mbuf;
-  struct passwd *pw;
-  struct group *gr;
-  gid_t mygid, spoofgid, *gidarray;
-  uid_t spoofuid;
-  pid_t mypid;
-  const char *logname;
-  FILE *ovfile;
-  char *ovbuf;
-  int ovavail, ovused;
-  char pipepathbuf[PIPEPATHMAXLEN], catnamebuf[sizeof(int)*3+30];
-  struct sigaction sig;
-
-#ifdef NDEBUG
-# error Do not disable assertions in this security-critical code !
-#endif
-
-  mypid= getpid(); if (mypid == (pid_t)-1) syscallerror("getpid");
-  myuid= getuid(); if (myuid == (uid_t)-1) syscallerror("getuid");
-  mygid= getgid(); if (mygid == (gid_t)-1) syscallerror("getgid");
-  ngids= getgroups(0,0); if (ngids == (gid_t)-1) syscallerror("getgroups(0,0)");
-  gidarray= xmalloc(sizeof(gid_t)*ngids);
-  if (getgroups(ngids,gidarray) != ngids) syscallerror("getgroups(ngids,)");
-  priv_suspend();
+  int fd;
 
-  assert(argv[0]);
+  assert((*argvp)[0]);
   of_file(0,"stdin",fd0key);
   of_file(0,"stdout",fd1key);
   of_file(0,"stderr",fd2key);
 
-  for (argpp= argv+1;
+  for (argpp= *argvp+1;
        (argp= *argpp) && *argp == '-' && argp[1];
        argpp++) {
     if (!*++argp) usageerror("unknown option/argument `%s'",*argpp);
@@ -768,6 +829,11 @@ int main(int argc, char *const *argv) {
   if (!*argpp) usageerror(overridetype == ot_builtin ?
                          "no service name given after options and service user" :
                          "no builtin service given after options");
+
+  *argcp-= (argpp-*argvp);
+  *argvp= argpp;
+
+  if (*argcp > MAX_ARGSDEFVAR) usageerror("far too many arguments");
   
   for (fd=0; fd<fdsetupsize; fd++) {
     if (!fdsetup[fd].filename) continue;
@@ -775,16 +841,13 @@ int main(int argc, char *const *argv) {
     assert(fdsetup[fd].mods & (fdm_read|fdm_write));
     fdsetup[fd].mods |= (fdsetup[fd].mods & fdm_read) ? fdm_close : fdm_wait;
   }
+}
 
-  if (setvbuf(stderr,stderrbuf,_IOLBF,sizeof(stderrbuf)))
-    syscallerror("set buffering on stderr");
-  if (setvbuf(stdout,stdoutbuf,_IOFBF,sizeof(stdoutbuf)))
-    syscallerror("set buffering on stdout");
-
-  argc-= (argpp-argv);
-  argv= argpp;
-  if (argc > MAX_ARGSDEFVAR) usageerror("far too many arguments");
-  if (ngids > MAX_GIDS) miscerror("caller is in far too many gids");
+static void determine_users(void) {
+  int ngidssize;
+  char **mem;
+  struct passwd *pw;
+  struct group *gr;
   
   spoofuid= myuid;
   spoofgid= mygid;
@@ -824,24 +887,33 @@ int main(int argc, char *const *argv) {
       for (mem= gr->gr_mem; *mem && strcmp(*mem,logname); mem++);
       if (!*mem) continue;
       if (ngids>=ngidssize) {
+      if (ngids>=MAX_GIDS) miscerror("spoofed user is member of too many groups");
        ngidssize= (ngids+5)<<1;
        gidarray= xrealloc(gidarray,sizeof(gid_t)*ngidssize);
       }
       gidarray[ngids++]= gr->gr_gid;
     }
   }
+}
 
+static void determine_cwd(void) {
   cwdbufsize= 0; cwdbuf= 0;
-  if (!hidecwd) {
-    for (;;) {
-      assert(cwdbufsize < INT_MAX/3);
-      cwdbufsize <<= 1; cwdbufsize+= 100;
-      cwdbuf= xrealloc(cwdbuf,cwdbufsize);
-      cwdbuf[cwdbufsize-1]= 0;
-      if (getcwd(cwdbuf,cwdbufsize-1)) { cwdbufsize= strlen(cwdbuf); break; }
-      if (errno != ERANGE) { cwdbufsize= 0; break; }
-    }
+
+  if (hidecwd) return;
+  
+  for (;;) {
+    if (cwdbufsize > MAX_GENERAL_STRING) { cwdbufsize= 0; free(cwdbuf); break; }
+    cwdbufsize <<= 1; cwdbufsize+= 100;
+    cwdbuf= xrealloc(cwdbuf,cwdbufsize);
+    cwdbuf[cwdbufsize-1]= 0;
+    if (getcwd(cwdbuf,cwdbufsize-1)) { cwdbufsize= strlen(cwdbuf); break; }
+    if (errno != ERANGE) { cwdbufsize= 0; free(cwdbuf); break; }
   }
+}
+
+static void process_override(const char *servicename) {
+  FILE *ovfile;
+  int ovavail, l, c;
 
   switch (overridetype) {
   case ot_none:
@@ -849,13 +921,13 @@ int main(int argc, char *const *argv) {
     ovbuf= 0;
     break;
   case ot_builtin:
-    l= strlen(argv[0]);
+    l= strlen(servicename);
     if (l >= MAX_OVERRIDE_LEN-20)
       miscerror("builtin service string is too long (%d, max is %d)",
                l,MAX_OVERRIDE_LEN-21);
     l+= 20;
     ovbuf= xmalloc(l);
-    snprintf(ovbuf,l,"execute-builtin %s\n",argv[0]);
+    snprintf(ovbuf,l,"execute-builtin %s\n",servicename);
     ovused= strlen(ovbuf);
     break;
   case ot_string:
@@ -887,6 +959,19 @@ int main(int argc, char *const *argv) {
   default:
     abort();
   }
+}
+
+static int server_connect(void) {
+  struct sockaddr_un ssockname;
+  int sfd;
+  struct sigaction sig;
+  sigset_t sset;
+
+  sigemptyset(&sset);
+  sigaddset(&sset,SIGCHLD);
+  sigaddset(&sset,SIGALRM);
+  sigaddset(&sset,SIGPIPE);
+  if (sigprocmask(SIG_UNBLOCK,&sset,0)) syscallerror("preliminarily unblock signals");
 
   sig.sa_handler= SIG_IGN;
   sigemptyset(&sig.sa_mask);
@@ -905,8 +990,11 @@ int main(int argc, char *const *argv) {
       syscallerror("uservd daemon is not running - service not available");
     syscallerror("unable to connect to uservd daemon");
   }
-  priv_suspend();
 
+  return sfd;
+}
+
+static void server_handshake(int sfd) {
   srfile= fdopen(sfd,"r");
   if (!srfile) syscallerror("turn socket fd into FILE* for read");
   if (setvbuf(srfile,0,_IOFBF,BUFSIZ)) syscallerror("set buffering on socket reads");
@@ -919,26 +1007,39 @@ int main(int argc, char *const *argv) {
   checkmagic(opening_mbuf.magic,OPENING_MAGIC,"in opening message");
   if (memcmp(protocolchecksumversion,opening_mbuf.protocolchecksumversion,PCSUMSIZE))
     protoerror("protocol version checksum mismatch - server not same as client");
+}
 
+static void server_preparepipes(void) {
+  char pipepathbuf[PIPEPATHMAXLEN+2];
+  int fd, tempfd;
+  
   for (fd=0; fd<fdsetupsize; fd++) {
     if (!fdsetup[fd].filename) continue;
+    pipepathbuf[PIPEPATHMAXLEN]= 0;
     sprintf(pipepathbuf, PIPEPATHFORMAT,
             (unsigned long)mypid, (unsigned long)opening_mbuf.serverpid, fd);
+    assert(!pipepathbuf[PIPEPATHMAXLEN]);
     priv_resume();
     if (unlink(pipepathbuf) && errno != ENOENT)
       syscallerror("remove any old pipe `%s'",pipepathbuf);
     if (mkfifo(pipepathbuf,0600)) /* permissions are irrelevant */
       syscallerror("create pipe `%s'",pipepathbuf);
     tempfd= open(pipepathbuf,O_RDWR);
-    if (tempfd == -1) syscallerror("prelim open pipe `%s' for read+write",pipepathbuf);
+    if (tempfd<0) syscallerror("prelim open pipe `%s' for read+write",pipepathbuf);
     assert(fdsetup[fd].mods & (fdm_read|fdm_write));
     fdsetup[fd].pipefd=
       open(pipepathbuf, (fdsetup[fd].mods & fdm_read) ? O_WRONLY : O_RDONLY);
-    if (fdsetup[fd].pipefd == -1) syscallerror("real open pipe `%s'",pipepathbuf);
+    if (fdsetup[fd].pipefd<0) syscallerror("real open pipe `%s'",pipepathbuf);
     if (close(tempfd)) syscallerror("close prelim fd onto pipe `%s'",pipepathbuf);
     priv_suspend();
   }
+}
 
+static void server_sendrequest(int argc, char *const *argv) {
+  struct request_msg request_mbuf;
+  unsigned long ul;
+  int fd, i;
+  
   memset(&request_mbuf,0,sizeof(request_mbuf));
   request_mbuf.magic= REQUEST_MAGIC;
   request_mbuf.clientpid= getpid();
@@ -977,14 +1078,27 @@ int main(int argc, char *const *argv) {
   }
   ul= REQUEST_END_MAGIC; xfwrite(&ul,sizeof(ul),swfile);
   xfflush(swfile);
+}
 
-  priv_permanentlyrevokesuspended(); /* Must not do this before we give our real id */
-
+static void server_awaitconfirm(void) {
+  struct progress_msg progress_mbuf;
+  
   getprogress(&progress_mbuf,srfile);
   if (progress_mbuf.type != pt_ok)
     protoerror("progress message during configuration phase"
               " unexpected type %d",progress_mbuf.type);
+}
 
+static void prepare_asynchsignals(void) {
+  static char stderrbuf[BUFSIZ], stdoutbuf[BUFSIZ];
+  
+  struct sigaction sig;
+
+  if (setvbuf(stderr,stderrbuf,_IOLBF,sizeof(stderrbuf)))
+    syscallerror("set buffering on stderr");
+  if (setvbuf(stdout,stdoutbuf,_IOFBF,sizeof(stdoutbuf)))
+    syscallerror("set buffering on stdout");
+  
   sig.sa_handler= sighandler_chld;
   sigemptyset(&sig.sa_mask);
   sigaddset(&sig.sa_mask,SIGCHLD);
@@ -995,11 +1109,29 @@ int main(int argc, char *const *argv) {
   sig.sa_handler= sighandler_alrm;
   if (sigaction(SIGALRM,&sig,0)) syscallerror("set up sigalrm handler");
 
+  if (!timeout) return;
+  if (alarm(timeout)<0) syscallerror("set up timeout alarm");
+}
+
+static void catdup(const char *which, int from, int to) {
+  if (dup2(from,to)<0) {
+    blocksignals(SIG_BLOCK);
+    fprintf(stderr,"userv: %s: cannot dup for %s: %s\n",which,
+           to?"stdout":"stdin", strerror(errno));
+    exit(-1);
+  }
+}
+
+static void connect_pipes(void) {
+  struct sigaction sig;
+  char catnamebuf[sizeof(int)*3+30];
+  int fd, reading;
+  
   for (fd=0; fd<fdsetupsize; fd++) {
     if (!fdsetup[fd].filename) continue;
     if (!(fdsetup[fd].mods & fdm_fd)) {
       fdsetup[fd].copyfd=
-       open(fdsetup[fd].filename,fdsetup[fd].oflags,0777);
+       open(fdsetup[fd].filename,fdsetup[fd].oflags|O_NOCTTY,0777);
       if (fdsetup[fd].copyfd<0)
        syscallerror("open file `%s' for fd %d",fdsetup[fd].filename,fd);
     }
@@ -1027,9 +1159,10 @@ int main(int argc, char *const *argv) {
       if (close(fdsetup[fd].copyfd)) syscallerror("close real fd for %d",fd);
     if (close(fdsetup[fd].pipefd)) syscallerror("close pipe fd for %d",fd);
   }
+}
 
-  if (timeout)
-    if (alarm(timeout)<0) syscallerror("set up timeout alarm");
+static void server_sendconfirm(void) {
+  struct event_msg event_mbuf;
 
   blocksignals(SIG_BLOCK);
   memset(&event_mbuf,0,sizeof(event_mbuf));
@@ -1038,13 +1171,23 @@ int main(int argc, char *const *argv) {
   xfwrite(&event_mbuf,sizeof(event_mbuf),swfile);
   xfflush(swfile);
   blocksignals(SIG_UNBLOCK);
+}
 
+static int server_awaitcompletion(void) {
+  struct progress_msg progress_mbuf;
+  
   getprogress(&progress_mbuf,srfile);
   if (progress_mbuf.type != pt_terminated)
     protoerror("progress message during execution phase"
               " unexpected type %d",progress_mbuf.type);
 
   swfile= 0;
+  return progress_mbuf.data.terminated.status;
+}
+
+static void dispose_remaining_pipes(void) {
+  sigset_t sset;
+  int fd, r;
 
   blocksignals(SIG_BLOCK);
   for (fd=0; fd<fdsetupsize; fd++) {
@@ -1065,10 +1208,13 @@ int main(int argc, char *const *argv) {
     if (r && errno != EINTR) syscallerror("sigsuspend failed in unexpected way");
     blocksignals(SIG_UNBLOCK);
   }
+}
 
+static void NONRETURNING process_exitstatus(int status) {
   blocksignals(SIG_BLOCK);
 
-  status= progress_mbuf.data.terminated.status;
+  if (systemerror) _exit(255);
+
   if (sigpipeok && signalsexit != se_stdout && WIFSIGNALED(status) &&
       WTERMSIG(status)==SIGPIPE && !WCOREDUMP(status)) status= 0;
   
@@ -1107,6 +1253,37 @@ int main(int argc, char *const *argv) {
     break;
   }
       
-  fprintf(stderr,"unknown wait status %d\n",status);
+  fprintf(stderr,"userv: unknown wait status %d\n",status);
   _exit(-1);
 }
+
+int main(int argc, char *const *argv) {
+  int status, socketfd;
+
+#ifdef NDEBUG
+# error Do not disable assertions in this security-critical code !
+#endif
+
+  security_init();
+  parse_arguments(&argc,&argv);
+  determine_users();
+  determine_cwd();
+  process_override(argv[0]);
+
+  socketfd= server_connect();
+  priv_suspend();
+  server_handshake(socketfd);
+  server_preparepipes();
+  server_sendrequest(argc,argv);
+
+  priv_permanentlyrevokesuspended();
+  
+  server_awaitconfirm();
+  prepare_asynchsignals();
+  connect_pipes();
+  server_sendconfirm();
+  status= server_awaitcompletion();
+  
+  dispose_remaining_pipes();
+  process_exitstatus(status);
+}
index 73e015275594688e67fb039c489d63d9c77cb0b6..2a2db41eb2c315b63ff26c0549b44ffca324f74a 100644 (file)
--- a/common.h
+++ b/common.h
@@ -43,7 +43,7 @@ static const unsigned char protocolchecksumversion[PCSUMSIZE]= {
 #ifndef PIPEFORMAT
 # define PIPEFORMAT "%lx.%lx.%x"
 # define PIPEPATTERN "[0-9a-f]*.[0-9a-f]*.*[0-9a-f]"
-# define PIPEFORMATEXTEND (sizeof(long)*2*2+sizeof(int)*2+1)
+# define PIPEFORMATEXTEND (sizeof(unsigned long)*2*2+sizeof(int)*2+3)
 # define PIPEMAXLEN (sizeof(PIPEFORMAT)+PIPEFORMATEXTEND)
 #endif
 
@@ -55,6 +55,7 @@ static const unsigned char protocolchecksumversion[PCSUMSIZE]= {
 #define MAX_ALLOW_FD 1024
 #define MAX_GENERAL_STRING (1024*1024)
 #define MAX_OVERRIDE_LEN MAX_GENERAL_STRING
+#define MAX_ERRMSG_STRING 4096
 #define MAX_ARGSDEFVAR 4096
 #define MAX_GIDS 1024
 
@@ -85,14 +86,16 @@ struct request_msg {
   int serviceuserlen;
   int servicelen;
   int lognamelen;
-  int cwdlen;
+  int cwdlen, overridelen;
   uid_t callinguid;
-  int ngids, nreadfds, nwritefds, nargs, nvars, overridelen;
+  int ngids, nreadfds, nwritefds, nargs, nvars;
   /* Followed by:
    *   serviceuserlen bytes for the service user (unterminated)
    *   servicelen bytes for the service (unterminated)
    *   lognamelen bytes for the login name (unterminated)
    *   cwdlen bytes for the cwd (unterminated)
+   *   overridelen bytes for the override data (with extra \n but unterminated),
+   *    or nothing if overridelen==-1
    *   ngids gid_ts for the primary group and supplementary groups
    *   nreadfds and then nwritefds ints for the file descriptors
    *   for each of the nargs arguments
@@ -115,10 +118,9 @@ struct progress_msg {
     struct { int status; } terminated;
   } data;
   /* follwed by variable-length part:
-   *  for ok: nothing
-   *  for errmsg: messagelen bytes for the error message (unterminated)
-   *              unsigned long PROGRESS_MAGIC
-   *  for terminated: nothing
+   *  for ok, failed, terminated: nothing
+   *  for errmsg: messagelen bytes for the error message (unterminated, no \n)
+   *              unsigned long PROGRESS_ERRMSG_END_MAGIC
    */
 };
 
index b42a7289a46c25b02d3157e2d5515f1c848c7d38..fb5a128a2b7f052aaa11907b453c0e2343df551d 100644 (file)
@@ -32,6 +32,7 @@ AC_ARG_ENABLE(debug,
  if test "x$enable_debug" = xyes; then
   DEBUGDEFS="-DDEBUG -DVARDIR='\"$crdir/vd\"' -DSYSTEMCONFIGDIR='\"$crdir/slash-etc\"' -DSERVICEUSERDIR='\"$crdir/tilde\"'"
   DEBUGLIBS=-lefence
+  echo will build debugging version
  elif test "x$enable_debug" != xno; then
   AC_MSG_ERROR(--enable-debug does not allow any arguments except 'yes' and 'no')
  fi
@@ -77,10 +78,10 @@ DPKG_CACHED_TRY_COMPILE(__attribute__((,,)),dpkg_cv_c_attribute_supported,,
    AC_MSG_RESULT(yes)
    AC_DEFINE(HAVE_GNUC25_NORETURN),
    AC_MSG_RESULT(no))
-  DPKG_CACHED_TRY_COMPILE(__attribute__((const)),dpkg_cv_c_attribute_const,,
-   [extern int testfunction(int x) __attribute__((const))],
+  DPKG_CACHED_TRY_COMPILE(__attribute__((unused)),dpkg_cv_c_attribute_unused,,
+   [extern int testfunction(int x) __attribute__((unused))],
    AC_MSG_RESULT(yes)
-   AC_DEFINE(HAVE_GNUC25_CONST),
+   AC_DEFINE(HAVE_GNUC25_UNUSED),
    AC_MSG_RESULT(no))
   DPKG_CACHED_TRY_COMPILE(__attribute__((format...)),dpkg_cv_attribute_format,,
    [extern int testfunction(char *y, ...) __attribute__((format(printf,1,2)))],
index 7b06bf7b77a6e6f3cc1c8a609059c7c433352623..710d854a173f5aa87c5be21bffa836ede22295ee 100644 (file)
--- a/daemon.h
+++ b/daemon.h
@@ -65,8 +65,8 @@
 #define EMPTYINCLUDELOOKUP          ":empty"
 
 #define USERCONFIGDIRBASE           SYSTEMUSERVCONFIGDIR
-#define USERCONFIGDIR               HIDDENPREFIX USERCONFIGDIRBASE
-#define USERUSERVCONFIGPATH         USERDIR "/" USERCONFIGDIR
+#define USERCONFIGDIR               "." USERCONFIGDIRBASE
+#define USERUSERVCONFIGPATH         "~/" USERCONFIGDIR
 #define USERRCFILEPATH              USERUSERVCONFIGPATH "/" USERRCFILE
 #define SYSTEMUSERVCONFIGPATH       SYSTEMCONFIGDIR "/" SYSTEMUSERVCONFIGDIR
 #define SYSTEMRCFILEDEFAULTPATH     SYSTEMUSERVCONFIGPATH "/" SYSTEMRCFILEDEFAULT
@@ -74,9 +74,6 @@
 #define SHELLLISTPATH               SYSTEMCONFIGDIR "/" SHELLLIST
 #define SETENVIRONMENTPATH          SYSTEMCONFIGDIR "/" SETENVIRONMENT
 
-#define USERDIR                     "~"
-#define HIDDENPREFIX                "."
-
 #define USERVD_LOGIDENT "uservd"
 #define USERVD_LOGFACILITY LOG_DAEMON
 #define DEFUSERLOGFACILITY LOG_DAEMON
 "
 
 #define MAX_INCLUDE_NEST 40
-#define MAX_ERRMSG_LEN 2048
+#define MAX_ERRMSG_LEN (MAX_ERRMSG_STRING-1024)
 #define ERRMSG_RESERVE_ERRNO 128
 
 int parse_string(const char *string, const char *descrip, int isinternal);
@@ -138,12 +135,12 @@ int synchread(int fd, int ch);
 const char *defaultpath(void);
 
 struct fdstate {
-  int iswrite, realfd, holdfd;
+  int iswrite; /* 0 or 1; -1 if not open */
+  int realfd, holdfd; /* -1 if not open */
   int wantstate;
   /* tokv_word_requirefd, tokv_word_allowfd, tokv_nullfd, tokv_word_rejectfd
    * (all of which have tokt_wantfdstate set) */
-  int wantrw;
-  /* tokv_word_read, tokv_word_write */
+  int wantrw; /* tokv_word_read, tokv_word_write, 0 for either/both */
 };
 
 struct keyvaluepair { char *key, *value; };
index 2f360606017eb44ba2695832c90e79015f3d2f3e..849f233115545749f24e1f0ff6d78e574789dc14 100644 (file)
@@ -57,14 +57,17 @@ static directive_fnt dfi_includeuserrcfile, dfi_includeclientconfig;
 /* directive functions return:
  *  0 for success having scanned up to and including end of line but not beyond,
  *  or tokv_error or tokv_quit.
+ * They expect to parse the whitespace before their parameters (if any).
  */
 
-typedef int parmcondition_fnt(int ctoken, char **parmvalues, int *rtrue);
+typedef int parmcondition_fnt(int ctoken, char *const *parmvalues, int *rtrue);
 static parmcondition_fnt pcf_glob, pcf_range, pcf_grep;
 /* all conditional functions return tokv_error for failure or 0 for success
  *  at parsing and testing, in which case *rtrue is set to 0 or 1.
  *  On success they have scanned up to and including the condition's
- *  terminating newline.
+ *  terminating newline; the pcf_... functions expect to parse the whitespace
+ *  between the parameter name and the condition's arguments.
+ * Otherwise they return tokv_error.
  * The parameter-based conditionals take a list of parameter values
  * as obtained from the parameter functions and pa_parameter,
  * and do _not_ free it.
@@ -108,6 +111,7 @@ static int lr_fdwant_readwrite; /* -1=never, 0=opt, 1=always */
 struct parser_state {
   int lineno, reportlineno, notedreferer, isinternal;
   const char *filename;
+  struct stat filestab;
   YY_BUFFER_STATE ybuf;
   struct parser_state *upstate;
 };
@@ -116,7 +120,8 @@ static struct parser_state *cstate;
 
 struct error_handling {
   int handling; /* One of the error handling modes tokt_ehandlemode */
-  int logfacility, loglevel, filekeep;
+  int logfacility, loglevel;
+  int filekeep; /* File is in use by higher-level errors-push, leave it open */
   FILE *file;
   char *filename;
 };
@@ -125,8 +130,12 @@ static struct error_handling eh = { tokv_word_errorstostderr, 0,0,0,0,0 };
 
 static int dequote(char *inplace);
 
+#define YY_NO_UNPUT
+
 %}
+
 %option noyywrap
+
 %%
 
 dnl simple words
@@ -141,10 +150,10 @@ changequote({*,*})
                        }
 [0-9]{1,8}-[0-9]{1,8}  {
                          char *ep;
-                         lr_min=(int)strtoul(yytext,&ep,10);
+                         lr_min= (int)strtoul(yytext,&ep,10);
                          assert(*ep == HYPHEN);
                          assert(*++ep);
-                         lr_max=(int)strtoul(ep,&ep,10);
+                         lr_max= (int)strtoul(ep,&ep,10);
                          assert(!*ep);
                          if (lr_max < lr_min) {
                            parseerrprint("fd range has min > max");
@@ -184,4 +193,3 @@ changequote(`,')
 '
 divert(-1)
 undivert
-
diff --git a/lib.c b/lib.c
index 6c3cd0aff62924cd3e98449ae30616b54ce164a4..e36382927e158d007eed9bdbb13a3246c73f8f97 100644 (file)
--- a/lib.c
+++ b/lib.c
 #include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
+#include <sys/types.h>
 
 #include "config.h"
+#include "common.h"
 #include "lib.h"
 
 char *xstrcat3save(const char *a, const char *b, const char *c) {
@@ -67,10 +69,12 @@ void *xrealloc(void *p, size_t s) {
   return p;
 }
 
-void makeroom(char **buffer, int *size, int needed) {
-  if (*size >= needed) return;
+int makeroom(char **buffer, int *size, int needed) {
+  if (needed > MAX_GENERAL_STRING) return -1;
+  if (*size >= needed) return 0;
   *buffer= xrealloc(*buffer,needed);
   *size= needed;
+  return 0;
 }
 
 void vsnyprintf(char *buffer, size_t size, const char *fmt, va_list al) {
diff --git a/lib.h b/lib.h
index fc9d01604a596e0dd1dde1522a2bbba3960c36b0..81ff9fcec04fdcd18ad7da34f5c89ed91632a67f 100644 (file)
--- a/lib.h
+++ b/lib.h
@@ -30,7 +30,8 @@ void miscerror(const char *what) NONRETURNING;
 void syscallerror(const char *what) NONRETURNING;
 void *xmalloc(size_t s);
 void *xrealloc(void *p, size_t s);
-void makeroom(char **buffer, int *size, int needed);
+int makeroom(char **buffer, int *size, int needed);
+/* makeroom returns -1 if needed was far too large; otherwise returns 0. */
 
 /* It doesn't appear to be documented whether [v]snprintf put a null
  * in if they overrun.  GNU libc does, but I don't want to rely on
index be2553ae019155adae0a61d32bfb7c0ddfa79636..7f66c4ea354307512365fc9f1cdaf7e7fcaf96d4 100644 (file)
@@ -130,7 +130,7 @@ int main(int argc, char *const *argv) {
   checkstalepipes();
 
   mfd= socket(AF_UNIX,SOCK_STREAM,0);
-  if (!mfd) { syslog(LOG_CRIT,"cannot create master socket: %m"); exit(4); }
+  if (mfd<0) { syslog(LOG_CRIT,"cannot create master socket: %m"); exit(4); }
 
   assert(sizeof(ssockname.sun_path) > sizeof(RENDEZVOUS));
   ssockname.sun_family= AF_UNIX;
@@ -156,7 +156,7 @@ int main(int argc, char *const *argv) {
     if (sfd<0) {
       errno= e;
       if (errno == EINTR) continue;
-      if (errno == ENOMEM) {
+      if (errno == ENOMEM || errno == EPROTO || errno == EAGAIN) {
         syslog(LOG_ERR,"unable to accept connection: %m");
        continue;
       } else {
index 4b55743c1791d99a2ffab60f8bda6e43e662b5d1..898bbfac2c3f2f0d9e1d8ac6e3d2ba1cfd0092fa 100644 (file)
--- a/parser.c
+++ b/parser.c
@@ -3,7 +3,8 @@
  * configuration file parser; this file is actually #included from
  * lexer.c, which is generated using flex from lexer.l, in turn from
  * lexer.l.m4.  It's in a separate file so that we don't have to worry
- * about m4 quoting &c.
+ * about m4 quoting &c., but we have to #include it so that the C
+ * objects from the lexer are available.
  *
  * Copyright (C)1996-1997 Ian Jackson
  *
  * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
-static void useless(void) { (void)yyunput; (void)useless; /* to shut up GCC ! */ }
+static int parse_file(const char *string, int *didexist);
+static int parser(int allowce);
+int parse_string(const char *string, const char *descrip, int isinternal);
+
+/*
+ * Error-handling routines
+ */
 
 static void closeerrorfile(void) {
   if (eh.file && !eh.filekeep) {
@@ -102,6 +109,25 @@ void parseerrprint(const char *fmt, ...) {
   va_end(al);
 }
 
+static int unexpected(int found, int wanted, const char *wantedstr) {
+  /* pass wanted==-1 if you know it's not what you wanted;
+   * otherwise this function will check it for you.
+   */
+  if (found == wanted) return 0;
+  if (found == tokv_error) return found;
+  parseerrprint("found %s, expected %s",printtoken(found),wantedstr);
+  return tokv_error;
+}
+
+static int stringoverflow(const char *where) {
+  parseerrprint("string buffer became far too large building %s",where);
+  return tokv_error;
+}
+
+/*
+ * General assistance functions
+ */
+
 static void freecharparray(char **array) {
   char **pp;
 
@@ -146,27 +172,34 @@ static int dequote(char *inplace) {
       }
     }
   }
-  assert(*p); assert(!*++p); return tokv_quotedstring;
+  assert(*p); assert(!*++p);
+  return tokv_quotedstring;
 }
 
 const char *printtoken(int token) {
+  /* Returns pointer to static buffer, overwritten by next call. */
+  static const char keywordfmt[]= "keyword `%s'";
+  static const char operatorfmt[]= "operator `%s'";
+  
   static char buf[250];
   
   char *q;
   const char *p;
-  int i, c;
+  int i, c, l;
   
   if ((token & tokm_repres) == tokr_word) {
-    assert(strlen(yytext)+50<sizeof(buf));
-    sprintf(buf,"word `%s'",yytext);
+    assert(strlen(yytext)+sizeof(keywordfmt)<sizeof(buf));
+    snyprintf(buf,sizeof(buf),keywordfmt,yytext);
     return buf;
   } else if (token & tokt_number) {
-    sprintf(buf,"number %d",lr_min); return buf;
+    snyprintf(buf,sizeof(buf),"number %d",lr_min); return buf;
+  } else if (token & tokt_fdrange && lr_max==-1) {
+    snyprintf(buf,sizeof(buf),"fdrange %d-",lr_min); return buf;
   } else if (token & tokt_fdrange) {
-    sprintf(buf,"fdrange %d..%d",lr_min,lr_max); return buf;
+    snyprintf(buf,sizeof(buf),"fdrange %d-%d",lr_min,lr_max); return buf;
   } else if ((token & tokm_repres) == tokr_punct) {
-    assert(strlen(yytext)+50<sizeof(buf));
-    sprintf(buf,"operator `%s'",yytext);
+    assert(strlen(yytext)+sizeof(operatorfmt)<sizeof(buf));
+    snyprintf(buf,sizeof(buf),operatorfmt,yytext);
     return buf;
   } else if (token & tokt_string) {
     switch (token) {
@@ -175,11 +208,12 @@ const char *printtoken(int token) {
     default: abort();
     }
     strcat(buf," `");
-    i= sizeof(buf)-50; p= yytext; q= buf+strlen(buf);
-    while (i-- >0 && (c= *p++)) {
+    l= strlen(buf); i= sizeof(buf)-l-2; p= yytext; q= buf+l;
+    while ((c= *p++)) {
+      if (i-- <= 0) { q--; strcpy(q-3,"..."); break; }
       if (isspace(c)) c= ' ';
       else if (!isprint(c) || iscntrl(c)) c= '?';
-      *q++= c;
+      else *q++= c;
     }
     strcpy(q,"'");
     return buf;
@@ -196,57 +230,34 @@ const char *printtoken(int token) {
 }
 
 static const char *string2path(const char *in) {
-  static char *p=0;
-  static int pl=0;
+  /* Returned pointers become invalid on next call.
+   * May return 0, having printed an error message.
+   */
+  static char *p;
+  static int pl;
   
   if (strncmp(in,"~/",2)) return in;
-  makeroom(&p,&pl,strlen(serviceuser_dir)+1+strlen(in+2)+1);
+  if (makeroom(&p,&pl,strlen(serviceuser_dir)+1+strlen(in+2)+1)) {
+    stringoverflow("pathname");
+    return 0;
+  }
   snyprintf(p,pl,"%s/%s",serviceuser_dir,in+2);
   return p;
 }
 
-static void parser_push(struct parser_state *usestate,
-                       const char *newfile,
-                       YY_BUFFER_STATE ybuf,
-                       int isinternal) {
-  usestate->lineno= 1;
-  usestate->reportlineno= 1;
-  usestate->filename= newfile;
-  usestate->notedreferer= 0;
-  usestate->isinternal= isinternal;
-  usestate->ybuf= ybuf;
-  usestate->upstate= cstate;
-
-  cstate= usestate;
-  yy_switch_to_buffer(ybuf);
-}
-
-static void parser_pop(void) {
-  struct parser_state *oldstate;
-
-  oldstate= cstate;
-  cstate= cstate->upstate;
-  if (cstate) yy_switch_to_buffer(cstate->ybuf);
-  yy_delete_buffer(oldstate->ybuf);
-}
-
-/* parser component functions pa_ return tokv_error or 0,
- *  having scanned exactly what they were expecting
- * complete argument list parsers paa_ return tokv_error or 0,
- *  having scanned up to and including the newline
+/*
+ * Parser component functions for parsing parameters to directives
+ *
+ * Functions pa_... parse just one parameter,
+ *  and return tokv_error or 0, having scanned exactly what they were expecting
+ * Functions paa_... parse complete parameter lists, including the leading lwsp,
+ *  and return tokv_error or 0, having scanned up to and including the newline
+ *
+ * For any function which takes `const char **rv' the
+ * value returned in *rv is overwritten by repeated calls to
+ * any of those functions (whether the same or another).
  */
 
-static int unexpected(int found, int wanted, const char *wantedstr) {
-  /* pass wanted==-1 if you know it's not what you wanted;
-   * otherwise this function will check it for you.
-   */
-  if (found == wanted) return 0;
-  if (found != tokv_error) {
-    parseerrprint("found %s, expected %s",printtoken(found),wantedstr);
-  }
-  return tokv_error;
-}
-
 static int pa_mnl(void) {
   return unexpected(yylex(),tokv_newline,"newline");
 }
@@ -254,16 +265,7 @@ static int pa_mwsp(void) {
   return unexpected(yylex(),tokv_lwsp,"linear whitespace");
 }
 
-static void parm_1string(char ***rvalues, const char *tocopy) {
-  char **a;
-  a= xmalloc(sizeof(char*)*2);
-  a[0]= xstrsave(tocopy);
-  a[1]= 0;
-  *rvalues= a;
-}
-
 static int pa_string(const char **rv) {
-  /* Value returned in *rv is overwritten by repeated calls */
   static char *p= 0;
   static int pl= 0;
 
@@ -273,14 +275,24 @@ static int pa_string(const char **rv) {
   r= yylex(); if (r == tokv_error) return r;
   if ((r & tokm_repres) == tokr_nonstring) return unexpected(r,-1,"string");
   l= strlen(yytext)+1;
-  makeroom(&p,&pl,l);
+  if (makeroom(&p,&pl,l)) return stringoverflow("string argument");
   strcpy(p,yytext); *rv= p;
 
   return 0;
 }  
 
+static int pa_numberdollar(int *value_r) {
+  /* Also parses the whitespace beforehand. */
+  int r;
+
+  r= pa_mwsp(); if (r) return r;
+  r= yylex(); if (r == tokv_error || r == tokv_dollar) return r;
+  if (unexpected(r,tokv_ordinal,"expected number or dollar")) return tokv_error;
+  *value_r= lr_min;
+  return r;
+}
+
 static int paa_1string(const char **rv) {
-  /* Value returned in *rv is overwritten by repeated calls */
   int r;
 
   r= pa_string(rv); if (r) return r;
@@ -288,112 +300,95 @@ static int paa_1string(const char **rv) {
 }  
 
 static int paa_1path(const char **rv) {
-  /* Value returned in *rv is overwritten by repeated calls */
   const char *cp;
   int r;
   
   r= paa_1string(&cp); if (r) return r;
-  *rv= string2path(cp); return 0;
+  *rv= string2path(cp); if (!*rv) return tokv_error;
+  return 0;
 }
 
-static int pa_parameter(char ***rvalues, char **rname) {
-  /* Scans a single parameter token and calls the appropriate parameter
-   * function, returning tokv_error or 0 just like the parameter function.
-   * If rname is non-null then the name of the parameter (malloc'd) will
-   * be stored in it.
+static int paa_pathargs(const char **path_r, char ***newargs_r) {
+  /* Repeated calls do _not_ overwrite newargs_r; caller must free.
+   * Repeated calls _do_ overwrite string returned in path_r.
+   * Any call to this function invalidates previous returns in *rv.
    */
-  int token, r, i;
-  char *name;
+  char **newargs;
+  const char *string;
+  int used, size, r;
 
-  token= yylex(); if (token == tokv_error) return token;
-  name= xstrsave(yytext);
-  if ((token & tokm_repres) != tokr_nonstring &&
-      !memcmp(yytext,"u-",2) && strlen(yytext)>=3) {
-    for (i=0;
-        i<request_mbuf.nvars && strcmp(yytext+2,defvararray[i].key);
-        i++);
-    if (i>=request_mbuf.nvars) {
-      *rvalues= xmalloc(sizeof(char*));
-      **rvalues= 0;
-    } else {
-      parm_1string(rvalues,defvararray[i].value);
+  r= pa_string(&string); if (r == tokv_error) return r;
+  *path_r= string2path(string); if (!*path_r) return tokv_error;
+  
+  used=0; size=0;
+  newargs= xmalloc(sizeof(char*)*(size+1));
+
+  for (;;) {
+    r= yylex(); if (r == tokv_error) goto error;
+    if (r==tokv_newline) break;
+    if (unexpected(r,tokv_lwsp,"newline after or whitespace between arguments")) {
+      r= tokv_error; goto error;
     }
-  } else {
-    if (!(token & tokt_parameter)) {
-      free(name);
-      return unexpected(token,-1,"parameter name");
+    r= yylex(); if (r==tokv_error) goto error;
+    if ((r & tokm_repres) == tokr_nonstring) {
+      r= unexpected(r,-1,"string for command argument");
+      goto error;
     }
-    r= (lr_parameter)(token,rvalues);
-    if (r) { free(name); return r; }
+    if (used>=size) {
+      if (used >= MAX_ARGSDEFVAR) {
+       parseerrprint("far too many arguments to service program");
+       r= tokv_error; goto error;
+      }
+      size= (used+5)<<1;
+      newargs= xrealloc(newargs,sizeof(char*)*(size+1));
+    }
+    newargs[used++]= xstrsave(yytext);
   }
-  debug_dumpparameter(name,*rvalues);
-  if (rname) *rname= name;
-  else free(name);
+  newargs[used]= 0;
+  *newargs_r= newargs;
   return 0;
+  
+error:
+  newargs[used]=0;
+  freecharparray(newargs);
+  return r;
 }
 
-static int pa_condition(int *rtrue) {
-  /* Scans up to and including the newline following the condition;
-   * may scan more than one line if the condition is a multi-line
-   * one.  Returns 0 (setting *rtrue) or tokv_error.  Expects to
-   * scan the whitespace before its condition.
-   */
-  int r, token, andor, ctrue, actrue;
-  char **parmvalues;
+static int paa_message(const char **message_r) {
+  /* Returned value is invalidated by repeated calls. */
+  static char *buildbuf;
+  static int buildbuflen;
+
+  int r, tl;
 
   r= pa_mwsp(); if (r) return r;
-  token= yylex();
-  if (token == tokv_error) {
-    return token;
-  } else if (token == tokv_not) {
-    r= pa_condition(&ctrue); if (r) return r;
-    *rtrue= !ctrue; return 0;
-  } else if (token == tokv_openparen) {
-    andor= 0; actrue= -1;
-    for (;;) {
-      cstate->reportlineno= cstate->lineno;
-      r= pa_condition(&ctrue); if (r) return r;
-      switch (andor) {
-      case 0:         assert(actrue==-1); actrue= ctrue;           break;
-      case tokv_and:  assert(actrue>=0); actrue= actrue && ctrue;  break;
-      case tokv_or:   assert(actrue>=0); actrue= actrue || ctrue;  break;
-      default:        abort();
-      }
-      do { token= yylex(); } while (token == tokv_lwsp);
-      if (token == tokv_error) return token;
-      if (token == tokv_closeparen) break;
-      if (andor) {
-       r= unexpected(token,andor,"same conjunction as before"); if (r) return r;
-      } else {
-        if (token != tokv_and && token != tokv_or)
-          return unexpected(token,-1,"first conjunction inside connective");
-        andor= token;
-      }
+  tl= 1;
+  if (makeroom(&buildbuf,&buildbuflen,50)) return stringoverflow("start of message");
+  buildbuf[0]= 0;
+  for (;;) {
+    r= yylex(); if (r == tokv_error) return r;
+    if (r == tokv_eof) {
+      parseerrprint("unexpected end of file in message text");
+      return tokv_error;
     }
-    r= pa_mnl(); if (r) return r;
-    *rtrue= actrue; return 0;
-  } else if (token & tokt_parmcondition) {
-    r= pa_mwsp(); if (r) return r;
-    r= pa_parameter(&parmvalues,0); if (r) return r;
-    r= (lr_parmcond)(token,parmvalues,rtrue); freecharparray(parmvalues);
-    return r;
+    if (r == tokv_newline) break;
+    tl+= strlen(yytext);
+    if (makeroom(&buildbuf,&buildbuflen,tl)) return stringoverflow("message");
+    strcat(buildbuf,yytext);
   }
-  return unexpected(token,-1,"condition");
-}
-
-static int pa_numberdollar(int *rv) {
-  /* Also parses the whitespace beforehand. */
-  int r;
-
-  r= pa_mwsp(); if (r) return r;
-  r= yylex(); if (r == tokv_error || r == tokv_dollar) return r;
-  if (unexpected(r,tokv_ordinal,"expected number or dollar")) return tokv_error;
-  *rv= lr_min; return r;
+  *message_r= buildbuf;
+  return 0;
 }
 
-/* Main parser routines */
+/*
+ * Skipping routines (used by e.g. the `if' code).
+ */
 
 static int skiptoeol(void) {
+  /* Returns 0 if OK, having just parsed the newline
+   * or tokv_error if an error occurs, leaving the position at the error
+   * (which may be EOF or a syntax error token).
+   */
   int token;
 
   do { token= yylex(); }
@@ -406,9 +401,11 @@ static int skiptoeol(void) {
 }
 
 static int skip(int allowce) {
-  /* Scans a piece of config without executing it.
-   * Returns tokv_error, or the tokt_controlend token
-   * with type allowce if one was found.
+  /* Scans a piece of config without executing it.  Returns
+   * tokv_error (leaving the parsing state at the error),
+   * or the tokt_controlend token with type allowce if one
+   * was found (in which case rest of line, including any whitespace
+   * after tokt_controlend, has not been parsed), or tokv_eof.
    */
   int token, r;
 
@@ -419,7 +416,7 @@ static int skip(int allowce) {
       return token;
     } else if (token & tokt_controlend) {
       if (allowce == lr_controlend) return token;
-      return unexpected(token,-1,"control structure end of a different kind");
+      else return unexpected(token,-1,"control structure end of a different kind");
     } else if (token & tokt_controlstart) {
       r= token;
       while (r & tokt_controlstart) {
@@ -435,100 +432,130 @@ static int skip(int allowce) {
     r= skiptoeol(); if (r) return r;
   }
 }
-      
-static int parser(int allowce) {
-  /* Returns:
-   *  an exception (error, eof or quit)
-   *   then rest of `file' is uninteresting
-   * or
-   *  token if allowce was !0 and equal to token's controlend
-   *   then rest of `file' not scanned yet
-   */
-  int token, r;
 
-  for (;;) { /* loop over lines */
-    cstate->reportlineno= cstate->lineno;
-    do { token= yylex(); } while (token == tokv_lwsp);
-    if (token & tokt_exception) {
-      return token;
-    } else if (token & tokt_controlend) {
-      if (lr_controlend == allowce) return token;
-      return unexpected(token,-1,"directive (not this kind of control structure end)");
-    } else if (token & tokt_directive) {
-      r= (lr_dir)(token); if (r) { assert(r & tokt_exception); return r; }
-    } else if (token == tokv_newline) {
-      /* ignore blank links (and comment-only lines) */
-    } else {
-      return unexpected(token,-1,"directive");
-    }
-  }
-}
+/*
+ * Routines for parsing and getting the values of parameters
+ */
 
-int parse_string(const char *string, const char *descrip, int isinternal) {
-  /* Returns the same things as parser, except that tokv_eof
-   * is turned into 0. */
-  struct parser_state usestate;
-  YY_BUFFER_STATE ybuf;
-  int r;
+static void parm_1string(char ***rvalues, const char *tocopy) {
+  char **a;
+  a= xmalloc(sizeof(char*)*2);
+  a[0]= xstrsave(tocopy);
+  a[1]= 0;
+  *rvalues= a;
+}
 
-  ybuf= yy_scan_string(string);
-  if (!ybuf) syscallerror("unable to create flex buffer for internal string");
-  parser_push(&usestate,descrip,ybuf,isinternal);
-  
-  r= parser(0);
+static char *parm_ulong(unsigned long ul) {
+  char *p;
+  int l;
 
-  parser_pop();
-  if (r == tokv_eof) r= 0;
-  return r;
+  l= CHAR_BIT*sizeof(unsigned long)/3+4;
+  p= xmalloc(l);
+  snyprintf(p,l,"%lu",ul);
+  return p;
 }
 
-static int parse_file(const char *string, int *didexist) {
-  /* Returns the same things as parser, except that tokv_eof
-   * is turned into 0. */
-  static int fileparselevel= 0;
+static int parm_usernameuid(char ***rvalues, const char *name, uid_t id) {
+  char **a;
   
-  struct parser_state usestate;
-  YY_BUFFER_STATE ybuf;
-  int r;
-  FILE *file;
-
-  if (fileparselevel >= MAX_INCLUDE_NEST) {
-    parseerrprint("too many nested levels of included files");
-    return tokv_error;
-  }
-  file= fopen(string,"r"); if (!file) {
-    if (errno == ENOENT) {
-      if (didexist) *didexist= 0;
-      return 0;
-    }
-    parseerrprint("unable to open config file `%s': %s",string,strerror(errno));
-    return tokv_error;
-  }
-  if (didexist) *didexist= 1;
+  a= xmalloc(sizeof(char*)*3);
+  a[0]= xstrsave(name);
+  a[1]= parm_ulong(id);
+  a[2]= 0;
+  *rvalues= a;
+  return 0;
+}
 
-  ybuf= yy_create_buffer(file,YY_BUF_SIZE);
-  if (!ybuf) syscallerror("unable to create flex buffer for file");
-  parser_push(&usestate,string,ybuf,0);
-  fileparselevel++;
+static int parm_groups(char ***rvalues, int size,
+                      gid_t *gids, const char *const *groups) {
+  char **a;
+  int i;
   
-  r= parser(0);
-  if (ferror(file)) {
-    parseerrprint("error reading configuration file `%s'",string);
-    r= tokv_error;
+  if (size >= 2 && gids[0] == gids[1]) { size--; gids++; groups++; }
+  a= xmalloc(sizeof(char*)*(size+1)*2);
+  for (i=0; i<size; i++) {
+    a[i]= xstrsave(groups[i]);
+    a[size+i]= parm_ulong(gids[i]);
   }
-  
-  fileparselevel--;
-  parser_pop();
-  fclose(file);
-  if (r == tokv_eof) r= 0;
-  return r;
+  a[size*2]= 0;
+  *rvalues= a;
+  return 0;
+}  
+
+static int pf_service(int ptoken, char ***rvalues) {
+  parm_1string(rvalues,service); return 0;
+}
+
+static int pf_callinguser(int ptoken, char ***rvalues) {
+  return parm_usernameuid(rvalues,logname,request_mbuf.callinguid);
+}
+
+static int pf_serviceuser(int ptoken, char ***rvalues) {
+  return parm_usernameuid(rvalues,serviceuser,serviceuser_uid);
+}
+
+static int pf_callinggroup(int ptoken, char ***rvalues) {
+  return parm_groups(rvalues,request_mbuf.ngids,calling_gids,calling_groups);
+}
+
+static int pf_servicegroup(int ptoken, char ***rvalues) {
+  return parm_groups(rvalues,service_ngids,service_gids,service_groups);
+}
+
+static int pf_callingusershell(int ptoken, char ***rvalues) {
+  parm_1string(rvalues,callinguser_shell); return 0;
+}
+
+static int pf_serviceusershell(int ptoken, char ***rvalues) {
+  parm_1string(rvalues,serviceuser_shell); return 0;
+}
+
+static int pa_parameter(char ***rvalues, char **rname) {
+  /* Scans a single parameter token and calls the appropriate parameter
+   * function, returning tokv_error (leaving the parser state wherever),
+   * or 0 on success (having just parsed the parameter name).
+   * If rname is non-null then the name of the parameter (malloc'd) will
+   * be stored in it.
+   *
+   * Caller must free *rvalues using freecharparray.
+   */
+  int token, r, i;
+  char *name;
+
+  token= yylex(); if (token == tokv_error) return token;
+  name= xstrsave(yytext);
+  if ((token & tokm_repres) != tokr_nonstring &&
+      !memcmp(yytext,"u-",2) && strlen(yytext)>=3) {
+    for (i=0;
+        i<request_mbuf.nvars && strcmp(yytext+2,defvararray[i].key);
+        i++);
+    if (i>=request_mbuf.nvars) {
+      *rvalues= xmalloc(sizeof(char*));
+      **rvalues= 0;
+    } else {
+      parm_1string(rvalues,defvararray[i].value);
+    }
+  } else if (token & tokt_parameter) {
+    r= (lr_parameter)(token,rvalues);
+    if (r) { free(name); return r; }
+  } else {
+    free(name);
+    return unexpected(token,-1,"parameter name");
+  }
+  debug_dumpparameter(name,*rvalues);
+  if (rname) *rname= name;
+  else free(name);
+  return 0;
 }
 
-/* Parameter-based conditional functions (parmcondition's) */
+/*
+ * Routines for parsing conditions, including
+ * parameter-based conditional functions (parmcondition's).
+ */
 
-int pcf_glob(int ctoken, char **pv, int *rtrue) {
+int pcf_glob(int ctoken, char *const *pv, int *rtrue) {
   int token, actrue, r;
-  char **pp;
+  char *const *pp;
 
   actrue= 0;
   r= pa_mwsp(); if (r) return r;
@@ -546,9 +573,10 @@ int pcf_glob(int ctoken, char **pv, int *rtrue) {
   return 0;
 }
 
-int pcf_range(int ctoken, char **pv, int *rtrue) {
+int pcf_range(int ctoken, char *const *pv, int *rtrue) {
   int mintoken, min, maxtoken, max, r;
-  char **pp, *ep;
+  char *const *pp;
+  char *ep;
   unsigned long v;
 
   r= pa_mwsp(); if (r) return r;
@@ -565,14 +593,16 @@ int pcf_range(int ctoken, char **pv, int *rtrue) {
   *rtrue= 0; return 0;
 }
 
-int pcf_grep(int ctoken, char **pv, int *rtrue) {
+int pcf_grep(int ctoken, char *const *pv, int *rtrue) {
   FILE *file;
   const char *cp;
-  char **pp, *buf, *p;
+  char *const *pp;
+  char *buf, *p;
   int r, maxlen, l, c, actrue, posstrue;
   
   r= paa_1path(&cp); if (r) return r;
-  file= fopen(cp,"r"); if (!file) {
+  file= fopen(cp,"r");
+  if (!file) {
     parseerrprint("unable to open file `%s' for grep: %s",cp,strerror(errno));
     return tokv_error;
   }
@@ -587,7 +617,8 @@ int pcf_grep(int ctoken, char **pv, int *rtrue) {
     if (c=='\n' || c==EOF || isspace(c)) {
       while (p>buf && isspace(p[-1])) --p;
       *p= 0; posstrue= 0;
-      for (pp= pv; *pp; pp++) if (!strcmp(*pp,buf)) { posstrue= 1; break; }
+      for (pp= pv; !posstrue && *pp; pp++)
+       if (!strcmp(*pp,buf)) posstrue= 1;
     } else {
       posstrue= 0;
     }
@@ -605,125 +636,67 @@ int pcf_grep(int ctoken, char **pv, int *rtrue) {
     fclose(file); free(buf); return tokv_error;
   }
   assert(actrue || feof(file));
-  fclose(file); free(buf); *rtrue= actrue; return 0;
+  fclose(file); free(buf); *rtrue= actrue;
+  return 0;
 } 
 
-/* Parameter functions and associated `common code' functions */
-
-int pf_service(int ptoken, char ***rvalues) {
-  parm_1string(rvalues,service); return 0;
-}
-
-static char *parm_ulong(unsigned long ul) {
-  char *p;
-  int l;
-
-  l= CHAR_BIT*sizeof(unsigned long)/3+4;
-  p= xmalloc(l);
-  snyprintf(p,l,"%lu",ul);
-  return p;
-}
-
-static int parm_usernameuid(char ***rvalues, const char *name, uid_t id) {
-  char **a;
-  a= xmalloc(sizeof(char*)*3);
-  a[0]= xstrsave(name);
-  a[1]= parm_ulong(id);
-  a[2]= 0;
-  *rvalues= a; return 0;
-}
-
-int pf_callinguser(int ptoken, char ***rvalues) {
-  return parm_usernameuid(rvalues,logname,request_mbuf.callinguid);
-}
-
-int pf_serviceuser(int ptoken, char ***rvalues) {
-  return parm_usernameuid(rvalues,serviceuser,serviceuser_uid);
-}
-
-static char *parm_gidname(gid_t id) {
-  static char ebuf[200];
-
-  struct group *ge;
-
-  ge= getgrgid(id);
-  if (!ge) {
-    sprintf(ebuf,"look up group with id %lu",(unsigned long)id);
-    syscallerror(ebuf);
-  }
-  return xstrsave(ge->gr_name);
-}
-
-static int parm_gids(char ***rvalues, int size, gid_t *list) {
-  char **a;
-  int i;
-  
-  if (size >= 2 && list[0] == list[1]) { size--; list++; }
-  a= xmalloc(sizeof(char*)*(size+1)*2);
-  for (i=0; i<size; i++) {
-    a[i]= parm_gidname(list[i]);
-    a[size+i]= parm_ulong(list[i]);
-  }
-  a[size*2]= 0;
-  *rvalues= a; return 0;
-}  
-
-int pf_callinggroup(int ptoken, char ***rvalues) {
-  return parm_gids(rvalues,request_mbuf.ngids,calling_gids);
-}
-
-int pf_servicegroup(int ptoken, char ***rvalues) {
-  return parm_gids(rvalues,service_ngids,service_gids);
-}
-
-int pf_callingusershell(int ptoken, char ***rvalues) {
-  parm_1string(rvalues,callinguser_shell); return 0;
-}
-
-int pf_serviceusershell(int ptoken, char ***rvalues) {
-  parm_1string(rvalues,serviceuser_shell); return 0;
-}
-
-/* Directive functions and associated `common code' functions */
-
-static int paa_pathargs(const char **rv, char ***newargs_r) {
-  /* Repeated calls do _not_ overwrite newargs_r; caller must free.
-   * Repeated calls _do_ overwrite string returned in rv.
+static int pa_condition(int *rtrue) {
+  /* Scans up to and including the newline following the condition;
+   * may scan more than one line if the condition is a multi-line
+   * one.  Returns 0 (setting *rtrue, and parsing up to and including
+   * the last newline) or tokv_error.
+   * Expects to scan the whitespace before its condition.
    */
-  char **newargs;
-  int used, size, r;
+  int r, token, andor, ctrue, actrue;
+  char **parmvalues;
 
-  used=0; size=0;
-  newargs= xmalloc(sizeof(char*)*(size+1));
-  r= pa_string(rv); if (r == tokv_error) return r;
-  for (;;) {
-    r= yylex(); if (r == tokv_error) goto error;
-    if (r==tokv_newline) break;
-    if (unexpected(r,tokv_lwsp,"newline after or whitespace between arguments")) {
-      r= tokv_error;
-      goto error;
-    }
-    r= yylex(); if (r==tokv_error) goto error;
-    if ((r & tokm_repres) == tokr_nonstring) {
-      r= unexpected(r,-1,"string for command argument");
-      goto error;
-    }
-    if (used>=size) {
-      size= (used+5)<<2;
-      newargs= xrealloc(newargs,sizeof(char*)*(size+1));
+  r= pa_mwsp(); if (r) return r;
+  token= yylex();
+  if (token == tokv_error) {
+    return token;
+  } else if (token == tokv_not) {
+    r= pa_condition(&ctrue); if (r) return r;
+    *rtrue= !ctrue; return 0;
+  } else if (token == tokv_openparen) {
+    andor= 0; actrue= -1;
+    for (;;) {
+      cstate->reportlineno= cstate->lineno;
+      r= pa_condition(&ctrue); if (r) return r;
+      switch (andor) {
+      case 0:         assert(actrue==-1); actrue= ctrue;           break;
+      case tokv_and:  assert(actrue>=0); actrue= actrue && ctrue;  break;
+      case tokv_or:   assert(actrue>=0); actrue= actrue || ctrue;  break;
+      default:        abort();
+      }
+      do { token= yylex(); } while (token == tokv_lwsp);
+      if (token == tokv_error) return token;
+      if (token == tokv_closeparen) break;
+      if (andor) {
+       r= unexpected(token,andor,"same conjunction as before"); if (r) return r;
+      } else {
+        if (token != tokv_and && token != tokv_or)
+          return unexpected(token,-1,"first conjunction inside connective");
+        andor= token;
+      }
     }
-    newargs[used++]= xstrsave(yytext);
+    r= pa_mnl(); if (r) return r;
+    *rtrue= actrue; return 0;
+  } else if (token & tokt_parmcondition) {
+    r= pa_mwsp(); if (r) return r;
+    r= pa_parameter(&parmvalues,0); if (r) return r;
+    r= (lr_parmcond)(token,parmvalues,rtrue); freecharparray(parmvalues);
+    return r;
+  } else {
+    return unexpected(token,-1,"condition");
   }
-  newargs[used]= 0;
-  *newargs_r= newargs;
-  return 0;
-  
-error:
-  newargs[used]=0;
-  freecharparray(newargs);
-  return r;
 }
 
+/*
+ * Directive functions and associated `common code' functions
+ */
+
+/* Directives specifying the service program (execute-... and reset) */
+
 static void execreset(void) {
   execute= 0;
   execbuiltin= 0;
@@ -797,7 +770,7 @@ int df_executefromdirectory(int dtoken) {
   }
   if (!S_ISREG(stab.st_mode)) {
     parseerrprint("file `%s' in execute-from-directory is not an ordinary file"
-                 " or link to one (mode=0%o)",fn,stab.st_mode);
+                 " or link to one (mode=0%lo)",fn,(unsigned long)stab.st_mode);
     free(fn); freecharparray(newargs); return tokv_error;
   }
   execreset();
@@ -807,6 +780,8 @@ int df_executefromdirectory(int dtoken) {
   return 0;
 }
 
+/* Parsing builtin service requests (execute-builtin) */
+
 static int bispa_none(char ***rnewargs) {
   return pa_mnl();
 }
@@ -848,110 +823,145 @@ int df_executebuiltin(int dtoken) {
   return 0;
 }
 
-int df_errorstostderr(int dtoken) {
+/* Directives for changing other execution parameters */
+
+int dfg_setflag(int dtoken) {
   int r;
-  
+
   r= pa_mnl(); if (r) return r;
-  closeerrorfile(); eh.handling= dtoken; return 0;
+  *lr_flag= lr_flagval;
+  return 0;
 }
 
-int df_errorstosyslog(int dtoken) {
-  int token, level, facility;
+int df_reset(int dtoken) {
+  int r;
 
-  facility= DEFUSERLOGFACILITY;
-  level= DEFUSERLOGLEVEL;
-  token= yylex();
-  if (token == tokv_lwsp) {
-    token= yylex(); if (token == tokv_error) return token;
-    if (!(token & tokt_logfacility))
-      return unexpected(token,-1,"syslog facility (or end of line)");
-    facility= lr_logfacility;
-    token= yylex();
-  }    
-  if (token == tokv_lwsp) {
-    token= yylex(); if (token == tokv_error) return token;
-    if (!(token & tokt_loglevel))
-      return unexpected(token,-1,"syslog level (or end of line) after facility");
-    level= lr_loglevel;
-    token= yylex();
-  }
-  if (unexpected(token,tokv_newline,"end of line somewhere after errors-to-syslog"))
-    return tokv_error;
-  closeerrorfile(); eh.handling= tokv_word_errorstosyslog;
-  eh.logfacility= facility; eh.loglevel= level; return 0;
+  r= pa_mnl(); if (r) return r;
+  r= parse_string(RESET_CONFIGURATION,"<builtin reset configuration>",1);
+  assert(!r);
+  return 0;  
 }
 
-int df_errorstofile(int dtoken) {
-  const char *cp;
-  FILE *file;
-  int r;
-  
-  r= paa_1path(&cp); if (r) return r;
-  file= fopen(cp,"a");
-  if (!file) {
-    parseerrprint("unable to open error log file `%s': %s",cp,strerror(errno));
+int dfg_fdwant(int dtoken) {
+  int fdmin, fdmax, r, needreadwrite, havereadwrite, fd;
+
+  needreadwrite= lr_fdwant_readwrite;
+  r= pa_mwsp(); if (r) return r;
+  r= yylex(); if (r == tokv_error) return r;
+  if (!(r & tokt_fdrange)) return unexpected(r,-1,"file descriptor range");
+  fdmin= lr_min; fdmax= lr_max;
+  if (fdmin<0 || fdmin>MAX_ALLOW_FD ||
+      (fdmax != -1 && fdmax<0) || fdmax>MAX_ALLOW_FD) {
+    parseerrprint("file descriptor in range is negative or far too large");
     return tokv_error;
   }
-  if (setvbuf(file,0,_IOLBF,MAX_ERRMSG_LEN)) {
-    parseerrprint("unable to set line buffering on errors file: %s",strerror(errno));
-    fclose(file); return tokv_error;
+  r= yylex(); if (r == tokv_error) return r;
+  if (r == tokv_newline) {
+    if (needreadwrite > 0) {
+      parseerrprint("read or write is required");
+      return tokv_error;
+    }
+    havereadwrite= 0;
+  } else if (r == tokv_lwsp) {
+    if (needreadwrite < 0) {
+      parseerrprint("read or write not allowed"); return tokv_error;
+    }
+    r= yylex(); if (r == tokv_error) return r;
+    if (!(r & tokt_readwrite))
+      return unexpected(r,-1,"read or write (or perhaps newline)");
+    havereadwrite= r;
+    r= pa_mnl(); if (r) return r;
+  } else {
+    return unexpected(r,-1,"whitespace before read or write or newline");
   }
-  closeerrorfile(); eh.handling= tokv_word_errorstofile;
-  eh.file= file; eh.filename= xstrsave(cp); return 0;
+  ensurefdarray(fdmin);
+  if (fdmax == -1) {
+    if (!(dtoken == tokv_word_rejectfd || dtoken == tokv_word_ignorefd))
+      parseerrprint("unspecified maximum only allowed with reject-fd and ignore-fd");
+    fdmax= fdarrayused-1;
+    restfdwantstate= dtoken;
+    restfdwantrw= havereadwrite;
+  }
+  ensurefdarray(fdmax);
+  for (fd=fdmin; fd<=fdmax; fd++) {
+    fdarray[fd].wantstate= dtoken;
+    fdarray[fd].wantrw= havereadwrite;
+  }
+  return 0;
 }
 
-int dfg_setflag(int dtoken) {
-  int r;
-
-  r= pa_mnl(); if (r) return r;
-  *lr_flag= lr_flagval; return 0;
-}
+/* Directives for changing error handling */
 
-int df_reset(int dtoken) {
+int df_errorstostderr(int dtoken) {
   int r;
-
+  
   r= pa_mnl(); if (r) return r;
-  r= parse_string(RESET_CONFIGURATION,"<builtin reset configuration>",1);
-  assert(!r); return 0;  
+  closeerrorfile(); eh.handling= dtoken;
+  return 0;
 }
 
-int df_cd(int dtoken) {
-  const char *cp;
-  int r;
+int df_errorstosyslog(int dtoken) {
+  int token, level, facility;
 
-  r= paa_1path(&cp); if (r) return r;
-  if (!chdir(cp)) return 0;
-  parseerrprint("unable to change directory to `%s': %s",cp,strerror(errno));
-  return tokv_error;
+  facility= DEFUSERLOGFACILITY;
+  level= DEFUSERLOGLEVEL;
+  token= yylex();
+  if (token == tokv_lwsp) {
+    token= yylex(); if (token == tokv_error) return token;
+    if (!(token & tokt_logfacility))
+      return unexpected(token,-1,"syslog facility (or end of line)");
+    facility= lr_logfacility;
+    token= yylex();
+  }    
+  if (token == tokv_lwsp) {
+    token= yylex(); if (token == tokv_error) return token;
+    if (!(token & tokt_loglevel))
+      return unexpected(token,-1,"syslog level (or end of line) after facility");
+    level= lr_loglevel;
+    token= yylex();
+  }
+  if (unexpected(token,tokv_newline,"end of line after errors-to-syslog"))
+    return tokv_error;
+  closeerrorfile(); eh.handling= tokv_word_errorstosyslog;
+  eh.logfacility= facility; eh.loglevel= level;
+  return 0;
 }
 
-int df_userrcfile(int dtoken) {
+int df_errorstofile(int dtoken) {
   const char *cp;
+  FILE *file;
   int r;
-
+  
   r= paa_1path(&cp); if (r) return r;
-  free(userrcfile); userrcfile= xstrsave(cp);
+  file= fopen(cp,"a");
+  if (!file) {
+    parseerrprint("unable to open error log file `%s': %s",cp,strerror(errno));
+    return tokv_error;
+  }
+  if (setvbuf(file,0,_IOLBF,MAX_ERRMSG_LEN)) {
+    parseerrprint("unable to set line buffering on errors file: %s",strerror(errno));
+    fclose(file); return tokv_error;
+  }
+  closeerrorfile(); eh.handling= tokv_word_errorstofile;
+  eh.file= file; eh.filename= xstrsave(cp);
   return 0;
 }
 
+/* Directives for including other files or configuration data */
+
 int dfi_includeuserrcfile(int dtoken) {
   int r;
 
   r= pa_mnl(); if (r) return r;
-  if (userrcfile) return parse_file(userrcfile,0);
-  parseerrprint("_include-user-rcfile (for-internal-use directive) "
-               "found but user-rcfile not set"); return tokv_error;
+  assert(userrcfile);
+  return parse_file(userrcfile,0);
 }
 
 int dfi_includeclientconfig(int dtoken) {
   int r;
 
   r= pa_mnl(); if (r) return r;
-  if (!overridedata) {
-    parseerrprint("_include-client-config (for-internal-use directive) "
-                 "found but configuration not overridden");
-    return tokv_error;
-  }
+  assert(overridedata);
   return parse_string(overridedata,"<configuration override data>",0);
 }
 
@@ -969,47 +979,6 @@ int df_include(int dtoken) {
   return tokv_error;
 }
 
-static int paa_message(const char **message_r) {
-  static char *buildbuf;
-  static int buildbuflen;
-
-  int r, tl;
-
-  r= pa_mwsp(); if (r) return r;
-  tl= 1;
-  makeroom(&buildbuf,&buildbuflen,10);
-  buildbuf[0]= 0;
-  for (;;) {
-    r= yylex(); if (r == tokv_error) return r;
-    if (r == tokv_eof) {
-      parseerrprint("unexpected end of file in message text"); return tokv_error;
-    }
-    if (r == tokv_newline) break;
-    tl+= strlen(yytext);
-    makeroom(&buildbuf,&buildbuflen,tl);
-    strcat(buildbuf,yytext);
-  }
-  *message_r= buildbuf;
-  return 0;
-}
-
-int df_message(int dtoken) {
-  const char *mp;
-  int r;
-
-  r= paa_message(&mp); if (r) return r;
-  parseerrprint("`message' directive: %s",mp);
-  return 0;
-}
-
-int df_error(int dtoken) {
-  const char *mp;
-  int r;
-
-  r= paa_message(&mp); if (r) return r;
-  parseerrprint("`error' directive: %s",mp); return tokv_error;
-}
-
 int df_includedirectory(int dtoken) {
   static char *buildbuf=0;
   static int buildbuflen=0;
@@ -1030,16 +999,18 @@ int df_includedirectory(int dtoken) {
     tel= strlen(de->d_name);
     if (!tel) continue;
     p= de->d_name;
-    if (!isalnum(*p)) continue;
+    if (!*p || !isalnum(*p)) continue;
     while ((c= *++p)) if (!(isalnum(c) || c=='-')) break;
     if (c) continue;
-    makeroom(&buildbuf,&buildbuflen,cpl+1+tel+1);
+    if (makeroom(&buildbuf,&buildbuflen,cpl+1+tel+1))
+      return stringoverflow("pathname in directory");
     snyprintf(buildbuf,buildbuflen,"%s/%s",cp,de->d_name);
     r= parse_file(buildbuf,&found); if (r) { closedir(d); return r; }
     if (!found) {
       parseerrprint("unable to open file `%s' in included directory `%s': %s",
                    de->d_name,cp,strerror(errno));
-      closedir(d); return tokv_error;
+      closedir(d);
+      return tokv_error;
     }
   }
   if (closedir(d)) {
@@ -1072,7 +1043,8 @@ int df_includelookup(int dtoken) {
   done= 0;
   cpl= strlen(cp);
   if (!parmvalues[0]) {
-    makeroom(&buildbuf,&buildbuflen,cpl+1+sizeof(NONEINCLUDELOOKUP));
+    if (makeroom(&buildbuf,&buildbuflen,cpl+1+sizeof(NONEINCLUDELOOKUP)))
+      return stringoverflow("pathname in directory for lookup of undefined parameter");
     snyprintf(buildbuf,buildbuflen,"%s/" NONEINCLUDELOOKUP,cp);
     r= parse_file(buildbuf,&thisdone);
     if (r) { freecharparray(parmvalues); return r; }
@@ -1081,18 +1053,23 @@ int df_includelookup(int dtoken) {
     for (pp=parmvalues;
         *pp && (!done || dtoken == tokv_word_includelookupall);
         pp++) {
-      makeroom(&buildbuf,&buildbuflen,
-              cpl+1+strlen(*pp)*2+3+sizeof(EMPTYINCLUDELOOKUP)+1);
+      if (makeroom(&buildbuf,&buildbuflen,
+                  cpl+1+strlen(*pp)*2+3+sizeof(EMPTYINCLUDELOOKUP)+1))
+       return stringoverflow("pathname in directory for lookup");
       strcpy(buildbuf,cp);
       p= *pp; q= buildbuf+cpl;
       *q++= '/';
-      if (*p=='.') *q++= ':';
-      while ((c= *p++)) {
-       if (c=='/') { *q++= ':'; c='-'; }
-       else if (c==':') { *q++= ':'; }
-       *q++= c;
+      if (!*p) {
+       strcpy(q,EMPTYINCLUDELOOKUP);
+      } else {
+       if (*p=='.') *q++= ':';
+       while ((c= *p++)) {
+         if (c=='/') { *q++= ':'; c='-'; }
+         else if (c==':') { *q++= ':'; }
+         *q++= c;
+       }
+       *q++= 0;
       }
-      *q++= 0;
       r= parse_file(buildbuf,&thisdone);
       if (r) { freecharparray(parmvalues); return r; }
       if (thisdone) done= 1;
@@ -1100,33 +1077,33 @@ int df_includelookup(int dtoken) {
   }
   freecharparray(parmvalues);
   if (!done) {
-    makeroom(&buildbuf,&buildbuflen,
-             cpl+1+sizeof(DEFAULTINCLUDELOOKUP));
+    if (makeroom(&buildbuf,&buildbuflen,
+                cpl+1+sizeof(DEFAULTINCLUDELOOKUP)))
+      return stringoverflow("pathname in directory for lookup of default");
     snyprintf(buildbuf,buildbuflen,"%s/" DEFAULTINCLUDELOOKUP,cp);
     r= parse_file(buildbuf,0); if (r) return r;
   }
   return 0;
 }
 
+/* Control constructs */
+
 int df_catchquit(int dtoken) {
   int r;
 
   r= pa_mnl(); if (r) return r;
   r= parser(tokv_word_catchquit);
-  if (r & tokt_controlend) {
-    assert(r == tokv_word_hctac);
-    r= pa_mnl();
-  } else if (r == tokv_quit || r == tokv_error) {
+  if (r == tokv_quit || r == tokv_error) {
     if (r == tokv_error) {
       r= parse_string(RESET_CONFIGURATION,
                      "<builtin reset configuration (caught error)>",1);
       assert(!r);
     }
     r= skip(tokv_word_catchquit);
-    if (r & tokt_controlend) {
-      assert(r == tokv_word_hctac);
-      r= pa_mnl();
-    }
+  }
+  if (r & tokt_controlend) {
+    assert(r == tokv_word_hctac);
+    r= pa_mnl();
   }
   return r;
 }
@@ -1139,65 +1116,77 @@ int df_if(int dtoken) {
     r= pa_condition(&true); if (r) return r;
     if (!done && true) { r= parser(tokv_word_if); done= 1; }
     else { r= skip(tokv_word_if); }
-    if (!(r & tokv_word_if)) return r;
+    if (!(r & tokt_controlend)) return r;
   } while (r == tokv_word_elif);
   if (r == tokv_word_else) {
     r= pa_mnl(); if (r) return r;
     cstate->reportlineno= cstate->lineno;
     if (done) r= skip(tokv_word_if);
     else r= parser(tokv_word_if);
-    if (!(r & tokv_word_if)) return r;
+    if (!(r & tokt_controlend)) return r;
   }
   if (unexpected(r,tokv_word_fi,"`fi' to end `if'")) return tokv_error;
   return pa_mnl();
 }
 
-int dfg_fdwant(int dtoken) {
-  int fdmin, fdmax, r, needreadwrite, havereadwrite, fd;
+int df_errorspush(int dt) {
+  struct error_handling save;
+  int r;
 
-  needreadwrite= lr_fdwant_readwrite;
-  r= pa_mwsp(); if (r) return r;
-  r= yylex(); if (r == tokv_error) return r;
-  if (!(r & tokt_fdrange)) return r;
-  fdmin= lr_min; fdmax= lr_max;
-  r= yylex(); if (r == tokv_error) return r;
-  if (r == tokv_newline) {
-    if (needreadwrite > 0) {
-      parseerrprint("read or write is required"); return tokv_error;
-    }
-    havereadwrite= 0;
-  } else if (r == tokv_lwsp) {
-    if (needreadwrite < 0) {
-      parseerrprint("read or write not allowed"); return tokv_error;
-    }
-    r= yylex(); if (r == tokv_error) return r;
-    if (!(r & tokt_readwrite))
-      return unexpected(r,-1,"read or write (or perhaps newline)");
-    havereadwrite= r;
-    r= pa_mnl(); if (r) return r;
-  } else {
-    return unexpected(r,-1,"whitespace before read or write or newline");
-  }
-  ensurefdarray(fdmin);
-  if (fdmax == -1) {
-    if (!(dtoken == tokv_word_rejectfd || dtoken == tokv_word_ignorefd))
-      parseerrprint("unspecified maximum only allowed with reject-fd and ignore-fd");
-    fdmax= fdarrayused-1;
-    restfdwantstate= dtoken;
-    restfdwantrw= havereadwrite;
-  }    
-  for (fd=fdmin; fd<=fdmax; fd++) {
-    fdarray[fd].wantstate= dtoken;
-    fdarray[fd].wantrw= havereadwrite;
+  r= pa_mnl(); if (r) return r;
+
+  save= eh;
+  eh.filekeep= 1;
+
+  r= parser(tokv_word_errorspush);
+
+  closeerrorfile();
+  eh= save;
+
+  if (r & tokt_controlend) {
+    assert(r == tokv_word_srorre);
+    r= pa_mnl();
   }
+  return r;
+}
+
+/* Miscelleanous directives */
+
+int df_cd(int dtoken) {
+  const char *cp;
+  int r;
+
+  r= paa_1path(&cp); if (r) return r;
+  if (!chdir(cp)) return 0;
+  parseerrprint("unable to change directory to `%s': %s",cp,strerror(errno));
+  return tokv_error;
+}
+
+int df_userrcfile(int dtoken) {
+  const char *cp;
+  int r;
+
+  r= paa_1path(&cp); if (r) return r;
+  free(userrcfile); userrcfile= xstrsave(cp);
   return 0;
 }
 
-int df_quit(int dtoken) {
+int df_message(int dtoken) {
+  const char *mp;
   int r;
 
-  r= pa_mnl(); if (r) return r;
-  return tokv_quit;
+  r= paa_message(&mp); if (r) return r;
+  parseerrprint("`message' directive: %s",mp);
+  return 0;
+}
+
+int df_error(int dtoken) {
+  const char *mp;
+  int r;
+
+  r= paa_message(&mp); if (r) return r;
+  parseerrprint("`error' directive: %s",mp);
+  return tokv_error;
 }
 
 int df_eof(int dtoken) {
@@ -1207,23 +1196,150 @@ int df_eof(int dtoken) {
   return tokv_eof;
 }
 
-int df_errorspush(int dt) {
-  struct error_handling save;
+int df_quit(int dtoken) {
   int r;
 
   r= pa_mnl(); if (r) return r;
+  return tokv_quit;
+}
 
-  save= eh;
-  eh.filekeep= 1;
+/*
+ * Main parser routines
+ */
+      
+static void parser_push(struct parser_state *usestate,
+                       const char *newfile,
+                       const struct stat *newfilestab,
+                       YY_BUFFER_STATE ybuf,
+                       int isinternal) {
+  usestate->lineno= 1;
+  usestate->reportlineno= 1;
+  usestate->filename= newfile;
+  usestate->filestab= *newfilestab;
+  usestate->notedreferer= 0;
+  usestate->isinternal= isinternal;
+  usestate->ybuf= ybuf;
+  usestate->upstate= cstate;
 
-  r= parser(tokv_word_errorspush);
+  cstate= usestate;
+  yy_switch_to_buffer(ybuf);
+}
 
-  closeerrorfile();
-  eh= save;
+static void parser_pop(void) {
+  struct parser_state *oldstate;
 
-  if (r & tokt_controlend) {
-    assert(r == tokv_word_srorre);
-    r= pa_mnl();
+  oldstate= cstate;
+  cstate= cstate->upstate;
+  if (cstate) yy_switch_to_buffer(cstate->ybuf);
+  yy_delete_buffer(oldstate->ybuf);
+}
+
+int parse_string(const char *string, const char *descrip, int isinternal) {
+  /* Returns the same things as parser, except that tokv_eof
+   * is turned into 0.
+   */
+  static const struct stat blankstab;
+  
+  struct parser_state usestate;
+  YY_BUFFER_STATE ybuf;
+  int r;
+
+  ybuf= yy_scan_string(string);
+  if (!ybuf) syscallerror("unable to create flex buffer for internal string");
+  parser_push(&usestate,descrip,&blankstab,ybuf,isinternal);
+  
+  r= parser(0);
+
+  parser_pop();
+  if (r == tokv_eof) r= 0;
+  return r;
+}
+
+static int parse_file(const char *string, int *didexist) {
+  /* Returns the same things as parser, except that tokv_eof
+   * is turned into 0.  If *didexist is 0 then errno will
+   * have been set.
+   */
+  static int fileparselevel= 0;
+  
+  struct parser_state usestate, *checkrecurse;
+  YY_BUFFER_STATE ybuf;
+  int r;
+  FILE *file;
+  struct stat newstab;
+
+  if (fileparselevel >= MAX_INCLUDE_NEST) {
+    parseerrprint("too many nested levels of included files");
+    return tokv_error;
   }
+  file= fopen(string,"r");
+  if (!file) {
+    if (errno == ENOENT) {
+      if (didexist) *didexist= 0;
+      return 0;
+    }
+    parseerrprint("unable to open config file `%s': %s",string,strerror(errno));
+    return tokv_error;
+  }
+  r= fstat(fileno(file),&newstab); if (r) syscallerror("unable to fstat new file");
+  for (checkrecurse= cstate; checkrecurse; checkrecurse= checkrecurse->upstate) {
+    if (!checkrecurse->filestab.st_mode) continue;
+    if (newstab.st_dev==checkrecurse->filestab.st_dev &&
+       newstab.st_ino==checkrecurse->filestab.st_ino) {
+      parseerrprint("recursion detected - config file `%s' calls itself",string);
+      fclose(file);
+      return tokv_error;
+    }
+  }
+  
+  if (didexist) *didexist= 1;
+
+  ybuf= yy_create_buffer(file,YY_BUF_SIZE);
+  if (!ybuf) syscallerror("unable to create flex buffer for file");
+  parser_push(&usestate,string,&newstab,ybuf,0);
+  fileparselevel++;
+  
+  r= parser(0);
+  if (ferror(file)) {
+    parseerrprint("error reading configuration file `%s'",string);
+    r= tokv_error;
+  }
+  
+  fileparselevel--;
+  parser_pop();
+  fclose(file);
+  if (r == tokv_eof) r= 0;
   return r;
 }
+
+static int parser(int allowce) {
+  /* Returns:
+   *  an exception (error, eof or quit)
+   *   then rest of `file' is uninteresting
+   * or
+   *  token if allowce was !0 and equal to token's controlend
+   *   then rest of `file' (including rest of line with the
+   *   controlend - even the whitespace) not scanned yet
+   */
+  int token, r;
+
+  for (;;) { /* loop over lines */
+    cstate->reportlineno= cstate->lineno;
+    do { token= yylex(); } while (token == tokv_lwsp);
+    if (token & tokt_exception) {
+      return token;
+    } else if (token & tokt_controlend) {
+      if (lr_controlend == allowce) return token;
+      else return unexpected(token,-1,"directive (not this kind of"
+                            " control structure end)");
+    } else if (token & tokt_directive) {
+      if ((token & tokt_internal) && !cstate->isinternal)
+       return unexpected(token,-1,"published directive, not internal-use-only one");
+      r= (lr_dir)(token); if (r) { assert(r & tokt_exception); return r; }
+    } else if (token == tokv_newline) {
+      /* ignore blank lines (and comment-only lines) */
+    } else {
+      return unexpected(token,-1,"directive");
+    }
+  }
+}
index 109c5010dfd6deacd70e3fb68a2074f81890943e..35eba59ccd115c9091a277637b6b38e422a0f22a 100644 (file)
--- a/process.c
+++ b/process.c
@@ -19,7 +19,8 @@
  * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
-/* We do some horrible asynchronous stuff with signals.
+/*
+ * We do some horrible asynchronous stuff with signals.
  *
  * The following objects &c. are used in signal handlers and so
  * must be protected by calls to blocksignals if they are used in
@@ -178,7 +179,12 @@ static void getevent(struct event_msg *event_r) {
     switch (event_r->type) {
     case et_closereadfd:
       fd= event_r->data.closereadfd.fd;
-      assert(fd<fdarrayused);
+      if (fd >= fdarrayused) {
+       blocksignals();
+       syslog(LOG_ERR,"client sent bad file descriptor %d to close (max %d)",
+              fd,fdarrayused-1);
+       disconnect(12);
+      }
       if (fdarray[fd].holdfd!=-1) {
        if (close(fdarray[fd].holdfd)) syscallerror("cannot close holding fd");
        fdarray[fd].holdfd= -1;
@@ -358,20 +364,6 @@ void senderrmsgstderr(const char *errmsg) {
   xfflush(swfile);
 }
 
-static void getgroupnames(int ngids, gid_t *list, const char ***names_r) {
-  const char **names;
-  struct group *cgrp;
-  int i;
-  
-  names= xmalloc(sizeof(char*)*ngids);
-  for (i=0; i<ngids; i++) {
-    cgrp= getgrgid(list[i]);
-    if (!cgrp) miscerror("get group entry");
-    names[i]= xstrsave(cgrp->gr_name);
-  }
-  *names_r= names;
-}
-  
 /* The per-request main program and its subfunctions. */
 
 static void setup_comms(int sfd) {
@@ -484,6 +476,20 @@ static void establish_pipes(void) {
   }
 }
 
+static void groupnames(int ngids, gid_t *gids, const char ***names_r) {
+  const char **names;
+  struct group *gr;
+  int i;
+  
+  names= xmalloc(sizeof(char*)*ngids);
+  for (i=0; i<ngids; i++) {
+    gr= getgrgid(gids[i]);
+    if (!gr) miscerror("get group entry");
+    names[i]= xstrsave(gr->gr_name);
+  }
+  *names_r= names;
+}
+  
 static void lookup_uidsgids(void) {
   struct passwd *pw;
 
@@ -513,22 +519,55 @@ static void lookup_uidsgids(void) {
   if (getgroups(service_ngids,service_gids+1) != service_ngids)
     syscallerror("getgroups(size,list)");
 
-  getgroupnames(service_ngids,service_gids,&service_groups);
-  getgroupnames(request_mbuf.ngids,calling_gids,&calling_groups);
+  groupnames(request_mbuf.ngids,calling_gids,&calling_groups);
+  groupnames(service_ngids,service_gids,&service_groups);
 }
 
-static void check_find_executable(void) {
-  int r, partsize;
+static void findinpath(char *program) {
   char *part, *exectry;
   const char *string, *delim, *nextstring;
   struct stat stab;
+  int r, partsize;
+  
+  if (strchr(program,'/')) {
+    r= stat(program,&stab);
+    if (r) syscallfailure("failed check for program (containing slash) `%s'",program);
+    execpath= program;
+  } else {
+    string= getenv("PATH");
+    if (!string) string= defaultpath();
+    while (string) {
+      delim= strchr(string,':');
+      if (delim) {
+       if (delim-string > MAX_GENERAL_STRING)
+         failure("execute-from-path, but PATH component too long");
+       partsize= delim-string;
+       nextstring= delim+1;
+      } else {
+       partsize= strlen(string);
+       nextstring= 0;
+      }
+      part= xstrsubsave(string,partsize);
+      exectry= part[0] ? xstrcat3save(part,"/",program) : xstrsave(program);
+      free(part);
+      r= stat(exectry,&stab);
+      if (!r) { execpath= exectry; break; }
+      free(exectry);
+      string= nextstring;
+    }
+    if (!execpath) failure("program `%s' not found on default PATH",program);
+  }
+}
+  
+static void check_find_executable(void) {
+  struct stat stab;
+  int r;
 
   switch (execute) {
   case tokv_word_reject:
     failure("request rejected");
   case tokv_word_execute:
-    r= stat(execpath,&stab);
-    if (r) syscallfailure("checking for executable `%s'",execpath);
+    findinpath(execpath);
     break;
   case tokv_word_executefromdirectory:
     r= stat(execpath,&stab);
@@ -537,41 +576,37 @@ static void check_find_executable(void) {
   case tokv_word_executebuiltin:
     break;
   case tokv_word_executefrompath:
-    if (strchr(service,'/')) {
-      r= stat(service,&stab);
-      if (r) syscallfailure("execute-from-path (contains slash)"
-                           " cannot check for executable `%s'",service);
-      execpath= service;
-    } else {
-      string= getenv("PATH");
-      if (!string) string= defaultpath();
-      while (string) {
-       delim= strchr(string,':');
-       if (delim) {
-         if (delim-string > MAX_GENERAL_STRING)
-           failure("execute-from-path, but PATH component too long");
-         partsize= delim-string;
-         nextstring= delim+1;
-       } else {
-         partsize= strlen(string);
-         nextstring= 0;
-       }
-       part= xstrsubsave(string,partsize);
-       exectry= part[0] ? xstrcat3save(part,"/",service) : xstrsave(service);
-       free(part);
-       r= stat(exectry,&stab);
-       if (!r) { execpath= exectry; break; }
-       free(exectry);
-       string= nextstring;
-      }
-      if (!execpath) failure("execute-from-path, but program `%s' not found",service);
-    }
+    findinpath(service);
     break;
   default:
     abort();
   }
 }
 
+static void makenonexistentfd(int fd) {
+  if (fdarray[fd].realfd == -1) {
+    assert(fdarray[fd].holdfd == -1);
+  } else {
+    if (close(fdarray[fd].realfd))
+      syscallfailure("close unwanted file descriptor %d",fd);
+    fdarray[fd].realfd= -1;
+  
+    if (fdarray[fd].holdfd != -1) {
+      if (close(fdarray[fd].holdfd))
+       syscallfailure("close unwanted hold descriptor for %d",fd);
+    }
+  }
+}
+
+static void makenullfd(int fd) {
+  fdarray[fd].realfd= open("/dev/null",
+                          fdarray[fd].wantrw == tokv_word_read ? O_RDONLY :
+                          fdarray[fd].wantrw == tokv_word_write ? O_WRONLY :
+                          0);
+  if (fdarray[fd].realfd<0)
+    syscallfailure("cannot open /dev/null for null or allowed, unprovided fd");
+}
+
 static void check_fds(void) {
   int fd;
   
@@ -589,35 +624,27 @@ static void check_fds(void) {
        failure("file descriptor %d provided but rejected",fd);
       break;
     case tokv_word_ignorefd:
-      if (fdarray[fd].realfd != -1)
-       if (close(fdarray[fd].realfd))
-         syscallfailure("close unwanted file descriptor %d",fd);
-      fdarray[fd].realfd= -1;
+      makenonexistentfd(fd);
       break;
     case tokv_word_nullfd:
-      if (fdarray[fd].realfd != -1) close(fdarray[fd].realfd);
-      fdarray[fd].realfd= open("/dev/null",
-                              fdarray[fd].iswrite == -1 ? O_RDWR :
-                              fdarray[fd].iswrite ? O_WRONLY : O_RDONLY);
-      if (fdarray[fd].realfd<0)
-       syscallfailure("cannot open /dev/null for null fd");
+      makenonexistentfd(fd);
+      makenullfd(fd);
       break;
     case tokv_word_requirefd:
       if (fdarray[fd].realfd == -1)
-       failure("file descriptor %d not provided but required",fd);
+       failure("file descriptor %d required but not provided",fd);
+      assert(fdarray[fd].holdfd == -1);
       /* fall through */
     case tokv_word_allowfd:
       if (fdarray[fd].realfd == -1) {
-       fdarray[fd].iswrite= (fdarray[fd].wantrw == tokv_word_write);
-       fdarray[fd].realfd= open("/dev/null",fdarray[fd].iswrite ? O_WRONLY : O_RDONLY);
-       if (fdarray[fd].realfd<0)
-         syscallfailure("cannot open /dev/null for allowed but not provided fd");
+       assert(fdarray[fd].holdfd == -1);
+       makenullfd(fd);
       } else {
        if (fdarray[fd].iswrite) {
-         if (fdarray[fd].wantrw != tokv_word_write)
+         if (fdarray[fd].wantrw == tokv_word_read)
            failure("file descriptor %d provided write, wanted read",fd);
        } else {
-         if (fdarray[fd].wantrw != tokv_word_read)
+         if (fdarray[fd].wantrw == tokv_word_write)
            failure("file descriptor %d provided read, wanted write",fd);
        }
       }
@@ -644,6 +671,13 @@ static void fork_service_synch(void) {
   r= socketpair(AF_UNIX,SOCK_STREAM,0,synchsocket);
   if (r) syscallerror("cannot create socket for synch");
 
+  /* Danger here.  Firstly, we start handling signals asynchronously.
+   * Secondly after we fork the service we want it to put
+   * itself in a separate process group so that we can kill it and all
+   * its children - but, we mustn't kill the whole pgrp before it has
+   * done that (or we kill ourselves) and it mustn't fork until it
+   * knows that we are going to kill it the right way ...
+   */
   sig.sa_handler= sighandler_chld;
   sigemptyset(&sig.sa_mask);
   sigaddset(&sig.sa_mask,SIGCHLD);
index 71412a7266f7fd5804b37992dfa1d94a0b389f7b..2b110126abc0fa5fabf93d530eb5c6ff8733c77c 100644 (file)
@@ -68,23 +68,23 @@ void bisexec_version(const char *const *argv) {
         "production version"
 #endif
         " - protocol magic number %08lx\n"
-        "protocol checksum: ",
-        BASE_MAGIC);
-  for (i=0, p=protocolchecksumversion; i<sizeof(protocolchecksumversion); i++, p++)
-    printf("%02x",*p);
-  printf("\n"
-        "rendezvous socket: `" RENDEZVOUSPATH "'\n"
-        "system config dir: `" SYSTEMCONFIGDIR "'\n"
-        "pipe filename format: `%s' (max length %d)\n"
         "maximums:    fd %-10d                general string %d\n"
-        "             gids %-10d              override length %d\n\n"
+        "             gids %-10d              override length %d\n"
         "             args or variables %-10d error message %d\n"
-        "             nested inclusion %-10d  errno string reserve %d\n",
-        PIPEFORMAT, PIPEMAXLEN,
+        "             nested inclusion %-10d  errno string reserve %d\n"
+        "protocol checksum: ",
+        BASE_MAGIC,
         MAX_ALLOW_FD, MAX_GENERAL_STRING,
         MAX_GIDS, MAX_OVERRIDE_LEN,
         MAX_ARGSDEFVAR, ERRMSG_RESERVE_ERRNO,
         MAX_INCLUDE_NEST, MAX_ERRMSG_LEN);
+  for (i=0, p=protocolchecksumversion; i<sizeof(protocolchecksumversion); i++, p++)
+    printf("%02x",*p);
+  printf("\n"
+        "rendezvous socket: `" RENDEZVOUSPATH "'\n"
+        "system config dir: `" SYSTEMCONFIGDIR "'\n"
+        "pipe filename format: `%s' (max length %d)\n",
+        PIPEFORMAT, PIPEMAXLEN);
   serv_checkstdoutexit();
 }
 
index 5fc9ce6348edbe83bd2b17be75d903d76c16d081..8757851a9675f07f8fe533802deec568ddeec70f 100644 (file)
@@ -191,11 +191,10 @@ other words are allowed.  The <var/filename/ may also be <tt/stdin/,
 <p>
 
 If no <var/modifiers/ which imply <tt/read/ or <tt/write/ are used it
-is as if <tt/read/ had been specified, except that if the
-filedescriptor 1 or 2 of the service is being opened (either specified
-numerically or with <tt/stdout/ or <tt/stderr/) it is as if
-<tt/overwrite/ had been specified (or <tt/write/ if only <tt/fd/ was
-specified).
+is as if <tt/write/ had been specified, except that if the
+filedescriptor 0 of the service is being opened (either specified
+numerically or with <tt/stdin/) it is as if <tt/overwrite/ had been
+specified (or <tt/write/ if only <tt/fd/ was specified).
 <p>
 
 The client will also use <tt/O_NOCTTY/ when opening files specified by
@@ -372,7 +371,10 @@ contain one or more real newlines.
 Pretend to the service that it is being called by <var/user/ (which
 may be a username or a uid).  This will also affect the group and
 supplementary groups supplied to the service; they will be the
-standard group and supplementary groups for <var/user/.
+standard group and supplementary groups for <var/user/.  The
+<tt/--spoof-user/ option will <em/not/ affect which user is chosen if
+the service user is specified as just <tt/-/; in this case the service
+user will be the real calling user.
 
 </taglist>
 
@@ -863,13 +865,13 @@ directive which modifies any particuar setting will take effect.
 Reject the request.  <prgn/execute/, <prgn/execute-from-directory/ and
 <prgn/execute-from-path/ will change this setting.
 
-<tag/<tt/execute <var/pathname/ [<var/argument/ ...]//
+<tag/<tt/execute <var/program/ [<var/argument/ ...]//
 <item>
-Execute the program <var/pathname/, with the arguments as specified,
+Execute the program <var/program/, with the arguments as specified,
 followed by any arguments given to the client if
 <prgn/no-suppress-args/ is in effect.  It is an error for the
 execution to fail when it is attempted (after all the configuration
-has been parsed).  If <var/pathname/ does not contain a slash it will
+has been parsed).  If <var/program/ does not contain a slash it will
 be searched for on the service user's path.
 
 <tag/<tt/execute-from-directory <var/pathname/ [<var/argument/ ...]//
@@ -1233,6 +1235,30 @@ and the service.
 <chapt id="notes">Applications and notes on use
 <p>
 
+<sect id="standards">Standard services and directory management
+<p>
+
+In later versions of this specification standard service names and
+interfaces for common services such as mail delivery and WWW CGI
+scripts will be specified.
+<p>
+
+<prgn/userv/-using applications and system services which hide
+<prgn/userv/ behind wrapper scripts may need to store information in
+the user's filespace to preserve the correct placement of the security
+perimiters.  Such applications should usually do so in a directory
+(created by them) <tt>~/.userv/.servdata/<var/service/</>, where
+<var/service/ is the service name or application in question.
+<p>
+
+The use of a dot-directory inside <tt>~/.userv</> will hopefully avoid
+the user becoming confused by finding parts of a semi-privileged
+application's internal state in their filespace, and or discourage
+them from fiddling with and thus corrupting it.  (Note that such
+applications should of course not rely for their global integrity on
+the integrity of the data on the user's side of the security
+boundary.)
+
 <sect id="reducepriv">Reducing the number of absolutely privileged subsystems
 <p>