X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=userv.git;a=blobdiff_plain;f=client.c;h=523a7be3d1dd7ffd98c224b2a2678512fda5a9ff;hp=8ef35a58513f68cf6beeeeb3ea8e8748e9ef7607;hb=d237116707105a3ca6cf367a672bc5263cf7856d;hpb=dbad0a53f8c54ac65c5859d3e213a10821b797ff diff --git a/client.c b/client.c index 8ef35a5..523a7be 100644 --- 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 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 @@ -19,6 +19,36 @@ * 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 #include #include @@ -29,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -41,19 +72,9 @@ #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, @@ -75,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; @@ -165,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; @@ -214,6 +206,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) { } @@ -235,6 +254,52 @@ static void priv_permanentlyrevokesuspended(void) { } #endif +static void checkmagic(unsigned long was, unsigned long should, const char *when) { + if (was != should) + protoerror("magic number %s was %08lx, expected %08lx",when,was,should); +} + +static void getprogress(struct progress_msg *progress_r, FILE *file) { + int i, c; + unsigned long ul; + + for (;;) { + xfread(progress_r,sizeof(struct progress_msg),file); + checkmagic(progress_r->magic,PROGRESS_MAGIC,"in progress message"); + switch (progress_r->type) { + case pt_failed: + blocksignals(SIG_BLOCK); + fputs("userv: uservd reports that service failed\n",stderr); + exit(-1); + case pt_errmsg: + blocksignals(SIG_BLOCK); + fputs("uservd: ",stderr); + if (progress_r->data.errmsg.messagelen>MAX_ERRMSG_STRING) + protoerror("stderr message length %d is far too long", + progress_r->data.errmsg.messagelen); + for (i=0; idata.errmsg.messagelen; i++) { + c= working_getc(file); + if (c==EOF) protoreaderror(file,"in error message"); + if (isprint(c)) putc(c,stderr); + else fprintf(stderr,"\\x%02x",(unsigned char)c); + } + putc('\n',stderr); + if (ferror(stderr)) syscallerror("printing error message"); + xfread(&ul,sizeof(ul),file); + checkmagic(ul,PROGRESS_ERRMSG_END_MAGIC,"after error message"); + blocksignals(SIG_UNBLOCK); + break; + default: + return; + } + } +} + +/* + * Functions which are called only during setup, before + * the signal asynchronicity starts. They can do anything they like. + */ + static void *xmalloc(size_t s) { void *p; p= malloc(s?s:1); @@ -248,58 +313,198 @@ static void *xrealloc(void *p, size_t s) { return p; } -static void xfread(void *p, size_t sz, FILE *file) { - size_t nr; - nr= fread(p,1,sz,file); - if (nr != sz) protoreaderror(file,"in data"); -} - -static void xfwrite(const void *p, size_t sz, FILE *file) { - size_t nr; - nr= fwrite(p,1,sz,file); if (nr == sz) return; - syscallerror("writing to server"); -} - static void xfwritestring(const char *s, FILE *file) { int l; l= strlen(s); + 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) 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(void) { - if (fprintf(stderr, + if (fputs( "usage: userv [--] [ ...]\n" + "usage: userv -B|--builtin [--] [ ...]\n" "options: -f|--file []=\n" " -D|--defvar =\n" " -t|--timeout \n" " -S|--signals |number|number-nocore|highbit|stdout\n" " -w|--fdwait =wait|nowait|close\n" " -P|--sigpipe -H|--hidecwd -h|--help --copyright\n" - " --override } available only to\n" - " --override-file } root or same user\n" + " --override } available only\n" + " --override-file } to root\n" + " --spoof-user } 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", + stderr) < 0) + syscallerror("write usage to stderr"); } static void NONRETURNPRINTFFORMAT(1,2) usageerror(const char *fmt, ...) { va_list al; va_start(al,fmt); - fprintf(stderr,"userv: "); + fputs("userv: ",stderr); vfprintf(stderr,fmt,al); - fprintf(stderr,"\n\n"); + fputs("\n\n",stderr); usage(); exit(-1); } -static void addfdmodifier(struct fdsetupstate *fdsus, int fd, const char *key) { +static const struct fdmodifierinfo fdmodifierinfos[]= { + { "read", fdm_read, + fdm_write, + O_RDONLY }, + { "write", fdm_write, + fdm_read, + O_WRONLY }, + { "overwrite", fdm_write|fdm_create|fdm_truncate, + fdm_read|fdm_fd|fdm_exclusive, + O_WRONLY|O_CREAT|O_TRUNC }, + { "create", fdm_write|fdm_create, + fdm_read|fdm_fd, + O_WRONLY|O_CREAT }, + { "creat", fdm_write|fdm_create, + fdm_read|fdm_fd, + O_WRONLY|O_CREAT }, + { "exclusive", fdm_write|fdm_create|fdm_exclusive, + fdm_read|fdm_fd|fdm_truncate, + O_WRONLY|O_CREAT|O_EXCL }, + { "excl", fdm_write|fdm_create|fdm_exclusive, + fdm_read|fdm_fd|fdm_truncate, + O_WRONLY|O_CREAT|O_EXCL }, + { "truncate", fdm_write|fdm_truncate, + fdm_read|fdm_fd|fdm_exclusive, + O_WRONLY|O_CREAT|O_EXCL }, + { "trunc", fdm_write|fdm_truncate, + fdm_read|fdm_fd|fdm_exclusive, + O_WRONLY|O_CREAT|O_EXCL }, + { "append", fdm_write|fdm_append, + fdm_read|fdm_fd, + O_WRONLY|O_CREAT|O_APPEND }, + { "sync", fdm_write|fdm_sync, + fdm_read|fdm_fd, + O_WRONLY|O_CREAT|O_SYNC }, + { "wait", fdm_wait, + fdm_nowait|fdm_close, + 0 }, + { "nowait", fdm_nowait, + fdm_wait|fdm_close, + 0 }, + { "close", fdm_close, + fdm_wait|fdm_nowait, + 0 }, + { "fd", fdm_fd, + fdm_create|fdm_exclusive|fdm_truncate|fdm_append|fdm_sync, + 0 }, + { 0 } +}; + +static void addfdmodifier(int fd, const char *key) { const struct fdmodifierinfo *fdmip; if (!*key) return; @@ -342,11 +547,13 @@ static void of_file(const struct optioninfo *oip, const char *value, char *key) fdsetup= xrealloc(fdsetup,sizeof(struct fdsetupstate)*fdsetupsize); while (oldarraysize < fdsetupsize) { fdsetup[oldarraysize].filename= 0; + fdsetup[oldarraysize].pipefd= -1; fdsetup[oldarraysize].copyfd= -1; fdsetup[oldarraysize].mods= 0; + fdsetup[oldarraysize].oflags= 0; fdsetup[oldarraysize].catpid= -1; fdsetup[oldarraysize].killed= 0; - fdsetup[oldarraysize++].filename= 0; + fdsetup[oldarraysize].filename= 0; oldarraysize++; } } @@ -358,15 +565,15 @@ static void of_file(const struct optioninfo *oip, const char *value, char *key) key= delim; delim= strchr(key,','); if (delim) *delim++= 0; - addfdmodifier(&fdsetup[fd],fd,key); + addfdmodifier(fd,key); } if (!(fdsetup[fd].mods & (fdm_read|fdm_write))) { - if (fd != 1 && fd != 2) { - addfdmodifier(&fdsetup[fd],fd,"read"); + if (fd == 0) { + addfdmodifier(fd,"read"); } else if (fdsetup[fd].mods & fdm_fd) { - addfdmodifier(&fdsetup[fd],fd,"write"); + addfdmodifier(fd,"write"); } else { - addfdmodifier(&fdsetup[fd],fd,"overwrite"); + addfdmodifier(fd,"overwrite"); } } if (fdsetup[fd].mods & fdm_fd) { @@ -375,13 +582,13 @@ static void of_file(const struct optioninfo *oip, const char *value, char *key) copyfd= strtoul(value,&delim,0); if (*delim) usageerror("value part of argument to --file with fd modifier must be " - "numeric or fd name- `%s' is not recognised",value); + "numeric or fd name - `%s' is not recognised",value); else if (copyfd > MAX_ALLOW_FD) usageerror("file descriptor %lu named as target of file descriptor redirection" " (for file descriptor %lu) is larger than maximum allowed (%d)", copyfd,fd,MAX_ALLOW_FD); } - do { r= fstat(copyfd,&stab); } while (r && errno==EINTR); + r= fstat(copyfd,&stab); if (r) { if (oip) syscallerror("check filedescriptor %lu (named as target of file " "descriptor redirection for %lu)",copyfd,fd); @@ -402,7 +609,7 @@ static void of_fdwait(const struct optioninfo *oip, const char *value, char *key ul= strtoul(key,&delim,0); if (*delim) usageerror("first part of argument to --fdwait must be " "numeric or fd name - `%s' is not recognised",key); - if (ul>INT_MAX) usageerror("first part of argument to --fdwait is far too large"); + if (ul>MAX_ALLOW_FD) usageerror("first part of argument to --fdwait is too large"); fd= ul; } if (fd >= fdsetupsize || !fdsetup[fd].filename) @@ -417,26 +624,37 @@ 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=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= 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; @@ -465,8 +683,8 @@ static void of_help(const struct optioninfo *oip, const char *value, char *key) } static void of_copyright(const struct optioninfo *oip, const char *value, char *key) { - if (fprintf(stdout, -" 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" @@ -476,13 +694,17 @@ static void of_copyright(const struct optioninfo *oip, const char *value, char * " 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 or\n" +" with userv; if not, write to Ian Jackson 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; @@ -494,6 +716,11 @@ static void of_overridefile(const struct optioninfo *oip, 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 }, @@ -502,10 +729,12 @@ const struct optioninfo optioninfos[]= { { '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, "copyright", 0, of_copyright }, { 0, "override", 1, of_override }, { 0, "override-file", 1, of_overridefile }, + { 0, "spoof-user", 1, of_spoofuser }, { 0, 0 } }; @@ -513,13 +742,14 @@ static void callvalueoption(const struct optioninfo *oip, char *arg) { 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 { @@ -527,179 +757,42 @@ static void callvalueoption(const struct optioninfo *oip, char *arg) { } } -static void checkmagic(unsigned long was, unsigned long should, const char *when) { - if (was != should) - protoerror("magic number %s was %08lx, expected %08lx",when,was,should); -} - -static void getprogress(struct progress_msg *progress_r, FILE *file) { - int i, c; - unsigned long ul; - - for (;;) { - xfread(progress_r,sizeof(struct progress_msg),file); - switch (progress_r->type) { - case pt_failed: - blocksignals(SIG_BLOCK); - fputs("userv: uservd reports that service failed\n",stderr); - exit(-1); - case pt_errmsg: - blocksignals(SIG_BLOCK); - fputs("uservd: ",stderr); - if (progress_r->data.errmsg.messagelen>4096) - protoerror("stderr message length %d is far too long", - progress_r->data.errmsg.messagelen); - for (i=0; idata.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) 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 */ @@ -733,9 +826,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; fdpw_uid != myuid) loginname= 0; + } + if (!loginname) { + pw= getpwuid(myuid); if (!pw) miscerror("cannot determine your login name"); + loginname= 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: @@ -818,6 +965,19 @@ int main(int argc, char *const *argv) { default: abort(); } +} + +static int server_connect(void) { + struct sockaddr_un ssockname; + int sfd; + struct sigaction sig; + sigset_t sset; + + sigemptyset(&sset); + sigaddset(&sset,SIGCHLD); + sigaddset(&sset,SIGALRM); + sigaddset(&sset,SIGPIPE); + if (sigprocmask(SIG_UNBLOCK,&sset,0)) syscallerror("preliminarily unblock signals"); sig.sa_handler= SIG_IGN; sigemptyset(&sig.sa_mask); @@ -834,10 +994,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) + syscallerror("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"); @@ -850,34 +1014,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=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