tokens.h
pcsum.h
-english.lp
+version.h
+spec.sgml
+
*.sasp*
*.lout*
*.li
* 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:
# 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)
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)
lexer.l: language.i4
+spec.sgml: spec.sgml.in Makefile
+ sed -e '/<version><\/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
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
# 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:
--- /dev/null
+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 <ian@chiark.greenend.org.uk>.
+
+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.
/* 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
#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
# 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'`
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
+/*
+ * Here too, we do some horrible asynchronous stuff with signals.
+ *
+ * The following objects &c. are used in signal handlers and so
+ * must be protected by calls to blocksignals if they are used in
+ * the main program:
+ * stderr
+ * swfile
+ *
+ * The following objects are used in the main program unprotected
+ * and so must not be used in signal handlers:
+ * srfile
+ * fdsetup[].copyfd
+ * fdsetup[].pipefd
+ *
+ * The following objects/functions are not modified/called after the
+ * asynchronicity starts:
+ * malloc
+ * fdsetupsize
+ * fdsetup[].mods
+ * fdsetup[].filename
+ * fdsetup[].oflags
+ * results of argument parsing
+ *
+ * systemerror, swfile, fdsetup[].catpid and fdsetup[].killed are used
+ * for communication between the main thread and the signal handlers.
+ *
+ * All the signal handlers save errno so that is OK too.
+ */
+
#include <fcntl.h>
#include <stdarg.h>
#include <errno.h>
#include "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,
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 };
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;
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;
}
}
+/* Functions which may be called either from signal handlers or from
+ * the main thread. They block signals in case they are on the main
+ * thread, and may only use signal handler objects. None of them
+ * return. If they did they'd have to restore the signal mask.
+ */
+
static void NONRETURNPRINTFFORMAT(1,2) miscerror(const char *fmt, ...) {
va_list al;
exit(-1);
}
+/*
+ * 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) { }
}
#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; i<progress_r->data.errmsg.messagelen; i++) {
+ c= getc(file); if (c==EOF) protoreaderror(file,"in error message");
+ if (isprint(c)) putc(c,stderr);
+ else fprintf(stderr,"\\x%02x",(unsigned char)c);
+ }
+ putc('\n',stderr);
+ if (ferror(stderr)) syscallerror("printing error message");
+ xfread(&ul,sizeof(ul),file);
+ checkmagic(ul,PROGRESS_ERRMSG_END_MAGIC,"after error message");
+ blocksignals(SIG_UNBLOCK);
+ break;
+ default:
+ return;
+ }
+ }
+}
+
+/*
+ * 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);
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);
xfwrite(s,sizeof(*s)*l,file);
}
-static void xfflush(FILE *file) {
- if (fflush(file)) syscallerror("flush server socket");
+static void xfwritefds(int modifier, int expected, FILE *file) {
+ int i, fdcount;
+
+ for (i=0, fdcount=0; i<fdsetupsize; i++) {
+ if (!(fdsetup[i].filename && (fdsetup[i].mods & modifier)))
+ continue;
+ xfwrite(&i,sizeof(int),file); fdcount++;
+ }
+ assert(fdcount == expected);
}
+/* Functions which may be called from signal handlers. These
+ * may use signal-handler objects. The main program may only
+ * call them with signals blocked, and they may not use any
+ * main-thread objects.
+ */
+
+static void disconnect(void) /* DOES return, unlike in daemon */ {
+ struct event_msg event_mbuf;
+ int r;
+
+ if (swfile) {
+ memset(&event_mbuf,0,sizeof(event_mbuf));
+ event_mbuf.magic= EVENT_MAGIC;
+ event_mbuf.type= et_disconnect;
+ r= fwrite(&event_mbuf,1,sizeof(event_mbuf),swfile);
+ if ((r != sizeof(event_mbuf) || fflush(swfile)) && errno != EPIPE)
+ syscallerror("write to server when disconnecting\n");
+ }
+ systemerror= 1;
+}
+
+static void sighandler_alrm(int ignored) /* DOES return, unlike in daemon */ {
+ int es;
+ es= errno;
+ fputs("userv: timeout\n",stderr);
+ disconnect();
+ errno= es;
+}
+
+static void sighandler_chld(int ignored) /* DOES return, unlike in daemon */ {
+ struct event_msg event_mbuf;
+ pid_t child;
+ int status, fd, r, es;
+
+ es= errno;
+ for (;;) {
+ child= wait3(&status,WNOHANG,0);
+ if (child == 0 || (child == -1 && errno == ECHILD)) break;
+ if (child == -1) syscallerror("wait for child process (in sigchld handler)");
+ for (fd=0; fd<fdsetupsize && fdsetup[fd].catpid != child; fd++);
+ if (fd>=fdsetupsize) continue; /* perhaps the caller gave us children */
+ if ((WIFEXITED(status) && WEXITSTATUS(status)==0) ||
+ (WIFSIGNALED(status) && WTERMSIG(status)==SIGPIPE) ||
+ (fdsetup[fd].killed && WIFSIGNALED(status) && WTERMSIG(status)==SIGKILL)) {
+ if (swfile && fdsetup[fd].mods & fdm_read) {
+ memset(&event_mbuf,0,sizeof(event_mbuf));
+ event_mbuf.magic= EVENT_MAGIC;
+ event_mbuf.type= et_closereadfd;
+ 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 <options> [--] <service-user> <service-name> [<argument> ...]\n"
"usage: userv <options> -B|--builtin [--] <builtin-service> [<info-argument> ...]\n"
"options: -f|--file <fd>[<fdmodifiers>]=<filename>\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");
+ "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;
fdsetup= xrealloc(fdsetup,sizeof(struct fdsetupstate)*fdsetupsize);
while (oldarraysize < fdsetupsize) {
fdsetup[oldarraysize].filename= 0;
+ fdsetup[oldarraysize].pipefd= -1;
fdsetup[oldarraysize].copyfd= -1;
fdsetup[oldarraysize].mods= 0;
+ fdsetup[oldarraysize].oflags= 0;
fdsetup[oldarraysize].catpid= -1;
fdsetup[oldarraysize].killed= 0;
- fdsetup[oldarraysize++].filename= 0;
+ fdsetup[oldarraysize].filename= 0;
oldarraysize++;
}
}
key= delim;
delim= strchr(key,',');
if (delim) *delim++= 0;
- addfdmodifier(&fdsetup[fd],fd,key);
+ addfdmodifier(fd,key);
}
if (!(fdsetup[fd].mods & (fdm_read|fdm_write))) {
- if (fd != 1 && fd != 2) {
- addfdmodifier(&fdsetup[fd],fd,"read");
+ if (fd == 0) {
+ addfdmodifier(fd,"read");
} else if (fdsetup[fd].mods & fdm_fd) {
- addfdmodifier(&fdsetup[fd],fd,"write");
+ addfdmodifier(fd,"write");
} else {
- addfdmodifier(&fdsetup[fd],fd,"overwrite");
+ addfdmodifier(fd,"overwrite");
}
}
if (fdsetup[fd].mods & fdm_fd) {
copyfd= strtoul(value,&delim,0);
if (*delim)
usageerror("value part of argument to --file with fd modifier must be "
- "numeric or fd name- `%s' is not recognised",value);
+ "numeric or fd name - `%s' is not recognised",value);
else if (copyfd > MAX_ALLOW_FD)
usageerror("file descriptor %lu named as target of file descriptor redirection"
" (for file descriptor %lu) is larger than maximum allowed (%d)",
copyfd,fd,MAX_ALLOW_FD);
}
- do { r= fstat(copyfd,&stab); } while (r && errno==EINTR);
+ r= fstat(copyfd,&stab);
if (r) {
if (oip) syscallerror("check filedescriptor %lu (named as target of file "
"descriptor redirection for %lu)",copyfd,fd);
ul= strtoul(key,&delim,0);
if (*delim) usageerror("first part of argument to --fdwait must be "
"numeric or fd name - `%s' is not recognised",key);
- if (ul>INT_MAX) usageerror("first part of argument to --fdwait is far too large");
+ if (ul>MAX_ALLOW_FD) usageerror("first part of argument to --fdwait is too large");
fd= ul;
}
if (fd >= fdsetupsize || !fdsetup[fd].filename)
static void of_timeout(const struct optioninfo *oip, const char *value, char *key) {
char *endp;
- timeout= strtoul(value,&endp,0);
+ unsigned long ul;
+
+ ul= strtoul(value,&endp,0);
if (*endp) usageerror("timeout value `%s' must be a plain decimal string",value);
- if (timeout>INT_MAX) usageerror("timeout value %lu too large",timeout);
+ if (ul>INT_MAX) usageerror("timeout value %lu too large",ul);
+ timeout= ul;
}
static void of_signals(const struct optioninfo *oip, const char *value, char *key) {
unsigned long numvalue;
char *endp;
+
numvalue= strtoul(value,&endp,0);
if (*endp) {
if (!strcmp(value,"number")) signalsexit= se_number;
}
static void of_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"
" You should have received a copy of the GNU General Public License along\n"
" with userv; if not, write to Ian Jackson <ian@chiark.greenend.org.uk> or\n"
" to the Free Software Foundation, 59 Temple Place - Suite 330, Boston,\n"
-" MA 02111-1307, USA.\n"
- ) == EOF) syscallerror("write usage to stderr");
+" MA 02111-1307, USA.\n",
+ stdout) < 0) syscallerror("write usage to stderr");
exit(0);
}
}
}
-static void checkmagic(unsigned long was, unsigned long should, const char *when) {
- if (was != should)
- protoerror("magic number %s was %08lx, expected %08lx",when,was,should);
-}
-
-static void getprogress(struct progress_msg *progress_r, FILE *file) {
- int i, c;
- unsigned long ul;
-
- for (;;) {
- xfread(progress_r,sizeof(struct progress_msg),file);
- switch (progress_r->type) {
- case pt_failed:
- blocksignals(SIG_BLOCK);
- fputs("userv: uservd reports that service failed\n",stderr);
- exit(-1);
- case pt_errmsg:
- blocksignals(SIG_BLOCK);
- fputs("uservd: ",stderr);
- if (progress_r->data.errmsg.messagelen>4096)
- protoerror("stderr message length %d is far too long",
- progress_r->data.errmsg.messagelen);
- for (i=0; i<progress_r->data.errmsg.messagelen; i++) {
- c= getc(file); if (c==EOF) protoreaderror(file,"in error message");
- if (isprint(c)) putc(c,stderr);
- else fprintf(stderr,"\\x%02x",(unsigned char)c);
- }
- putc('\n',stderr);
- if (ferror(stderr)) syscallerror("printing error message");
- xfread(&ul,sizeof(ul),file);
- checkmagic(ul,PROGRESS_ERRMSG_END_MAGIC,"after error message");
- blocksignals(SIG_UNBLOCK);
- break;
- default:
- return;
- }
- }
-}
-
-static void xfwritefds(int modifier, int expected, FILE *file) {
- int i, fdcount;
-
- for (i=0, fdcount=0; i<fdsetupsize; i++) {
- if (!(fdsetup[i].filename && (fdsetup[i].mods & modifier)))
- continue;
- xfwrite(&i,sizeof(int),file); fdcount++;
- }
- assert(fdcount == expected);
-}
-
-static void disconnect(void) /* DOES return, unlike in daemon */ {
- struct event_msg event_mbuf;
-
- if (!swfile) {
- fputs("userv: failed, after service program terminated\n",stderr);
- _exit(255);
- }
- memset(&event_mbuf,0,sizeof(event_mbuf));
- event_mbuf.magic= EVENT_MAGIC;
- event_mbuf.type= et_disconnect;
- xfwrite(&event_mbuf,sizeof(event_mbuf),swfile);
- xfflush(swfile);
-}
-
-static void sighandler_alrm(int ignored) /* DOES return, unlike in daemon */ {
- int es;
- es= errno;
- fputs("userv: timeout\n",stderr);
- disconnect();
- errno= es;
-}
-
-static void sighandler_chld(int ignored) /* DOES return, unlike in daemon */ {
- struct event_msg event_mbuf;
- pid_t child;
- int status, fd, r, es;
+/*
+ * Main thread main processing functions - in order of execution.
+ */
- es= errno;
- for (;;) {
- child= wait3(&status,WNOHANG,0);
- if (child == 0 || (child == -1 && errno == ECHILD)) break;
- if (child == -1) syscallerror("wait for child process (in sigchld handler)");
- for (fd=0; fd<fdsetupsize && fdsetup[fd].catpid != child; fd++);
- if (fd>=fdsetupsize) continue; /* perhaps the invoker gave us children */
- if ((WIFEXITED(status) && WEXITSTATUS(status)==0) ||
- (WIFSIGNALED(status) && WTERMSIG(status)==SIGPIPE) ||
- (fdsetup[fd].killed && WIFSIGNALED(status) && WTERMSIG(status)==SIGKILL)) {
- if (swfile && fdsetup[fd].mods & fdm_read) {
- memset(&event_mbuf,0,sizeof(event_mbuf));
- event_mbuf.magic= EVENT_MAGIC;
- event_mbuf.type= et_closereadfd;
- r= fwrite(&event_mbuf,1,sizeof(event_mbuf),swfile);
- if (r != sizeof(event_mbuf) || fflush(swfile))
- if (errno != EPIPE) syscallerror("inform service of closed read fd");
- }
- } else {
- if (WIFEXITED(status))
- fprintf(stderr,"userv: cat for fd %d exited with error exit status %d\n",
- fd,WEXITSTATUS(status));
- else if (WIFSIGNALED(status))
- if (WCOREDUMP(status))
- fprintf(stderr,"userv: cat for fd %d dumped core due to signal %s (%d)\n",
- fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
- else
- fprintf(stderr,"userv: cat for fd %d terminated by signal %s (%d)\n",
- fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
- else
- fprintf(stderr,"userv: cat for fd %d gave unknown wait status %d\n",
- fd,status);
- disconnect();
- }
- fdsetup[fd].catpid= -1;
- }
- errno= es;
-}
+static void security_init(void) {
+ /* May not open any file descriptors. */
+
+ mypid= getpid(); if (mypid == (pid_t)-1) syscallerror("getpid");
+ myuid= getuid(); if (myuid == (uid_t)-1) syscallerror("getuid");
+ mygid= getgid(); if (mygid == (gid_t)-1) syscallerror("getgid");
+ ngids= getgroups(0,0); if (ngids == (gid_t)-1) syscallerror("getgroups(0,0)");
+ gidarray= xmalloc(sizeof(gid_t)*ngids);
+ if (getgroups(ngids,gidarray) != ngids) syscallerror("getgroups(ngids,)");
+
+ priv_suspend();
-static void catdup(const char *which, int from, int to) {
- if (dup2(from,to)<0) {
- blocksignals(SIG_BLOCK);
- fprintf(stderr,"userv: %s: cannot dup for %s: %s\n",which,
- to?"stdout":"stdin", strerror(errno));
- exit(-1);
- }
+ if (ngids > MAX_GIDS) miscerror("caller is in far too many gids");
}
-int main(int argc, char *const *argv) {
+static void parse_arguments(int *argcp, char *const **argvp) {
static char fd0key[]= "stdin,fd,read";
static char fd1key[]= "stdout,fd,write";
static char fd2key[]= "stderr,fd,write";
- static char stderrbuf[BUFSIZ], stdoutbuf[1024];
-
+
char *const *argpp;
char *argp;
const struct optioninfo *oip;
- struct sockaddr_un ssockname;
- int sfd, ngids, i, 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);
if (!*argpp) usageerror(overridetype == ot_builtin ?
"no service name given after options and service user" :
"no builtin service given after options");
+
+ *argcp-= (argpp-*argvp);
+ *argvp= argpp;
+
+ if (*argcp > MAX_ARGSDEFVAR) usageerror("far too many arguments");
for (fd=0; fd<fdsetupsize; fd++) {
if (!fdsetup[fd].filename) continue;
assert(fdsetup[fd].mods & (fdm_read|fdm_write));
fdsetup[fd].mods |= (fdsetup[fd].mods & fdm_read) ? fdm_close : fdm_wait;
}
+}
- if (setvbuf(stderr,stderrbuf,_IOLBF,sizeof(stderrbuf)))
- syscallerror("set buffering on stderr");
- if (setvbuf(stdout,stdoutbuf,_IOFBF,sizeof(stdoutbuf)))
- syscallerror("set buffering on stdout");
-
- argc-= (argpp-argv);
- argv= argpp;
- if (argc > 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;
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:
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:
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);
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");
checkmagic(opening_mbuf.magic,OPENING_MAGIC,"in opening message");
if (memcmp(protocolchecksumversion,opening_mbuf.protocolchecksumversion,PCSUMSIZE))
protoerror("protocol version checksum mismatch - server not same as client");
+}
+static void server_preparepipes(void) {
+ char pipepathbuf[PIPEPATHMAXLEN+2];
+ int fd, tempfd;
+
for (fd=0; fd<fdsetupsize; fd++) {
if (!fdsetup[fd].filename) continue;
+ pipepathbuf[PIPEPATHMAXLEN]= 0;
sprintf(pipepathbuf, PIPEPATHFORMAT,
(unsigned long)mypid, (unsigned long)opening_mbuf.serverpid, fd);
+ assert(!pipepathbuf[PIPEPATHMAXLEN]);
priv_resume();
if (unlink(pipepathbuf) && errno != ENOENT)
syscallerror("remove any old pipe `%s'",pipepathbuf);
if (mkfifo(pipepathbuf,0600)) /* permissions are irrelevant */
syscallerror("create pipe `%s'",pipepathbuf);
tempfd= open(pipepathbuf,O_RDWR);
- if (tempfd == -1) syscallerror("prelim open pipe `%s' for read+write",pipepathbuf);
+ if (tempfd<0) syscallerror("prelim open pipe `%s' for read+write",pipepathbuf);
assert(fdsetup[fd].mods & (fdm_read|fdm_write));
fdsetup[fd].pipefd=
open(pipepathbuf, (fdsetup[fd].mods & fdm_read) ? O_WRONLY : O_RDONLY);
- if (fdsetup[fd].pipefd == -1) syscallerror("real open pipe `%s'",pipepathbuf);
+ if (fdsetup[fd].pipefd<0) syscallerror("real open pipe `%s'",pipepathbuf);
if (close(tempfd)) syscallerror("close prelim fd onto pipe `%s'",pipepathbuf);
priv_suspend();
}
+}
+static void server_sendrequest(int argc, char *const *argv) {
+ struct request_msg request_mbuf;
+ unsigned long ul;
+ int fd, i;
+
memset(&request_mbuf,0,sizeof(request_mbuf));
request_mbuf.magic= REQUEST_MAGIC;
request_mbuf.clientpid= getpid();
}
ul= REQUEST_END_MAGIC; xfwrite(&ul,sizeof(ul),swfile);
xfflush(swfile);
+}
- priv_permanentlyrevokesuspended(); /* Must not do this before we give our real id */
-
+static void server_awaitconfirm(void) {
+ struct progress_msg progress_mbuf;
+
getprogress(&progress_mbuf,srfile);
if (progress_mbuf.type != pt_ok)
protoerror("progress message during configuration phase"
" unexpected type %d",progress_mbuf.type);
+}
+static void prepare_asynchsignals(void) {
+ static char stderrbuf[BUFSIZ], stdoutbuf[BUFSIZ];
+
+ struct sigaction sig;
+
+ if (setvbuf(stderr,stderrbuf,_IOLBF,sizeof(stderrbuf)))
+ syscallerror("set buffering on stderr");
+ if (setvbuf(stdout,stdoutbuf,_IOFBF,sizeof(stdoutbuf)))
+ syscallerror("set buffering on stdout");
+
sig.sa_handler= sighandler_chld;
sigemptyset(&sig.sa_mask);
sigaddset(&sig.sa_mask,SIGCHLD);
sig.sa_handler= sighandler_alrm;
if (sigaction(SIGALRM,&sig,0)) syscallerror("set up sigalrm handler");
+ if (!timeout) return;
+ if (alarm(timeout)<0) syscallerror("set up timeout alarm");
+}
+
+static void catdup(const char *which, int from, int to) {
+ if (dup2(from,to)<0) {
+ blocksignals(SIG_BLOCK);
+ fprintf(stderr,"userv: %s: cannot dup for %s: %s\n",which,
+ to?"stdout":"stdin", strerror(errno));
+ exit(-1);
+ }
+}
+
+static void connect_pipes(void) {
+ struct sigaction sig;
+ char catnamebuf[sizeof(int)*3+30];
+ int fd, reading;
+
for (fd=0; fd<fdsetupsize; fd++) {
if (!fdsetup[fd].filename) continue;
if (!(fdsetup[fd].mods & fdm_fd)) {
fdsetup[fd].copyfd=
- open(fdsetup[fd].filename,fdsetup[fd].oflags,0777);
+ open(fdsetup[fd].filename,fdsetup[fd].oflags|O_NOCTTY,0777);
if (fdsetup[fd].copyfd<0)
syscallerror("open file `%s' for fd %d",fdsetup[fd].filename,fd);
}
if (close(fdsetup[fd].copyfd)) syscallerror("close real fd for %d",fd);
if (close(fdsetup[fd].pipefd)) syscallerror("close pipe fd for %d",fd);
}
+}
- if (timeout)
- if (alarm(timeout)<0) syscallerror("set up timeout alarm");
+static void server_sendconfirm(void) {
+ struct event_msg event_mbuf;
blocksignals(SIG_BLOCK);
memset(&event_mbuf,0,sizeof(event_mbuf));
xfwrite(&event_mbuf,sizeof(event_mbuf),swfile);
xfflush(swfile);
blocksignals(SIG_UNBLOCK);
+}
+static int server_awaitcompletion(void) {
+ struct progress_msg progress_mbuf;
+
getprogress(&progress_mbuf,srfile);
if (progress_mbuf.type != pt_terminated)
protoerror("progress message during execution phase"
" unexpected type %d",progress_mbuf.type);
swfile= 0;
+ return progress_mbuf.data.terminated.status;
+}
+
+static void dispose_remaining_pipes(void) {
+ sigset_t sset;
+ int fd, r;
blocksignals(SIG_BLOCK);
for (fd=0; fd<fdsetupsize; fd++) {
if (r && errno != EINTR) syscallerror("sigsuspend failed in unexpected way");
blocksignals(SIG_UNBLOCK);
}
+}
+static void NONRETURNING process_exitstatus(int status) {
blocksignals(SIG_BLOCK);
- status= progress_mbuf.data.terminated.status;
+ if (systemerror) _exit(255);
+
if (sigpipeok && signalsexit != se_stdout && WIFSIGNALED(status) &&
WTERMSIG(status)==SIGPIPE && !WCOREDUMP(status)) status= 0;
break;
}
- fprintf(stderr,"unknown wait status %d\n",status);
+ fprintf(stderr,"userv: unknown wait status %d\n",status);
_exit(-1);
}
+
+int main(int argc, char *const *argv) {
+ int status, socketfd;
+
+#ifdef NDEBUG
+# error Do not disable assertions in this security-critical code !
+#endif
+
+ security_init();
+ parse_arguments(&argc,&argv);
+ determine_users();
+ determine_cwd();
+ process_override(argv[0]);
+
+ socketfd= server_connect();
+ priv_suspend();
+ server_handshake(socketfd);
+ server_preparepipes();
+ server_sendrequest(argc,argv);
+
+ priv_permanentlyrevokesuspended();
+
+ server_awaitconfirm();
+ prepare_asynchsignals();
+ connect_pipes();
+ server_sendconfirm();
+ status= server_awaitcompletion();
+
+ dispose_remaining_pipes();
+ process_exitstatus(status);
+}
#ifndef PIPEFORMAT
# define PIPEFORMAT "%lx.%lx.%x"
# define PIPEPATTERN "[0-9a-f]*.[0-9a-f]*.*[0-9a-f]"
-# define PIPEFORMATEXTEND (sizeof(long)*2*2+sizeof(int)*2+1)
+# define PIPEFORMATEXTEND (sizeof(unsigned long)*2*2+sizeof(int)*2+3)
# define PIPEMAXLEN (sizeof(PIPEFORMAT)+PIPEFORMATEXTEND)
#endif
#define MAX_ALLOW_FD 1024
#define MAX_GENERAL_STRING (1024*1024)
#define MAX_OVERRIDE_LEN MAX_GENERAL_STRING
+#define MAX_ERRMSG_STRING 4096
#define MAX_ARGSDEFVAR 4096
#define MAX_GIDS 1024
int serviceuserlen;
int servicelen;
int lognamelen;
- int cwdlen;
+ int cwdlen, overridelen;
uid_t callinguid;
- int ngids, nreadfds, nwritefds, nargs, nvars, overridelen;
+ int ngids, nreadfds, nwritefds, nargs, nvars;
/* Followed by:
* serviceuserlen bytes for the service user (unterminated)
* servicelen bytes for the service (unterminated)
* lognamelen bytes for the login name (unterminated)
* cwdlen bytes for the cwd (unterminated)
+ * overridelen bytes for the override data (with extra \n but unterminated),
+ * or nothing if overridelen==-1
* ngids gid_ts for the primary group and supplementary groups
* nreadfds and then nwritefds ints for the file descriptors
* for each of the nargs arguments
struct { int status; } terminated;
} data;
/* follwed by variable-length part:
- * for ok: nothing
- * for errmsg: messagelen bytes for the error message (unterminated)
- * unsigned long PROGRESS_MAGIC
- * for terminated: nothing
+ * for ok, failed, terminated: nothing
+ * for errmsg: messagelen bytes for the error message (unterminated, no \n)
+ * unsigned long PROGRESS_ERRMSG_END_MAGIC
*/
};
if test "x$enable_debug" = xyes; then
DEBUGDEFS="-DDEBUG -DVARDIR='\"$crdir/vd\"' -DSYSTEMCONFIGDIR='\"$crdir/slash-etc\"' -DSERVICEUSERDIR='\"$crdir/tilde\"'"
DEBUGLIBS=-lefence
+ echo will build debugging version
elif test "x$enable_debug" != xno; then
AC_MSG_ERROR(--enable-debug does not allow any arguments except 'yes' and 'no')
fi
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_GNUC25_NORETURN),
AC_MSG_RESULT(no))
- DPKG_CACHED_TRY_COMPILE(__attribute__((const)),dpkg_cv_c_attribute_const,,
- [extern int testfunction(int x) __attribute__((const))],
+ DPKG_CACHED_TRY_COMPILE(__attribute__((unused)),dpkg_cv_c_attribute_unused,,
+ [extern int testfunction(int x) __attribute__((unused))],
AC_MSG_RESULT(yes)
- AC_DEFINE(HAVE_GNUC25_CONST),
+ AC_DEFINE(HAVE_GNUC25_UNUSED),
AC_MSG_RESULT(no))
DPKG_CACHED_TRY_COMPILE(__attribute__((format...)),dpkg_cv_attribute_format,,
[extern int testfunction(char *y, ...) __attribute__((format(printf,1,2)))],
#define EMPTYINCLUDELOOKUP ":empty"
#define USERCONFIGDIRBASE SYSTEMUSERVCONFIGDIR
-#define USERCONFIGDIR HIDDENPREFIX USERCONFIGDIRBASE
-#define USERUSERVCONFIGPATH USERDIR "/" USERCONFIGDIR
+#define USERCONFIGDIR "." USERCONFIGDIRBASE
+#define USERUSERVCONFIGPATH "~/" USERCONFIGDIR
#define USERRCFILEPATH USERUSERVCONFIGPATH "/" USERRCFILE
#define SYSTEMUSERVCONFIGPATH SYSTEMCONFIGDIR "/" SYSTEMUSERVCONFIGDIR
#define SYSTEMRCFILEDEFAULTPATH SYSTEMUSERVCONFIGPATH "/" SYSTEMRCFILEDEFAULT
#define SHELLLISTPATH SYSTEMCONFIGDIR "/" SHELLLIST
#define SETENVIRONMENTPATH SYSTEMCONFIGDIR "/" SETENVIRONMENT
-#define USERDIR "~"
-#define HIDDENPREFIX "."
-
#define USERVD_LOGIDENT "uservd"
#define USERVD_LOGFACILITY LOG_DAEMON
#define DEFUSERLOGFACILITY LOG_DAEMON
"
#define MAX_INCLUDE_NEST 40
-#define MAX_ERRMSG_LEN 2048
+#define MAX_ERRMSG_LEN (MAX_ERRMSG_STRING-1024)
#define ERRMSG_RESERVE_ERRNO 128
int parse_string(const char *string, const char *descrip, int isinternal);
const char *defaultpath(void);
struct fdstate {
- int iswrite, realfd, holdfd;
+ int iswrite; /* 0 or 1; -1 if not open */
+ int realfd, holdfd; /* -1 if not open */
int wantstate;
/* tokv_word_requirefd, tokv_word_allowfd, tokv_nullfd, tokv_word_rejectfd
* (all of which have tokt_wantfdstate set) */
- int wantrw;
- /* tokv_word_read, tokv_word_write */
+ int wantrw; /* tokv_word_read, tokv_word_write, 0 for either/both */
};
struct keyvaluepair { char *key, *value; };
/* directive functions return:
* 0 for success having scanned up to and including end of line but not beyond,
* or tokv_error or tokv_quit.
+ * They expect to parse the whitespace before their parameters (if any).
*/
-typedef int parmcondition_fnt(int ctoken, char **parmvalues, int *rtrue);
+typedef int parmcondition_fnt(int ctoken, char *const *parmvalues, int *rtrue);
static parmcondition_fnt pcf_glob, pcf_range, pcf_grep;
/* all conditional functions return tokv_error for failure or 0 for success
* at parsing and testing, in which case *rtrue is set to 0 or 1.
* On success they have scanned up to and including the condition's
- * terminating newline.
+ * terminating newline; the pcf_... functions expect to parse the whitespace
+ * between the parameter name and the condition's arguments.
+ * Otherwise they return tokv_error.
* The parameter-based conditionals take a list of parameter values
* as obtained from the parameter functions and pa_parameter,
* and do _not_ free it.
struct parser_state {
int lineno, reportlineno, notedreferer, isinternal;
const char *filename;
+ struct stat filestab;
YY_BUFFER_STATE ybuf;
struct parser_state *upstate;
};
struct error_handling {
int handling; /* One of the error handling modes tokt_ehandlemode */
- int logfacility, loglevel, filekeep;
+ int logfacility, loglevel;
+ int filekeep; /* File is in use by higher-level errors-push, leave it open */
FILE *file;
char *filename;
};
static int dequote(char *inplace);
+#define YY_NO_UNPUT
+
%}
+
%option noyywrap
+
%%
dnl simple words
}
[0-9]{1,8}-[0-9]{1,8} {
char *ep;
- lr_min=(int)strtoul(yytext,&ep,10);
+ lr_min= (int)strtoul(yytext,&ep,10);
assert(*ep == HYPHEN);
assert(*++ep);
- lr_max=(int)strtoul(ep,&ep,10);
+ lr_max= (int)strtoul(ep,&ep,10);
assert(!*ep);
if (lr_max < lr_min) {
parseerrprint("fd range has min > max");
'
divert(-1)
undivert
-
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
+#include <sys/types.h>
#include "config.h"
+#include "common.h"
#include "lib.h"
char *xstrcat3save(const char *a, const char *b, const char *c) {
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) {
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
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;
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 {
* 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
*
* 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) {
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;
}
}
}
- 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)+50<sizeof(buf));
- sprintf(buf,"word `%s'",yytext);
+ assert(strlen(yytext)+sizeof(keywordfmt)<sizeof(buf));
+ snyprintf(buf,sizeof(buf),keywordfmt,yytext);
return buf;
} else if (token & tokt_number) {
- sprintf(buf,"number %d",lr_min); return buf;
+ snyprintf(buf,sizeof(buf),"number %d",lr_min); return buf;
+ } else if (token & tokt_fdrange && lr_max==-1) {
+ snyprintf(buf,sizeof(buf),"fdrange %d-",lr_min); return buf;
} else if (token & tokt_fdrange) {
- sprintf(buf,"fdrange %d..%d",lr_min,lr_max); return buf;
+ snyprintf(buf,sizeof(buf),"fdrange %d-%d",lr_min,lr_max); return buf;
} else if ((token & tokm_repres) == tokr_punct) {
- assert(strlen(yytext)+50<sizeof(buf));
- sprintf(buf,"operator `%s'",yytext);
+ assert(strlen(yytext)+sizeof(operatorfmt)<sizeof(buf));
+ snyprintf(buf,sizeof(buf),operatorfmt,yytext);
return buf;
} else if (token & tokt_string) {
switch (token) {
default: abort();
}
strcat(buf," `");
- i= sizeof(buf)-50; p= yytext; q= buf+strlen(buf);
- while (i-- >0 && (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;
}
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");
}
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;
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;
}
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 && strcmp(yytext+2,defvararray[i].key);
- i++);
- if (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(); }
}
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;
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) {
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<size; i++) {
+ a[i]= xstrsave(groups[i]);
+ a[size+i]= parm_ulong(gids[i]);
}
-
- fileparselevel--;
- parser_pop();
- fclose(file);
- if (r == tokv_eof) r= 0;
- return r;
+ a[size*2]= 0;
+ *rvalues= a;
+ return 0;
+}
+
+static int pf_service(int ptoken, char ***rvalues) {
+ parm_1string(rvalues,service); return 0;
+}
+
+static int pf_callinguser(int ptoken, char ***rvalues) {
+ return parm_usernameuid(rvalues,logname,request_mbuf.callinguid);
+}
+
+static int pf_serviceuser(int ptoken, char ***rvalues) {
+ return parm_usernameuid(rvalues,serviceuser,serviceuser_uid);
+}
+
+static int pf_callinggroup(int ptoken, char ***rvalues) {
+ return parm_groups(rvalues,request_mbuf.ngids,calling_gids,calling_groups);
+}
+
+static int pf_servicegroup(int ptoken, char ***rvalues) {
+ return parm_groups(rvalues,service_ngids,service_gids,service_groups);
+}
+
+static int pf_callingusershell(int ptoken, char ***rvalues) {
+ parm_1string(rvalues,callinguser_shell); return 0;
+}
+
+static int pf_serviceusershell(int ptoken, char ***rvalues) {
+ parm_1string(rvalues,serviceuser_shell); return 0;
+}
+
+static int pa_parameter(char ***rvalues, char **rname) {
+ /* Scans a single parameter token and calls the appropriate parameter
+ * function, returning tokv_error (leaving the parser state wherever),
+ * or 0 on success (having just parsed the parameter name).
+ * If rname is non-null then the name of the parameter (malloc'd) will
+ * be stored in it.
+ *
+ * Caller must free *rvalues using freecharparray.
+ */
+ int token, r, i;
+ char *name;
+
+ 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 && strcmp(yytext+2,defvararray[i].key);
+ i++);
+ if (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;
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;
*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;
}
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;
}
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; i++) {
- a[i]= parm_gidname(list[i]);
- a[size+i]= parm_ulong(list[i]);
- }
- a[size*2]= 0;
- *rvalues= a; return 0;
-}
-
-int pf_callinggroup(int ptoken, char ***rvalues) {
- return parm_gids(rvalues,request_mbuf.ngids,calling_gids);
-}
-
-int pf_servicegroup(int ptoken, char ***rvalues) {
- return parm_gids(rvalues,service_ngids,service_gids);
-}
-
-int pf_callingusershell(int ptoken, char ***rvalues) {
- parm_1string(rvalues,callinguser_shell); return 0;
-}
-
-int pf_serviceusershell(int ptoken, char ***rvalues) {
- parm_1string(rvalues,serviceuser_shell); return 0;
-}
-
-/* Directive functions and associated `common code' functions */
-
-static int paa_pathargs(const char **rv, char ***newargs_r) {
- /* Repeated calls do _not_ overwrite newargs_r; caller must free.
- * Repeated calls _do_ overwrite string returned in rv.
+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, and parsing up to and including
+ * the last newline) or tokv_error.
+ * Expects to scan the whitespace before its condition.
*/
- char **newargs;
- int used, size, r;
+ int r, token, andor, ctrue, actrue;
+ char **parmvalues;
- used=0; size=0;
- newargs= xmalloc(sizeof(char*)*(size+1));
- r= pa_string(rv); if (r == tokv_error) return r;
- 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;
- }
- r= yylex(); if (r==tokv_error) goto error;
- if ((r & tokm_repres) == tokr_nonstring) {
- r= unexpected(r,-1,"string for command argument");
- goto error;
- }
- if (used>=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;
}
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();
return 0;
}
+/* Parsing builtin service requests (execute-builtin) */
+
static int bispa_none(char ***rnewargs) {
return pa_mnl();
}
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,"<builtin 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,"<builtin 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,"<configuration override data>",0);
}
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;
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)) {
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; }
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;
}
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,
"<builtin reset configuration (caught error)>",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;
}
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) {
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");
+ }
+ }
+}
* 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
switch (event_r->type) {
case et_closereadfd:
fd= event_r->data.closereadfd.fd;
- assert(fd<fdarrayused);
+ if (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;
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; i<ngids; i++) {
- cgrp= getgrgid(list[i]);
- if (!cgrp) miscerror("get group entry");
- names[i]= xstrsave(cgrp->gr_name);
- }
- *names_r= names;
-}
-
/* The per-request main program and its subfunctions. */
static void setup_comms(int sfd) {
}
}
+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; i<ngids; i++) {
+ gr= getgrgid(gids[i]);
+ if (!gr) miscerror("get group entry");
+ names[i]= xstrsave(gr->gr_name);
+ }
+ *names_r= names;
+}
+
static void lookup_uidsgids(void) {
struct passwd *pw;
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);
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;
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);
}
}
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);
"production version"
#endif
" - protocol magic number %08lx\n"
- "protocol checksum: ",
- BASE_MAGIC);
- for (i=0, p=protocolchecksumversion; i<sizeof(protocolchecksumversion); i++, p++)
- printf("%02x",*p);
- printf("\n"
- "rendezvous socket: `" RENDEZVOUSPATH "'\n"
- "system config dir: `" SYSTEMCONFIGDIR "'\n"
- "pipe filename format: `%s' (max length %d)\n"
"maximums: fd %-10d general string %d\n"
- " gids %-10d override length %d\n\n"
+ " gids %-10d override length %d\n"
" args or variables %-10d error message %d\n"
- " nested inclusion %-10d errno string reserve %d\n",
- PIPEFORMAT, PIPEMAXLEN,
+ " nested inclusion %-10d errno string reserve %d\n"
+ "protocol checksum: ",
+ BASE_MAGIC,
MAX_ALLOW_FD, MAX_GENERAL_STRING,
MAX_GIDS, MAX_OVERRIDE_LEN,
MAX_ARGSDEFVAR, ERRMSG_RESERVE_ERRNO,
MAX_INCLUDE_NEST, MAX_ERRMSG_LEN);
+ for (i=0, p=protocolchecksumversion; i<sizeof(protocolchecksumversion); i++, p++)
+ printf("%02x",*p);
+ printf("\n"
+ "rendezvous socket: `" RENDEZVOUSPATH "'\n"
+ "system config dir: `" SYSTEMCONFIGDIR "'\n"
+ "pipe filename format: `%s' (max length %d)\n",
+ PIPEFORMAT, PIPEMAXLEN);
serv_checkstdoutexit();
}
<p>
If no <var/modifiers/ which imply <tt/read/ or <tt/write/ are used it
-is as if <tt/read/ had been specified, except that if the
-filedescriptor 1 or 2 of the service is being opened (either specified
-numerically or with <tt/stdout/ or <tt/stderr/) it is as if
-<tt/overwrite/ had been specified (or <tt/write/ if only <tt/fd/ was
-specified).
+is as if <tt/write/ had been specified, except that if the
+filedescriptor 0 of the service is being opened (either specified
+numerically or with <tt/stdin/) it is as if <tt/overwrite/ had been
+specified (or <tt/write/ if only <tt/fd/ was specified).
<p>
The client will also use <tt/O_NOCTTY/ when opening files specified by
Pretend to the service that it is being called by <var/user/ (which
may be a username or a uid). This will also affect the group and
supplementary groups supplied to the service; they will be the
-standard group and supplementary groups for <var/user/.
+standard group and supplementary groups for <var/user/. The
+<tt/--spoof-user/ option will <em/not/ affect which user is chosen if
+the service user is specified as just <tt/-/; in this case the service
+user will be the real calling user.
</taglist>
Reject the request. <prgn/execute/, <prgn/execute-from-directory/ and
<prgn/execute-from-path/ will change this setting.
-<tag/<tt/execute <var/pathname/ [<var/argument/ ...]//
+<tag/<tt/execute <var/program/ [<var/argument/ ...]//
<item>
-Execute the program <var/pathname/, with the arguments as specified,
+Execute the program <var/program/, with the arguments as specified,
followed by any arguments given to the client if
<prgn/no-suppress-args/ is in effect. It is an error for the
execution to fail when it is attempted (after all the configuration
-has been parsed). If <var/pathname/ does not contain a slash it will
+has been parsed). If <var/program/ does not contain a slash it will
be searched for on the service user's path.
<tag/<tt/execute-from-directory <var/pathname/ [<var/argument/ ...]//
<chapt id="notes">Applications and notes on use
<p>
+<sect id="standards">Standard services and directory management
+<p>
+
+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.
+<p>
+
+<prgn/userv/-using applications and system services which hide
+<prgn/userv/ behind wrapper scripts may need to store information in
+the user's filespace to preserve the correct placement of the security
+perimiters. Such applications should usually do so in a directory
+(created by them) <tt>~/.userv/.servdata/<var/service/</>, where
+<var/service/ is the service name or application in question.
+<p>
+
+The use of a dot-directory inside <tt>~/.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.)
+
<sect id="reducepriv">Reducing the number of absolutely privileged subsystems
<p>