/* * "whinetd" main loop * * $Id: whinetd.c,v 1.19 2002/01/02 14:52:31 matthew Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_CONFIG "/etc/inetd.conf" #include "whinetd.h" /* These are global for the benefit of signal handlers */ static sigset_t sigscaught; static int sigpipefd = -1; static void sigcatcher(int sig); int qlen = 50; int main(int argc, char **argv) { int reloadconf = 1, retry_failed = 0; int debug = 0, nodaemon = 0; int srvcount = 0, lsnsrvcount = 0, failcount = 0, maxfd = -1; int sigfd = -1; struct wh_srv *conf = NULL; fd_set lsnfds; char *conffile = DEFAULT_CONFIG; sigset_t sigset; /* Option parsing */ { int opt; while((opt = getopt(argc, argv, "diq:")) != -1) { switch(opt) { case 'd': debug = 1; case 'i': nodaemon = 1; break; case 'q': qlen = atoi(argv[optind]); break; default: fprintf(stderr, "usage: %s [-di] [-q len] [config file]\n", argv[0]); return 1; } } if(argc > optind) conffile = argv[optind]; if(qlen < 8) qlen = 8; } openlog(argv[0], LOG_PID | (debug ? LOG_PERROR : 0), LOG_DAEMON); /* Daemonise */ if(!nodaemon) { switch(fork()) { case -1: syslog(LOG_ERR, "can't fork: %m"); return 1; case 0: break; default: return 0; } } if(nodaemon && !debug && setsid()) { syslog(LOG_ERR, "can't setsid: %m"); return 1; } if(!debug) { int fd = open(_PATH_DEVNULL, O_RDWR, 0); if (fd >= 0) { if(fd != STDIN_FILENO) dup2(fd, STDIN_FILENO); if(fd != STDOUT_FILENO) dup2(fd, STDOUT_FILENO); if(fd != STDERR_FILENO) dup2(fd, STDERR_FILENO); } if (fd > STDERR_FILENO) close(fd); chdir("/"); } /* We use a pipe for notifying us of signals */ { int fd[2]; if(pipe(fd)) { syslog(LOG_ERR, "can't make pipe: %m"); return 1; } set_nonblock(sigpipefd = fd[1], 1); set_cloexec(sigpipefd, 1); set_nonblock(sigfd = fd[0], 1); set_cloexec(sigfd, 1); } /* Catch signals */ sigemptyset(&sigscaught); { struct sigaction sa; sa.sa_handler = sigcatcher; sa.sa_flags = SA_RESTART; sigemptyset(&sigset); sigaction(SIGTERM, &sa, NULL); sigaddset(&sigset, SIGTERM); sigaction(SIGINT, &sa, NULL); sigaddset(&sigset, SIGINT); sigaction(SIGHUP, &sa, NULL); sigaddset(&sigset, SIGHUP); sigaction(SIGALRM, &sa, NULL); sigaddset(&sigset, SIGALRM); sa.sa_flags |= SA_NOCLDSTOP; sigaction(SIGCHLD, &sa, NULL); sigaddset(&sigset, SIGCHLD); } /* Main loop */ for(;;) { if(reloadconf) { conf = loadconf(wh_parse_inetd, conffile, conf); reloadconf = retry_failed = 0; } { struct wh_srv *i; int s = 0, l = 0, f = 0, m = sigfd; FD_ZERO(&lsnfds); FD_SET(sigfd, &lsnfds); if(retry_failed) { for(i = conf; i; i = i->next) if(i->state == FAILED) if(bind_service(i)) i->state = FAILED; retry_failed = 0; } for(i = conf; i; i = i->next) { if((i->state == WAIT) && (i->instances < i->maxinstances)) i->state = LISTEN; if(i->state == FAILED) f++; else if(i->fd >= 0) { s++; if((i->state == LISTEN) && (!i->maxinstances || (i->instances < i->maxinstances))) { FD_SET(i->fd, &lsnfds); l++; if(i->fd > m) m = i->fd; } } } if(!s) syslog(LOG_INFO, "warn: no services"); srvcount = s; lsnsrvcount = l; failcount = f; maxfd = m; } if(failcount) { /* Arrange a wakeup call to retry failed services */ alarm(5*60); } /* Now we await something happening */ if(select(maxfd+1, &lsnfds, NULL, NULL, NULL) < 0) { if(errno != EINTR) { syslog(LOG_ERR, "select() failed: %m"); sleep(1); continue; } } /* Did we catch a signal? */ if(FD_ISSET(sigfd, &lsnfds)) { char c[100]; sigprocmask(SIG_BLOCK, &sigset, NULL); while((read(sigfd, &c, sizeof(c))) == sizeof(c)) { /* Is there a better way to flush a pipe? */ } if(sigismember(&sigscaught, SIGTERM)) { syslog(LOG_INFO, "caught SIGTERM, shutting down"); break; } if(sigismember(&sigscaught, SIGHUP)) reloadconf=1; if(sigismember(&sigscaught, SIGALRM)) retry_failed = 1; if(sigismember(&sigscaught, SIGCHLD)) { pid_t chld; int st; while((chld = waitpid(-1, &st, WNOHANG)) > 0) { /* XXX - look at exit status, log errors */ struct wh_srv *i; for(i = conf; i; i = i->next) { if(i->instances && i->children) { int j; for(j = 0; j < i->maxinstances; j++) { if(i->children[j] == chld) { i->children[j] = 0; i->instances--; goto child_found; } } } } child_found: } } sigemptyset(&sigscaught); sigprocmask(SIG_UNBLOCK, &sigset, NULL); } /* * Yes, else. If select() was interrupted by a signal, we * can't believe the fd_sets */ else { /* Someone wants a service, yay! */ struct wh_srv *i; for(i = conf; i; i = i->next) { if((i->state == LISTEN) && FD_ISSET(i->fd, &lsnfds)) { i->state = i->handler(i); } } } } closelog(); return 0; } static void sigcatcher(int sig) { char c = 0; if((sig == SIGINT)) sig = SIGTERM; if((sig == SIGTERM) && sigismember(&sigscaught, sig)) { syslog(LOG_ERR, "TERM request failed, KILLing self"); raise(SIGKILL); } sigaddset(&sigscaught, sig); write(sigpipefd, &c, 1); } static int become_user(struct wh_srv *s) { /* XXX - is this adequate? */ if(s->gcount && setgroups(s->gcount, s->suppgrps)) return -1; if(setgid(s->groupid) || setuid(s->userid)) return -1; return 0; } static int do_exec(struct wh_srv *s, int fd) { if(become_user(s)) { syslog(LOG_ERR, "couldn't become user for service \"%s\"", s->servname); return -1; } if((dup2(fd, 0) < 0) || (dup2(fd, 1) < 0) || (dup2(fd, 2) < 0)) { syslog(LOG_ERR, "can't dup socket: %m"); return -1; } #if 0 /* We cloexecd this, didn't we? */ if(fd > 2) close(fd); #endif execv(s->progname, s->progargs); syslog(LOG_ERR, "couldn't exec(\"%s\"): %m", s->progname); return -1; } enum srv_state wh_launch_wait(struct wh_srv *srv) { pid_t child; switch((child = vfork())) { case 0: break; case -1: syslog(LOG_ERR, "can't fork: %m"); return srv->state; default: srv->instances++; *srv->children = child; return WAIT; } exit(!!do_exec(srv, srv->fd)); } enum srv_state wh_launch_nowait(struct wh_srv *srv) { pid_t child; int cfd; /* * We accept(2) in the parent to avoid racing with the select() loop * * We could do this the other way around with vfork(2), if we could * trust it to be a "real" vfork */ set_ndelay(srv->fd, 1); /* XXX - fetch address for access control? */ if((cfd = accept(srv->fd, NULL, NULL)) < 0) { syslog(LOG_ERR, "can't accept(%d): %m", srv->fd); return srv->state; } set_ndelay(srv->fd, 0); switch((child = fork())) { case 0: break; case -1: close(cfd); syslog(LOG_ERR, "can't fork: %m"); return srv->state; default: srv->instances++; close(cfd); if(srv->maxinstances) { int i; for(i = 0; i < srv->maxinstances; i++) { if(!srv->children[i]) { srv->children[i] = child; break; } } if(i == srv->maxinstances) abort(); } return srv->state; } exit(!!do_exec(srv, cfd)); }