From db59ee1476515a65cfcca10a3059d8ccb2d24d32 Mon Sep 17 00:00:00 2001 From: ian Date: Thu, 18 Sep 1997 01:58:49 +0000 Subject: [PATCH] Internal code review complete. --- .cvsignore | 4 +- INSTALL | 8 +- Makefile.in | 21 +- README | 29 ++ acconfig.h | 18 +- buildship | 2 +- client.c | 753 ++++++++++++++++++++------------ common.h | 16 +- configure.in | 7 +- daemon.h | 15 +- lexer.l.m4 | 20 +- lib.c | 8 +- lib.h | 3 +- overlord.c | 4 +- parser.c | 1186 +++++++++++++++++++++++++++----------------------- process.c | 170 +++++--- servexec.c | 22 +- spec.sgml.in | 44 +- 18 files changed, 1370 insertions(+), 960 deletions(-) create mode 100644 README diff --git a/.cvsignore b/.cvsignore index a778764..564911f 100644 --- a/.cvsignore +++ b/.cvsignore @@ -6,7 +6,9 @@ lexer.c tokens.h pcsum.h -english.lp +version.h +spec.sgml + *.sasp* *.lout* *.li diff --git a/INSTALL b/INSTALL index 8c5db28..baf762f 100644 --- a/INSTALL +++ b/INSTALL @@ -47,11 +47,15 @@ System interfaces: * Unix-domain (AF_UNIX) stream sockets, for use with: * BSD sockets - socket(), bind(), listen(), accept(), connect(); * socketpair(2); -* lstat(2) (though stat(2) will be safe on systems without symlinks). +* lstat(2) (though stat(2) will be safe on systems without symlinks, + if you say -Dlstat=stat). * Pipes: * creating using pipe(2) and mkfifo(2); * proper interaction between open(O_RDWR), open(O_RDONLY), - open(O_WRONLY), close(), dup2, EPIPE, SIGPIPE, &c.; + open(O_WRONLY), close(), dup2, EPIPE, SIGPIPE, &c. + (ie, opening pipes with O_RDWR never blocks; EPIPE happens + if you write with no readers; EOF happens if you read with + no buffered data and writers) * POSIX signal handling - sigaction(2), sigprocmask(2), sigsuspend(2); To format the documentation: diff --git a/Makefile.in b/Makefile.in index 4ef8e7d..08b0611 100644 --- a/Makefile.in +++ b/Makefile.in @@ -16,8 +16,10 @@ # along with userv; if not, write to the Free Software # Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +VERSION=0.55 + CC=@CC@ -CFLAGS=@CFLAGS@ $(XCFLAGS) +CFLAGS=@CFLAGS@ $(XCFLAGS) -DVERSION='"$(VERSION)"' OPTIMISE=@OPTIMISE@ CPPFLAGS=@DEBUGDEFS@ $(XCPPFLAGS) LDLIBS=@DEBUGLIBS@ $(XLDLIBS) @@ -38,15 +40,15 @@ etcdir=/etc etcsubdir=$(etcdir)/userv SOURCES= Makefile.in configure.in acconfig.h \ - client.c common.h version.h \ + client.c common.h \ overlord.c process.c servexec.c \ daemon.h debug.c parser.c lib.c lib.h \ language.i4 lexer.l.m4 tokens.h.m4 ALSOSHIP= system.default system.override \ - spec.sgml overview.fig overview.ps \ + spec.sgml.in overview.fig overview.ps \ COPYING INSTALL buildship install-sh .cvsignore GENSHIP= lexer.l lexer.c tokens.h configure config.h.in \ - spec.html spec.ps overview.ps + spec.sgml spec.html spec.ps overview.ps SHIPTARGETS= $(SOURCES) $(ALSOSHIP) $(GENSHIP) @@ -66,6 +68,10 @@ daemon: overlord.o process.o servexec.o parserlexer.o debug.o lib.o lexer.l: language.i4 +spec.sgml: spec.sgml.in Makefile + sed -e '/<\/version>/ s/>/&$(VERSION)/' \ + spec.sgml.in >$@.new && mv -f $@.new $@ + client.o: config.h common.h pcsum.h version.h process.o: config.h common.h pcsum.h daemon.h lib.h tokens.h @@ -74,7 +80,7 @@ overlord.o: config.h common.h pcsum.h daemon.h servexec.o: config.h common.h pcsum.h daemon.h lib.h version.h -lib.o: config.h lib.h +lib.o: config.h common.h lib.h debug.o: config.h common.h pcsum.h daemon.h lib.h tokens.h @@ -82,11 +88,14 @@ parserlexer.o: lexer.c parser.c config.h common.h pcsum.h daemon.h lib.h tokens. # lexer.c #include's parser.c at the end. Blame flex. $(CC) -c $(CPPFLAGS) $(CFLAGS) lexer.c -o $@ -pcsum.h: common.h version.h config.h config.status Makefile +pcsum.h: common.h config.h config.status Makefile cat $^ | md5sum | sed -e 's/../0x&,/g; s/,$$//;' >pcsum.h.new cmp pcsum.h.new pcsum.h || mv -f pcsum.h.new pcsum.h @rm -f pcsum.h.new +version.h: Makefile + echo '#define VERSION "$(VERSION)"' >$@.new && mv -f $@.new $@ + tokens.h: language.i4 autoconf configure: diff --git a/README b/README new file mode 100644 index 0000000..c327d7a --- /dev/null +++ b/README @@ -0,0 +1,29 @@ +This is userv, a trust boundary management service for Unix. + +Using userv, one program can call another without either of them +having to trust the other completely. This enables applications such +as mail delivery, cron, user-provided CGI scripts and so forth to +operate without needing to be given root privilege. + +THIS VERSION IS ALPHA AND PROVIDED FOR REVIEW. + +For installation requirements and instructions please see INSTALL. +For usage documentation and discussion see the manual in spec.* +(various formats via Debiandoc-SGML in spec.sgml). + +userv is Copyright (C)1996-7 Ian Jackson . + +userv 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 (at your +option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with userv as the file COPYING; if not, email me at the address +above or write to the Free Software Foundation, 59 Temple Place - +Suite 330, Boston, MA 02111-1307, USA. diff --git a/acconfig.h b/acconfig.h index 998b8e3..160a27c 100644 --- a/acconfig.h +++ b/acconfig.h @@ -22,8 +22,8 @@ /* Define if function attributes a la GCC 2.5 and higher are available. */ #undef HAVE_GNUC25_ATTRIB -/* Define if constant functions a la GCC 2.5 and higher are available. */ -#undef HAVE_GNUC25_CONST +/* Define if unused functions a la GCC 2.5 and higher are available. */ +#undef HAVE_GNUC25_UNUSED /* Define if nonreturning functions a la GCC 2.5 and higher are available. */ #undef HAVE_GNUC25_NORETURN @@ -71,14 +71,14 @@ #define NONRETURNPRINTFFORMAT(si,tc) FUNCATTR((ATTRPRINTF(si,tc),ATTRNORETURN)) #endif -/* GNU C constant functions, or null. */ -#ifndef ATTRCONST -#ifdef HAVE_GNUC25_CONST -#define ATTRCONST const +/* GNU C unused functions, or null. */ +#ifndef ATTRUNUSED +#ifdef HAVE_GNUC25_UNUSED +#define ATTRUNUSED unused #else -#define ATTRCONST +#define ATTRUNUSED #endif #endif -#ifndef CONSTANT -#define CONSTANT FUNCATTR((ATTRCONST)) +#ifndef UNUSED +#define UNUSED FUNCATTR((ATTRUNUSED)) #endif diff --git a/buildship b/buildship index 759c30e..b6d13f3 100755 --- a/buildship +++ b/buildship @@ -2,7 +2,7 @@ # to release, check out a fresh copy and then run this set -e -version=`sed -n 's/^#define VERSION \"\(.*\)\" *$/\1/p' version.h` +version=`sed -n 's/^VERSION=\(.*\) *$/\1/p' Makefile` targz=userv-$version.tar.gz tag=`echo release-$version | sed -e 's/\./-/g'` diff --git a/client.c b/client.c index 02d77a9..3e9f08f 100644 --- a/client.c +++ b/client.c @@ -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 @@ -44,17 +74,6 @@ #include "common.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,60 +95,11 @@ 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 }; @@ -137,8 +107,9 @@ 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; static struct constkeyvaluepair *defvararray; @@ -149,7 +120,18 @@ 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 *logname; +static char *cwdbuf; +static size_t cwdbufsize; +static char *ovbuf; +static int ovused, systemerror; static void blocksignals(int how) { sigset_t set; @@ -168,6 +150,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; @@ -217,6 +205,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= 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 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,6 +253,51 @@ 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= 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); @@ -251,18 +311,6 @@ 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); @@ -271,12 +319,108 @@ 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; + 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" @@ -291,21 +435,71 @@ static void usage(void) { "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"); + "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; @@ -348,11 +542,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++; } } @@ -364,15 +560,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) { @@ -381,13 +577,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); @@ -408,7 +604,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) @@ -442,14 +638,18 @@ 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); + 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; @@ -478,7 +678,7 @@ 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, + if (fputs( " userv - user service daemon and client; copyright (C)1996-1997 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" @@ -491,8 +691,8 @@ static void of_copyright(const struct optioninfo *oip, const char *value, char * " 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"); +" MA 02111-1307, USA.\n", + stdout) < 0) syscallerror("write usage to stderr"); exit(0); } @@ -551,180 +751,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 == (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, 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); @@ -768,6 +829,11 @@ int main(int argc, char *const *argv) { 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; @@ -824,24 +887,33 @@ int main(int argc, char *const *argv) { for (mem= gr->gr_mem; *mem && strcmp(*mem,logname); 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: @@ -849,13 +921,13 @@ int main(int argc, char *const *argv) { ovbuf= 0; break; case ot_builtin: - l= strlen(argv[0]); + 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",argv[0]); + snprintf(ovbuf,l,"execute-builtin %s\n",servicename); ovused= strlen(ovbuf); break; case ot_string: @@ -887,6 +959,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); @@ -905,8 +990,11 @@ int main(int argc, char *const *argv) { syscallerror("uservd daemon is not running - service not available"); syscallerror("unable to connect to uservd daemon"); } - 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"); @@ -919,26 +1007,39 @@ 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 max"); @@ -184,4 +193,3 @@ changequote(`,') ' divert(-1) undivert - diff --git a/lib.c b/lib.c index 6c3cd0a..e363829 100644 --- a/lib.c +++ b/lib.c @@ -25,8 +25,10 @@ #include #include #include +#include #include "config.h" +#include "common.h" #include "lib.h" char *xstrcat3save(const char *a, const char *b, const char *c) { @@ -67,10 +69,12 @@ void *xrealloc(void *p, size_t s) { return p; } -void makeroom(char **buffer, int *size, int needed) { - if (*size >= needed) return; +int makeroom(char **buffer, int *size, int needed) { + if (needed > MAX_GENERAL_STRING) return -1; + if (*size >= needed) return 0; *buffer= xrealloc(*buffer,needed); *size= needed; + return 0; } void vsnyprintf(char *buffer, size_t size, const char *fmt, va_list al) { diff --git a/lib.h b/lib.h index fc9d016..81ff9fc 100644 --- a/lib.h +++ b/lib.h @@ -30,7 +30,8 @@ void miscerror(const char *what) NONRETURNING; void syscallerror(const char *what) NONRETURNING; void *xmalloc(size_t s); void *xrealloc(void *p, size_t s); -void makeroom(char **buffer, int *size, int needed); +int makeroom(char **buffer, int *size, int needed); +/* makeroom returns -1 if needed was far too large; otherwise returns 0. */ /* It doesn't appear to be documented whether [v]snprintf put a null * in if they overrun. GNU libc does, but I don't want to rely on diff --git a/overlord.c b/overlord.c index be2553a..7f66c4e 100644 --- a/overlord.c +++ b/overlord.c @@ -130,7 +130,7 @@ int main(int argc, char *const *argv) { checkstalepipes(); mfd= socket(AF_UNIX,SOCK_STREAM,0); - if (!mfd) { syslog(LOG_CRIT,"cannot create master socket: %m"); exit(4); } + if (mfd<0) { syslog(LOG_CRIT,"cannot create master socket: %m"); exit(4); } assert(sizeof(ssockname.sun_path) > sizeof(RENDEZVOUS)); ssockname.sun_family= AF_UNIX; @@ -156,7 +156,7 @@ int main(int argc, char *const *argv) { if (sfd<0) { errno= e; if (errno == EINTR) continue; - if (errno == ENOMEM) { + if (errno == ENOMEM || errno == EPROTO || errno == EAGAIN) { syslog(LOG_ERR,"unable to accept connection: %m"); continue; } else { diff --git a/parser.c b/parser.c index 4b55743..898bbfa 100644 --- a/parser.c +++ b/parser.c @@ -3,7 +3,8 @@ * configuration file parser; this file is actually #included from * lexer.c, which is generated using flex from lexer.l, in turn from * lexer.l.m4. It's in a separate file so that we don't have to worry - * about m4 quoting &c. + * about m4 quoting &c., but we have to #include it so that the C + * objects from the lexer are available. * * Copyright (C)1996-1997 Ian Jackson * @@ -22,7 +23,13 @@ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -static void useless(void) { (void)yyunput; (void)useless; /* to shut up GCC ! */ } +static int parse_file(const char *string, int *didexist); +static int parser(int allowce); +int parse_string(const char *string, const char *descrip, int isinternal); + +/* + * Error-handling routines + */ static void closeerrorfile(void) { if (eh.file && !eh.filekeep) { @@ -102,6 +109,25 @@ void parseerrprint(const char *fmt, ...) { va_end(al); } +static int unexpected(int found, int wanted, const char *wantedstr) { + /* pass wanted==-1 if you know it's not what you wanted; + * otherwise this function will check it for you. + */ + if (found == wanted) return 0; + if (found == tokv_error) return found; + parseerrprint("found %s, expected %s",printtoken(found),wantedstr); + return tokv_error; +} + +static int stringoverflow(const char *where) { + parseerrprint("string buffer became far too large building %s",where); + return tokv_error; +} + +/* + * General assistance functions + */ + static void freecharparray(char **array) { char **pp; @@ -146,27 +172,34 @@ static int dequote(char *inplace) { } } } - assert(*p); assert(!*++p); return tokv_quotedstring; + assert(*p); assert(!*++p); + return tokv_quotedstring; } const char *printtoken(int token) { + /* Returns pointer to static buffer, overwritten by next call. */ + static const char keywordfmt[]= "keyword `%s'"; + static const char operatorfmt[]= "operator `%s'"; + static char buf[250]; char *q; const char *p; - int i, c; + int i, c, l; if ((token & tokm_repres) == tokr_word) { - assert(strlen(yytext)+500 && (c= *p++)) { + l= strlen(buf); i= sizeof(buf)-l-2; p= yytext; q= buf+l; + while ((c= *p++)) { + if (i-- <= 0) { q--; strcpy(q-3,"..."); break; } if (isspace(c)) c= ' '; else if (!isprint(c) || iscntrl(c)) c= '?'; - *q++= c; + else *q++= c; } strcpy(q,"'"); return buf; @@ -196,57 +230,34 @@ const char *printtoken(int token) { } static const char *string2path(const char *in) { - static char *p=0; - static int pl=0; + /* Returned pointers become invalid on next call. + * May return 0, having printed an error message. + */ + static char *p; + static int pl; if (strncmp(in,"~/",2)) return in; - makeroom(&p,&pl,strlen(serviceuser_dir)+1+strlen(in+2)+1); + if (makeroom(&p,&pl,strlen(serviceuser_dir)+1+strlen(in+2)+1)) { + stringoverflow("pathname"); + return 0; + } snyprintf(p,pl,"%s/%s",serviceuser_dir,in+2); return p; } -static void parser_push(struct parser_state *usestate, - const char *newfile, - YY_BUFFER_STATE ybuf, - int isinternal) { - usestate->lineno= 1; - usestate->reportlineno= 1; - usestate->filename= newfile; - usestate->notedreferer= 0; - usestate->isinternal= isinternal; - usestate->ybuf= ybuf; - usestate->upstate= cstate; - - cstate= usestate; - yy_switch_to_buffer(ybuf); -} - -static void parser_pop(void) { - struct parser_state *oldstate; - - oldstate= cstate; - cstate= cstate->upstate; - if (cstate) yy_switch_to_buffer(cstate->ybuf); - yy_delete_buffer(oldstate->ybuf); -} - -/* parser component functions pa_ return tokv_error or 0, - * having scanned exactly what they were expecting - * complete argument list parsers paa_ return tokv_error or 0, - * having scanned up to and including the newline +/* + * Parser component functions for parsing parameters to directives + * + * Functions pa_... parse just one parameter, + * and return tokv_error or 0, having scanned exactly what they were expecting + * Functions paa_... parse complete parameter lists, including the leading lwsp, + * and return tokv_error or 0, having scanned up to and including the newline + * + * For any function which takes `const char **rv' the + * value returned in *rv is overwritten by repeated calls to + * any of those functions (whether the same or another). */ -static int unexpected(int found, int wanted, const char *wantedstr) { - /* pass wanted==-1 if you know it's not what you wanted; - * otherwise this function will check it for you. - */ - if (found == wanted) return 0; - if (found != tokv_error) { - parseerrprint("found %s, expected %s",printtoken(found),wantedstr); - } - return tokv_error; -} - static int pa_mnl(void) { return unexpected(yylex(),tokv_newline,"newline"); } @@ -254,16 +265,7 @@ static int pa_mwsp(void) { return unexpected(yylex(),tokv_lwsp,"linear whitespace"); } -static void parm_1string(char ***rvalues, const char *tocopy) { - char **a; - a= xmalloc(sizeof(char*)*2); - a[0]= xstrsave(tocopy); - a[1]= 0; - *rvalues= a; -} - static int pa_string(const char **rv) { - /* Value returned in *rv is overwritten by repeated calls */ static char *p= 0; static int pl= 0; @@ -273,14 +275,24 @@ static int pa_string(const char **rv) { r= yylex(); if (r == tokv_error) return r; if ((r & tokm_repres) == tokr_nonstring) return unexpected(r,-1,"string"); l= strlen(yytext)+1; - makeroom(&p,&pl,l); + if (makeroom(&p,&pl,l)) return stringoverflow("string argument"); strcpy(p,yytext); *rv= p; return 0; } +static int pa_numberdollar(int *value_r) { + /* Also parses the whitespace beforehand. */ + int r; + + r= pa_mwsp(); if (r) return r; + r= yylex(); if (r == tokv_error || r == tokv_dollar) return r; + if (unexpected(r,tokv_ordinal,"expected number or dollar")) return tokv_error; + *value_r= lr_min; + return r; +} + static int paa_1string(const char **rv) { - /* Value returned in *rv is overwritten by repeated calls */ int r; r= pa_string(rv); if (r) return r; @@ -288,112 +300,95 @@ static int paa_1string(const char **rv) { } static int paa_1path(const char **rv) { - /* Value returned in *rv is overwritten by repeated calls */ const char *cp; int r; r= paa_1string(&cp); if (r) return r; - *rv= string2path(cp); return 0; + *rv= string2path(cp); if (!*rv) return tokv_error; + return 0; } -static int pa_parameter(char ***rvalues, char **rname) { - /* Scans a single parameter token and calls the appropriate parameter - * function, returning tokv_error or 0 just like the parameter function. - * If rname is non-null then the name of the parameter (malloc'd) will - * be stored in it. +static int paa_pathargs(const char **path_r, char ***newargs_r) { + /* Repeated calls do _not_ overwrite newargs_r; caller must free. + * Repeated calls _do_ overwrite string returned in path_r. + * Any call to this function invalidates previous returns in *rv. */ - int token, r, i; - char *name; + char **newargs; + const char *string; + int used, size, r; - token= yylex(); if (token == tokv_error) return token; - name= xstrsave(yytext); - if ((token & tokm_repres) != tokr_nonstring && - !memcmp(yytext,"u-",2) && strlen(yytext)>=3) { - for (i=0; - i=request_mbuf.nvars) { - *rvalues= xmalloc(sizeof(char*)); - **rvalues= 0; - } else { - parm_1string(rvalues,defvararray[i].value); + r= pa_string(&string); if (r == tokv_error) return r; + *path_r= string2path(string); if (!*path_r) return tokv_error; + + used=0; size=0; + newargs= xmalloc(sizeof(char*)*(size+1)); + + for (;;) { + r= yylex(); if (r == tokv_error) goto error; + if (r==tokv_newline) break; + if (unexpected(r,tokv_lwsp,"newline after or whitespace between arguments")) { + r= tokv_error; goto error; } - } else { - if (!(token & tokt_parameter)) { - free(name); - return unexpected(token,-1,"parameter name"); + r= yylex(); if (r==tokv_error) goto error; + if ((r & tokm_repres) == tokr_nonstring) { + r= unexpected(r,-1,"string for command argument"); + goto error; } - r= (lr_parameter)(token,rvalues); - if (r) { free(name); return r; } + if (used>=size) { + if (used >= MAX_ARGSDEFVAR) { + parseerrprint("far too many arguments to service program"); + r= tokv_error; goto error; + } + size= (used+5)<<1; + newargs= xrealloc(newargs,sizeof(char*)*(size+1)); + } + newargs[used++]= xstrsave(yytext); } - debug_dumpparameter(name,*rvalues); - if (rname) *rname= name; - else free(name); + newargs[used]= 0; + *newargs_r= newargs; return 0; + +error: + newargs[used]=0; + freecharparray(newargs); + return r; } -static int pa_condition(int *rtrue) { - /* Scans up to and including the newline following the condition; - * may scan more than one line if the condition is a multi-line - * one. Returns 0 (setting *rtrue) or tokv_error. Expects to - * scan the whitespace before its condition. - */ - int r, token, andor, ctrue, actrue; - char **parmvalues; +static int paa_message(const char **message_r) { + /* Returned value is invalidated by repeated calls. */ + static char *buildbuf; + static int buildbuflen; + + int r, tl; r= pa_mwsp(); if (r) return r; - token= yylex(); - if (token == tokv_error) { - return token; - } else if (token == tokv_not) { - r= pa_condition(&ctrue); if (r) return r; - *rtrue= !ctrue; return 0; - } else if (token == tokv_openparen) { - andor= 0; actrue= -1; - for (;;) { - cstate->reportlineno= cstate->lineno; - r= pa_condition(&ctrue); if (r) return r; - switch (andor) { - case 0: assert(actrue==-1); actrue= ctrue; break; - case tokv_and: assert(actrue>=0); actrue= actrue && ctrue; break; - case tokv_or: assert(actrue>=0); actrue= actrue || ctrue; break; - default: abort(); - } - do { token= yylex(); } while (token == tokv_lwsp); - if (token == tokv_error) return token; - if (token == tokv_closeparen) break; - if (andor) { - r= unexpected(token,andor,"same conjunction as before"); if (r) return r; - } else { - if (token != tokv_and && token != tokv_or) - return unexpected(token,-1,"first conjunction inside connective"); - andor= token; - } + tl= 1; + if (makeroom(&buildbuf,&buildbuflen,50)) return stringoverflow("start of message"); + buildbuf[0]= 0; + for (;;) { + r= yylex(); if (r == tokv_error) return r; + if (r == tokv_eof) { + parseerrprint("unexpected end of file in message text"); + return tokv_error; } - r= pa_mnl(); if (r) return r; - *rtrue= actrue; return 0; - } else if (token & tokt_parmcondition) { - r= pa_mwsp(); if (r) return r; - r= pa_parameter(&parmvalues,0); if (r) return r; - r= (lr_parmcond)(token,parmvalues,rtrue); freecharparray(parmvalues); - return r; + if (r == tokv_newline) break; + tl+= strlen(yytext); + if (makeroom(&buildbuf,&buildbuflen,tl)) return stringoverflow("message"); + strcat(buildbuf,yytext); } - return unexpected(token,-1,"condition"); -} - -static int pa_numberdollar(int *rv) { - /* Also parses the whitespace beforehand. */ - int r; - - r= pa_mwsp(); if (r) return r; - r= yylex(); if (r == tokv_error || r == tokv_dollar) return r; - if (unexpected(r,tokv_ordinal,"expected number or dollar")) return tokv_error; - *rv= lr_min; return r; + *message_r= buildbuf; + return 0; } -/* Main parser routines */ +/* + * Skipping routines (used by e.g. the `if' code). + */ static int skiptoeol(void) { + /* Returns 0 if OK, having just parsed the newline + * or tokv_error if an error occurs, leaving the position at the error + * (which may be EOF or a syntax error token). + */ int token; do { token= yylex(); } @@ -406,9 +401,11 @@ static int skiptoeol(void) { } static int skip(int allowce) { - /* Scans a piece of config without executing it. - * Returns tokv_error, or the tokt_controlend token - * with type allowce if one was found. + /* Scans a piece of config without executing it. Returns + * tokv_error (leaving the parsing state at the error), + * or the tokt_controlend token with type allowce if one + * was found (in which case rest of line, including any whitespace + * after tokt_controlend, has not been parsed), or tokv_eof. */ int token, r; @@ -419,7 +416,7 @@ static int skip(int allowce) { return token; } else if (token & tokt_controlend) { if (allowce == lr_controlend) return token; - return unexpected(token,-1,"control structure end of a different kind"); + else return unexpected(token,-1,"control structure end of a different kind"); } else if (token & tokt_controlstart) { r= token; while (r & tokt_controlstart) { @@ -435,100 +432,130 @@ static int skip(int allowce) { r= skiptoeol(); if (r) return r; } } - -static int parser(int allowce) { - /* Returns: - * an exception (error, eof or quit) - * then rest of `file' is uninteresting - * or - * token if allowce was !0 and equal to token's controlend - * then rest of `file' not scanned yet - */ - int token, r; - for (;;) { /* loop over lines */ - cstate->reportlineno= cstate->lineno; - do { token= yylex(); } while (token == tokv_lwsp); - if (token & tokt_exception) { - return token; - } else if (token & tokt_controlend) { - if (lr_controlend == allowce) return token; - return unexpected(token,-1,"directive (not this kind of control structure end)"); - } else if (token & tokt_directive) { - r= (lr_dir)(token); if (r) { assert(r & tokt_exception); return r; } - } else if (token == tokv_newline) { - /* ignore blank links (and comment-only lines) */ - } else { - return unexpected(token,-1,"directive"); - } - } -} +/* + * Routines for parsing and getting the values of parameters + */ -int parse_string(const char *string, const char *descrip, int isinternal) { - /* Returns the same things as parser, except that tokv_eof - * is turned into 0. */ - struct parser_state usestate; - YY_BUFFER_STATE ybuf; - int r; +static void parm_1string(char ***rvalues, const char *tocopy) { + char **a; + a= xmalloc(sizeof(char*)*2); + a[0]= xstrsave(tocopy); + a[1]= 0; + *rvalues= a; +} - ybuf= yy_scan_string(string); - if (!ybuf) syscallerror("unable to create flex buffer for internal string"); - parser_push(&usestate,descrip,ybuf,isinternal); - - r= parser(0); +static char *parm_ulong(unsigned long ul) { + char *p; + int l; - parser_pop(); - if (r == tokv_eof) r= 0; - return r; + l= CHAR_BIT*sizeof(unsigned long)/3+4; + p= xmalloc(l); + snyprintf(p,l,"%lu",ul); + return p; } -static int parse_file(const char *string, int *didexist) { - /* Returns the same things as parser, except that tokv_eof - * is turned into 0. */ - static int fileparselevel= 0; +static int parm_usernameuid(char ***rvalues, const char *name, uid_t id) { + char **a; - struct parser_state usestate; - YY_BUFFER_STATE ybuf; - int r; - FILE *file; - - if (fileparselevel >= MAX_INCLUDE_NEST) { - parseerrprint("too many nested levels of included files"); - return tokv_error; - } - file= fopen(string,"r"); if (!file) { - if (errno == ENOENT) { - if (didexist) *didexist= 0; - return 0; - } - parseerrprint("unable to open config file `%s': %s",string,strerror(errno)); - return tokv_error; - } - if (didexist) *didexist= 1; + a= xmalloc(sizeof(char*)*3); + a[0]= xstrsave(name); + a[1]= parm_ulong(id); + a[2]= 0; + *rvalues= a; + return 0; +} - ybuf= yy_create_buffer(file,YY_BUF_SIZE); - if (!ybuf) syscallerror("unable to create flex buffer for file"); - parser_push(&usestate,string,ybuf,0); - fileparselevel++; +static int parm_groups(char ***rvalues, int size, + gid_t *gids, const char *const *groups) { + char **a; + int i; - r= parser(0); - if (ferror(file)) { - parseerrprint("error reading configuration file `%s'",string); - r= tokv_error; + if (size >= 2 && gids[0] == gids[1]) { size--; gids++; groups++; } + a= xmalloc(sizeof(char*)*(size+1)*2); + for (i=0; i=3) { + for (i=0; + i=request_mbuf.nvars) { + *rvalues= xmalloc(sizeof(char*)); + **rvalues= 0; + } else { + parm_1string(rvalues,defvararray[i].value); + } + } else if (token & tokt_parameter) { + r= (lr_parameter)(token,rvalues); + if (r) { free(name); return r; } + } else { + free(name); + return unexpected(token,-1,"parameter name"); + } + debug_dumpparameter(name,*rvalues); + if (rname) *rname= name; + else free(name); + return 0; } -/* Parameter-based conditional functions (parmcondition's) */ +/* + * Routines for parsing conditions, including + * parameter-based conditional functions (parmcondition's). + */ -int pcf_glob(int ctoken, char **pv, int *rtrue) { +int pcf_glob(int ctoken, char *const *pv, int *rtrue) { int token, actrue, r; - char **pp; + char *const *pp; actrue= 0; r= pa_mwsp(); if (r) return r; @@ -546,9 +573,10 @@ int pcf_glob(int ctoken, char **pv, int *rtrue) { return 0; } -int pcf_range(int ctoken, char **pv, int *rtrue) { +int pcf_range(int ctoken, char *const *pv, int *rtrue) { int mintoken, min, maxtoken, max, r; - char **pp, *ep; + char *const *pp; + char *ep; unsigned long v; r= pa_mwsp(); if (r) return r; @@ -565,14 +593,16 @@ int pcf_range(int ctoken, char **pv, int *rtrue) { *rtrue= 0; return 0; } -int pcf_grep(int ctoken, char **pv, int *rtrue) { +int pcf_grep(int ctoken, char *const *pv, int *rtrue) { FILE *file; const char *cp; - char **pp, *buf, *p; + char *const *pp; + char *buf, *p; int r, maxlen, l, c, actrue, posstrue; r= paa_1path(&cp); if (r) return r; - file= fopen(cp,"r"); if (!file) { + file= fopen(cp,"r"); + if (!file) { parseerrprint("unable to open file `%s' for grep: %s",cp,strerror(errno)); return tokv_error; } @@ -587,7 +617,8 @@ int pcf_grep(int ctoken, char **pv, int *rtrue) { if (c=='\n' || c==EOF || isspace(c)) { while (p>buf && isspace(p[-1])) --p; *p= 0; posstrue= 0; - for (pp= pv; *pp; pp++) if (!strcmp(*pp,buf)) { posstrue= 1; break; } + for (pp= pv; !posstrue && *pp; pp++) + if (!strcmp(*pp,buf)) posstrue= 1; } else { posstrue= 0; } @@ -605,125 +636,67 @@ int pcf_grep(int ctoken, char **pv, int *rtrue) { fclose(file); free(buf); return tokv_error; } assert(actrue || feof(file)); - fclose(file); free(buf); *rtrue= actrue; return 0; + fclose(file); free(buf); *rtrue= actrue; + return 0; } -/* Parameter functions and associated `common code' functions */ - -int pf_service(int ptoken, char ***rvalues) { - parm_1string(rvalues,service); return 0; -} - -static char *parm_ulong(unsigned long ul) { - char *p; - int l; - - l= CHAR_BIT*sizeof(unsigned long)/3+4; - p= xmalloc(l); - snyprintf(p,l,"%lu",ul); - return p; -} - -static int parm_usernameuid(char ***rvalues, const char *name, uid_t id) { - char **a; - a= xmalloc(sizeof(char*)*3); - a[0]= xstrsave(name); - a[1]= parm_ulong(id); - a[2]= 0; - *rvalues= a; return 0; -} - -int pf_callinguser(int ptoken, char ***rvalues) { - return parm_usernameuid(rvalues,logname,request_mbuf.callinguid); -} - -int pf_serviceuser(int ptoken, char ***rvalues) { - return parm_usernameuid(rvalues,serviceuser,serviceuser_uid); -} - -static char *parm_gidname(gid_t id) { - static char ebuf[200]; - - struct group *ge; - - ge= getgrgid(id); - if (!ge) { - sprintf(ebuf,"look up group with id %lu",(unsigned long)id); - syscallerror(ebuf); - } - return xstrsave(ge->gr_name); -} - -static int parm_gids(char ***rvalues, int size, gid_t *list) { - char **a; - int i; - - if (size >= 2 && list[0] == list[1]) { size--; list++; } - a= xmalloc(sizeof(char*)*(size+1)*2); - for (i=0; i=size) { - size= (used+5)<<2; - newargs= xrealloc(newargs,sizeof(char*)*(size+1)); + r= pa_mwsp(); if (r) return r; + token= yylex(); + if (token == tokv_error) { + return token; + } else if (token == tokv_not) { + r= pa_condition(&ctrue); if (r) return r; + *rtrue= !ctrue; return 0; + } else if (token == tokv_openparen) { + andor= 0; actrue= -1; + for (;;) { + cstate->reportlineno= cstate->lineno; + r= pa_condition(&ctrue); if (r) return r; + switch (andor) { + case 0: assert(actrue==-1); actrue= ctrue; break; + case tokv_and: assert(actrue>=0); actrue= actrue && ctrue; break; + case tokv_or: assert(actrue>=0); actrue= actrue || ctrue; break; + default: abort(); + } + do { token= yylex(); } while (token == tokv_lwsp); + if (token == tokv_error) return token; + if (token == tokv_closeparen) break; + if (andor) { + r= unexpected(token,andor,"same conjunction as before"); if (r) return r; + } else { + if (token != tokv_and && token != tokv_or) + return unexpected(token,-1,"first conjunction inside connective"); + andor= token; + } } - newargs[used++]= xstrsave(yytext); + r= pa_mnl(); if (r) return r; + *rtrue= actrue; return 0; + } else if (token & tokt_parmcondition) { + r= pa_mwsp(); if (r) return r; + r= pa_parameter(&parmvalues,0); if (r) return r; + r= (lr_parmcond)(token,parmvalues,rtrue); freecharparray(parmvalues); + return r; + } else { + return unexpected(token,-1,"condition"); } - newargs[used]= 0; - *newargs_r= newargs; - return 0; - -error: - newargs[used]=0; - freecharparray(newargs); - return r; } +/* + * Directive functions and associated `common code' functions + */ + +/* Directives specifying the service program (execute-... and reset) */ + static void execreset(void) { execute= 0; execbuiltin= 0; @@ -797,7 +770,7 @@ int df_executefromdirectory(int dtoken) { } if (!S_ISREG(stab.st_mode)) { parseerrprint("file `%s' in execute-from-directory is not an ordinary file" - " or link to one (mode=0%o)",fn,stab.st_mode); + " or link to one (mode=0%lo)",fn,(unsigned long)stab.st_mode); free(fn); freecharparray(newargs); return tokv_error; } execreset(); @@ -807,6 +780,8 @@ int df_executefromdirectory(int dtoken) { return 0; } +/* Parsing builtin service requests (execute-builtin) */ + static int bispa_none(char ***rnewargs) { return pa_mnl(); } @@ -848,110 +823,145 @@ int df_executebuiltin(int dtoken) { return 0; } -int df_errorstostderr(int dtoken) { +/* Directives for changing other execution parameters */ + +int dfg_setflag(int dtoken) { int r; - + r= pa_mnl(); if (r) return r; - closeerrorfile(); eh.handling= dtoken; return 0; + *lr_flag= lr_flagval; + return 0; } -int df_errorstosyslog(int dtoken) { - int token, level, facility; +int df_reset(int dtoken) { + int r; - facility= DEFUSERLOGFACILITY; - level= DEFUSERLOGLEVEL; - token= yylex(); - if (token == tokv_lwsp) { - token= yylex(); if (token == tokv_error) return token; - if (!(token & tokt_logfacility)) - return unexpected(token,-1,"syslog facility (or end of line)"); - facility= lr_logfacility; - token= yylex(); - } - if (token == tokv_lwsp) { - token= yylex(); if (token == tokv_error) return token; - if (!(token & tokt_loglevel)) - return unexpected(token,-1,"syslog level (or end of line) after facility"); - level= lr_loglevel; - token= yylex(); - } - if (unexpected(token,tokv_newline,"end of line somewhere after errors-to-syslog")) - return tokv_error; - closeerrorfile(); eh.handling= tokv_word_errorstosyslog; - eh.logfacility= facility; eh.loglevel= level; return 0; + r= pa_mnl(); if (r) return r; + r= parse_string(RESET_CONFIGURATION,"",1); + assert(!r); + return 0; } -int df_errorstofile(int dtoken) { - const char *cp; - FILE *file; - int r; - - r= paa_1path(&cp); if (r) return r; - file= fopen(cp,"a"); - if (!file) { - parseerrprint("unable to open error log file `%s': %s",cp,strerror(errno)); +int dfg_fdwant(int dtoken) { + int fdmin, fdmax, r, needreadwrite, havereadwrite, fd; + + needreadwrite= lr_fdwant_readwrite; + r= pa_mwsp(); if (r) return r; + r= yylex(); if (r == tokv_error) return r; + if (!(r & tokt_fdrange)) return unexpected(r,-1,"file descriptor range"); + fdmin= lr_min; fdmax= lr_max; + if (fdmin<0 || fdmin>MAX_ALLOW_FD || + (fdmax != -1 && fdmax<0) || fdmax>MAX_ALLOW_FD) { + parseerrprint("file descriptor in range is negative or far too large"); return tokv_error; } - if (setvbuf(file,0,_IOLBF,MAX_ERRMSG_LEN)) { - parseerrprint("unable to set line buffering on errors file: %s",strerror(errno)); - fclose(file); return tokv_error; + r= yylex(); if (r == tokv_error) return r; + if (r == tokv_newline) { + if (needreadwrite > 0) { + parseerrprint("read or write is required"); + return tokv_error; + } + havereadwrite= 0; + } else if (r == tokv_lwsp) { + if (needreadwrite < 0) { + parseerrprint("read or write not allowed"); return tokv_error; + } + r= yylex(); if (r == tokv_error) return r; + if (!(r & tokt_readwrite)) + return unexpected(r,-1,"read or write (or perhaps newline)"); + havereadwrite= r; + r= pa_mnl(); if (r) return r; + } else { + return unexpected(r,-1,"whitespace before read or write or newline"); } - closeerrorfile(); eh.handling= tokv_word_errorstofile; - eh.file= file; eh.filename= xstrsave(cp); return 0; + ensurefdarray(fdmin); + if (fdmax == -1) { + if (!(dtoken == tokv_word_rejectfd || dtoken == tokv_word_ignorefd)) + parseerrprint("unspecified maximum only allowed with reject-fd and ignore-fd"); + fdmax= fdarrayused-1; + restfdwantstate= dtoken; + restfdwantrw= havereadwrite; + } + ensurefdarray(fdmax); + for (fd=fdmin; fd<=fdmax; fd++) { + fdarray[fd].wantstate= dtoken; + fdarray[fd].wantrw= havereadwrite; + } + return 0; } -int dfg_setflag(int dtoken) { - int r; - - r= pa_mnl(); if (r) return r; - *lr_flag= lr_flagval; return 0; -} +/* Directives for changing error handling */ -int df_reset(int dtoken) { +int df_errorstostderr(int dtoken) { int r; - + r= pa_mnl(); if (r) return r; - r= parse_string(RESET_CONFIGURATION,"",1); - assert(!r); return 0; + closeerrorfile(); eh.handling= dtoken; + return 0; } -int df_cd(int dtoken) { - const char *cp; - int r; +int df_errorstosyslog(int dtoken) { + int token, level, facility; - r= paa_1path(&cp); if (r) return r; - if (!chdir(cp)) return 0; - parseerrprint("unable to change directory to `%s': %s",cp,strerror(errno)); - return tokv_error; + facility= DEFUSERLOGFACILITY; + level= DEFUSERLOGLEVEL; + token= yylex(); + if (token == tokv_lwsp) { + token= yylex(); if (token == tokv_error) return token; + if (!(token & tokt_logfacility)) + return unexpected(token,-1,"syslog facility (or end of line)"); + facility= lr_logfacility; + token= yylex(); + } + if (token == tokv_lwsp) { + token= yylex(); if (token == tokv_error) return token; + if (!(token & tokt_loglevel)) + return unexpected(token,-1,"syslog level (or end of line) after facility"); + level= lr_loglevel; + token= yylex(); + } + if (unexpected(token,tokv_newline,"end of line after errors-to-syslog")) + return tokv_error; + closeerrorfile(); eh.handling= tokv_word_errorstosyslog; + eh.logfacility= facility; eh.loglevel= level; + return 0; } -int df_userrcfile(int dtoken) { +int df_errorstofile(int dtoken) { const char *cp; + FILE *file; int r; - + r= paa_1path(&cp); if (r) return r; - free(userrcfile); userrcfile= xstrsave(cp); + file= fopen(cp,"a"); + if (!file) { + parseerrprint("unable to open error log file `%s': %s",cp,strerror(errno)); + return tokv_error; + } + if (setvbuf(file,0,_IOLBF,MAX_ERRMSG_LEN)) { + parseerrprint("unable to set line buffering on errors file: %s",strerror(errno)); + fclose(file); return tokv_error; + } + closeerrorfile(); eh.handling= tokv_word_errorstofile; + eh.file= file; eh.filename= xstrsave(cp); return 0; } +/* Directives for including other files or configuration data */ + int dfi_includeuserrcfile(int dtoken) { int r; r= pa_mnl(); if (r) return r; - if (userrcfile) return parse_file(userrcfile,0); - parseerrprint("_include-user-rcfile (for-internal-use directive) " - "found but user-rcfile not set"); return tokv_error; + assert(userrcfile); + return parse_file(userrcfile,0); } int dfi_includeclientconfig(int dtoken) { int r; r= pa_mnl(); if (r) return r; - if (!overridedata) { - parseerrprint("_include-client-config (for-internal-use directive) " - "found but configuration not overridden"); - return tokv_error; - } + assert(overridedata); return parse_string(overridedata,"",0); } @@ -969,47 +979,6 @@ int df_include(int dtoken) { return tokv_error; } -static int paa_message(const char **message_r) { - static char *buildbuf; - static int buildbuflen; - - int r, tl; - - r= pa_mwsp(); if (r) return r; - tl= 1; - makeroom(&buildbuf,&buildbuflen,10); - buildbuf[0]= 0; - for (;;) { - r= yylex(); if (r == tokv_error) return r; - if (r == tokv_eof) { - parseerrprint("unexpected end of file in message text"); return tokv_error; - } - if (r == tokv_newline) break; - tl+= strlen(yytext); - makeroom(&buildbuf,&buildbuflen,tl); - strcat(buildbuf,yytext); - } - *message_r= buildbuf; - return 0; -} - -int df_message(int dtoken) { - const char *mp; - int r; - - r= paa_message(&mp); if (r) return r; - parseerrprint("`message' directive: %s",mp); - return 0; -} - -int df_error(int dtoken) { - const char *mp; - int r; - - r= paa_message(&mp); if (r) return r; - parseerrprint("`error' directive: %s",mp); return tokv_error; -} - int df_includedirectory(int dtoken) { static char *buildbuf=0; static int buildbuflen=0; @@ -1030,16 +999,18 @@ int df_includedirectory(int dtoken) { tel= strlen(de->d_name); if (!tel) continue; p= de->d_name; - if (!isalnum(*p)) continue; + if (!*p || !isalnum(*p)) continue; while ((c= *++p)) if (!(isalnum(c) || c=='-')) break; if (c) continue; - makeroom(&buildbuf,&buildbuflen,cpl+1+tel+1); + if (makeroom(&buildbuf,&buildbuflen,cpl+1+tel+1)) + return stringoverflow("pathname in directory"); snyprintf(buildbuf,buildbuflen,"%s/%s",cp,de->d_name); r= parse_file(buildbuf,&found); if (r) { closedir(d); return r; } if (!found) { parseerrprint("unable to open file `%s' in included directory `%s': %s", de->d_name,cp,strerror(errno)); - closedir(d); return tokv_error; + closedir(d); + return tokv_error; } } if (closedir(d)) { @@ -1072,7 +1043,8 @@ int df_includelookup(int dtoken) { done= 0; cpl= strlen(cp); if (!parmvalues[0]) { - makeroom(&buildbuf,&buildbuflen,cpl+1+sizeof(NONEINCLUDELOOKUP)); + if (makeroom(&buildbuf,&buildbuflen,cpl+1+sizeof(NONEINCLUDELOOKUP))) + return stringoverflow("pathname in directory for lookup of undefined parameter"); snyprintf(buildbuf,buildbuflen,"%s/" NONEINCLUDELOOKUP,cp); r= parse_file(buildbuf,&thisdone); if (r) { freecharparray(parmvalues); return r; } @@ -1081,18 +1053,23 @@ int df_includelookup(int dtoken) { for (pp=parmvalues; *pp && (!done || dtoken == tokv_word_includelookupall); pp++) { - makeroom(&buildbuf,&buildbuflen, - cpl+1+strlen(*pp)*2+3+sizeof(EMPTYINCLUDELOOKUP)+1); + if (makeroom(&buildbuf,&buildbuflen, + cpl+1+strlen(*pp)*2+3+sizeof(EMPTYINCLUDELOOKUP)+1)) + return stringoverflow("pathname in directory for lookup"); strcpy(buildbuf,cp); p= *pp; q= buildbuf+cpl; *q++= '/'; - if (*p=='.') *q++= ':'; - while ((c= *p++)) { - if (c=='/') { *q++= ':'; c='-'; } - else if (c==':') { *q++= ':'; } - *q++= c; + if (!*p) { + strcpy(q,EMPTYINCLUDELOOKUP); + } else { + if (*p=='.') *q++= ':'; + while ((c= *p++)) { + if (c=='/') { *q++= ':'; c='-'; } + else if (c==':') { *q++= ':'; } + *q++= c; + } + *q++= 0; } - *q++= 0; r= parse_file(buildbuf,&thisdone); if (r) { freecharparray(parmvalues); return r; } if (thisdone) done= 1; @@ -1100,33 +1077,33 @@ int df_includelookup(int dtoken) { } freecharparray(parmvalues); if (!done) { - makeroom(&buildbuf,&buildbuflen, - cpl+1+sizeof(DEFAULTINCLUDELOOKUP)); + if (makeroom(&buildbuf,&buildbuflen, + cpl+1+sizeof(DEFAULTINCLUDELOOKUP))) + return stringoverflow("pathname in directory for lookup of default"); snyprintf(buildbuf,buildbuflen,"%s/" DEFAULTINCLUDELOOKUP,cp); r= parse_file(buildbuf,0); if (r) return r; } return 0; } +/* Control constructs */ + int df_catchquit(int dtoken) { int r; r= pa_mnl(); if (r) return r; r= parser(tokv_word_catchquit); - if (r & tokt_controlend) { - assert(r == tokv_word_hctac); - r= pa_mnl(); - } else if (r == tokv_quit || r == tokv_error) { + if (r == tokv_quit || r == tokv_error) { if (r == tokv_error) { r= parse_string(RESET_CONFIGURATION, "",1); assert(!r); } r= skip(tokv_word_catchquit); - if (r & tokt_controlend) { - assert(r == tokv_word_hctac); - r= pa_mnl(); - } + } + if (r & tokt_controlend) { + assert(r == tokv_word_hctac); + r= pa_mnl(); } return r; } @@ -1139,65 +1116,77 @@ int df_if(int dtoken) { r= pa_condition(&true); if (r) return r; if (!done && true) { r= parser(tokv_word_if); done= 1; } else { r= skip(tokv_word_if); } - if (!(r & tokv_word_if)) return r; + if (!(r & tokt_controlend)) return r; } while (r == tokv_word_elif); if (r == tokv_word_else) { r= pa_mnl(); if (r) return r; cstate->reportlineno= cstate->lineno; if (done) r= skip(tokv_word_if); else r= parser(tokv_word_if); - if (!(r & tokv_word_if)) return r; + if (!(r & tokt_controlend)) return r; } if (unexpected(r,tokv_word_fi,"`fi' to end `if'")) return tokv_error; return pa_mnl(); } -int dfg_fdwant(int dtoken) { - int fdmin, fdmax, r, needreadwrite, havereadwrite, fd; +int df_errorspush(int dt) { + struct error_handling save; + int r; - needreadwrite= lr_fdwant_readwrite; - r= pa_mwsp(); if (r) return r; - r= yylex(); if (r == tokv_error) return r; - if (!(r & tokt_fdrange)) return r; - fdmin= lr_min; fdmax= lr_max; - r= yylex(); if (r == tokv_error) return r; - if (r == tokv_newline) { - if (needreadwrite > 0) { - parseerrprint("read or write is required"); return tokv_error; - } - havereadwrite= 0; - } else if (r == tokv_lwsp) { - if (needreadwrite < 0) { - parseerrprint("read or write not allowed"); return tokv_error; - } - r= yylex(); if (r == tokv_error) return r; - if (!(r & tokt_readwrite)) - return unexpected(r,-1,"read or write (or perhaps newline)"); - havereadwrite= r; - r= pa_mnl(); if (r) return r; - } else { - return unexpected(r,-1,"whitespace before read or write or newline"); - } - ensurefdarray(fdmin); - if (fdmax == -1) { - if (!(dtoken == tokv_word_rejectfd || dtoken == tokv_word_ignorefd)) - parseerrprint("unspecified maximum only allowed with reject-fd and ignore-fd"); - fdmax= fdarrayused-1; - restfdwantstate= dtoken; - restfdwantrw= havereadwrite; - } - for (fd=fdmin; fd<=fdmax; fd++) { - fdarray[fd].wantstate= dtoken; - fdarray[fd].wantrw= havereadwrite; + r= pa_mnl(); if (r) return r; + + save= eh; + eh.filekeep= 1; + + r= parser(tokv_word_errorspush); + + closeerrorfile(); + eh= save; + + if (r & tokt_controlend) { + assert(r == tokv_word_srorre); + r= pa_mnl(); } + return r; +} + +/* Miscelleanous directives */ + +int df_cd(int dtoken) { + const char *cp; + int r; + + r= paa_1path(&cp); if (r) return r; + if (!chdir(cp)) return 0; + parseerrprint("unable to change directory to `%s': %s",cp,strerror(errno)); + return tokv_error; +} + +int df_userrcfile(int dtoken) { + const char *cp; + int r; + + r= paa_1path(&cp); if (r) return r; + free(userrcfile); userrcfile= xstrsave(cp); return 0; } -int df_quit(int dtoken) { +int df_message(int dtoken) { + const char *mp; int r; - r= pa_mnl(); if (r) return r; - return tokv_quit; + r= paa_message(&mp); if (r) return r; + parseerrprint("`message' directive: %s",mp); + return 0; +} + +int df_error(int dtoken) { + const char *mp; + int r; + + r= paa_message(&mp); if (r) return r; + parseerrprint("`error' directive: %s",mp); + return tokv_error; } int df_eof(int dtoken) { @@ -1207,23 +1196,150 @@ int df_eof(int dtoken) { return tokv_eof; } -int df_errorspush(int dt) { - struct error_handling save; +int df_quit(int dtoken) { int r; r= pa_mnl(); if (r) return r; + return tokv_quit; +} - save= eh; - eh.filekeep= 1; +/* + * Main parser routines + */ + +static void parser_push(struct parser_state *usestate, + const char *newfile, + const struct stat *newfilestab, + YY_BUFFER_STATE ybuf, + int isinternal) { + usestate->lineno= 1; + usestate->reportlineno= 1; + usestate->filename= newfile; + usestate->filestab= *newfilestab; + usestate->notedreferer= 0; + usestate->isinternal= isinternal; + usestate->ybuf= ybuf; + usestate->upstate= cstate; - r= parser(tokv_word_errorspush); + cstate= usestate; + yy_switch_to_buffer(ybuf); +} - closeerrorfile(); - eh= save; +static void parser_pop(void) { + struct parser_state *oldstate; - if (r & tokt_controlend) { - assert(r == tokv_word_srorre); - r= pa_mnl(); + oldstate= cstate; + cstate= cstate->upstate; + if (cstate) yy_switch_to_buffer(cstate->ybuf); + yy_delete_buffer(oldstate->ybuf); +} + +int parse_string(const char *string, const char *descrip, int isinternal) { + /* Returns the same things as parser, except that tokv_eof + * is turned into 0. + */ + static const struct stat blankstab; + + struct parser_state usestate; + YY_BUFFER_STATE ybuf; + int r; + + ybuf= yy_scan_string(string); + if (!ybuf) syscallerror("unable to create flex buffer for internal string"); + parser_push(&usestate,descrip,&blankstab,ybuf,isinternal); + + r= parser(0); + + parser_pop(); + if (r == tokv_eof) r= 0; + return r; +} + +static int parse_file(const char *string, int *didexist) { + /* Returns the same things as parser, except that tokv_eof + * is turned into 0. If *didexist is 0 then errno will + * have been set. + */ + static int fileparselevel= 0; + + struct parser_state usestate, *checkrecurse; + YY_BUFFER_STATE ybuf; + int r; + FILE *file; + struct stat newstab; + + if (fileparselevel >= MAX_INCLUDE_NEST) { + parseerrprint("too many nested levels of included files"); + return tokv_error; } + file= fopen(string,"r"); + if (!file) { + if (errno == ENOENT) { + if (didexist) *didexist= 0; + return 0; + } + parseerrprint("unable to open config file `%s': %s",string,strerror(errno)); + return tokv_error; + } + r= fstat(fileno(file),&newstab); if (r) syscallerror("unable to fstat new file"); + for (checkrecurse= cstate; checkrecurse; checkrecurse= checkrecurse->upstate) { + if (!checkrecurse->filestab.st_mode) continue; + if (newstab.st_dev==checkrecurse->filestab.st_dev && + newstab.st_ino==checkrecurse->filestab.st_ino) { + parseerrprint("recursion detected - config file `%s' calls itself",string); + fclose(file); + return tokv_error; + } + } + + if (didexist) *didexist= 1; + + ybuf= yy_create_buffer(file,YY_BUF_SIZE); + if (!ybuf) syscallerror("unable to create flex buffer for file"); + parser_push(&usestate,string,&newstab,ybuf,0); + fileparselevel++; + + r= parser(0); + if (ferror(file)) { + parseerrprint("error reading configuration file `%s'",string); + r= tokv_error; + } + + fileparselevel--; + parser_pop(); + fclose(file); + if (r == tokv_eof) r= 0; return r; } + +static int parser(int allowce) { + /* Returns: + * an exception (error, eof or quit) + * then rest of `file' is uninteresting + * or + * token if allowce was !0 and equal to token's controlend + * then rest of `file' (including rest of line with the + * controlend - even the whitespace) not scanned yet + */ + int token, r; + + for (;;) { /* loop over lines */ + cstate->reportlineno= cstate->lineno; + do { token= yylex(); } while (token == tokv_lwsp); + if (token & tokt_exception) { + return token; + } else if (token & tokt_controlend) { + if (lr_controlend == allowce) return token; + else return unexpected(token,-1,"directive (not this kind of" + " control structure end)"); + } else if (token & tokt_directive) { + if ((token & tokt_internal) && !cstate->isinternal) + return unexpected(token,-1,"published directive, not internal-use-only one"); + r= (lr_dir)(token); if (r) { assert(r & tokt_exception); return r; } + } else if (token == tokv_newline) { + /* ignore blank lines (and comment-only lines) */ + } else { + return unexpected(token,-1,"directive"); + } + } +} diff --git a/process.c b/process.c index 109c501..35eba59 100644 --- a/process.c +++ b/process.c @@ -19,7 +19,8 @@ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -/* We do some horrible asynchronous stuff with signals. +/* + * 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 @@ -178,7 +179,12 @@ static void getevent(struct event_msg *event_r) { switch (event_r->type) { case et_closereadfd: fd= event_r->data.closereadfd.fd; - assert(fd= fdarrayused) { + blocksignals(); + syslog(LOG_ERR,"client sent bad file descriptor %d to close (max %d)", + fd,fdarrayused-1); + disconnect(12); + } if (fdarray[fd].holdfd!=-1) { if (close(fdarray[fd].holdfd)) syscallerror("cannot close holding fd"); fdarray[fd].holdfd= -1; @@ -358,20 +364,6 @@ void senderrmsgstderr(const char *errmsg) { xfflush(swfile); } -static void getgroupnames(int ngids, gid_t *list, const char ***names_r) { - const char **names; - struct group *cgrp; - int i; - - names= xmalloc(sizeof(char*)*ngids); - for (i=0; igr_name); - } - *names_r= names; -} - /* The per-request main program and its subfunctions. */ static void setup_comms(int sfd) { @@ -484,6 +476,20 @@ static void establish_pipes(void) { } } +static void groupnames(int ngids, gid_t *gids, const char ***names_r) { + const char **names; + struct group *gr; + int i; + + names= xmalloc(sizeof(char*)*ngids); + for (i=0; igr_name); + } + *names_r= names; +} + static void lookup_uidsgids(void) { struct passwd *pw; @@ -513,22 +519,55 @@ static void lookup_uidsgids(void) { if (getgroups(service_ngids,service_gids+1) != service_ngids) syscallerror("getgroups(size,list)"); - getgroupnames(service_ngids,service_gids,&service_groups); - getgroupnames(request_mbuf.ngids,calling_gids,&calling_groups); + groupnames(request_mbuf.ngids,calling_gids,&calling_groups); + groupnames(service_ngids,service_gids,&service_groups); } -static void check_find_executable(void) { - int r, partsize; +static void findinpath(char *program) { char *part, *exectry; const char *string, *delim, *nextstring; struct stat stab; + int r, partsize; + + if (strchr(program,'/')) { + r= stat(program,&stab); + if (r) syscallfailure("failed check for program (containing slash) `%s'",program); + execpath= program; + } else { + string= getenv("PATH"); + if (!string) string= defaultpath(); + while (string) { + delim= strchr(string,':'); + if (delim) { + if (delim-string > MAX_GENERAL_STRING) + failure("execute-from-path, but PATH component too long"); + partsize= delim-string; + nextstring= delim+1; + } else { + partsize= strlen(string); + nextstring= 0; + } + part= xstrsubsave(string,partsize); + exectry= part[0] ? xstrcat3save(part,"/",program) : xstrsave(program); + free(part); + r= stat(exectry,&stab); + if (!r) { execpath= exectry; break; } + free(exectry); + string= nextstring; + } + if (!execpath) failure("program `%s' not found on default PATH",program); + } +} + +static void check_find_executable(void) { + struct stat stab; + int r; switch (execute) { case tokv_word_reject: failure("request rejected"); case tokv_word_execute: - r= stat(execpath,&stab); - if (r) syscallfailure("checking for executable `%s'",execpath); + findinpath(execpath); break; case tokv_word_executefromdirectory: r= stat(execpath,&stab); @@ -537,41 +576,37 @@ static void check_find_executable(void) { case tokv_word_executebuiltin: break; case tokv_word_executefrompath: - if (strchr(service,'/')) { - r= stat(service,&stab); - if (r) syscallfailure("execute-from-path (contains slash)" - " cannot check for executable `%s'",service); - execpath= service; - } else { - string= getenv("PATH"); - if (!string) string= defaultpath(); - while (string) { - delim= strchr(string,':'); - if (delim) { - if (delim-string > MAX_GENERAL_STRING) - failure("execute-from-path, but PATH component too long"); - partsize= delim-string; - nextstring= delim+1; - } else { - partsize= strlen(string); - nextstring= 0; - } - part= xstrsubsave(string,partsize); - exectry= part[0] ? xstrcat3save(part,"/",service) : xstrsave(service); - free(part); - r= stat(exectry,&stab); - if (!r) { execpath= exectry; break; } - free(exectry); - string= nextstring; - } - if (!execpath) failure("execute-from-path, but program `%s' not found",service); - } + findinpath(service); break; default: abort(); } } +static void makenonexistentfd(int fd) { + if (fdarray[fd].realfd == -1) { + assert(fdarray[fd].holdfd == -1); + } else { + if (close(fdarray[fd].realfd)) + syscallfailure("close unwanted file descriptor %d",fd); + fdarray[fd].realfd= -1; + + if (fdarray[fd].holdfd != -1) { + if (close(fdarray[fd].holdfd)) + syscallfailure("close unwanted hold descriptor for %d",fd); + } + } +} + +static void makenullfd(int fd) { + fdarray[fd].realfd= open("/dev/null", + fdarray[fd].wantrw == tokv_word_read ? O_RDONLY : + fdarray[fd].wantrw == tokv_word_write ? O_WRONLY : + 0); + if (fdarray[fd].realfd<0) + syscallfailure("cannot open /dev/null for null or allowed, unprovided fd"); +} + static void check_fds(void) { int fd; @@ -589,35 +624,27 @@ static void check_fds(void) { failure("file descriptor %d provided but rejected",fd); break; case tokv_word_ignorefd: - if (fdarray[fd].realfd != -1) - if (close(fdarray[fd].realfd)) - syscallfailure("close unwanted file descriptor %d",fd); - fdarray[fd].realfd= -1; + makenonexistentfd(fd); break; case tokv_word_nullfd: - if (fdarray[fd].realfd != -1) close(fdarray[fd].realfd); - fdarray[fd].realfd= open("/dev/null", - fdarray[fd].iswrite == -1 ? O_RDWR : - fdarray[fd].iswrite ? O_WRONLY : O_RDONLY); - if (fdarray[fd].realfd<0) - syscallfailure("cannot open /dev/null for null fd"); + makenonexistentfd(fd); + makenullfd(fd); break; case tokv_word_requirefd: if (fdarray[fd].realfd == -1) - failure("file descriptor %d not provided but required",fd); + failure("file descriptor %d required but not provided",fd); + assert(fdarray[fd].holdfd == -1); /* fall through */ case tokv_word_allowfd: if (fdarray[fd].realfd == -1) { - fdarray[fd].iswrite= (fdarray[fd].wantrw == tokv_word_write); - fdarray[fd].realfd= open("/dev/null",fdarray[fd].iswrite ? O_WRONLY : O_RDONLY); - if (fdarray[fd].realfd<0) - syscallfailure("cannot open /dev/null for allowed but not provided fd"); + assert(fdarray[fd].holdfd == -1); + makenullfd(fd); } else { if (fdarray[fd].iswrite) { - if (fdarray[fd].wantrw != tokv_word_write) + if (fdarray[fd].wantrw == tokv_word_read) failure("file descriptor %d provided write, wanted read",fd); } else { - if (fdarray[fd].wantrw != tokv_word_read) + if (fdarray[fd].wantrw == tokv_word_write) failure("file descriptor %d provided read, wanted write",fd); } } @@ -644,6 +671,13 @@ static void fork_service_synch(void) { r= socketpair(AF_UNIX,SOCK_STREAM,0,synchsocket); if (r) syscallerror("cannot create socket for synch"); + /* Danger here. Firstly, we start handling signals asynchronously. + * Secondly after we fork the service we want it to put + * itself in a separate process group so that we can kill it and all + * its children - but, we mustn't kill the whole pgrp before it has + * done that (or we kill ourselves) and it mustn't fork until it + * knows that we are going to kill it the right way ... + */ sig.sa_handler= sighandler_chld; sigemptyset(&sig.sa_mask); sigaddset(&sig.sa_mask,SIGCHLD); diff --git a/servexec.c b/servexec.c index 71412a7..2b11012 100644 --- a/servexec.c +++ b/servexec.c @@ -68,23 +68,23 @@ void bisexec_version(const char *const *argv) { "production version" #endif " - protocol magic number %08lx\n" - "protocol checksum: ", - BASE_MAGIC); - for (i=0, p=protocolchecksumversion; i If no The client will also use @@ -863,13 +865,13 @@ directive which modifies any particuar setting will take effect. Reject the request. -Execute the program Applications and notes on use

+Standard services and directory management +

+ +In later versions of this specification standard service names and +interfaces for common services such as mail delivery and WWW CGI +scripts will be specified. +

+ +~/.userv/.servdata/, where + + +The use of a dot-directory inside ~/.userv will hopefully avoid +the user becoming confused by finding parts of a semi-privileged +application's internal state in their filespace, and or discourage +them from fiddling with and thus corrupting it. (Note that such +applications should of course not rely for their global integrity on +the integrity of the data on the user's side of the security +boundary.) + Reducing the number of absolutely privileged subsystems

-- 2.30.2