/*
 * userv - client.c
 * client code
 *
 * userv is copyright Ian Jackson and other contributors.
 * See README for full authorship information.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 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; if not, see <http://www.gnu.org/licenses/>.
 */

/*
 * 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/resource.h>
#include <sys/wait.h>

#include "config.h"
#include "common.h"
#include "both.h"
#include "version.h"

enum fdmodifiervalues {
  fdm_read=       00001,
  fdm_write=      00002,
  fdm_create=     00004,
  fdm_exclusive=  00010,
  fdm_truncate=   00020,
  fdm_append=     00040,
  fdm_sync=       00100,
  fdm_fd=         00200,
  fdm_wait=       01000,
  fdm_nowait=     02000,
  fdm_close=      04000,
};

struct fdmodifierinfo {
  const char *string;
  int implies;
  int conflicts;
  int oflags;
};

struct fdsetupstate {
  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, 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, 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 *loginname;
static char *cwdbuf;
static size_t cwdbufsize;
static char *ovbuf;
static int ovused, systemerror, socketfd;

static void blocksignals(int how) {
  sigset_t set;
  static const char blockerrmsg[]= "userv: failed to [un]block signals: ";
  const char *str;
  int unused;

  sigemptyset(&set);
  sigaddset(&set,SIGCHLD);
  sigaddset(&set,SIGALRM);
  if (sigprocmask(how,&set,0)) {
    str= strerror(errno);
    unused= write(2,blockerrmsg,sizeof(blockerrmsg)-1);
    unused= write(2,str,strlen(str));
    unused= write(2,"\n",1);
    (void)unused;
    exit(-1);
  }
}

/* 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;

  blocksignals(SIG_BLOCK);
  va_start(al,fmt);
  fprintf(stderr,"userv: failure: ");
  vfprintf(stderr,fmt,al);
  fprintf(stderr,"\n");
  exit(-1);
}

static void NONRETURNPRINTFFORMAT(1,2) fsyscallerror(const char *fmt, ...) {
  va_list al;
  int e;

  e= errno;
  blocksignals(SIG_BLOCK);
  va_start(al,fmt);
  fprintf(stderr,"userv: system call failure: ");
  vfprintf(stderr,fmt,al);
  fprintf(stderr,": %s\n",strerror(e));
  exit(-1);
}

void syscallerror(const char *what) {
  fsyscallerror("%s",what);
}

static void NONRETURNING protoreaderror(FILE *file, const char *where) {
  int e;

  e= errno;
  blocksignals(SIG_BLOCK);
  if (ferror(file)) {
    fprintf(stderr,"userv: failure: read error %s: %s\n",where,strerror(e));
  } else {
    assert(feof(file));
    fprintf(stderr,"userv: internal failure: EOF from server %s\n",where);
  }
  exit(-1);
}

static void NONRETURNPRINTFFORMAT(1,2) protoerror(const char *fmt, ...) {
  va_list al;

  blocksignals(SIG_BLOCK);
  va_start(al,fmt);
  fprintf(stderr,"userv: internal failure: protocol error: ");
  vfprintf(stderr,fmt,al);
  fprintf(stderr,"\n");
  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) { }
static void priv_permanentlyrevokesuspended(void) { }
#else
static void priv_suspend(void) {
  if (setreuid(0,myuid) != 0) syscallerror("suspend root setreuid(0,myuid)");
}
static void priv_resume(void) {
  if (setreuid(myuid,0) != 0) syscallerror("resume root setreuid(myuid,0)");
}
static void priv_permanentlyrevokesuspended(void) {
  if (setreuid(myuid,myuid) != 0) syscallerror("revoke root setreuid(myuid,myuid)");
  if (setreuid(myuid,myuid) != 0) syscallerror("rerevoke root setreuid(myuid,myuid)");
  if (myuid) {
    if (!setreuid(myuid,0)) miscerror("revoked root but setreuid(0,0) succeeded !");
    if (errno != EPERM) syscallerror("revoked and setreuid(myuid,0) unexpected error");
  }
}
#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= 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;
    }
  }
}

/*
 * 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 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 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 ".\n"
    COPYRIGHT("","\n"),
            stream) < 0)
    syscallerror("write usage message");
}

static void NONRETURNPRINTFFORMAT(1,2) usageerror(const char *fmt, ...) {
  va_list al;
  va_start(al,fmt);
  fputs("userv: ",stderr);
  vfprintf(stderr,fmt,al);
  fputs("\n\n",stderr);
  usage(stderr);
  exit(-1);
}

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 &&
	 !(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",
	       (int)key_len,key,fd);
  fdsetup[fd].mods |= fdmip->implies;
  fdsetup[fd].oflags |= fdmip->oflags;
}

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;
  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;
      oldarraysize++;
    }
  }
  fdsetup[fd].filename= value;
  fdsetup[fd].oflags= 0;
  fdsetup[fd].mods= 0;
  fdsetup[fd].copyfd= -1;
  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 == 0) {
      addfdmodifier_fixed(fd,"read");
    } else if (fdsetup[fd].mods & fdm_fd) {
      addfdmodifier_fixed(fd,"write");
    } else {
      addfdmodifier_fixed(fd,"overwrite");
    }
  }
  if (fdsetup[fd].mods & fdm_fd) {
    copyfd= strtofd(value,0,
		    "value part of argument to --file with fd modifier");
    r= fstat(copyfd,&stab);
    if (r) {
      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;
  }
}

static void of_fdwait(const struct optioninfo *oip, const char *value, char *key) {
  const struct fdmodifierinfo *fdmip;
  int fd;
  
  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++);
  if (!fdmip->string || !(fdmip->implies & (fdm_wait|fdm_nowait|fdm_close)))
    usageerror("value for --fdwait must be `wait', `nowait' or `close', not `%s'",value);
  fdsetup[fd].mods &= ~(fdm_wait|fdm_nowait|fdm_close);
  fdsetup[fd].mods |= fdmip->implies;
}

static void of_defvar(const struct optioninfo *oip, const char *value, char *key) {
  int i;

  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==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;
  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;

  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;
    else if (!strcmp(value,"stdout")) signalsexit= se_stdout;
    else usageerror("value `%s' for --signals not understood",value);
  } else {
    if (numvalue<0 || numvalue>255)
      usageerror("value %lu for --signals not 0...255",numvalue);
    signalsexit= numvalue;
  }
}

static void of_sigpipe(const struct optioninfo *oip, const char *value, char *key) {
  sigpipeok=1;
}

static void of_hidecwd(const struct optioninfo *oip, const char *value, char *key) {
  hidecwd=1;
}

static void of_help(const struct optioninfo *oip, const char *value, char *key) {
  usage(stdout);
  if (fclose(stdout)) syscallerror("fclose stdout after writing usage message");
  exit(0);
}

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_copyright(const struct optioninfo *oip, const char *value, char *key) {
  if (fputs(
" userv - user service daemon and client\n\n"
COPYRIGHT(" ","\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 3 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, see <http://www.gnu.org/licenses/>.\n",
	    stdout) < 0) syscallerror("write usage to stderr");
  exit(0);
}

static void of_builtin(const struct optioninfo *oip, const char *value, char *key) {
  overridetype= ot_builtin;
}

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;
}

static void of_spoofuser(const struct optioninfo *oip,
			 const char *value, char *key) {
  spoofuser= 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      },
  { '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                                  }
};

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);
  }
}

/*
 * Main thread main processing functions - in order of execution.
 */

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 == -1) syscallerror("getgroups(0,0)");
  gidarray= xmalloc(sizeof(gid_t)*ngids);
  if (getgroups(ngids,gidarray) != ngids) syscallerror("getgroups(ngids,)");
  
  priv_suspend();

  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= *argvp+1;
       (argp= *argpp) && *argp == '-' && argp[1];
       argpp++) {
    if (!*++argp) usageerror("unknown option/argument `%s'",*argpp);
    if (*argp == '-') { /* Two hyphens */
      if (!*++argp) { argpp++; break; /* End of options. */ }
      for (oip= optioninfos; oip->full && strcmp(oip->full,argp); oip++);
      if (!oip->full) usageerror("unknown long option `%s'",*argpp);
      if (oip->values) {
        if (!argpp[1]) usageerror("long option `%s' needs a value",*argpp);
        callvalueoption(oip,*++argpp);
      } else {
        (oip->fn)(oip,0,0);
      }
    } else {
      for (; *argp; argp++) {
        for (oip= optioninfos; oip->full && oip->abbrev != *argp; oip++);
        if (!oip->full) usageerror("unknown short option `-%c' in argument `%s'",
                                    *argp, *argpp);
        if (oip->values) {
          if (argp[1]) {
            argp++;
          } else {
            if (!argpp[1]) usageerror("short option `-%c' in argument `%s' needs"
                                      " a value",*argp,*argpp);
            argp= *++argpp;
          }
          callvalueoption(oip,argp);
          break; /* No more options in this argument, go on to the next one. */
        } else {
          (oip->fn)(oip,0,0);
        }
      }
    }
  }
  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;
    if (fdsetup[fd].mods & (fdm_wait|fdm_nowait|fdm_close)) continue;
    assert(fdsetup[fd].mods & (fdm_read|fdm_write));
    fdsetup[fd].mods |= (fdsetup[fd].mods & fdm_read) ? fdm_close : fdm_wait;
  }
}

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 || spoofuser) && myuid != 0 && myuid != serviceuid)
    miscerror("--override and --spoof options only available to root or to"
              " the user who will be providing the service");

  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) 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);
    snprintf(ovbuf,l+2,"%s\n",overridevalue);
    ovused= l+1;
    break;
  case ot_file:
    ovfile= fopen(overridevalue,"r");
    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);
      if (ovused >= MAX_OVERRIDE_LEN)
        miscerror("override file is too long (max is %d)",MAX_OVERRIDE_LEN);
      if (ovused >= ovavail) {
        ovavail+=80; ovavail<<=2; ovbuf= xrealloc(ovbuf,ovavail);
      }
      ovbuf[ovused++]= c;
    }
    if (ferror(ovfile) || fclose(ovfile))
      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);
  sig.sa_flags= 0;
  if (sigaction(SIGPIPE,&sig,0)) syscallerror("ignore sigpipe");

  sfd= socket(AF_UNIX,SOCK_STREAM,0);
  if (!sfd) syscallerror("create client socket");

  assert(sizeof(ssockname.sun_path) > sizeof(RENDEZVOUSPATH));
  ssockname.sun_family= AF_UNIX;
  strcpy(ssockname.sun_path,RENDEZVOUSPATH);
  priv_resume();
  while (connect(sfd,(struct sockaddr*)&ssockname,sizeof(ssockname))) {
    if (errno == ECONNREFUSED || errno == ENOENT)
      syscallerror("uservd daemon is not running - service not available");
    if (errno != EINTR)
      fsyscallerror("unable to connect to uservd daemon: %m");
  }

  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");

  swfile= fdopen(sfd,"w");
  if (!swfile) syscallerror("turn socket fd into FILE* for write");
  if (setvbuf(swfile,0,_IOFBF,BUFSIZ)) syscallerror("set buffering on socket writes");

  xfread(&opening_mbuf,sizeof(opening_mbuf),srfile);
  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)
      fsyscallerror("remove any old pipe `%s'",pipepathbuf);
    if (mkfifo(pipepathbuf,0600)) /* permissions are irrelevant */
      fsyscallerror("create pipe `%s'",pipepathbuf);
    tempfd= open(pipepathbuf,O_RDWR);
    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<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.loginnamelen= strlen(loginname);
  request_mbuf.spoofed= spoofuser ? 1 : 0;
  request_mbuf.cwdlen= cwdbufsize;
  request_mbuf.callinguid= spoofuid;
  request_mbuf.ngids= ngids+1;
  request_mbuf.nreadfds= 0;
  request_mbuf.nwritefds= 0;
  for (fd=0; fd<fdsetupsize; fd++) {
    if (!fdsetup[fd].filename) continue;
    assert(fdsetup[fd].mods & (fdm_write|fdm_read));
    if (fdsetup[fd].mods & fdm_write) request_mbuf.nwritefds++;
    else request_mbuf.nreadfds++;
  }
  request_mbuf.nargs= argc-1;
  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(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(&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<defvarused; i++) {
    xfwritestring(defvararray[i].key,swfile);
    xfwritestring(defvararray[i].value,swfile);
  }
  ul= REQUEST_END_MAGIC; xfwrite(&ul,sizeof(ul),swfile);
  xfflush(swfile);
}

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);
  sigaddset(&sig.sa_mask,SIGALRM);
  sig.sa_flags= 0;
  if (sigaction(SIGCHLD,&sig,0)) syscallerror("set up sigchld handler");

  sig.sa_handler= sighandler_alrm;
  if (sigaction(SIGALRM,&sig,0)) syscallerror("set up sigalrm handler");

  if (!timeout) return;
  alarm(timeout);
}


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|O_NOCTTY,0777);
      if (fdsetup[fd].copyfd<0)
	fsyscallerror("open file `%s' for fd %d",fdsetup[fd].filename,fd);
    }
    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;
      if (sigaction(SIGPIPE,&sig,0)) {
	fprintf(stderr,"userv: %s: reset sigpipe handler for cat: %s",
		catnamebuf,strerror(errno));
	exit(-1);
      }
      reading= fdsetup[fd].mods & fdm_read;
      catdup(catnamebuf, fdsetup[fd].copyfd, reading ? 0 : 1);
      catdup(catnamebuf, fdsetup[fd].pipefd, reading ? 1 : 0);
      if (close(socketfd))
	fsyscallerror("%s: close client socket for for cat",catnamebuf);
      close_unwanted_pipes();
      execl("/bin/cat",catnamebuf,(char*)0);
      fprintf(stderr,"userv: %s: cannot exec `cat': %s\n",catnamebuf,strerror(errno));
      exit(-1);
    }
  }
  close_unwanted_pipes();
}

static void server_sendconfirm(void) {
  struct event_msg event_mbuf;

  blocksignals(SIG_BLOCK);
  memset(&event_mbuf,0,sizeof(event_mbuf));
  event_mbuf.magic= EVENT_MAGIC;
  event_mbuf.type= et_confirm;
  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)) fsyscallerror("kill cat for %d",fd);
    fdsetup[fd].killed= 1;
  }
  blocksignals(SIG_UNBLOCK);

  for (;;) {
    blocksignals(SIG_BLOCK);
    for (fd=0;
	 fd<fdsetupsize && !(fdsetup[fd].catpid!=-1 && (fdsetup[fd].mods & fdm_wait));
	 fd++);
    if (fd>=fdsetupsize) break;
    sigemptyset(&sset);
    r= sigsuspend(&sset);
    if (r && errno != EINTR) syscallerror("sigsuspend failed in unexpected way");
    blocksignals(SIG_UNBLOCK);
  }
}

static void NONRETURNING process_exitstatus(int status) {
  blocksignals(SIG_BLOCK);

  if (systemerror) _exit(255);

  if (sigpipeok && signalsexit != se_stdout && WIFSIGNALED(status) &&
      WTERMSIG(status)==SIGPIPE && !WCOREDUMP(status)) status= 0;
  
  switch (signalsexit) {
  case se_number:
  case se_numbernocore:
    if (WIFEXITED(status))
      _exit(WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
      _exit(WTERMSIG(status) + (signalsexit==se_number && WCOREDUMP(status) ? 128 : 0));
    break;
  case se_highbit:
    if (WIFEXITED(status))
      _exit(WEXITSTATUS(status)<=127 ? WEXITSTATUS(status) : 127);
    else if (WIFSIGNALED(status) && WTERMSIG(status)<=126)
      _exit(WTERMSIG(status)+128);
    break;
  case se_stdout:
    printf("\n%d %d ",(status>>8)&0x0ff,status&0x0ff);
    if (WIFEXITED(status))
      printf("exited with code %d",WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
      printf("killed by %s (signal %d)%s",
	     strsignal(WTERMSIG(status)),WTERMSIG(status),
	     WCOREDUMP(status) ? ", core dumped " : "");
    else
      printf("unknown wait status");
    putchar('\n');
    if (ferror(stdout) || fflush(stdout)) syscallerror("write exit status to stdout");
    _exit(0);
  default:
    if (WIFEXITED(status))
      _exit(WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
      _exit(signalsexit);
    break;
  }
      
  fprintf(stderr,"userv: unknown wait status %d\n",status);
  _exit(-1);
}

int main(int argc, char *const *argv) {
  int status;

#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);
}
