X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=userv.git;a=blobdiff_plain;f=overlord.c;h=dfb58a2f21ec497bc133f403450c07373b6a87dd;hp=be2553ae019155adae0a61d32bfb7c0ddfa79636;hb=2851bae8a75683704426a5443a58e69be32c3915;hpb=9f56f874416db295bdb50d448bd99cdd34db969d diff --git a/overlord.c b/overlord.c index be2553a..dfb58a2 100644 --- a/overlord.c +++ b/overlord.c @@ -2,7 +2,7 @@ * userv - overlord.c * daemon main program, collects request and forks handlers * - * Copyright (C)1996-1997 Ian Jackson + * Copyright (C)1996-1997,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,6 @@ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include #include #include #include @@ -28,10 +27,12 @@ #include #include #include +#include #include -#include #include #include +#include +#include #include #include @@ -39,6 +40,11 @@ #include "common.h" #include "daemon.h" +pid_t overlordpid; + +static pid_t checkpid= -1, detachpid= -1; +static sig_atomic_t needcheck= 1; + static void checkstalepipes(void) { /* There is an unimportant race here. If there is a stale pipe but * another pair of processes with the same pids is about to create a @@ -75,79 +81,282 @@ static void checkstalepipes(void) { static void sighandler_chld(int x) { pid_t r; - int status, es; + int status, es, ar; es= errno; for (;;) { r= waitpid((pid_t)-1,&status,WNOHANG); if (!r || (r==-1 && errno==ECHILD)) break; if (r==-1) { syslog(LOG_ERR,"wait in sigchild handler gave error: %m"); break; } - if (WIFSIGNALED(status)) { - if (WCOREDUMP(status)) - syslog(LOG_ERR,"call pid %ld dumped core due to signal %s",(long)r, + if (r==detachpid) { + if (WIFEXITED(status) && WEXITSTATUS(status)==4) _exit(4); + fprintf(stderr,"uservd: detaching child failed with unexpected code %d\n",status); + exit(6); + } + if (r==checkpid) { + if (WIFEXITED(status)) { + if (!WEXITSTATUS(status)) { + syslog(LOG_WARNING,"no longer the uservd - exiting"); + _exit(2); + } else if (WEXITSTATUS(status)!=1) { + syslog(LOG_ERR,"check pid %ld exited with status %d", + (long)checkpid,WEXITSTATUS(status)); + } + } else if (WIFSIGNALED(status)) { + if (WTERMSIG(status) == SIGALRM && !WCOREDUMP(status)) { + syslog(LOG_WARNING,"check timed out; no longer the uservd - exiting"); + _exit(2); + } else { + syslog(LOG_ERR,"check pid %ld %s due to signal %s", + (long)checkpid, + WCOREDUMP(status) ? "dumped core" : "died", + strsignal(WTERMSIG(status))); + } + } else { + syslog(LOG_ERR,"check pid %ld died due to unknown reason, code %d", + (long)checkpid,status); + } + checkpid= -1; + ar= alarm(USERVD_MYSELF_CHECK); + if (ar<0) { syslog(LOG_CRIT,"set alarm for next check: %m"); exit(5); } + } else { + if (WIFSIGNALED(status)) { + syslog(LOG_ERR,"call pid %ld %s due to signal %s", + (long)r, + WCOREDUMP(status) ? "dumped core" : "died", strsignal(WTERMSIG(status))); - else - syslog(LOG_ERR,"call pid %ld died due to signal %s", - (long)r,strsignal(WTERMSIG(status))); - } else if (!WIFEXITED(status)) { - syslog(LOG_ERR,"call pid %ld died due to unknown reason, code %ld", - (long)r,status); - } else if (WEXITSTATUS(status)>12) { - if (WEXITSTATUS(status)>24) - syslog(LOG_ERR,"call pid %ld exited with status %ld >24", - (long)r,WEXITSTATUS(status)); - checkstalepipes(); + } else if (!WIFEXITED(status)) { + syslog(LOG_ERR,"call pid %ld died due to unknown reason, code %d", + (long)r,status); + } else if (WEXITSTATUS(status)>12) { + if (WEXITSTATUS(status)>24) + syslog(LOG_ERR,"call pid %ld exited with status %d >24", + (long)r,WEXITSTATUS(status)); + checkstalepipes(); + } } } errno= es; return; } +static void sighandler_usr1(int x) { + _exit(0); +} + +static void sighandler_alrm(int x) { + needcheck= 1; +} + +static void sighandler_termint(int sig) { + syslog(LOG_NOTICE,"terminating due to signal %s",strsignal(sig)); + _exit(1); +} + static void blocksignals(int how) { int r; sigset_t set; sigemptyset(&set); sigaddset(&set,SIGCHLD); + sigaddset(&set,SIGALRM); + sigaddset(&set,SIGTERM); + sigaddset(&set,SIGINT); r= sigprocmask(how,&set,0); assert(!r); } +static void NONRETURNING docheck(void) { +#ifndef DEBUG + /* This subprocess exits with status 0 if the parent should die, + * 1 if it should not, and something else if it fails horribly. + */ + int sfd, r, remain; + unsigned char *p; + struct opening_msg opening_mbuf; + struct request_msg request_mbuf; + unsigned long endmagic; + struct sigaction sig; + struct sockaddr_un ssockname; + + openlog(USERVDCHECK_LOGIDENT,LOG_NDELAY|LOG_PID,USERVD_LOGFACILITY); + + sigemptyset(&sig.sa_mask); + sig.sa_flags= 0; + sig.sa_handler= SIG_IGN; + if (sigaction(SIGPIPE,&sig,0)) { syslog(LOG_ERR,"ignore sigpipe"); exit(1); } + + sig.sa_handler= SIG_DFL; + if (sigaction(SIGALRM,&sig,0)) { syslog(LOG_ERR,"default sigalarm"); exit(1); } + + sfd= socket(AF_UNIX,SOCK_STREAM,0); + if (!sfd) { syslog(LOG_ERR,"ignore sigpipe"); exit(1); } + + assert(sizeof(ssockname.sun_path) > sizeof(RENDEZVOUS)); + ssockname.sun_family= AF_UNIX; + strcpy(ssockname.sun_path,RENDEZVOUS); + + r= connect(sfd,(struct sockaddr*)&ssockname,sizeof(ssockname)); + if (r) { + if (errno == ECONNREFUSED || errno == ENOENT) + { syslog(LOG_WARNING,"real uservd daemon is not running: %m"); exit(0); } + syslog(LOG_ERR,"unable to connect to uservd daemon: %m"); exit(1); + } + + r= alarm(USERVD_MYSELF_TIMEOUT); + if (r<0) { syslog(LOG_ERR,"set alarm for read: %m"); exit(1); } + remain= sizeof(opening_mbuf); p= (unsigned char*)&opening_mbuf; + while (remain) { + r= read(sfd,p,remain); + if (r<0) { syslog(LOG_ERR,"read from server: %m"); exit(1); } + if (r==0) { syslog(LOG_ERR,"unexpected EOF from server"); exit(1); } + remain-= r; p+= r; + } + if (opening_mbuf.magic != OPENING_MAGIC) { + syslog(LOG_WARNING,"magic number mismatch"); + exit(0); + } + if (memcmp(opening_mbuf.protocolchecksumversion,protocolchecksumversion,PCSUMSIZE)) { + syslog(LOG_WARNING,"protocol checksum mismatch"); + exit(0); + } + if (opening_mbuf.overlordpid != overlordpid) { + syslog(LOG_WARNING,"overlord pid mismatch"); + exit(0); + } + memset(&request_mbuf,0,sizeof(request_mbuf)); + request_mbuf.magic= REQUEST_MAGIC; + request_mbuf.clientpid= -1; + request_mbuf.serviceuserlen= 0; + request_mbuf.servicelen= 0; + request_mbuf.loginnamelen= 0; + request_mbuf.spoofed= 0; + request_mbuf.cwdlen= 0; + request_mbuf.overridelen= -1; + request_mbuf.callinguid= -1; + request_mbuf.ngids= 0; + request_mbuf.nreadfds= 0; + request_mbuf.nwritefds= 0; + request_mbuf.nargs= 0; + request_mbuf.nvars= 0; + r= write(sfd,&request_mbuf,sizeof(request_mbuf)); + if (r==sizeof(request_mbuf)) { + endmagic= REQUEST_END_MAGIC; + write(sfd,&endmagic,sizeof(endmagic)); + } + syslog(LOG_NOTICE,"uservd[%ld] is running",(long)overlordpid); +#endif + exit(1); +} + +static void NONRETURNING startupsyscallerr(const char *what) { + fprintf(stderr, + "uservd: system call failed during startup:\n" + "uservd: %s: %s\n", + what,strerror(errno)); + exit(4); +} + int main(int argc, char *const *argv) { - int mfd, sfd, csocklen, e; - struct sigaction childact; + int mfd, sfd, nfd, csocklen, e, r, becomedaemon; + struct sigaction sigact; struct sockaddr_un ssockname, csockname; - pid_t child; + pid_t child, parentpid, sid; #ifdef NDEBUG abort(); /* Do not disable assertions in this security-critical code ! */ #endif - if (argc>1) { fputs("usage: uservd\n",stderr); exit(3); } + becomedaemon= 0; + + if (argv[1] && !strcmp(argv[1],"-daemon")) { + becomedaemon= 1; + argv++; argc--; + } + if (argc>1) { fputs("usage: uservd [-daemon]\n",stderr); exit(3); } - openlog(USERVD_LOGIDENT,LOG_NDELAY,USERVD_LOGFACILITY); + openlog(USERVD_LOGIDENT,LOG_NDELAY|LOG_PID,USERVD_LOGFACILITY); - if (chdir(VARDIR)) { syslog(LOG_CRIT,"cannot change to " VARDIR ": %m"); exit(4); } + if (chdir(VARDIR)) startupsyscallerr("cannot change to " VARDIR); checkstalepipes(); + overlordpid= parentpid= getpid(); + if (parentpid==-1) startupsyscallerr("cannot getpid"); + mfd= socket(AF_UNIX,SOCK_STREAM,0); - if (!mfd) { syslog(LOG_CRIT,"cannot create master socket: %m"); exit(4); } + if (mfd<0) startupsyscallerr("cannot create master socket"); assert(sizeof(ssockname.sun_path) > sizeof(RENDEZVOUS)); ssockname.sun_family= AF_UNIX; strcpy(ssockname.sun_path,RENDEZVOUS); unlink(RENDEZVOUS); - if (bind(mfd,(struct sockaddr*)&ssockname,sizeof(ssockname))) - { syslog(LOG_CRIT,"cannot bind master socket: %m"); exit(4); } - if (listen(mfd,5)) - { syslog(LOG_CRIT,"cannot listen on master socket: %m"); exit(4); } - - childact.sa_handler= sighandler_chld; - sigemptyset(&childact.sa_mask); - childact.sa_flags= SA_NOCLDSTOP; - if (sigaction(SIGCHLD,&childact,0)) - { syslog(LOG_CRIT,"cannot setup sigchld handler: %m"); exit(4); } + r= bind(mfd,(struct sockaddr*)&ssockname,sizeof(ssockname)); + if (r) startupsyscallerr("cannot bind master socket"); + if (listen(mfd,5)) startupsyscallerr("cannot listen on master socket"); + + sigemptyset(&sigact.sa_mask); + sigaddset(&sigact.sa_mask,SIGCHLD); + sigaddset(&sigact.sa_mask,SIGALRM); + sigact.sa_flags= SA_NOCLDSTOP; + + sigact.sa_handler= sighandler_chld; + if (sigaction(SIGCHLD,&sigact,0)) startupsyscallerr("cannot setup sigchld handler"); + + sigact.sa_handler= sighandler_alrm; + if (sigaction(SIGALRM,&sigact,0)) startupsyscallerr("cannot setup sigalrm handler"); + + if (becomedaemon) { + sigact.sa_handler= sighandler_usr1; + if (sigaction(SIGUSR1,&sigact,0)) startupsyscallerr("cannot setup sigusr1 handler"); + + detachpid= fork(); if (detachpid==-1) startupsyscallerr("cannot fork to detach"); + if (detachpid) { + pause(); + fputs("uservd: pause unexpectedly returned during detach\n",stderr); + exit(4); + } + sigact.sa_handler= SIG_DFL; + if (sigaction(SIGUSR1,&sigact,0)) startupsyscallerr("cannot restore sigusr1"); + } + + sigact.sa_handler= sighandler_termint; + if (sigaction(SIGTERM,&sigact,0)) startupsyscallerr("cannot setup sigterm handler"); + if (sigaction(SIGINT,&sigact,0)) startupsyscallerr("cannot setup sigint handler"); + + if (becomedaemon) { + nfd= open("/dev/null",O_RDWR); + if (nfd<0) startupsyscallerr("cannot open /dev/null"); + sid= setsid(); if (sid == -1) startupsyscallerr("cannot create new session"); + overlordpid= getpid(); + if (overlordpid == -1) startupsyscallerr("getpid after detach"); + if (dup2(nfd,0)<0 || dup2(nfd,1)<0) + startupsyscallerr("cannot dup /dev/null for stdin/out"); + r= kill(parentpid,SIGUSR1); if (r) startupsyscallerr("send SIGUSR1 to detach"); + r= dup2(nfd,2); + if (r<0) { syslog(LOG_CRIT,"cannot dup /dev/null for stderr: %m"); exit(5); } + close(nfd); + } + syslog(LOG_NOTICE,"started"); + for (;;) { + if (needcheck) { + assert(checkpid==-1); + for (;;) { + checkpid= fork(); + if (checkpid!=-1) { + if (!checkpid) docheck(); + break; + } else if (errno==EAGAIN) { + syslog(LOG_ERR,"fork for check - will wait and retry: %m"); + r= alarm(USERVD_CHECKFORK_RETRY); + if (r<0) { syslog(LOG_CRIT,"set alarm for retry check: %m"); exit(5); } + break; + } else if (errno!=EINTR) { + syslog(LOG_CRIT,"fork for check: %m"); exit(5); + } + } + needcheck= 0; + } csocklen= sizeof(csockname); blocksignals(SIG_UNBLOCK); sfd= accept(mfd,(struct sockaddr*)&csockname,&csocklen); @@ -156,7 +365,7 @@ int main(int argc, char *const *argv) { if (sfd<0) { errno= e; if (errno == EINTR) continue; - if (errno == ENOMEM) { + if (errno == ENOMEM || errno == EPROTO || errno == EAGAIN) { syslog(LOG_ERR,"unable to accept connection: %m"); continue; } else {