chiark / gitweb /
build-depends tetex-extra
[userv.git] / client.c
index 68ec6a4..5f60d0e 100644 (file)
--- a/client.c
+++ b/client.c
@@ -2,7 +2,7 @@
  * userv - client.c
  * client code
  *
- * Copyright (C)1996-1997 Ian Jackson
+ * Copyright (C)1996-1997,1999-2001,2003 Ian Jackson
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by
  * 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>
@@ -29,6 +59,7 @@
 #include <unistd.h>
 #include <ctype.h>
 #include <pwd.h>
+#include <grp.h>
 #include <signal.h>
 #include <limits.h>
 #include <sys/time.h>
 
 #include "config.h"
 #include "common.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;
-};
+#include "both.h"
+#include "version.h"
 
 enum fdmodifiervalues {
   fdm_read=       00001,
@@ -74,78 +96,43 @@ 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 };
-enum overridetypes { ot_none, ot_string, ot_file };
-
-static const char *serviceuser=0;
-static uid_t serviceuid, myuid;
-static struct fdsetupstate *fdsetup=0;
-static int fdsetupsize=0;
-static const char *(*defvarsarray)[2];
-static int defvarsavail=0, defvarsused=0;
-static unsigned long timeout=0;
+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;
+static struct fdsetupstate *fdsetup;
+static int fdsetupsize;
+static struct constkeyvaluepair *defvararray;
+static int defvaravail, defvarused;
+static unsigned long timeout;
 static int signalsexit=254;
-static int sigpipeok=0, hidecwd=0;
+static int sigpipeok, hidecwd;
 static int overridetype= ot_none;
-static const char *overridevalue;
+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 *loginname;
+static char *cwdbuf;
+static size_t cwdbufsize;
+static char *ovbuf;
+static int ovused, systemerror;
 
 static void blocksignals(int how) {
   sigset_t set;
@@ -164,6 +151,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;
 
@@ -175,7 +168,7 @@ static void NONRETURNPRINTFFORMAT(1,2) miscerror(const char *fmt, ...) {
   exit(-1);
 }
 
-static void NONRETURNPRINTFFORMAT(1,2) syscallerror(const char *fmt, ...) {
+static void NONRETURNPRINTFFORMAT(1,2) fsyscallerror(const char *fmt, ...) {
   va_list al;
   int e;
 
@@ -188,6 +181,10 @@ static void NONRETURNPRINTFFORMAT(1,2) syscallerror(const char *fmt, ...) {
   exit(-1);
 }
 
+void syscallerror(const char *what) {
+  fsyscallerror("%s",what);
+}
+
 static void NONRETURNING protoreaderror(FILE *file, const char *where) {
   int e;
 
@@ -213,6 +210,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= working_fread(p,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) { }
@@ -234,118 +258,351 @@ static void priv_permanentlyrevokesuspended(void) {
 }
 #endif
 
-static void *xmalloc(size_t s) {
-  void *p;
-  p= malloc(s?s:1);
-  if (!p) syscallerror("malloc (%lu bytes)",(unsigned long)s);
-  return p;
+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 *xrealloc(void *p, size_t s) {
-  p= realloc(p,s);
-  if (!p) syscallerror("realloc (%lu bytes)",(unsigned long)s);
-  return p;
-}
+static void getprogress(struct progress_msg *progress_r, FILE *file) {
+  int i, c;
+  unsigned long ul;
 
-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");
+  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= working_getc(file);
+       if (c==EOF) protoreaderror(file,"in error message");
+       if (ISCHAR(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 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");
-}
+/*
+ * Functions which are called only during setup, before
+ * the signal asynchronicity starts.  They can do anything they like.
+ */
+
+/* This includes xmalloc and xrealloc from both.c */
 
 static void xfwritestring(const char *s, FILE *file) {
   int l;
   l= strlen(s);
+  assert(l<=MAX_GENERAL_STRING);
   xfwrite(&l,sizeof(l),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");
+  }
+  systemerror= 1;
 }
 
-static void usage(void) {
-  if (fprintf(stderr,
-              "usage: userv <options> [--] <service-user> <service-name> [<argument> ...]\n"
-              "options: -f|--file <fd>[<fdmodifiers>]=<filename>\n"
-              "         -D|--defvar <name>=<value>\n"
-              "         -t|--timeout <seconds>\n"
-              "         -S|--signals <status>|number|number-nocore|highbit|stdout\n"
-              "         -w|--fdwait <fd>=wait|nowait|close\n"
-              "         -P|--sigpipe  -H|--hidecwd  -h|--help  --copyright\n"
-              "         --override <configuration-data> } available only to\n"
-              "         --override-file <filename>      } root or same user\n"
-              "fdmodifiers:            read    write  overwrite    trunc[ate]\n"
-              "(separate with commas)  append  sync   excl[usive]  creat[e]  fd\n\n"
-             "userv and uservd are copyright (C)1996-1997 Ian Jackson.\n"
-              "They come with NO WARRANTY; type `userv --copyright' for details.\n")
-      == EOF) syscallerror("write usage to stderr");
+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;
+        event_mbuf.data.closereadfd.fd= fd;
+       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(FILE *stream) {
+  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"
+    "         -D|--defvar <name>=<value>\n"
+    "         -t|--timeout <seconds>\n"
+    "         -S|--signals <status>|number|number-nocore|highbit|stdout\n"
+    "         -w|--fdwait <fd>=wait|nowait|close\n"
+    "         -P|--sigpipe  -H|--hidecwd  -h|--help|--version  --copyright\n"
+    "         --override <configuration-data> } available only\n"
+    "         --override-file <filename>      }  to root\n"
+    "         --spoof-user <username>         }  or same user\n"
+    "fdmodifiers:            read    write  overwrite    trunc[ate]\n"
+    "(separate with commas)  append  sync   excl[usive]  creat[e]  fd\n"
+    "userv -B 'X' ... is same as userv --override 'execute-builtin X' - 'X' ...\n"
+    "         for help, type `userv -B help'; remember to quote multi-word X\n"
+    "userv and uservd version " VERSION VEREXT "; copyright (C)1996-1999 Ian Jackson.\n"
+    "there is NO WARRANTY; type `userv --copyright' for details.\n",
+            stream) < 0)
+    syscallerror("write usage message");
 }
 
 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");
-  usage();
+  fputs("\n\n",stderr);
+  usage(stderr);
   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, size_t key_len) {
   const struct fdmodifierinfo *fdmip;
   
   if (!*key) return;
-  for (fdmip= fdmodifierinfos; fdmip->string && strcmp(fdmip->string,key); fdmip++);
-  if (!fdmip->string) usageerror("unknown fdmodifer `%s' for fd %d",key,fd);
+  for (fdmip= fdmodifierinfos;
+       fdmip->string &&
+        !(strlen(fdmip->string) == key_len &&
+          !memcmp(fdmip->string,key,key_len));
+       fdmip++);
+  if (!fdmip->string) usageerror("unknown fdmodifer `%.*s' for fd %d",
+                                (int)key_len,key,fd);
   if (fdmip->conflicts & fdsetup[fd].mods)
-    usageerror("fdmodifier `%s' conflicts with another for fd %d",key,fd);
+    usageerror("fdmodifier `%.*s' conflicts with another for fd %d",
+              (int)key_len,key,fd);
   fdsetup[fd].mods |= fdmip->implies;
   fdsetup[fd].oflags |= fdmip->oflags;
 }
 
-static int fdstdnumber(const char *string) {
-  if (!strcmp(string,"stdin")) return 0;
-  else if (!strcmp(string,"stdout")) return 1;
-  else if (!strcmp(string,"stderr")) return 2;
-  else return -1;
+static void addfdmodifier_fixed(int fd, const char *key) {
+  addfdmodifier(fd, key, strlen(key));
+}
+
+static int fdstdnumber(const char *string, const char **delim_r) {
+#define FN(v,s) do{                            \
+    if (!memcmp(string,s,sizeof(s)-1)) {       \
+      *delim_r= string+sizeof(s)-1;            \
+      return v;                                        \
+    }                                          \
+  }while(0)
+
+  FN(0,"stdin");
+  FN(1,"stdout");
+  FN(2,"stderr");
+
+  return -1;
 }
 
+static int strtofd(const char *string,
+                  const char **mods_r /* 0: no modifiers, must go to end */,
+                  const char *what) {
+  int fd;
+  unsigned long ul;
+  char *delim_v;
+  const char *mods;
+  
+  fd= fdstdnumber(string,&mods);
+  if (fd>=0) {
+    if (*mods && *mods != ',')
+      usageerror("%s, when it is `stdin', `stdout' or `stderr',"
+                " must be delimited with a comma from any following"
+                " modifiers - `%s' is not permitted",
+                what, mods);
+    goto parsed;
+  }
+
+  errno= 0;
+  ul= strtoul(string,&delim_v,10);
+  if (errno || delim_v == string || ul > INT_MAX)
+    usageerror("%s must be must be numeric file descriptor"
+              " or `stdin', `stdout' or `stderr'"
+              " - `%s' is not recognized",
+              what, string);
+  mods= delim_v;
+  fd= ul;
+
+ parsed:
+  if (*mods==',')
+    mods++;
+  
+  if (mods_r)
+    *mods_r= mods;
+  else if (*mods)
+    usageerror("%s must be must be only file descriptor"
+              " - trailing portion or modifiers `%s' not permitted",
+              what, mods);
+
+  if (fd > MAX_ALLOW_FD)
+    usageerror("%s file descriptor specified (%d)"
+              " is larger than maximum allowed (%d)",
+              what, fd, MAX_ALLOW_FD);
+  
+  return fd;
+}
+  
 static void of_file(const struct optioninfo *oip, const char *value, char *key) {
   unsigned long fd, copyfd;
   struct stat stab;
   int oldarraysize, r;
-  char *delim;
-
-  fd= strtoul(key,&delim,10);
-  if (delim == key) {
-    delim= strchr(key,',');
-    if (delim) *delim++= 0;
-    fd= fdstdnumber(key);
-    if (fd<0) usageerror("first part of argument to -f or --file must be numeric "
-                        "file descriptor or `stdin', `stdout' or `stderr' - `%s' "
-                        "is not recognized",key);
-  }
-  if (fd > MAX_ALLOW_FD)
-    usageerror("file descriptor specified (%lu) is larger than maximum allowed (%d)",
-              fd,MAX_ALLOW_FD);
+  size_t mod_len;
+  const char *mods, *delim;
+
+  fd= strtofd(key,&mods,"first part of argument to -f or --file");
+
   if (fd >= fdsetupsize) {
     oldarraysize= fdsetupsize;
     fdsetupsize+=2; fdsetupsize<<=1;
     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++;
     }
   }
@@ -353,38 +610,29 @@ static void of_file(const struct optioninfo *oip, const char *value, char *key)
   fdsetup[fd].oflags= 0;
   fdsetup[fd].mods= 0;
   fdsetup[fd].copyfd= -1;
-  while (delim && *delim) {
-    key= delim;
-    delim= strchr(key,',');
-    if (delim) *delim++= 0;
-    addfdmodifier(&fdsetup[fd],fd,key);
+  while (mods && *mods) {
+    delim= strchr(mods,',');
+    mod_len= delim ? delim-mods : strlen(mods);
+    addfdmodifier(fd,mods,mod_len);
+    mods= delim ? delim+1 : 0;
   }
   if (!(fdsetup[fd].mods & (fdm_read|fdm_write))) {
-    if (fd != 1 && fd != 2) {
-      addfdmodifier(&fdsetup[fd],fd,"read");
+    if (fd == 0) {
+      addfdmodifier_fixed(fd,"read");
     } else if (fdsetup[fd].mods & fdm_fd) {
-      addfdmodifier(&fdsetup[fd],fd,"write");
+      addfdmodifier_fixed(fd,"write");
     } else {
-      addfdmodifier(&fdsetup[fd],fd,"overwrite");
+      addfdmodifier_fixed(fd,"overwrite");
     }
   }
   if (fdsetup[fd].mods & fdm_fd) {
-    copyfd= fdstdnumber(value);
-    if (copyfd<0) {
-      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);
-      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);
+    copyfd= strtofd(value,0,
+                   "value part of argument to --file with fd modifier");
+    r= fstat(copyfd,&stab);
     if (r) {
-      if (oip) syscallerror("check filedescriptor %lu (named as target of file "
-                           "descriptor redirection for %lu)",copyfd,fd);
-      else syscallerror("check basic filedescriptor %lu at program start",copyfd);
+      if (oip) fsyscallerror("check filedescriptor %lu (named as target of file "
+                            "descriptor redirection for %lu)",copyfd,fd);
+      else fsyscallerror("check basic filedescriptor %lu at program start",copyfd);
     }
     fdsetup[fd].copyfd= copyfd;
   }
@@ -392,18 +640,9 @@ static void of_file(const struct optioninfo *oip, const char *value, char *key)
 
 static void of_fdwait(const struct optioninfo *oip, const char *value, char *key) {
   const struct fdmodifierinfo *fdmip;
-  unsigned long ul;
   int fd;
-  char *delim;
   
-  fd= fdstdnumber(key);
-  if (fd<0) {
-    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");
-    fd= ul;
-  }
+  fd= strtofd(key,0,"first part of argument to --fdwait");
   if (fd >= fdsetupsize || !fdsetup[fd].filename)
     usageerror("file descriptor %d specified in --fdwait option is not open",fd);
   for (fdmip= fdmodifierinfos; fdmip->string && strcmp(fdmip->string,value); fdmip++);
@@ -416,28 +655,42 @@ static void of_fdwait(const struct optioninfo *oip, const char *value, char *key
 static void of_defvar(const struct optioninfo *oip, const char *value, char *key) {
   int i;
 
-  for (i=0; i<defvarsused && strcmp(defvarsarray[i][0],key); i++);
-  if (i>=defvarsavail) {
-    defvarsavail+=10; defvarsavail<<=1;
-    defvarsarray= xrealloc(defvarsarray,sizeof(const char*)*2*defvarsavail);
+  if (!key[0])
+    usageerror("empty string not allowed as variable name");
+  if (strlen(key)>MAX_GENERAL_STRING)
+    usageerror("variable name `%s' is far too long",key);
+  if (strlen(value)>MAX_GENERAL_STRING)
+    usageerror("variable `%s' has value `%s' which is far too long",key,value);
+  for (i=0; i<defvarused && strcmp(defvararray[i].key,key); i++);
+  if (defvarused >= MAX_ARGSDEFVAR) usageerror("far too many --defvar or -D options");
+  if (i>=defvaravail) {
+    defvaravail+=10; defvaravail<<=1;
+    defvararray= xrealloc(defvararray,sizeof(struct constkeyvaluepair)*defvaravail);
   }
-  if (i==defvarsused) defvarsused++;
-  defvarsarray[i][0]= key;
-  defvarsarray[i][1]= value;
+  if (i==defvarused) defvarused++;
+  defvararray[i].key= key;
+  defvararray[i].value= value;
 }
 
 static void of_timeout(const struct optioninfo *oip, const char *value, char *key) {
   char *endp;
-  timeout= 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);
+  unsigned long ul;
+
+  errno= 0;
+  ul= strtoul(value,&endp,10);
+  if (errno || *endp)
+    usageerror("timeout value `%s' must be a plain decimal string",value);
+  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) {
+
+  errno= 0;
+  numvalue= strtoul(value,&endp,10);
+  if (errno || *endp) {
     if (!strcmp(value,"number")) signalsexit= se_number;
     else if (!strcmp(value,"number-nocore")) signalsexit= se_numbernocore;
     else if (!strcmp(value,"highbit")) signalsexit= se_highbit;
@@ -459,246 +712,129 @@ static void of_hidecwd(const struct optioninfo *oip, const char *value, char *ke
 }
 
 static void of_help(const struct optioninfo *oip, const char *value, char *key) {
-  usage();
+  usage(stdout);
+  if (fclose(stdout)) syscallerror("fclose stdout after writing usage message");
   exit(0);
 }
 
-static void of_copyright(const struct optioninfo *oip, const char *value, char *key) {
-  if (fprintf(stdout,
-" 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"
-" Foundation; either version 2 of the License, or (at your option) any\n"
-" later version.\n\n"
-" This program is distributed in the hope that it will be useful, but\n"
-" WITHOUT ANY WARRANTY; without even the implied warranty of\n"
-" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General\n"
-" Public License for more details.\n\n"
-" 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");
+static void of_version(const struct optioninfo *oip, const char *value, char *key) {
+  if (puts(VERSION VEREXT) == EOF || fclose(stdout))
+    syscallerror("write version number");
   exit(0);
 }
 
-static void of_override(const struct optioninfo *oip, const char *value, char *key) {
-  overridetype= ot_string;
-  overridevalue= value;
-}
-
-static void of_overridefile(const struct optioninfo *oip,
-                            const char *value, char *key) {
-  overridetype= ot_file;
-  overridevalue= value;
-}
-
-const struct optioninfo optioninfos[]= {
-  { 'f', "file",          2, of_file         },
-  { 'w', "fdwait",        2, of_fdwait       },
-  { 'D', "defvar",        2, of_defvar       },
-  { 't', "timeout",       1, of_timeout      },
-  { 'S', "signals",       1, of_signals      },
-  { 'P', "sigpipe",       0, of_sigpipe      },
-  { 'H', "hidecwd",       0, of_hidecwd      },
-  { 'h', "help",          0, of_help         },
-  {  0,  "copyright",     0, of_copyright    },
-  {  0,  "override",      1, of_override     },
-  {  0,  "override-file", 1, of_overridefile },
-  {  0,   0                                  }
-};
-
-static void callvalueoption(const struct optioninfo *oip, char *arg) {
-  char *equals;
-  if (oip->values == 2) {
-    equals= strchr(arg,'=');
-    if (!equals)
-      if (oip->abbrev)
-        usageerror("option --%s (-%c) passed argument `%s' with no `='",
-                   oip->full,oip->abbrev,arg);
-      else
-        usageerror("option --%s passed argument `%s' with no `='",
-                   oip->full,arg);
-    *equals++= 0;
-    (oip->fn)(oip,equals,arg);
-  } else {
-    (oip->fn)(oip,arg,0);
-  }
-}
-
-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;
+static void of_copyright(const struct optioninfo *oip, const char *value, char *key) {
+  if (fputs(
+" userv - user service daemon and client; copyright (C)1996-1999 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"
+" Foundation; either version 2 of the License, or (at your option) any\n"
+" later version.\n\n"
+" This program is distributed in the hope that it will be useful, but\n"
+" WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General\n"
+" Public License for more details.\n\n"
+" You should have received a copy of the GNU General Public License along\n"
+" with userv; if not, write to Ian Jackson <ian@davenant.greenend.org.uk> or\n"
+" to the Free Software Foundation, 59 Temple Place - Suite 330, Boston,\n"
+" MA 02111-1307, USA.\n",
+           stdout) < 0) syscallerror("write usage to stderr");
+  exit(0);
+}
 
-  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 of_builtin(const struct optioninfo *oip, const char *value, char *key) {
+  overridetype= ot_builtin;
 }
 
-static void disconnect(void) /* DOES return, unlike in daemon */ {
-  struct event_msg event_mbuf;
+static void of_override(const struct optioninfo *oip, const char *value, char *key) {
+  overridetype= ot_string;
+  overridevalue= value;
+}
 
-  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 of_overridefile(const struct optioninfo *oip,
+                            const char *value, char *key) {
+  overridetype= ot_file;
+  overridevalue= value;
 }
 
-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 of_spoofuser(const struct optioninfo *oip,
+                        const char *value, char *key) {
+  spoofuser= value;
 }
 
-static void sighandler_chld(int ignored) /* DOES return, unlike in daemon */ {
-  struct event_msg event_mbuf;
-  pid_t child;
-  int status, fd, r, es;
+const struct optioninfo optioninfos[]= {
+  { 'f', "file",          2, of_file         },
+  { 'w', "fdwait",        2, of_fdwait       },
+  { 'D', "defvar",        2, of_defvar       },
+  { 't', "timeout",       1, of_timeout      },
+  { 'S', "signals",       1, of_signals      },
+  { 'P', "sigpipe",       0, of_sigpipe      },
+  { 'H', "hidecwd",       0, of_hidecwd      },
+  { 'B', "builtin",       0, of_builtin      },
+  { 'h', "help",          0, of_help         },
+  {  0,  "version",       0, of_version      },
+  {  0,  "copyright",     0, of_copyright    },
+  {  0,  "override",      1, of_override     },
+  {  0,  "override-file", 1, of_overridefile },
+  {  0,  "spoof-user",    1, of_spoofuser    },
+  {  0,   0                                  }
+};
 
-  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));
+static void callvalueoption(const struct optioninfo *oip, char *arg) {
+  char *equals;
+  if (oip->values == 2) {
+    equals= strchr(arg,'=');
+    if (!equals) {
+      if (oip->abbrev)
+        usageerror("option --%s (-%c) passed argument `%s' with no `='",
+                   oip->full,oip->abbrev,arg);
       else
-       fprintf(stderr,"userv: cat for fd %d gave unknown wait status %d\n",
-               fd,status);
-      disconnect();
+        usageerror("option --%s passed argument `%s' with no `='",
+                   oip->full,arg);
     }
-    fdsetup[fd].catpid= -1;
+    *equals++= 0;
+    (oip->fn)(oip,equals,arg);
+  } else {
+    (oip->fn)(oip,arg,0);
   }
-  errno= es;
 }
 
-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);
-  }
-}
+/*
+ * Main thread main processing functions - in order of execution.
+ */
 
-int main(int argc, char *const *argv) {
-  static char fd0key[]= "stdin,fd,read";
-  static char fd1key[]= "stdout,fd,write";
-  static char fd2key[]= "stderr,fd,write";
-  static char stderrbuf[BUFSIZ], stdoutbuf[1024];
+static void security_init(void) {
+  /* May not open any file descriptors. */
   
-  char *const *argpp;
-  char *argp;
-  const struct optioninfo *oip;
-  struct sockaddr_un ssockname;
-  int sfd, ngids, i, j, tempfd, l, c, reading, fd, r, status;
-  sigset_t sset;
-  unsigned long ul;
-  size_t cwdbufsize;
-  char *cwdbuf;
-  struct opening_msg opening_mbuf;
-  struct request_msg request_mbuf;
-  struct progress_msg progress_mbuf;
-  struct event_msg event_mbuf;
-  struct passwd *pw;
-  gid_t mygid, *gidarray;
-  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();
 
-  assert(argv[0]);
+  if (ngids > MAX_GIDS) miscerror("caller is in far too many gids");
+}
+
+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";
+
+  char *const *argpp;
+  char *argp;
+  const struct optioninfo *oip;
+  int fd;
+
+  assert((*argvp)[0]);
   of_file(0,"stdin",fd0key);
   of_file(0,"stdout",fd1key);
   of_file(0,"stderr",fd2key);
 
-  for (argpp= argv+1;
-       (argp= *argpp) && *argp == '-';
+  for (argpp= *argvp+1;
+       (argp= *argpp) && *argp == '-' && argp[1];
        argpp++) {
     if (!*++argp) usageerror("unknown option/argument `%s'",*argpp);
     if (*argp == '-') { /* Two hyphens */
@@ -732,9 +868,20 @@ int main(int argc, char *const *argv) {
       }
     }
   }
-  if (!*argpp) usageerror("no service user given after options");
-  serviceuser= *argpp++;
-  if (!*argpp) usageerror("no service name given after options and service user");
+  if (overridetype == ot_builtin) {
+    serviceuser= "-";
+  } else {
+    if (!*argpp) usageerror("no service user given after options");
+    serviceuser= *argpp++;
+  }
+  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;
@@ -742,63 +889,106 @@ 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;
+static void determine_users(void) {
+  int ngidssize;
+  char **mem;
+  struct passwd *pw;
+  struct group *gr;
+  
+  spoofuid= myuid;
+  spoofgid= mygid;
+  loginname= getenv("LOGNAME");
+  if (!loginname) loginname= getenv("USER");
+  if (loginname) {
+    pw= getpwnam(loginname);
+    if (!pw || pw->pw_uid != myuid) loginname= 0;
+  }
+  if (!loginname) {
+    pw= getpwuid(myuid); if (!pw) miscerror("cannot determine your login name");
+    loginname= xstrsave(pw->pw_name);
+  }
 
+  if (!strcmp(serviceuser,"-")) serviceuser= loginname;
   pw= getpwnam(serviceuser);
   if (!pw) miscerror("requested service user `%s' is not a user",serviceuser);
   serviceuid= pw->pw_uid;
 
-  if (overridetype != ot_none && myuid != 0 && myuid != serviceuid)
-    miscerror("--override options only available to root or to"
+  if ((overridetype != ot_none || spoofuser) && myuid != 0 && myuid != serviceuid)
+    miscerror("--override and --spoof options only available to root or to"
               " the user who will be providing the service");
 
-  logname= getenv("LOGNAME");
-  if (!logname) logname= getenv("USER");
-  if (logname) {
-    pw= getpwnam(logname);
-    if (!pw || pw->pw_uid != myuid) logname= 0;
-  }
-  if (!logname) {
-    pw= getpwuid(myuid); if (!pw) syscallerror("cannot determine your login name");
-    logname= pw->pw_name;
+  if (spoofuser) {
+    loginname= spoofuser;
+    pw= getpwnam(loginname);
+    if (!pw) miscerror("spoofed login name `%s' is not valid",loginname);
+    spoofuid= pw->pw_uid;
+    spoofgid= pw->pw_gid;
+    ngidssize= ngids; ngids= 0;
+    if (ngidssize<5) {
+      ngidssize= 5;
+      gidarray= xrealloc(gidarray,sizeof(gid_t)*ngidssize);
+    }
+    gidarray[ngids++]= spoofgid;
+    while ((gr= getgrent())) { /* ouch! getgrent has no error behaviour! */
+      for (mem= gr->gr_mem; *mem && strcmp(*mem,loginname); 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:
     ovused= -1;
     ovbuf= 0;
     break;
+  case ot_builtin:
+    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",servicename);
+    ovused= strlen(ovbuf);
+    break;
   case ot_string:
     l= strlen(overridevalue);
     if (l >= MAX_OVERRIDE_LEN)
       miscerror("override string is too long (%d, max is %d)",l,MAX_OVERRIDE_LEN-1);
     ovbuf= xmalloc(l+2);
-    strcpy(ovbuf,overridevalue);
-    strcat(ovbuf,"\n");
+    snprintf(ovbuf,l+2,"%s\n",overridevalue);
     ovused= l+1;
     break;
   case ot_file:
     ovfile= fopen(overridevalue,"r");
-    if (!ovfile) syscallerror("open overriding configuration file `%s'",overridevalue);
+    if (!ovfile) fsyscallerror("open overriding configuration file `%s'",overridevalue);
     ovbuf= 0; ovavail= ovused= 0;
     while ((c= getc(ovfile)) != EOF) {
       if (!c) miscerror("overriding config file `%s' contains null(s)",overridevalue);
@@ -810,13 +1000,26 @@ int main(int argc, char *const *argv) {
       ovbuf[ovused++]= c;
     }
     if (ferror(ovfile) || fclose(ovfile))
-      syscallerror("read overriding configuration file `%s'",overridevalue);
+      fsyscallerror("read overriding configuration file `%s'",overridevalue);
     ovbuf= xrealloc(ovbuf,ovused+1);
     ovbuf[ovused]= 0;
     break;
   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);
@@ -833,10 +1036,14 @@ int main(int argc, char *const *argv) {
   while (connect(sfd,(struct sockaddr*)&ssockname,sizeof(ssockname))) {
     if (errno == ECONNREFUSED || errno == ENOENT)
       syscallerror("uservd daemon is not running - service not available");
-    syscallerror("unable to connect to uservd daemon");
+    if (errno != EINTR)
+      fsyscallerror("unable to connect to uservd daemon: %m");
   }
-  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");
@@ -849,34 +1056,48 @@ 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);
+      fsyscallerror("remove any old pipe `%s'",pipepathbuf);
     if (mkfifo(pipepathbuf,0600)) /* permissions are irrelevant */
-      syscallerror("create pipe `%s'",pipepathbuf);
+      fsyscallerror("create pipe `%s'",pipepathbuf);
     tempfd= open(pipepathbuf,O_RDWR);
-    if (tempfd == -1) syscallerror("prelim open pipe `%s' for read+write",pipepathbuf);
+    if (tempfd<0) fsyscallerror("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 (close(tempfd)) syscallerror("close prelim fd onto pipe `%s'",pipepathbuf);
+    if (fdsetup[fd].pipefd<0) fsyscallerror("real open pipe `%s'",pipepathbuf);
+    if (close(tempfd)) fsyscallerror("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();
   request_mbuf.serviceuserlen= strlen(serviceuser);
   request_mbuf.servicelen= strlen(argv[0]);
-  request_mbuf.lognamelen= strlen(logname);
+  request_mbuf.loginnamelen= strlen(loginname);
+  request_mbuf.spoofed= spoofuser ? 1 : 0;
   request_mbuf.cwdlen= cwdbufsize;
-  request_mbuf.callinguid= myuid;
+  request_mbuf.callinguid= spoofuid;
   request_mbuf.ngids= ngids+1;
   request_mbuf.nreadfds= 0;
   request_mbuf.nwritefds= 0;
@@ -887,33 +1108,47 @@ int main(int argc, char *const *argv) {
     else request_mbuf.nreadfds++;
   }
   request_mbuf.nargs= argc-1;
-  request_mbuf.nvars= defvarsused;
+  request_mbuf.nvars= defvarused;
   request_mbuf.overridelen= ovused;
   xfwrite(&request_mbuf,sizeof(request_mbuf),swfile);
   xfwrite(serviceuser,sizeof(*serviceuser)*request_mbuf.serviceuserlen,swfile);
   xfwrite(argv[0],sizeof(*argv[0])*request_mbuf.servicelen,swfile);
-  xfwrite(logname,sizeof(*logname)*request_mbuf.lognamelen,swfile);
+  xfwrite(loginname,sizeof(*loginname)*request_mbuf.loginnamelen,swfile);
   xfwrite(cwdbuf,sizeof(*cwdbuf)*request_mbuf.cwdlen,swfile);
   if (ovused>=0) xfwrite(ovbuf,sizeof(*ovbuf)*ovused,swfile);
-  xfwrite(&mygid,sizeof(gid_t),swfile);
+  xfwrite(&spoofgid,sizeof(gid_t),swfile);
   xfwrite(gidarray,sizeof(gid_t)*ngids,swfile);
   xfwritefds(fdm_read,request_mbuf.nreadfds,swfile);
   xfwritefds(fdm_write,request_mbuf.nwritefds,swfile);
   for (i=1; i<argc; i++)
     xfwritestring(argv[i],swfile);
-  for (i=0; i<defvarsused; i++)
-    for (j=0; j<2; j++)
-      xfwritestring(defvarsarray[i][j],swfile);
+  for (i=0; i<defvarused; i++) {
+    xfwritestring(defvararray[i].key,swfile);
+    xfwritestring(defvararray[i].value,swfile);
+  }
   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);
@@ -924,18 +1159,56 @@ 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 close_unwanted_pipes(void) {
+  int fd;
+
+  for (fd=0; fd<fdsetupsize; fd++) {
+    if (!fdsetup[fd].filename) continue;
+    if (close(fdsetup[fd].pipefd)) fsyscallerror("close pipe fd for %d",fd);
+    if (fdsetup[fd].copyfd>2)
+      if (close(fdsetup[fd].copyfd))
+       if (errno != EBADF)
+         /* EBADF can be induced if cmd line specifies same fd twice */
+         fsyscallerror("close real fd for %d",fd);
+  }
+}
+
+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;
+  pid_t child;
+  
   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);
+       fsyscallerror("open file `%s' for fd %d",fdsetup[fd].filename,fd);
     }
-    fdsetup[fd].catpid= fork();
-    if (fdsetup[fd].catpid==-1) syscallerror("fork for cat for fd %d",fd);
-    if (!fdsetup[fd].catpid) {
+    blocksignals(SIG_BLOCK);
+    child= fork();
+    fdsetup[fd].catpid= child;
+    blocksignals(SIG_UNBLOCK);
+    if (child==-1) fsyscallerror("fork for cat for fd %d",fd);
+    if (!child) {
       snprintf(catnamebuf,sizeof(catnamebuf),"cat fd%d",fd);
+      catnamebuf[sizeof(catnamebuf)-1]= 0;
       sig.sa_handler= SIG_DFL;
       sigemptyset(&sig.sa_mask);
       sig.sa_flags= 0;
@@ -944,21 +1217,20 @@ int main(int argc, char *const *argv) {
                catnamebuf,strerror(errno));
        exit(-1);
       }
-      catnamebuf[sizeof(catnamebuf)-1]= 0;
       reading= fdsetup[fd].mods & fdm_read;
       catdup(catnamebuf, fdsetup[fd].copyfd, reading ? 0 : 1);
       catdup(catnamebuf, fdsetup[fd].pipefd, reading ? 1 : 0);
-      execlp("cat",catnamebuf,(char*)0);
+      close_unwanted_pipes();
+      execl("/bin/cat",catnamebuf,(char*)0);
       fprintf(stderr,"userv: %s: cannot exec `cat': %s\n",catnamebuf,strerror(errno));
       exit(-1);
     }
-    if (fdsetup[fd].copyfd>2)
-      if (close(fdsetup[fd].copyfd)) syscallerror("close real fd for %d",fd);
-    if (close(fdsetup[fd].pipefd)) syscallerror("close pipe fd for %d",fd);
   }
+  close_unwanted_pipes();
+}
 
-  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));
@@ -967,18 +1239,28 @@ 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++) {
     if (!(fdsetup[fd].catpid!=-1 && (fdsetup[fd].mods & fdm_close))) continue;
-    if (kill(fdsetup[fd].catpid,SIGKILL)) syscallerror("kill cat for %d",fd);
+    if (kill(fdsetup[fd].catpid,SIGKILL)) fsyscallerror("kill cat for %d",fd);
     fdsetup[fd].killed= 1;
   }
   blocksignals(SIG_UNBLOCK);
@@ -994,10 +1276,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;
   
@@ -1036,6 +1321,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);
+}