/* * RADIUSD - Radius server, main file * * Author: * Emile van Bergen, emile@evbergen.xs4all.nl * * Permission to redistribute an original or modified version of this program * in source, intermediate or object code form is hereby granted exclusively * under the terms of the GNU General Public License, version 2. Please see the * file COPYING for details, or refer to http://www.gnu.org/copyleft/gpl.html. * * History: * 2001/01/22 - EvB - Created * 2001/12/01 - EvB - Fixed bug caused by not emptying a channel's receive * queue after its associated process died */ char radiusd_id[] = "RADIUSD - Copyright (C) 2001 Emile van Bergen."; /* * INCLUDES & DEFINES */ #include /* For struct tv */ #include #include #include #include /* For strerror */ #include /* For open(), dup2(), perhaps getopt() */ #include /* For malloc() and for some SysV's getopt() */ #include #include /* For O_* */ #include /* For time(), time_t */ #include /* For meta_newfromdict() */ #include /* For conf_new() etc */ #include /* For msg_* */ #include /* Also includes srvtypes.h */ #include /* For chan_handle_*; also includes subprocs */ #include /* * GLOBALS */ /* Usage message */ static char *usgmsg = "Usage: radiusd [-r raddb][-m moddir][-o stderr][-s syslogfacility][-q] \\\n" " [-{d|D} {misc|text|lang|proc|recv|send|all}][-b]\n" " radiusd -v\n" " radiusd -h\n"; /* Options */ static char *raddb = RADDB, *modbase = MODULES; static int nobackgr = 0; /* Own pid, Signalling pipe */ int pid = -1, spipe[2] = {-1, -1}; /* * FUNCTIONS */ void usage(char *message) { if (message) { write(2, "\n", 1); write(2, message, strlen(message)); write(2, "\n\n", 2); } write(2, usgmsg, strlen(usgmsg)); _exit(1); } void parseoptions(int argc, char **argv) { int c, syslogfac, quiet, fac, err, n; char *logfile; syslogfac = -1; quiet = 0; err = 0; logfile = 0; while((c = getopt(argc, argv, "r:m:o:s:d:D:qbvh")) != -1) { n = L_ERR; switch(c) { case 'r': raddb = optarg; break; case 'm': modbase = optarg; break; case 'o': logfile = optarg; break; case 's': syslogfac = msg_getsyslogfacbyname(optarg); /* If invalid, syslogfac is -1 which means none */ break; case 'q': quiet = 1; break; case 'D': n += 2; /* L_ERR to L_NOTICE to L_DEBUG */ case 'd': n += 2; /* L_ERR to L_NOTICE to L_DEBUG */ if (optarg && (fac = msg_getfacbyname(optarg)) != F_INVAL) { msg_setthresh(fac, n); break; } for(fac = 0; fac < F_CNT; fac++) msg_setthresh(fac, n); break; case 'b': nobackgr = 1; break; case 'v': usage( "OpenRADIUS " VERSION ". Copyright (C) 2001 Emile van Bergen / E-Advies.\n\n" "Permission to redistribute an original or modified version of this program\n" "in source, intermediate or object code form is hereby granted exclusively\n" "under the terms of the GNU General Public License, version 2. Please see the\n" "file COPYING for details, or refer to http://www.gnu.org/copyleft/gpl.html.\n" "\nDefault raddb : " RADDB "\nDefault moddir: " MODULES "\nDefault stderr: " LOGFILE); break; case 'h': case '?': usage(0); } } /* If no stderr logfile was specified, set default depending on nobackground flag. If we run in foreground, default is not to redirect, otherwise default is LOGFILE as set by Make.conf. */ if (!logfile && !nobackgr) logfile = LOGFILE; /* Redirect stderr to specified logfile, if any */ if (logfile) { n = open(logfile, O_WRONLY + O_CREAT + O_APPEND, 0x1b6); if (n == -1 || dup2(n, 2) == -1 || fcntl(2, F_SETFD, 0) == -1 || close(n) == -1) { msg(F_MISC, L_ERR, "parseoptions: ERROR: Could not open" " '%s' to redirect stderr to: %s!\n", logfile, strerror(errno)); usage(0); } } /* Init logging again, now according to specified options. */ msg_init(syslogfac, quiet); } void sighandler(int sig) { sigset_t set; /* If we receive a signal for a forked but not execed child, exit */ if (getpid() != pid) _exit(125); /* Get the current sigset from the pipe, if it's present */ sigemptyset(&set); read(spipe[PIPE_R], &set, sizeof(set)); /* Put the set (back) in the pipe */ sigaddset(&set, sig); write(spipe[PIPE_W], &set, sizeof(set)); #ifndef HAVE_SIGACTION signal(sig, sighandler); #endif } /* * MAIN */ int main(int argc, char **argv) { META *m; CONF *c; SOCK *s; JOB *j; IFACE *i; PROC *p; PROC_SET ps; sigset_t sigs, oldsigs; #ifdef HAVE_SIGACTION struct sigaction sa; #endif fd_set rfds, wfds; struct timeval tv, *tvp; int status, sockhighest, reaping, n; time_t t; /* Parse options */ parseoptions(argc, argv); /* Open dictionary */ m = meta_newfromdict(raddb); if (!m) { msg(F_MISC, L_ERR, "main: ERROR: Could not create " "dictionary!\n"); return 1; } /* Open configuration */ c = conf_new(m, raddb, modbase); if (!c) { msg(F_MISC, L_ERR, "main: ERROR: Could not process " "configuration!\n"); return 1; } /* Create nonblocking, noninherited signalling pipe */ if (pipe(spipe) == -1 || fcntl(spipe[PIPE_R], F_SETFD, 1) == -1 || fcntl(spipe[PIPE_W], F_SETFD, 1) == -1 || fcntl(spipe[PIPE_R], F_SETFL, O_NONBLOCK) == -1 || fcntl(spipe[PIPE_W], F_SETFL, O_NONBLOCK) == -1) { msg(F_MISC, L_ERR, "main: ERROR: Could not create signalling " "pipe: %s!\n", strerror(errno)); return 1; } /* Install handler */ #ifdef HAVE_SIGACTION memset(&sa, 0, sizeof(sa)); sa.sa_flags = SA_NOCLDSTOP + SA_RESTART; sa.sa_handler = sighandler; sigaction(SIGHUP, &sa, 0); sigaction(SIGINT, &sa, 0); sigaction(SIGQUIT, &sa, 0); sigaction(SIGTERM, &sa, 0); sigaction(SIGCHLD, &sa, 0); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, 0); #else signal(SIGHUP, sighandler); signal(SIGINT, sighandler); signal(SIGQUIT, sighandler); signal(SIGTERM, sighandler); signal(SIGCHLD, sighandler); signal(SIGPIPE, SIG_IGN); #endif /* Go to background */ if (!nobackgr) { switch(fork()) { case -1: msg(F_MISC, L_ERR, "main: ERROR: Could not " "go to background!\n"); return 1; case 0: break; default: return 0; } close(0); close(1); setsid(); /* Detach from session */ setpgid(getpid(), getpid()); /* Become pgroup leader */ } /* Save pid so the sighandler can exit in not-yet-execed childs */ pid = getpid(); /* Bind sockets and start subprocesses */ time(&t); if (conf_start(c, t) == -1) { msg(F_MISC,L_ERR, "main: ERROR: Problem with configuration!\n"); return 1; } /* Define initial fd set and timers */ memset(&ps, 0, sizeof(ps)); ps.p = c->procs; FD_ZERO(&(ps.rfds)); FD_ZERO(&(ps.wfds)); FD_SET(spipe[PIPE_R], &(ps.rfds)); sockhighest = spipe[PIPE_R]; for(s = c->sources; s; s = s->next) { FD_SET(s->fd, &(ps.rfds)); sockhighest = MAX(sockhighest, s->fd); } ps.highestfd = sockhighest; /* And... Go. */ msg(F_MISC, L_ERR, "main: Ready to answer requests.\n"); for(reaping = -1;;) { /* Select according to ps.rfds, ps.wfds and ps.firsttimer */ rfds = ps.rfds; wfds = ps.wfds; tvp = 0; if (ps.firsttimer) { tv.tv_sec = ps.firsttimer - t; tv.tv_usec = 0; if (tv.tv_sec < 0) tv.tv_sec = 0, tv.tv_usec = 1; tvp = &tv; } D1(msg(F_MISC, L_DEBUG, "main: Selecting, highest=%d, time=%d, " "firsttimer=%d, timeout=%d\n", ps.highestfd, t, ps.firsttimer, tvp ? tvp->tv_sec : 0)); n = select(ps.highestfd + 1, &rfds, &wfds, 0, tvp); if (n == -1) { if (errno == EINTR) continue; msg(F_MISC, L_ERR, "main: FATAL: Select() said: " "%s!\n", strerror(errno)); break; } time(&t); /* Handle signals - see if the pipe has the set present */ sigemptyset(&sigs); if (FD_ISSET(spipe[PIPE_R], &rfds)) { /* Mask all signals during test-and-reset (read) */ sigfillset(&sigs); sigprocmask(SIG_BLOCK, &sigs, &oldsigs); /* Latch the set in the pipe (if any) into 'sigs' */ sigemptyset(&sigs); n = read(spipe[PIPE_R], &sigs, sizeof(sigs)); if (n == -1 && errno != EAGAIN) { msg(F_MISC, L_ERR, "main: FATAL: Read() on " "signal pipe said: %s!\n", strerror(errno)); break; } /* Restore signal mask */ sigprocmask(SIG_SETMASK, &oldsigs, 0); } /* Check for exited childs */ if (sigismember(&sigs, SIGCHLD)) { /* Handle exited childs, if any */ while((n = waitpid(0, &status, WNOHANG)) > 0) { /* Find out which of our subprocesses exited */ for(p = c->procs; p && p->pid != n; p=p->next); if (!p) { msg(F_PROC, L_ERR, "main: BUG: Unknow" "n child exited!\n"); continue; } /* Clear this proc's FDs from the set */ if (p->rfd != -1) FD_CLR(p->rfd, &(ps.rfds)); if (p->wfd != -1) FD_CLR(p->wfd, &(ps.wfds)); /* If we're not reaping, call exit event handler and empty proc's channel's recv. queue. Otherwise decrease # of procs left. */ if (reaping == -1) { proc_handle_end(p, t, status); chan_flush(p->chan); } else { reaping--; msg(F_PROC, L_NOTICE, "main: Child %s[" "%d] exited, %d " "left.\n", p->argv[0], p->pid, reaping); /* Set dead state (pid = 0) */ p->state = PRS_WAITING; p->timer = 0; p->pid = 0; } } /* Exit main loop if we were reaping subprocesses and they have all died */ if (reaping == 0) break; } /* Check if we were requested to quit */ if (sigismember(&sigs, SIGINT) || sigismember(&sigs, SIGTERM) || sigismember(&sigs, SIGQUIT)) { msg(F_PROC,L_NOTICE, "main: Got termination request\n"); /* Send all running subprocesses a SIGTERM and set reaping to the number of processes we're waiting on to die before we exit ourselves. */ reaping = 0; for(p = ps.p; p; p = p->next) { if (p->pid && PRS_ISRUNNING(p->state)) { reaping++; proc_stop(p, t); } } /* If we don't have to wait for anybody, exit now */ if (reaping == 0) break; msg(F_PROC, L_NOTICE, "main: sent SIGTERM to %d " "running childs.\n", reaping); } /* Ok. Now loop through the sockets for rx readyness */ for(s = c->sources; s; s = s->next) if (FD_ISSET(s->fd, &rfds)){ /* Create new job from this source */ j = job_new_fromsock(s, t); if (!j) continue; do { /* job_run runs the expression on this job till we have replied or got an interface call */ i = job_run(c, j); } /* If job_run returned due to an interface call, we can job_toiface; if that fails and returns nonzero, we continue with job_run. */ while (i && job_toiface(i, j, t)); } /* Loop through the subprocs and handle their pending events; also always clear _all_ proc-related fds from the fd sets. */ for(p = ps.p; p; p = p->next) { /* If this proc has a reading fd, del it at first, and test it for readyness. We add it again if needed. */ if (p->rfd != -1) { FD_CLR(p->rfd, &(ps.rfds)); if (FD_ISSET(p->rfd, &rfds)) chan_handle_read(p->chan, t); } /* If this proc has a writing fd, del it at first, and test it for readyness. We add it again if needed. */ if (p->wfd != -1) { FD_CLR(p->wfd, &(ps.wfds)); if (FD_ISSET(p->wfd, &wfds)) chan_handle_write(p->chan, t); } /* Next, see if this proc's watchdog timed out. If the process has just restarted and there's work to do, kick it. */ if (p->timer && t >= p->timer) { proc_handle_timeout(p,t); if (p->state == PRS_IDLE) chan_handle_write(p->chan, t); } } /* Loop through subprocs again to get new event mask. This loop is separate because a chan_handle_ function may do something to another proc's state as well. */ ps.firsttimer = 0; ps.highestfd = sockhighest; for(p = ps.p; p; p = p->next) { /* Update this proc's bits in the fd sets */ if (p->rfd != -1 && (p->state & PRS_RECEIVING)) { FD_SET(p->rfd, &(ps.rfds)); ps.highestfd = MAX(ps.highestfd, p->rfd); } if (p->wfd != -1 && (p->state & PRS_SENDING)) { FD_SET(p->wfd, &(ps.wfds)); ps.highestfd = MAX(ps.highestfd, p->wfd); } /* And maintain the first expiring timer */ if (p->timer && (p->timer < ps.firsttimer || !ps.firsttimer)) { ps.firsttimer = p->timer; } } } conf_del(c); /* Kills subprocesses very forcefully */ meta_del(m); msg(F_MISC, L_ERR, "main: Server terminated.\n"); return 0; }