* userv - client.c
* client code
*
- * Copyright (C)1996-1997 Ian Jackson
+ * Copyright (C)1996-1997,1999 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>
#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"
+#include "both.h"
#include "version.h"
-struct optioninfo;
-
-typedef void optionfunction(const struct optioninfo*, const char *value, char *key);
-
-struct optioninfo {
- int abbrev;
- const char *full;
- int values; /* 0: no value; 1: single value; 2: key and value */
- optionfunction *fn;
-};
-
enum fdmodifiervalues {
fdm_read= 00001,
fdm_write= 00002,
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;
}
}
+/* 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;
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;
exit(-1);
}
+void syscallerror(const char *what) {
+ fsyscallerror("%s",what);
+}
+
static void NONRETURNING protoreaderror(FILE *file, const char *where) {
int e;
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) { }
}
#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 sighandler_alrm(int ignored) /* DOES return, unlike in daemon */ {
+ int es;
+ es= errno;
+ fputs("userv: timeout\n",stderr);
+ disconnect();
+ errno= es;
}
-static void usage(void) {
- if (fprintf(stderr,
+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 --copyright\n"
- " --override <configuration-data> } available only to\n"
- " --override-file <filename> } root or same user\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\n"
- "userv and uservd version " VERSION "; copyright (C)1996-1997 Ian Jackson.\n"
- "They come with NO WARRANTY; type `userv --copyright' for details.\n")
- == EOF) syscallerror("write usage to stderr");
+ "(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) {
const struct fdmodifierinfo *fdmip;
if (!*key) return;
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++;
}
}
key= delim;
delim= strchr(key,',');
if (delim) *delim++= 0;
- addfdmodifier(&fdsetup[fd],fd,key);
+ addfdmodifier(fd,key);
}
if (!(fdsetup[fd].mods & (fdm_read|fdm_write))) {
- if (fd != 1 && fd != 2) {
- addfdmodifier(&fdsetup[fd],fd,"read");
+ if (fd == 0) {
+ addfdmodifier(fd,"read");
} else if (fdsetup[fd].mods & fdm_fd) {
- addfdmodifier(&fdsetup[fd],fd,"write");
+ addfdmodifier(fd,"write");
} else {
- addfdmodifier(&fdsetup[fd],fd,"overwrite");
+ addfdmodifier(fd,"overwrite");
}
}
if (fdsetup[fd].mods & fdm_fd) {
copyfd= strtoul(value,&delim,0);
if (*delim)
usageerror("value part of argument to --file with fd modifier must be "
- "numeric or fd name- `%s' is not recognised",value);
+ "numeric or fd name - `%s' is not recognised",value);
else if (copyfd > MAX_ALLOW_FD)
usageerror("file descriptor %lu named as target of file descriptor redirection"
" (for file descriptor %lu) is larger than maximum allowed (%d)",
copyfd,fd,MAX_ALLOW_FD);
}
- do { r= fstat(copyfd,&stab); } while (r && errno==EINTR);
+ r= fstat(copyfd,&stab);
if (r) {
- if (oip) syscallerror("check filedescriptor %lu (named as target of file "
- "descriptor redirection for %lu)",copyfd,fd);
- 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;
}
ul= strtoul(key,&delim,0);
if (*delim) usageerror("first part of argument to --fdwait must be "
"numeric or fd name - `%s' is not recognised",key);
- if (ul>INT_MAX) usageerror("first part of argument to --fdwait is far too large");
+ if (ul>MAX_ALLOW_FD) usageerror("first part of argument to --fdwait is too large");
fd= ul;
}
if (fd >= fdsetupsize || !fdsetup[fd].filename)
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);
+ unsigned long ul;
+
+ ul= strtoul(value,&endp,0);
if (*endp) usageerror("timeout value `%s' must be a plain decimal string",value);
- if (timeout>INT_MAX) usageerror("timeout value %lu too large",timeout);
+ if (ul>INT_MAX) usageerror("timeout value %lu too large",ul);
+ timeout= ul;
}
static void of_signals(const struct optioninfo *oip, const char *value, char *key) {
unsigned long numvalue;
char *endp;
+
numvalue= strtoul(value,&endp,0);
if (*endp) {
if (!strcmp(value,"number")) signalsexit= se_number;
}
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_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 (fprintf(stdout,
-" userv - user service daemon and client; copyright (C)1996-1997 Ian Jackson\n\n"
+ 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"
" 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"
+" 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"
- ) == EOF) syscallerror("write usage to stderr");
+" MA 02111-1307, USA.\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;
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 },
{ '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 }
};
char *equals;
if (oip->values == 2) {
equals= strchr(arg,'=');
- if (!equals)
+ 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 {
}
}
-static void checkmagic(unsigned long was, unsigned long should, const char *when) {
- if (was != should)
- protoerror("magic number %s was %08lx, expected %08lx",when,was,should);
-}
-
-static void getprogress(struct progress_msg *progress_r, FILE *file) {
- int i, c;
- unsigned long ul;
-
- for (;;) {
- xfread(progress_r,sizeof(struct progress_msg),file);
- switch (progress_r->type) {
- case pt_failed:
- blocksignals(SIG_BLOCK);
- fputs("userv: uservd reports that service failed\n",stderr);
- exit(-1);
- case pt_errmsg:
- blocksignals(SIG_BLOCK);
- fputs("uservd: ",stderr);
- if (progress_r->data.errmsg.messagelen>4096)
- protoerror("stderr message length %d is far too long",
- progress_r->data.errmsg.messagelen);
- for (i=0; i<progress_r->data.errmsg.messagelen; i++) {
- c= getc(file); if (c==EOF) protoreaderror(file,"in error message");
- if (isprint(c)) putc(c,stderr);
- else fprintf(stderr,"\\x%02x",(unsigned char)c);
- }
- putc('\n',stderr);
- if (ferror(stderr)) syscallerror("printing error message");
- xfread(&ul,sizeof(ul),file);
- checkmagic(ul,PROGRESS_ERRMSG_END_MAGIC,"after error message");
- blocksignals(SIG_UNBLOCK);
- break;
- default:
- return;
- }
- }
-}
-
-static void xfwritefds(int modifier, int expected, FILE *file) {
- int i, fdcount;
-
- for (i=0, fdcount=0; i<fdsetupsize; i++) {
- if (!(fdsetup[i].filename && (fdsetup[i].mods & modifier)))
- continue;
- xfwrite(&i,sizeof(int),file); fdcount++;
- }
- assert(fdcount == expected);
-}
-
-static void disconnect(void) /* DOES return, unlike in daemon */ {
- struct event_msg event_mbuf;
-
- if (!swfile) {
- fputs("userv: failed, after service program terminated\n",stderr);
- _exit(255);
- }
- memset(&event_mbuf,0,sizeof(event_mbuf));
- event_mbuf.magic= EVENT_MAGIC;
- event_mbuf.type= et_disconnect;
- xfwrite(&event_mbuf,sizeof(event_mbuf),swfile);
- xfflush(swfile);
-}
-
-static void sighandler_alrm(int ignored) /* DOES return, unlike in daemon */ {
- int es;
- es= errno;
- fputs("userv: timeout\n",stderr);
- disconnect();
- errno= es;
-}
-
-static void sighandler_chld(int ignored) /* DOES return, unlike in daemon */ {
- struct event_msg event_mbuf;
- pid_t child;
- int status, fd, r, es;
+/*
+ * Main thread main processing functions - in order of execution.
+ */
- es= errno;
- for (;;) {
- child= wait3(&status,WNOHANG,0);
- if (child == 0 || (child == -1 && errno == ECHILD)) break;
- if (child == -1) syscallerror("wait for child process (in sigchld handler)");
- for (fd=0; fd<fdsetupsize && fdsetup[fd].catpid != child; fd++);
- if (fd>=fdsetupsize) continue; /* perhaps the invoker gave us children */
- if ((WIFEXITED(status) && WEXITSTATUS(status)==0) ||
- (WIFSIGNALED(status) && WTERMSIG(status)==SIGPIPE) ||
- (fdsetup[fd].killed && WIFSIGNALED(status) && WTERMSIG(status)==SIGKILL)) {
- if (swfile && fdsetup[fd].mods & fdm_read) {
- memset(&event_mbuf,0,sizeof(event_mbuf));
- event_mbuf.magic= EVENT_MAGIC;
- event_mbuf.type= et_closereadfd;
- r= fwrite(&event_mbuf,1,sizeof(event_mbuf),swfile);
- if (r != sizeof(event_mbuf) || fflush(swfile))
- if (errno != EPIPE) syscallerror("inform service of closed read fd");
- }
- } else {
- if (WIFEXITED(status))
- fprintf(stderr,"userv: cat for fd %d exited with error exit status %d\n",
- fd,WEXITSTATUS(status));
- else if (WIFSIGNALED(status))
- if (WCOREDUMP(status))
- fprintf(stderr,"userv: cat for fd %d dumped core due to signal %s (%d)\n",
- fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
- else
- fprintf(stderr,"userv: cat for fd %d terminated by signal %s (%d)\n",
- fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
- else
- fprintf(stderr,"userv: cat for fd %d gave unknown wait status %d\n",
- fd,status);
- disconnect();
- }
- fdsetup[fd].catpid= -1;
- }
- errno= es;
-}
+static void security_init(void) {
+ /* May not open any file descriptors. */
+
+ mypid= getpid(); if (mypid == (pid_t)-1) syscallerror("getpid");
+ myuid= getuid(); if (myuid == (uid_t)-1) syscallerror("getuid");
+ mygid= getgid(); if (mygid == (gid_t)-1) syscallerror("getgid");
+ ngids= getgroups(0,0); if (ngids == (gid_t)-1) syscallerror("getgroups(0,0)");
+ gidarray= xmalloc(sizeof(gid_t)*ngids);
+ if (getgroups(ngids,gidarray) != ngids) syscallerror("getgroups(ngids,)");
+
+ priv_suspend();
-static void catdup(const char *which, int from, int to) {
- if (dup2(from,to)<0) {
- blocksignals(SIG_BLOCK);
- fprintf(stderr,"userv: %s: cannot dup for %s: %s\n",which,
- to?"stdout":"stdin", strerror(errno));
- exit(-1);
- }
+ if (ngids > MAX_GIDS) miscerror("caller is in far too many gids");
}
-int main(int argc, char *const *argv) {
+static void parse_arguments(int *argcp, char *const **argvp) {
static char fd0key[]= "stdin,fd,read";
static char fd1key[]= "stdout,fd,write";
static char fd2key[]= "stderr,fd,write";
- static char stderrbuf[BUFSIZ], stdoutbuf[1024];
-
+
char *const *argpp;
char *argp;
const struct optioninfo *oip;
- struct sockaddr_un ssockname;
- int sfd, ngids, i, 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();
+ int fd;
- assert(argv[0]);
+ assert((*argvp)[0]);
of_file(0,"stdin",fd0key);
of_file(0,"stdout",fd1key);
of_file(0,"stderr",fd2key);
- for (argpp= argv+1;
- (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 */
}
}
}
- 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;
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);
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);
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");
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;
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);
sig.sa_handler= sighandler_alrm;
if (sigaction(SIGALRM,&sig,0)) syscallerror("set up sigalrm handler");
+ if (!timeout) return;
+ if (alarm(timeout)<0) syscallerror("set up timeout alarm");
+}
+
+static void catdup(const char *which, int from, int to) {
+ if (dup2(from,to)<0) {
+ blocksignals(SIG_BLOCK);
+ fprintf(stderr,"userv: %s: cannot dup for %s: %s\n",which,
+ to?"stdout":"stdin", strerror(errno));
+ exit(-1);
+ }
+}
+
+static void connect_pipes(void) {
+ struct sigaction sig;
+ char catnamebuf[sizeof(int)*3+30];
+ int fd, reading;
+ 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;
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);
+ 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);
+ if (close(fdsetup[fd].copyfd)) fsyscallerror("close real fd for %d",fd);
+ if (close(fdsetup[fd].pipefd)) fsyscallerror("close pipe fd for %d",fd);
}
+}
- if (timeout)
- if (alarm(timeout)<0) syscallerror("set up timeout alarm");
+static void server_sendconfirm(void) {
+ struct event_msg event_mbuf;
blocksignals(SIG_BLOCK);
memset(&event_mbuf,0,sizeof(event_mbuf));
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);
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;
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);
+}