X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=userv.git;a=blobdiff_plain;f=client.c;h=47600090ecc09fcda86200b41f3e08db83bf6463;hp=68ec6a4d1b9ff68dafbd821f3bb3c3078016ba94;hb=f53cd40f20431be077e96cb70e6dae9692276356;hpb=bcc2d35c94818f165137a0dcf9abab50ab7b5bb2 diff --git a/client.c b/client.c index 68ec6a4..4760009 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-2001,2003 Ian Jackson * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by @@ -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,17 +72,8 @@ #include "config.h" #include "common.h" - -struct optioninfo; - -typedef void optionfunction(const struct optioninfo*, const char *value, char *key); - -struct optioninfo { - int abbrev; - const char *full; - int values; /* 0: no value; 1: single value; 2: key and value */ - optionfunction *fn; -}; +#include "both.h" +#include "version.h" enum fdmodifiervalues { fdm_read= 00001, @@ -74,78 +96,43 @@ struct fdmodifierinfo { int oflags; }; -const struct fdmodifierinfo fdmodifierinfos[]= { - { "read", fdm_read, - fdm_write, - O_RDONLY }, - { "write", fdm_write, - fdm_read, - O_WRONLY }, - { "overwrite", fdm_write|fdm_create|fdm_truncate, - fdm_read|fdm_fd|fdm_exclusive, - O_WRONLY|O_CREAT|O_TRUNC }, - { "create", fdm_write|fdm_create, - fdm_read|fdm_fd, - O_WRONLY|O_CREAT }, - { "creat", fdm_write|fdm_create, - fdm_read|fdm_fd, - O_WRONLY|O_CREAT }, - { "exclusive", fdm_write|fdm_create|fdm_exclusive, - fdm_read|fdm_fd|fdm_truncate, - O_WRONLY|O_CREAT|O_EXCL }, - { "excl", fdm_write|fdm_create|fdm_exclusive, - fdm_read|fdm_fd|fdm_truncate, - O_WRONLY|O_CREAT|O_EXCL }, - { "truncate", fdm_write|fdm_truncate, - fdm_read|fdm_fd|fdm_exclusive, - O_WRONLY|O_CREAT|O_EXCL }, - { "trunc", fdm_write|fdm_truncate, - fdm_read|fdm_fd|fdm_exclusive, - O_WRONLY|O_CREAT|O_EXCL }, - { "append", fdm_write|fdm_append, - fdm_read|fdm_fd, - O_WRONLY|O_CREAT|O_APPEND }, - { "sync", fdm_write|fdm_sync, - fdm_read|fdm_fd, - O_WRONLY|O_CREAT|O_SYNC }, - { "wait", fdm_wait, - fdm_nowait|fdm_close, - 0 }, - { "nowait", fdm_nowait, - fdm_wait|fdm_close, - 0 }, - { "close", fdm_close, - fdm_wait|fdm_nowait, - 0 }, - { "fd", fdm_fd, - fdm_create|fdm_exclusive|fdm_truncate|fdm_append|fdm_sync, - 0 }, - { 0 } -}; - struct fdsetupstate { - const char *filename; - int copyfd; - int mods, oflags, pipefd, killed; - pid_t catpid; + const char *filename; /* non-null iff this fd has been specified */ + int copyfd; /* fd to copy, -1 unless mods & fdm_fd */ + int mods, oflags, pipefd, killed; /* 0,0,-1,0 unless otherwise set */ + pid_t catpid; /* -1 indicates no cat process */ }; enum signalsexitspecials { se_number=-100, se_numbernocore, se_highbit, se_stdout }; -enum overridetypes { ot_none, ot_string, ot_file }; - -static const char *serviceuser=0; -static uid_t serviceuid, myuid; -static struct fdsetupstate *fdsetup=0; -static int fdsetupsize=0; -static const char *(*defvarsarray)[2]; -static int defvarsavail=0, defvarsused=0; -static unsigned long timeout=0; +enum overridetypes { ot_none, ot_string, ot_file, ot_builtin }; + +struct constkeyvaluepair { const char *key, *value; }; + +/* Variables from command-line arguments */ +static const char *serviceuser; +static uid_t serviceuid; +static struct fdsetupstate *fdsetup; +static int fdsetupsize; +static struct constkeyvaluepair *defvararray; +static int defvaravail, defvarused; +static unsigned long timeout; static int signalsexit=254; -static int sigpipeok=0, hidecwd=0; +static int sigpipeok, hidecwd; static int overridetype= ot_none; -static const char *overridevalue; +static const char *overridevalue, *spoofuser=0; +/* Other state variables */ static FILE *srfile, *swfile; +static pid_t mypid; +static uid_t myuid, spoofuid; +static gid_t mygid, spoofgid, *gidarray; +static int ngids; +static struct opening_msg opening_mbuf; +static const char *loginname; +static char *cwdbuf; +static size_t cwdbufsize; +static char *ovbuf; +static int ovused, systemerror, socketfd; static void blocksignals(int how) { sigset_t set; @@ -164,6 +151,12 @@ static void blocksignals(int how) { } } +/* Functions which may be called either from signal handlers or from + * the main thread. They block signals in case they are on the main + * thread, and may only use signal handler objects. None of them + * return. If they did they'd have to restore the signal mask. + */ + static void NONRETURNPRINTFFORMAT(1,2) miscerror(const char *fmt, ...) { va_list al; @@ -175,7 +168,7 @@ static void NONRETURNPRINTFFORMAT(1,2) miscerror(const char *fmt, ...) { exit(-1); } -static void NONRETURNPRINTFFORMAT(1,2) syscallerror(const char *fmt, ...) { +static void NONRETURNPRINTFFORMAT(1,2) fsyscallerror(const char *fmt, ...) { va_list al; int e; @@ -188,6 +181,10 @@ static void NONRETURNPRINTFFORMAT(1,2) syscallerror(const char *fmt, ...) { exit(-1); } +void syscallerror(const char *what) { + fsyscallerror("%s",what); +} + static void NONRETURNING protoreaderror(FILE *file, const char *where) { int e; @@ -213,6 +210,33 @@ static void NONRETURNPRINTFFORMAT(1,2) protoerror(const char *fmt, ...) { exit(-1); } +/* + * General-purpose functions; these do nothing special about signals, + * except that they can call error-handlers which may block them + * to print error messages. + */ + +static void xfread(void *p, size_t sz, FILE *file) { + size_t nr; + nr= working_fread(p,sz,file); + if (nr != sz) protoreaderror(file,"in data"); +} + +static void xfwrite(const void *p, size_t sz, FILE *file) { + size_t nr; + nr= fwrite(p,1,sz,file); if (nr == sz) return; + syscallerror("writing to server"); +} + +static void xfflush(FILE *file) { + if (fflush(file)) syscallerror("flush server socket"); +} + +/* Functions which may be called only from the main thread. These may + * use main-thread objects and must block signals before using signal + * handler objects. + */ + #ifdef DEBUG static void priv_suspend(void) { } static void priv_resume(void) { } @@ -234,118 +258,351 @@ static void priv_permanentlyrevokesuspended(void) { } #endif -static void *xmalloc(size_t s) { - void *p; - p= malloc(s?s:1); - if (!p) syscallerror("malloc (%lu bytes)",(unsigned long)s); - return p; +static void checkmagic(unsigned long was, unsigned long should, const char *when) { + if (was != should) + protoerror("magic number %s was %08lx, expected %08lx",when,was,should); } -static void *xrealloc(void *p, size_t s) { - p= realloc(p,s); - if (!p) syscallerror("realloc (%lu bytes)",(unsigned long)s); - return p; -} +static void getprogress(struct progress_msg *progress_r, FILE *file) { + int i, c; + unsigned long ul; -static void xfread(void *p, size_t sz, FILE *file) { - size_t nr; - nr= fread(p,1,sz,file); - if (nr != sz) protoreaderror(file,"in data"); + for (;;) { + xfread(progress_r,sizeof(struct progress_msg),file); + checkmagic(progress_r->magic,PROGRESS_MAGIC,"in progress message"); + switch (progress_r->type) { + case pt_failed: + blocksignals(SIG_BLOCK); + fputs("userv: uservd reports that service failed\n",stderr); + exit(-1); + case pt_errmsg: + blocksignals(SIG_BLOCK); + fputs("uservd: ",stderr); + if (progress_r->data.errmsg.messagelen>MAX_ERRMSG_STRING) + protoerror("stderr message length %d is far too long", + progress_r->data.errmsg.messagelen); + for (i=0; idata.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 [--] [ ...]\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" - "fdmodifiers: read write overwrite trunc[ate]\n" - "(separate with commas) append sync excl[usive] creat[e] fd\n\n" - "userv and uservd are copyright (C)1996-1997 Ian Jackson.\n" - "They come with NO WARRANTY; type `userv --copyright' for details.\n") - == EOF) syscallerror("write usage to stderr"); +/* 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) 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 [--] [ ...]\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|--version --copyright\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" + "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); - fprintf(stderr,"userv: "); + fputs("userv: ",stderr); vfprintf(stderr,fmt,al); - fprintf(stderr,"\n\n"); - usage(); + fputs("\n\n",stderr); + usage(stderr); exit(-1); } -static void addfdmodifier(struct fdsetupstate *fdsus, int fd, const char *key) { +static const struct fdmodifierinfo fdmodifierinfos[]= { + { "read", fdm_read, + fdm_write, + O_RDONLY }, + { "write", fdm_write, + fdm_read, + O_WRONLY }, + { "overwrite", fdm_write|fdm_create|fdm_truncate, + fdm_read|fdm_fd|fdm_exclusive, + O_WRONLY|O_CREAT|O_TRUNC }, + { "create", fdm_write|fdm_create, + fdm_read|fdm_fd, + O_WRONLY|O_CREAT }, + { "creat", fdm_write|fdm_create, + fdm_read|fdm_fd, + O_WRONLY|O_CREAT }, + { "exclusive", fdm_write|fdm_create|fdm_exclusive, + fdm_read|fdm_fd|fdm_truncate, + O_WRONLY|O_CREAT|O_EXCL }, + { "excl", fdm_write|fdm_create|fdm_exclusive, + fdm_read|fdm_fd|fdm_truncate, + O_WRONLY|O_CREAT|O_EXCL }, + { "truncate", fdm_write|fdm_truncate, + fdm_read|fdm_fd|fdm_exclusive, + O_WRONLY|O_CREAT|O_EXCL }, + { "trunc", fdm_write|fdm_truncate, + fdm_read|fdm_fd|fdm_exclusive, + O_WRONLY|O_CREAT|O_EXCL }, + { "append", fdm_write|fdm_append, + fdm_read|fdm_fd, + O_WRONLY|O_CREAT|O_APPEND }, + { "sync", fdm_write|fdm_sync, + fdm_read|fdm_fd, + O_WRONLY|O_CREAT|O_SYNC }, + { "wait", fdm_wait, + fdm_nowait|fdm_close, + 0 }, + { "nowait", fdm_nowait, + fdm_wait|fdm_close, + 0 }, + { "close", fdm_close, + fdm_wait|fdm_nowait, + 0 }, + { "fd", fdm_fd, + fdm_create|fdm_exclusive|fdm_truncate|fdm_append|fdm_sync, + 0 }, + { 0 } +}; + +static void addfdmodifier(int fd, const char *key, size_t key_len) { const struct fdmodifierinfo *fdmip; if (!*key) return; - for (fdmip= fdmodifierinfos; fdmip->string && strcmp(fdmip->string,key); fdmip++); - if (!fdmip->string) usageerror("unknown fdmodifer `%s' for fd %d",key,fd); + for (fdmip= fdmodifierinfos; + fdmip->string && + !(strlen(fdmip->string) == key_len && + !memcmp(fdmip->string,key,key_len)); + fdmip++); + if (!fdmip->string) usageerror("unknown fdmodifer `%.*s' for fd %d", + (int)key_len,key,fd); if (fdmip->conflicts & fdsetup[fd].mods) - usageerror("fdmodifier `%s' conflicts with another for fd %d",key,fd); + usageerror("fdmodifier `%.*s' conflicts with another for fd %d", + (int)key_len,key,fd); fdsetup[fd].mods |= fdmip->implies; fdsetup[fd].oflags |= fdmip->oflags; } -static int fdstdnumber(const char *string) { - if (!strcmp(string,"stdin")) return 0; - else if (!strcmp(string,"stdout")) return 1; - else if (!strcmp(string,"stderr")) return 2; - else return -1; +static void addfdmodifier_fixed(int fd, const char *key) { + addfdmodifier(fd, key, strlen(key)); } +static int fdstdnumber(const char *string, const char **delim_r) { +#define FN(v,s) do{ \ + if (!memcmp(string,s,sizeof(s)-1)) { \ + *delim_r= string+sizeof(s)-1; \ + return v; \ + } \ + }while(0) + + FN(0,"stdin"); + FN(1,"stdout"); + FN(2,"stderr"); + + return -1; +} + +static int strtofd(const char *string, + const char **mods_r /* 0: no modifiers, must go to end */, + const char *what) { + int fd; + unsigned long ul; + char *delim_v; + const char *mods; + + fd= fdstdnumber(string,&mods); + if (fd>=0) { + if (*mods && *mods != ',') + usageerror("%s, when it is `stdin', `stdout' or `stderr'," + " must be delimited with a comma from any following" + " modifiers - `%s' is not permitted", + what, mods); + goto parsed; + } + + errno= 0; + ul= strtoul(string,&delim_v,10); + if (errno || delim_v == string || ul > INT_MAX) + usageerror("%s must be must be numeric file descriptor" + " or `stdin', `stdout' or `stderr'" + " - `%s' is not recognized", + what, string); + mods= delim_v; + fd= ul; + + parsed: + if (*mods==',') + mods++; + + if (mods_r) + *mods_r= mods; + else if (*mods) + usageerror("%s must be must be only file descriptor" + " - trailing portion or modifiers `%s' not permitted", + what, mods); + + if (fd > MAX_ALLOW_FD) + usageerror("%s file descriptor specified (%d)" + " is larger than maximum allowed (%d)", + what, fd, MAX_ALLOW_FD); + + return fd; +} + static void of_file(const struct optioninfo *oip, const char *value, char *key) { unsigned long fd, copyfd; struct stat stab; int oldarraysize, r; - char *delim; - - fd= strtoul(key,&delim,10); - if (delim == key) { - delim= strchr(key,','); - if (delim) *delim++= 0; - fd= fdstdnumber(key); - if (fd<0) usageerror("first part of argument to -f or --file must be numeric " - "file descriptor or `stdin', `stdout' or `stderr' - `%s' " - "is not recognized",key); - } - if (fd > MAX_ALLOW_FD) - usageerror("file descriptor specified (%lu) is larger than maximum allowed (%d)", - fd,MAX_ALLOW_FD); + size_t mod_len; + const char *mods, *delim; + + fd= strtofd(key,&mods,"first part of argument to -f or --file"); + if (fd >= fdsetupsize) { oldarraysize= fdsetupsize; fdsetupsize+=2; fdsetupsize<<=1; fdsetup= xrealloc(fdsetup,sizeof(struct fdsetupstate)*fdsetupsize); while (oldarraysize < fdsetupsize) { fdsetup[oldarraysize].filename= 0; + fdsetup[oldarraysize].pipefd= -1; fdsetup[oldarraysize].copyfd= -1; fdsetup[oldarraysize].mods= 0; + fdsetup[oldarraysize].oflags= 0; fdsetup[oldarraysize].catpid= -1; fdsetup[oldarraysize].killed= 0; - fdsetup[oldarraysize++].filename= 0; + fdsetup[oldarraysize].filename= 0; oldarraysize++; } } @@ -353,38 +610,29 @@ static void of_file(const struct optioninfo *oip, const char *value, char *key) fdsetup[fd].oflags= 0; fdsetup[fd].mods= 0; fdsetup[fd].copyfd= -1; - while (delim && *delim) { - key= delim; - delim= strchr(key,','); - if (delim) *delim++= 0; - addfdmodifier(&fdsetup[fd],fd,key); + while (mods && *mods) { + delim= strchr(mods,','); + mod_len= delim ? delim-mods : strlen(mods); + addfdmodifier(fd,mods,mod_len); + mods= delim ? delim+1 : 0; } if (!(fdsetup[fd].mods & (fdm_read|fdm_write))) { - if (fd != 1 && fd != 2) { - addfdmodifier(&fdsetup[fd],fd,"read"); + if (fd == 0) { + addfdmodifier_fixed(fd,"read"); } else if (fdsetup[fd].mods & fdm_fd) { - addfdmodifier(&fdsetup[fd],fd,"write"); + addfdmodifier_fixed(fd,"write"); } else { - addfdmodifier(&fdsetup[fd],fd,"overwrite"); + addfdmodifier_fixed(fd,"overwrite"); } } if (fdsetup[fd].mods & fdm_fd) { - copyfd= fdstdnumber(value); - if (copyfd<0) { - copyfd= strtoul(value,&delim,0); - if (*delim) - usageerror("value part of argument to --file with fd modifier must be " - "numeric or fd name- `%s' is not recognised",value); - else if (copyfd > MAX_ALLOW_FD) - usageerror("file descriptor %lu named as target of file descriptor redirection" - " (for file descriptor %lu) is larger than maximum allowed (%d)", - copyfd,fd,MAX_ALLOW_FD); - } - do { r= fstat(copyfd,&stab); } while (r && errno==EINTR); + copyfd= strtofd(value,0, + "value part of argument to --file with fd modifier"); + r= fstat(copyfd,&stab); if (r) { - if (oip) syscallerror("check filedescriptor %lu (named as target of file " - "descriptor redirection for %lu)",copyfd,fd); - else syscallerror("check basic filedescriptor %lu at program start",copyfd); + if (oip) fsyscallerror("check filedescriptor %lu (named as target of file " + "descriptor redirection for %lu)",copyfd,fd); + else fsyscallerror("check basic filedescriptor %lu at program start",copyfd); } fdsetup[fd].copyfd= copyfd; } @@ -392,18 +640,9 @@ static void of_file(const struct optioninfo *oip, const char *value, char *key) static void of_fdwait(const struct optioninfo *oip, const char *value, char *key) { const struct fdmodifierinfo *fdmip; - unsigned long ul; int fd; - char *delim; - fd= fdstdnumber(key); - if (fd<0) { - ul= strtoul(key,&delim,0); - if (*delim) usageerror("first part of argument to --fdwait must be " - "numeric or fd name - `%s' is not recognised",key); - if (ul>INT_MAX) usageerror("first part of argument to --fdwait is far too large"); - fd= ul; - } + fd= strtofd(key,0,"first part of argument to --fdwait"); if (fd >= fdsetupsize || !fdsetup[fd].filename) usageerror("file descriptor %d specified in --fdwait option is not open",fd); for (fdmip= fdmodifierinfos; fdmip->string && strcmp(fdmip->string,value); fdmip++); @@ -416,28 +655,42 @@ static void of_fdwait(const struct optioninfo *oip, const char *value, char *key static void of_defvar(const struct optioninfo *oip, const char *value, char *key) { int i; - for (i=0; i=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); - if (*endp) usageerror("timeout value `%s' must be a plain decimal string",value); - if (timeout>INT_MAX) usageerror("timeout value %lu too large",timeout); + unsigned long ul; + + errno= 0; + ul= strtoul(value,&endp,10); + if (errno || *endp) + usageerror("timeout value `%s' must be a plain decimal string",value); + if (ul>INT_MAX) usageerror("timeout value %lu too large",ul); + timeout= ul; } static void of_signals(const struct optioninfo *oip, const char *value, char *key) { unsigned long numvalue; char *endp; - numvalue= strtoul(value,&endp,0); - if (*endp) { + + errno= 0; + numvalue= strtoul(value,&endp,10); + if (errno || *endp) { if (!strcmp(value,"number")) signalsexit= se_number; else if (!strcmp(value,"number-nocore")) signalsexit= se_numbernocore; else if (!strcmp(value,"highbit")) signalsexit= se_highbit; @@ -459,13 +712,22 @@ static void of_hidecwd(const struct optioninfo *oip, const char *value, char *ke } static void of_help(const struct optioninfo *oip, const char *value, char *key) { - usage(); + usage(stdout); + if (fclose(stdout)) syscallerror("fclose stdout after writing usage message"); + exit(0); +} + +static void of_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\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 2 of the License, or (at your option) any\n" @@ -475,13 +737,15 @@ 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" -" to the Free Software Foundation, 59 Temple Place - Suite 330, Boston,\n" -" MA 02111-1307, USA.\n" - ) == EOF) syscallerror("write usage to stderr"); +" with userv; if not, see .\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; @@ -493,6 +757,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 }, @@ -501,10 +770,13 @@ 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, "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 } }; @@ -512,13 +784,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 { @@ -526,179 +799,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 == -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 */ @@ -732,9 +868,20 @@ int main(int argc, char *const *argv) { } } } - if (!*argpp) usageerror("no service user given after options"); - serviceuser= *argpp++; - if (!*argpp) usageerror("no service name given after options and service user"); + if (overridetype == ot_builtin) { + serviceuser= "-"; + } else { + if (!*argpp) usageerror("no service user given after options"); + serviceuser= *argpp++; + } + if (!*argpp) usageerror(overridetype == ot_builtin ? + "no service name given after options and service user" : + "no builtin service given after options"); + + *argcp-= (argpp-*argvp); + *argvp= argpp; + + if (*argcp > MAX_ARGSDEFVAR) usageerror("far too many arguments"); for (fd=0; fdpw_uid != myuid) loginname= 0; + } + if (!loginname) { + pw= getpwuid(myuid); if (!pw) miscerror("cannot determine your login name"); + loginname= xstrsave(pw->pw_name); + } + if (!strcmp(serviceuser,"-")) serviceuser= loginname; pw= getpwnam(serviceuser); if (!pw) miscerror("requested service user `%s' is not a user",serviceuser); serviceuid= pw->pw_uid; - if (overridetype != ot_none && myuid != 0 && myuid != serviceuid) - miscerror("--override options only available to root or to" + if ((overridetype != ot_none || spoofuser) && myuid != 0 && myuid != serviceuid) + miscerror("--override and --spoof options only available to root or to" " the user who will be providing the service"); - logname= getenv("LOGNAME"); - if (!logname) logname= getenv("USER"); - if (logname) { - pw= getpwnam(logname); - if (!pw || pw->pw_uid != myuid) logname= 0; - } - if (!logname) { - pw= getpwuid(myuid); if (!pw) syscallerror("cannot determine your login name"); - logname= pw->pw_name; + if (spoofuser) { + loginname= spoofuser; + pw= getpwnam(loginname); + if (!pw) miscerror("spoofed login name `%s' is not valid",loginname); + spoofuid= pw->pw_uid; + spoofgid= pw->pw_gid; + ngidssize= ngids; ngids= 0; + if (ngidssize<5) { + ngidssize= 5; + gidarray= xrealloc(gidarray,sizeof(gid_t)*ngidssize); + } + gidarray[ngids++]= spoofgid; + while ((gr= getgrent())) { /* ouch! getgrent has no error behaviour! */ + for (mem= gr->gr_mem; *mem && strcmp(*mem,loginname); mem++); + if (!*mem) continue; + if (ngids>=ngidssize) { + if (ngids>=MAX_GIDS) miscerror("spoofed user is member of too many groups"); + ngidssize= (ngids+5)<<1; + gidarray= xrealloc(gidarray,sizeof(gid_t)*ngidssize); + } + gidarray[ngids++]= gr->gr_gid; + } } +} +static void determine_cwd(void) { cwdbufsize= 0; cwdbuf= 0; - if (!hidecwd) { - for (;;) { - assert(cwdbufsize < INT_MAX/3); - cwdbufsize <<= 1; cwdbufsize+= 100; - cwdbuf= xrealloc(cwdbuf,cwdbufsize); - cwdbuf[cwdbufsize-1]= 0; - if (getcwd(cwdbuf,cwdbufsize-1)) { cwdbufsize= strlen(cwdbuf); break; } - if (errno != ERANGE) { cwdbufsize= 0; break; } - } + + if (hidecwd) return; + + for (;;) { + if (cwdbufsize > MAX_GENERAL_STRING) { cwdbufsize= 0; free(cwdbuf); break; } + cwdbufsize <<= 1; cwdbufsize+= 100; + cwdbuf= xrealloc(cwdbuf,cwdbufsize); + cwdbuf[cwdbufsize-1]= 0; + if (getcwd(cwdbuf,cwdbufsize-1)) { cwdbufsize= strlen(cwdbuf); break; } + if (errno != ERANGE) { cwdbufsize= 0; free(cwdbuf); break; } } +} + +static void process_override(const char *servicename) { + FILE *ovfile; + int ovavail, l, c; switch (overridetype) { case ot_none: ovused= -1; ovbuf= 0; break; + case ot_builtin: + l= strlen(servicename); + if (l >= MAX_OVERRIDE_LEN-20) + miscerror("builtin service string is too long (%d, max is %d)", + l,MAX_OVERRIDE_LEN-21); + l+= 20; + ovbuf= xmalloc(l); + snprintf(ovbuf,l,"execute-builtin %s\n",servicename); + ovused= strlen(ovbuf); + break; case ot_string: l= strlen(overridevalue); if (l >= MAX_OVERRIDE_LEN) miscerror("override string is too long (%d, max is %d)",l,MAX_OVERRIDE_LEN-1); ovbuf= xmalloc(l+2); - strcpy(ovbuf,overridevalue); - strcat(ovbuf,"\n"); + snprintf(ovbuf,l+2,"%s\n",overridevalue); ovused= l+1; break; case ot_file: ovfile= fopen(overridevalue,"r"); - if (!ovfile) syscallerror("open overriding configuration file `%s'",overridevalue); + if (!ovfile) fsyscallerror("open overriding configuration file `%s'",overridevalue); ovbuf= 0; ovavail= ovused= 0; while ((c= getc(ovfile)) != EOF) { if (!c) miscerror("overriding config file `%s' contains null(s)",overridevalue); @@ -810,13 +1000,26 @@ int main(int argc, char *const *argv) { ovbuf[ovused++]= c; } if (ferror(ovfile) || fclose(ovfile)) - syscallerror("read overriding configuration file `%s'",overridevalue); + fsyscallerror("read overriding configuration file `%s'",overridevalue); ovbuf= xrealloc(ovbuf,ovused+1); ovbuf[ovused]= 0; break; default: abort(); } +} + +static int server_connect(void) { + struct sockaddr_un ssockname; + int sfd; + struct sigaction sig; + sigset_t sset; + + sigemptyset(&sset); + sigaddset(&sset,SIGCHLD); + sigaddset(&sset,SIGALRM); + sigaddset(&sset,SIGPIPE); + if (sigprocmask(SIG_UNBLOCK,&sset,0)) syscallerror("preliminarily unblock signals"); sig.sa_handler= SIG_IGN; sigemptyset(&sig.sa_mask); @@ -833,10 +1036,14 @@ int main(int argc, char *const *argv) { while (connect(sfd,(struct sockaddr*)&ssockname,sizeof(ssockname))) { if (errno == ECONNREFUSED || errno == ENOENT) syscallerror("uservd daemon is not running - service not available"); - syscallerror("unable to connect to uservd daemon"); + if (errno != EINTR) + fsyscallerror("unable to connect to uservd daemon: %m"); } - priv_suspend(); + return sfd; +} + +static void server_handshake(int sfd) { srfile= fdopen(sfd,"r"); if (!srfile) syscallerror("turn socket fd into FILE* for read"); if (setvbuf(srfile,0,_IOFBF,BUFSIZ)) syscallerror("set buffering on socket reads"); @@ -849,34 +1056,48 @@ int main(int argc, char *const *argv) { checkmagic(opening_mbuf.magic,OPENING_MAGIC,"in opening message"); if (memcmp(protocolchecksumversion,opening_mbuf.protocolchecksumversion,PCSUMSIZE)) protoerror("protocol version checksum mismatch - server not same as client"); +} +static void server_preparepipes(void) { + char pipepathbuf[PIPEPATHMAXLEN+2]; + int fd, tempfd; + for (fd=0; fd=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; i2) + 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; fd2) - if (close(fdsetup[fd].copyfd)) syscallerror("close real fd for %d",fd); - if (close(fdsetup[fd].pipefd)) syscallerror("close pipe fd for %d",fd); } + close_unwanted_pipes(); +} - if (timeout) - if (alarm(timeout)<0) syscallerror("set up timeout alarm"); +static void server_sendconfirm(void) { + struct event_msg event_mbuf; blocksignals(SIG_BLOCK); memset(&event_mbuf,0,sizeof(event_mbuf)); @@ -967,18 +1241,28 @@ int main(int argc, char *const *argv) { xfwrite(&event_mbuf,sizeof(event_mbuf),swfile); xfflush(swfile); blocksignals(SIG_UNBLOCK); +} +static int server_awaitcompletion(void) { + struct progress_msg progress_mbuf; + getprogress(&progress_mbuf,srfile); if (progress_mbuf.type != pt_terminated) protoerror("progress message during execution phase" " unexpected type %d",progress_mbuf.type); swfile= 0; + return progress_mbuf.data.terminated.status; +} + +static void dispose_remaining_pipes(void) { + sigset_t sset; + int fd, r; blocksignals(SIG_BLOCK); for (fd=0; fd