X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=userv.git;a=blobdiff_plain;f=process.c;h=636255a286f18209d2eed766e18d42bf260ec169;hp=8d20ce0fe801ce655d1ad99d562c77451ff8289d;hb=e9be60c490674ee7590f8a241a51e6b8317b894c;hpb=9f56f874416db295bdb50d448bd99cdd34db969d diff --git a/process.c b/process.c index 8d20ce0..636255a 100644 --- a/process.c +++ b/process.c @@ -2,7 +2,7 @@ * userv - process.c * daemon code to process one request (is parent of service process) * - * Copyright (C)1996-1997 Ian Jackson + * Copyright (C)1996-1999 Ian Jackson * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by @@ -19,7 +19,8 @@ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -/* We do some horrible asynchronous stuff with signals. +/* + * We do some horrible asynchronous stuff with signals. * * The following objects &c. are used in signal handlers and so * must be protected by calls to blocksignals if they are used in @@ -40,7 +41,6 @@ #include #include #include -#include #include #include #include @@ -51,7 +51,9 @@ #include #include #include -#include +#include +#include +#include #include #include #include @@ -59,28 +61,30 @@ #include "config.h" #include "common.h" +#include "both.h" #include "daemon.h" #include "lib.h" #include "tokens.h" /* NB: defaults for the execution state are not set here, but in * the RESET_CONFIGURATION #define in daemon.h. */ -char **argarray; -char *((*defvararray)[2]); +struct request_msg request_mbuf; +struct keyvaluepair *defvararray; struct fdstate *fdarray; int fdarraysize, fdarrayused; int restfdwantstate= tokv_word_rejectfd, restfdwantrw; -struct request_msg request_mbuf; -char *serviceuser, *service, *logname, *cwd; +int service_ngids; +char **argarray; +char *serviceuser, *service, *loginname, *cwd; char *overridedata, *userrcfile; char *serviceuser_dir, *serviceuser_shell, *callinguser_shell; -int service_ngids; gid_t *calling_gids, *service_gids; -const char **calling_groups, **service_groups; uid_t serviceuser_uid=-1; +const char **calling_groups, **service_groups; char *execpath, **execargs; int execute; int setenvironment, suppressargs, disconnecthup; +builtinserviceexec_fnt *execbuiltin; int syslogopenfacility=-1; static FILE *swfile, *srfile; @@ -104,6 +108,10 @@ int synchread(int fd, int ch) { return 0; } +const char *defaultpath(void) { + return serviceuser_uid ? DEFAULTPATH_USER : DEFAULTPATH_ROOT; +} + /* General-purpose functions; these do nothing special about signals */ static void blocksignals(void) { @@ -120,7 +128,7 @@ static void xfwriteerror(void) { if (errno != EPIPE) syscallerror("writing to client"); blocksignals(); ensurelogopen(USERVD_LOGFACILITY); - syslog(LOG_DEBUG,"client went away (broken pipe)"); + syslog(LOG_INFO,"client went away (broken pipe)"); disconnect(8); } @@ -141,11 +149,11 @@ static void xfflush(FILE *file) { static void xfread(void *p, size_t sz) { size_t nr; - nr= fread(p,1,sz,srfile); if (nr == sz) return; + nr= working_fread(p,sz,srfile); if (nr == sz) return; if (ferror(srfile)) syscallerror("reading from client"); blocksignals(); assert(feof(srfile)); - syslog(LOG_DEBUG,"client went away (unexpected EOF)"); + syslog(LOG_INFO,"client went away (unexpected EOF)"); swfile= 0; disconnect(8); } @@ -173,7 +181,12 @@ static void getevent(struct event_msg *event_r) { switch (event_r->type) { case et_closereadfd: fd= event_r->data.closereadfd.fd; - assert(fd= fdarrayused) { + blocksignals(); + syslog(LOG_ERR,"client sent bad file descriptor %d to close (max %d)", + fd,fdarrayused-1); + disconnect(20); + } if (fdarray[fd].holdfd!=-1) { if (close(fdarray[fd].holdfd)) syscallerror("cannot close holding fd"); fdarray[fd].holdfd= -1; @@ -181,7 +194,7 @@ static void getevent(struct event_msg *event_r) { break; case et_disconnect: blocksignals(); - syslog(LOG_DEBUG,"client disconnected"); + syslog(LOG_INFO,"client disconnected"); disconnect(4); default: return; @@ -207,7 +220,7 @@ void syscallerror(const char *what) { e= errno; blocksignals(); syslog(LOG_ERR,"system call failure: %s: %s",what,strerror(e)); - disconnect(18); + disconnect(16); } /* Functions which may be called from signal handlers. These @@ -258,8 +271,18 @@ void NONRETURNING disconnect(int exitstatus) { _exit(exitstatus); } -static void NONRETURNING sighandler_chld(int ignored) { +static void reporttermination(int status) { struct progress_msg progress_mbuf; + + memset(&progress_mbuf,0,sizeof(progress_mbuf)); + progress_mbuf.magic= PROGRESS_MAGIC; + progress_mbuf.type= pt_terminated; + progress_mbuf.data.terminated.status= status; + xfwrite(&progress_mbuf,sizeof(progress_mbuf),swfile); + xfflush(swfile); +} + +static void NONRETURNING sighandler_chld(int ignored) { int status; pid_t returned; @@ -269,14 +292,8 @@ static void NONRETURNING sighandler_chld(int ignored) { if (returned!=child) syscallerror("spurious child process"); child= childtokill= -1; - memset(&progress_mbuf,0,sizeof(progress_mbuf)); - progress_mbuf.magic= PROGRESS_MAGIC; - progress_mbuf.type= pt_terminated; - progress_mbuf.data.terminated.status= status; - xfwrite(&progress_mbuf,sizeof(progress_mbuf),swfile); - xfflush(swfile); - - syslog(LOG_DEBUG,"service completed (status %d %d)",(status>>8)&0x0ff,status&0x0ff); + reporttermination(status); + syslog(LOG_INFO,"service completed (status %d %d)",(status>>8)&0x0ff,status&0x0ff); _exit(0); } @@ -316,7 +333,7 @@ static void NONRETURNING generalfailure(const char *prefix, int reserveerrno, strnytcat(errmsg,strerror(errnoval),sizeof(errmsg)); } senderrmsgstderr(errmsg); - syslog(LOG_DEBUG,"service failed (%s)",errmsg); + syslog(LOG_INFO,"service failed (%s)",errmsg); disconnect(12); } @@ -353,20 +370,6 @@ void senderrmsgstderr(const char *errmsg) { xfflush(swfile); } -static void getgroupnames(int ngids, gid_t *list, const char ***names_r) { - const char **names; - struct group *cgrp; - int i; - - names= xmalloc(sizeof(char*)*ngids); - for (i=0; igr_name); - } - *names_r= names; -} - /* The per-request main program and its subfunctions. */ static void setup_comms(int sfd) { @@ -402,19 +405,21 @@ static void send_opening(void) { memset(&opening_mbuf,0,sizeof(opening_mbuf)); opening_mbuf.magic= OPENING_MAGIC; memcpy(opening_mbuf.protocolchecksumversion,protocolchecksumversion,PCSUMSIZE); + opening_mbuf.overlordpid= overlordpid; opening_mbuf.serverpid= mypid; xfwrite(&opening_mbuf,sizeof(opening_mbuf),swfile); xfflush(swfile); } static void receive_request(void) { - int i,j, fd; + int i, fd; unsigned long ul; xfread(&request_mbuf,sizeof(request_mbuf)); serviceuser= xfreadsetstring(request_mbuf.serviceuserlen); service= xfreadsetstring(request_mbuf.servicelen); - logname= xfreadsetstring(request_mbuf.lognamelen); + assert(request_mbuf.spoofed==0 || request_mbuf.spoofed==1); + loginname= xfreadsetstring(request_mbuf.loginnamelen); cwd= xfreadsetstring(request_mbuf.cwdlen); if (request_mbuf.overridelen >= 0) { assert(request_mbuf.overridelen <= MAX_OVERRIDE_LEN); @@ -439,47 +444,66 @@ static void receive_request(void) { fdarray[fd].iswrite= (i>=request_mbuf.nreadfds); } - assert(request_mbuf.nargs <= MAX_ARGSDEFVARS); + assert(request_mbuf.nargs <= MAX_ARGSDEFVAR); argarray= xmalloc(sizeof(char*)*(request_mbuf.nargs)); for (i=0; igr_name); + } + *names_r= names; +} + static void lookup_uidsgids(void) { struct passwd *pw; - pw= getpwnam(logname); + pw= getpwnam(loginname); if (!pw) miscerror("look up calling user"); - assert(!strcmp(pw->pw_name,logname)); + assert(!strcmp(pw->pw_name,loginname)); callinguser_shell= xstrsave(pw->pw_shell); pw= getpwnam(serviceuser); @@ -488,12 +512,16 @@ static void lookup_uidsgids(void) { serviceuser_dir= xstrsave(nondebug_serviceuserdir(pw->pw_dir)); serviceuser_shell= xstrsave(pw->pw_shell); serviceuser_uid= pw->pw_uid; + + if (setregid(pw->pw_gid,pw->pw_gid)) syscallerror("setregid 1"); if (initgroups(pw->pw_name,pw->pw_gid)) syscallerror("initgroups"); if (setreuid(pw->pw_uid,pw->pw_uid)) syscallerror("setreuid 1"); if (setreuid(pw->pw_uid,pw->pw_uid)) syscallerror("setreuid 2"); - if (pw->pw_uid) + if (pw->pw_uid) { if (!setreuid(pw->pw_uid,0)) miscerror("setreuid 3 unexpectedly succeeded"); - if (errno != EPERM) syscallerror("setreuid 3 failed in unexpected way"); + if (errno != EPERM) syscallerror("setreuid 3 failed in unexpected way"); + } + if (setregid(pw->pw_gid,pw->pw_gid)) syscallerror("setregid 2"); service_ngids= getgroups(0,0); if (service_ngids == -1) syscallerror("getgroups(0,0)"); if (service_ngids > MAX_GIDS) miscerror("service user is in far too many groups"); @@ -502,63 +530,95 @@ static void lookup_uidsgids(void) { if (getgroups(service_ngids,service_gids+1) != service_ngids) syscallerror("getgroups(size,list)"); - getgroupnames(service_ngids,service_gids,&service_groups); - getgroupnames(request_mbuf.ngids,calling_gids,&calling_groups); + groupnames(request_mbuf.ngids,calling_gids,&calling_groups); + groupnames(service_ngids,service_gids,&service_groups); } -static void check_find_executable(void) { - int r, partsize; +static void findinpath(char *program) { char *part, *exectry; const char *string, *delim, *nextstring; struct stat stab; + int r, partsize; + + if (strchr(program,'/')) { + r= stat(program,&stab); + if (r) syscallfailure("failed check for program (containing slash) `%s'",program); + execpath= program; + } else { + string= getenv("PATH"); + if (!string) string= defaultpath(); + while (string) { + delim= strchr(string,':'); + if (delim) { + if (delim-string > MAX_GENERAL_STRING) + failure("execute-from-path, but PATH component too long"); + partsize= delim-string; + nextstring= delim+1; + } else { + partsize= strlen(string); + nextstring= 0; + } + part= xstrsubsave(string,partsize); + exectry= part[0] ? xstrcat3save(part,"/",program) : xstrsave(program); + free(part); + r= stat(exectry,&stab); + if (!r) { execpath= exectry; break; } + free(exectry); + string= nextstring; + } + if (!execpath) failure("program `%s' not found on default PATH",program); + } +} + +static void check_find_executable(void) { + struct stat stab; + int r; switch (execute) { case tokv_word_reject: failure("request rejected"); case tokv_word_execute: - r= stat(execpath,&stab); - if (r) syscallfailure("checking for executable `%s'",execpath); + findinpath(execpath); break; case tokv_word_executefromdirectory: r= stat(execpath,&stab); if (r) syscallfailure("checking for executable in directory, `%s'",execpath); break; + 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) failure("execute-from-path, but daemon inherited no PATH !"); - while (string) { - delim= strchr(string,':'); - if (delim) { - if (delim-string > INT_MAX) - 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); + fdarray[fd].holdfd= -1; + } + } +} + +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; @@ -576,35 +636,27 @@ static void check_fds(void) { failure("file descriptor %d provided but rejected",fd); break; case tokv_word_ignorefd: - if (fdarray[fd].realfd != -1) - if (close(fdarray[fd].realfd)) - syscallfailure("close unwanted file descriptor %d",fd); - fdarray[fd].realfd= -1; + makenonexistentfd(fd); break; case tokv_word_nullfd: - if (fdarray[fd].realfd != -1) close(fdarray[fd].realfd); - fdarray[fd].realfd= open("/dev/null", - fdarray[fd].iswrite == -1 ? O_RDWR : - fdarray[fd].iswrite ? O_WRONLY : O_RDONLY); - if (fdarray[fd].realfd == -1) - 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 == -1) - 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); } } @@ -631,6 +683,13 @@ static void fork_service_synch(void) { r= socketpair(AF_UNIX,SOCK_STREAM,0,synchsocket); if (r) syscallerror("cannot create socket for synch"); + /* Danger here. Firstly, we start handling signals asynchronously. + * Secondly after we fork the service we want it to put + * itself in a separate process group so that we can kill it and all + * its children - but, we mustn't kill the whole pgrp before it has + * done that (or we kill ourselves) and it mustn't fork until it + * knows that we are going to kill it the right way ... + */ sig.sa_handler= sighandler_chld; sigemptyset(&sig.sa_mask); sigaddset(&sig.sa_mask,SIGCHLD); @@ -663,9 +722,13 @@ void servicerequest(int sfd) { setup_comms(sfd); send_opening(); receive_request(); + if (request_mbuf.clientpid == (pid_t)-1) _exit(2); establish_pipes(); lookup_uidsgids(); debug_dumprequest(mypid); + syslog(LOG_INFO,"%s %s -> %s %c %s", + request_mbuf.spoofed ? "spoof" : "user", + loginname, serviceuser, overridedata?'!':':', service); if (overridedata) r= parse_string(TOPLEVEL_OVERRIDDEN_CONFIGURATION, @@ -675,7 +738,7 @@ void servicerequest(int sfd) { "",1); ensurelogopen(USERVD_LOGFACILITY); - if (r == tokv_error) failure("error encountered while parsing configuration files"); + if (r == tokv_error) failure("error encountered while parsing configuration"); assert(r == tokv_quit); debug_dumpexecsettings(); @@ -687,6 +750,18 @@ void servicerequest(int sfd) { getevent(&event_mbuf); assert(event_mbuf.type == et_confirm); + if (execbuiltin == bisexec_shutdown && !serviceuser_uid) { + /* The check for the uid is just so we can give a nice + * error message (in the actual code for bisexec_shutdown). + * If this is spoofed somehow then the unlink() will simply fail. + */ + r= unlink(RENDEZVOUSPATH); + if (r) syscallfailure("remove rendezvous socket %s",RENDEZVOUSPATH); + syslog(LOG_NOTICE,"arranging for termination, due to client request"); + reporttermination(0); + _exit(10); + } + fork_service_synch(); getevent(&event_mbuf);