X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=userv.git;a=blobdiff_plain;f=client.c;h=788b507699c188953c3f49b75a0bed8b42e5b3b2;hp=7a810b534aa98d42da10dad7135d4b5810a825a2;hb=HEAD;hpb=464d71c37246e556de9ec05f7b97af834a5224ee diff --git a/client.c b/client.c index 7a810b5..17fa6ce 100644 --- a/client.c +++ b/client.c @@ -2,11 +2,12 @@ * userv - client.c * client code * - * Copyright (C)1996-1997 Ian Jackson + * userv is copyright Ian Jackson and other contributors. + * See README for full authorship information. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but @@ -15,8 +16,37 @@ * General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with userv; if not, write to the Free Software - * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * along with userv; if not, see . + */ + +/* + * 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 @@ -42,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, @@ -76,71 +96,23 @@ 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 }; +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, myuid; +static uid_t serviceuid; static struct fdsetupstate *fdsetup; -static int fdsetupsize, builtin; +static int fdsetupsize; static struct constkeyvaluepair *defvararray; static int defvaravail, defvarused; static unsigned long timeout; @@ -149,25 +121,44 @@ static int sigpipeok, hidecwd; static int overridetype= ot_none; static const char *overridevalue, *spoofuser=0; +/* Other state variables */ static FILE *srfile, *swfile; +static pid_t mypid; +static uid_t myuid, spoofuid; +static gid_t mygid, spoofgid, *gidarray; +static int ngids; +static struct opening_msg opening_mbuf; +static const char *loginname; +static char *cwdbuf; +static size_t cwdbufsize; +static char *ovbuf; +static int ovused, systemerror, socketfd; static void blocksignals(int how) { sigset_t set; static const char blockerrmsg[]= "userv: failed to [un]block signals: "; const char *str; + int unused; sigemptyset(&set); sigaddset(&set,SIGCHLD); sigaddset(&set,SIGALRM); if (sigprocmask(how,&set,0)) { str= strerror(errno); - write(2,blockerrmsg,sizeof(blockerrmsg)-1); - write(2,str,strlen(str)); - write(2,"\n",1); + unused= write(2,blockerrmsg,sizeof(blockerrmsg)-1); + unused= write(2,str,strlen(str)); + unused= write(2,"\n",1); + (void)unused; exit(-1); } } +/* Functions which may be called either from signal handlers or from + * the main thread. They block signals in case they are on the main + * thread, and may only use signal handler objects. None of them + * return. If they did they'd have to restore the signal mask. + */ + static void NONRETURNPRINTFFORMAT(1,2) miscerror(const char *fmt, ...) { va_list al; @@ -179,7 +170,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; @@ -192,6 +183,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; @@ -217,6 +212,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) { } @@ -238,30 +260,53 @@ 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; @@ -271,12 +316,109 @@ static void xfwritestring(const char *s, FILE *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(FILE *stream) { + if (fputs( "usage: userv [--] [ ...]\n" "usage: userv -B|--builtin [--] [ ...]\n" "options: -f|--file []=\n" @@ -284,75 +426,185 @@ static void usage(void) { " -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" + " -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\n" - "userv and uservd version " VERSION "; copyright (C)1996-1997 Ian Jackson.\n" - "there is 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 ".\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++; } } @@ -360,38 +612,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; } @@ -399,18 +642,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++); @@ -442,16 +676,23 @@ static void of_defvar(const struct optioninfo *oip, const char *value, char *key 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; @@ -472,34 +713,41 @@ static void of_hidecwd(const struct optioninfo *oip, const char *value, char *ke hidecwd=1; } -static void of_builtin(const struct optioninfo *oip, const char *value, char *key) { - builtin=1; +static void of_help(const struct optioninfo *oip, const char *value, char *key) { + usage(stdout); + if (fclose(stdout)) syscallerror("fclose stdout after writing usage message"); + exit(0); } -static void of_help(const struct optioninfo *oip, const char *value, char *key) { - usage(); +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" +" Foundation; either version 3 of the License, or (at your option) any\n" " later version.\n\n" " This program is distributed in the hope that it will be useful, but\n" " WITHOUT ANY WARRANTY; without even the implied warranty of\n" " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n" " Public License for more details.\n\n" " You should have received a copy of the GNU General Public License along\n" -" with userv; if not, 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; @@ -526,6 +774,7 @@ const struct optioninfo optioninfos[]= { { '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 }, @@ -537,13 +786,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 { @@ -551,180 +801,41 @@ 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, tempfd, l, c, reading, fd, r, status, ngidssize; - sigset_t sset; - unsigned long ul; - size_t cwdbufsize; - char *cwdbuf, **mem; - struct opening_msg opening_mbuf; - struct request_msg request_mbuf; - struct progress_msg progress_mbuf; - struct event_msg event_mbuf; - struct passwd *pw; - struct group *gr; - gid_t mygid, spoofgid, *gidarray; - uid_t spoofuid; - 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; + for (argpp= *argvp+1; (argp= *argpp) && *argp == '-' && argp[1]; argpp++) { if (!*++argp) usageerror("unknown option/argument `%s'",*argpp); @@ -759,15 +870,20 @@ int main(int argc, char *const *argv) { } } } - if (builtin) { + if (overridetype == ot_builtin) { serviceuser= "-"; } else { if (!*argpp) usageerror("no service user given after options"); serviceuser= *argpp++; } - if (!*argpp) usageerror(builtin ? + 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 MAX_ARGSDEFVAR) usageerror("far too many arguments"); - if (ngids > MAX_GIDS) miscerror("caller is in far too many gids"); +static void determine_users(void) { + int ngidssize; + char **mem; + struct passwd *pw; + struct group *gr; spoofuid= myuid; spoofgid= mygid; - logname= getenv("LOGNAME"); - if (!logname) logname= getenv("USER"); - if (logname) { - pw= getpwnam(logname); - if (!pw || pw->pw_uid != myuid) logname= 0; + loginname= getenv("LOGNAME"); + if (!loginname) loginname= getenv("USER"); + if (loginname) { + pw= getpwnam(loginname); + if (!pw || pw->pw_uid != myuid) loginname= 0; } - if (!logname) { + if (!loginname) { pw= getpwuid(myuid); if (!pw) miscerror("cannot determine your login name"); - logname= pw->pw_name; + loginname= xstrsave(pw->pw_name); } - if (!strcmp(serviceuser,"-")) serviceuser= logname; + if (!strcmp(serviceuser,"-")) serviceuser= loginname; pw= getpwnam(serviceuser); if (!pw) miscerror("requested service user `%s' is not a user",serviceuser); serviceuid= pw->pw_uid; @@ -809,9 +922,9 @@ int main(int argc, char *const *argv) { " the user who will be providing the service"); if (spoofuser) { - logname= spoofuser; - pw= getpwnam(logname); - if (!pw) miscerror("spoofed login name `%s' is not valid",logname); + 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; @@ -821,45 +934,63 @@ int main(int argc, char *const *argv) { } gidarray[ngids++]= spoofgid; while ((gr= getgrent())) { /* ouch! getgrent has no error behaviour! */ - for (mem= gr->gr_mem; *mem && strcmp(*mem,logname); mem++); + 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); @@ -871,13 +1002,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); @@ -894,10 +1038,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"); @@ -910,32 +1058,46 @@ 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(&spoofgid,sizeof(gid_t),swfile); @@ -968,14 +1130,27 @@ int main(int argc, char *const *argv) { } 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); @@ -986,17 +1161,54 @@ int main(int argc, char *const *argv) { sig.sa_handler= sighandler_alrm; if (sigaction(SIGALRM,&sig,0)) syscallerror("set up sigalrm handler"); + if (!timeout) return; + alarm(timeout); +} + + +static void close_unwanted_pipes(void) { + int fd; + + for (fd=0; fd2) + 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)); @@ -1029,18 +1243,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