/* * Copyright (c) 1983,1991 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * David A. Holland. * * Busybox port by Vladimir Oleynik (C) 2001-2003 * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* * Inetd - Internet super-server * * This program invokes all internet services as needed. * connection-oriented services are invoked each time a * connection is made, by creating a process. This process * is passed the connection as file descriptor 0 and is * expected to do a getpeername to find out the source host * and port. * * Datagram oriented services are invoked when a datagram * arrives; a process is created and passed a pending message * on file descriptor 0. Datagram servers may either connect * to their peer, freeing up the original socket for inetd * to receive further messages on, or ``take over the socket'', * processing all arriving datagrams and, eventually, timing * out. The first type of server is said to be ``multi-threaded''; * the second type of server ``single-threaded''. * * Inetd uses a configuration file which is read at startup * and, possibly, at some later time in response to a hangup signal. * The configuration file is ``free format'' with fields given in the * order shown below. Continuation lines for an entry must being with * a space or tab. All fields must be present in each entry. * * service name must be in /etc/services * socket type stream/dgram/raw/rdm/seqpacket * protocol must be in /etc/protocols * wait/nowait[.max] single-threaded/multi-threaded, max # * user[.group] user/group to run daemon as * server program full path name * server program arguments maximum of MAXARGS (20) * * RPC services unsuported * * Comment lines are indicated by a `#' in column 1. */ /* * Here's the scoop concerning the user.group feature: * * 1) No group listed. * * a) for root: NO setuid() or setgid() is done * * b) nonroot: setuid() * setgid(primary group as found in passwd) * initgroups(name, primary group) * * 2) set-group-option on. * * a) for root: NO setuid() * setgid(specified group) * setgroups(1, specified group) * * b) nonroot: setuid() * setgid(specified group) * initgroups(name, specified group) * * All supplementary groups are discarded at startup in case inetd was * run manually. */ #define __USE_BSD_SIGNAL #include "busybox.h" #include #include #include #include #include #include #include #include #include #ifndef __linux__ #ifndef RLIMIT_NOFILE #define RLIMIT_NOFILE RLIMIT_OFILE #endif #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _PATH_INETDCONF "/etc/inetd.conf" #define _PATH_INETDPID "/var/run/inetd.pid" #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif #define TOOMANY 40 /* don't start more than TOOMANY */ #define CNT_INTVL 60 /* servers in CNT_INTVL sec. */ #define RETRYTIME (60*10) /* retry after bind or server fail */ #ifndef OPEN_MAX #define OPEN_MAX 64 #endif /* Reserve some descriptors, 3 stdio + at least: 1 log, 1 conf. file */ #define FD_MARGIN (8) static int rlim_ofile_cur = OPEN_MAX; #ifdef RLIMIT_NOFILE static struct rlimit rlim_ofile; #endif #define INETD_UNSUPPORT_BILTIN 1 /* Check unsupporting builtin */ #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_ECHO #undef INETD_UNSUPPORT_BILTIN #endif #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DISCARD #undef INETD_UNSUPPORT_BILTIN #endif #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_TIME #undef INETD_UNSUPPORT_BILTIN #endif #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DAYTIME #undef INETD_UNSUPPORT_BILTIN #endif #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_CHARGEN #undef INETD_UNSUPPORT_BILTIN #endif static struct servtab { char *se_service; /* name of service */ int se_socktype; /* type of socket to use */ int se_family; /* address family */ char *se_proto; /* protocol used */ short se_wait; /* single threaded server */ short se_checked; /* looked at during merge */ char *se_user; /* user name to run as */ char *se_group; /* group name to run as */ #ifndef INETD_UNSUPPORT_BILTIN const struct biltin *se_bi; /* if built-in, description */ #endif char *se_server; /* server program */ #define MAXARGV 20 char *se_argv[MAXARGV+1]; /* program arguments */ int se_fd; /* open descriptor */ union { struct sockaddr se_un_ctrladdr; struct sockaddr_in se_un_ctrladdr_in; struct sockaddr_un se_un_ctrladdr_un; } se_un; /* bound address */ #define se_ctrladdr se_un.se_un_ctrladdr #define se_ctrladdr_in se_un.se_un_ctrladdr_in #define se_ctrladdr_un se_un.se_un_ctrladdr_un int se_ctrladdr_size; int se_max; /* max # of instances of this service */ int se_count; /* number started since se_time */ struct timeval se_time; /* start of se_count */ struct servtab *se_next; } *servtab; /* Length of socket listen queue. Should be per-service probably. */ static int global_queuelen = 128; static int nsock, maxsock; static fd_set allsock; static int timingout; static sigset_t blockmask, emptymask; /* Echo received data */ #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_ECHO static void echo_stream(int, struct servtab *); static void echo_dg(int, struct servtab *); #endif /* Internet /dev/null */ #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DISCARD static void discard_stream(int, struct servtab *); static void discard_dg(int, struct servtab *); #endif /* Return 32 bit time since 1900 */ #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_TIME static void machtime_stream(int, struct servtab *); static void machtime_dg(int, struct servtab *); #endif /* Return human-readable time */ #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DAYTIME static void daytime_stream(int, struct servtab *); static void daytime_dg(int, struct servtab *); #endif /* Familiar character generator */ #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_CHARGEN static void chargen_stream(int, struct servtab *); static void chargen_dg(int, struct servtab *); #endif #ifndef INETD_UNSUPPORT_BILTIN struct biltin { const char *bi_service; /* internally provided service name */ int bi_socktype; /* type of socket supported */ short bi_fork; /* 1 if should fork before call */ short bi_wait; /* 1 if should wait for child */ void (*bi_fn)(int, struct servtab *); /* fn which performs it */ }; static const struct biltin biltins[] = { #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_ECHO /* Echo received data */ { "echo", SOCK_STREAM, 1, 0, echo_stream, }, { "echo", SOCK_DGRAM, 0, 0, echo_dg, }, #endif #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DISCARD /* Internet /dev/null */ { "discard", SOCK_STREAM, 1, 0, discard_stream, }, { "discard", SOCK_DGRAM, 0, 0, discard_dg, }, #endif #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_TIME /* Return 32 bit time since 1900 */ { "time", SOCK_STREAM, 0, 0, machtime_stream, }, { "time", SOCK_DGRAM, 0, 0, machtime_dg, }, #endif #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DAYTIME /* Return human-readable time */ { "daytime", SOCK_STREAM, 0, 0, daytime_stream, }, { "daytime", SOCK_DGRAM, 0, 0, daytime_dg, }, #endif #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_CHARGEN /* Familiar character generator */ { "chargen", SOCK_STREAM, 1, 0, chargen_stream, }, { "chargen", SOCK_DGRAM, 0, 0, chargen_dg, }, #endif { NULL, 0, 0, 0, NULL } }; #endif /* INETD_UNSUPPORT_BILTIN */ #define NUMINT (sizeof(intab) / sizeof(struct inent)) static const char *CONFIG = _PATH_INETDCONF; #define BCOPY(s, d, z) memcpy(d, s, z) static void syslog_err_and_discard_dg(int se_socktype, const char *msg, ...) __attribute__ ((noreturn, format (printf, 2, 3))); static void syslog_err_and_discard_dg(int se_socktype, const char *msg, ...) { char buf[50]; va_list p; va_start(p, msg); vsyslog(LOG_ERR, msg, p); if (se_socktype != SOCK_STREAM) recv(0, buf, sizeof (buf), 0); _exit(1); } static FILE *fconfig; static char line[256]; static FILE * setconfig(void) { FILE *f = fconfig; if (f != NULL) { fseek(f, 0L, L_SET); } else { f = fconfig = fopen(CONFIG, "r"); if(f == NULL) syslog(LOG_ERR, "%s: %m", CONFIG); } return f; } static char * nextline(void) { char *cp; FILE *fd = fconfig; if (fgets(line, sizeof (line), fd) == NULL) return ((char *)0); cp = strchr(line, '\n'); if (cp) *cp = '\0'; return (line); } static char * skip(char **cpp) { char *cp = *cpp; char *start; if (*cpp == NULL) return ((char *)0); again: while (*cp == ' ' || *cp == '\t') cp++; if (*cp == '\0') { int c; c = getc(fconfig); (void) ungetc(c, fconfig); if (c == ' ' || c == '\t') if ((cp = nextline()) != NULL) goto again; *cpp = NULL; return NULL; } start = cp; while (*cp && *cp != ' ' && *cp != '\t') cp++; if (*cp != '\0') *cp++ = '\0'; *cpp = cp; return (start); } static char * newstr(char *cp) { cp = strdup(cp ? cp : ""); if (cp) return(cp); syslog_err_and_discard_dg(SOCK_STREAM, "strdup: %m"); } static struct servtab * getconfigent(void) { static struct servtab serv; struct servtab *sep = &serv; int argc; char *cp, *arg; more: while ((cp = nextline()) && *cp == '#') ; if (cp == NULL) return ((struct servtab *)0); memset((char *)sep, 0, sizeof *sep); sep->se_service = newstr(skip(&cp)); arg = skip(&cp); if (arg == NULL) goto more; if (strcmp(arg, "stream") == 0) sep->se_socktype = SOCK_STREAM; else if (strcmp(arg, "dgram") == 0) sep->se_socktype = SOCK_DGRAM; else if (strcmp(arg, "rdm") == 0) sep->se_socktype = SOCK_RDM; else if (strcmp(arg, "seqpacket") == 0) sep->se_socktype = SOCK_SEQPACKET; else if (strcmp(arg, "raw") == 0) sep->se_socktype = SOCK_RAW; else sep->se_socktype = -1; sep->se_proto = newstr(skip(&cp)); if (strcmp(sep->se_proto, "unix") == 0) { sep->se_family = AF_UNIX; } else { sep->se_family = AF_INET; if (strncmp(sep->se_proto, "rpc/", 4) == 0) { syslog(LOG_ERR, "%s: rpc services not suported", sep->se_service); goto more; } } arg = skip(&cp); if (arg == NULL) goto more; { char *s = strchr(arg, '.'); if (s) { *s++ = '\0'; sep->se_max = atoi(s); } else sep->se_max = TOOMANY; } sep->se_wait = strcmp(arg, "wait") == 0; sep->se_user = newstr(skip(&cp)); sep->se_group = strchr(sep->se_user, '.'); if (sep->se_group) { *sep->se_group++ = '\0'; } sep->se_server = newstr(skip(&cp)); if (strcmp(sep->se_server, "internal") == 0) { #ifndef INETD_UNSUPPORT_BILTIN const struct biltin *bi; for (bi = biltins; bi->bi_service; bi++) if (bi->bi_socktype == sep->se_socktype && strcmp(bi->bi_service, sep->se_service) == 0) break; if (bi->bi_service == 0) { syslog(LOG_ERR, "internal service %s unknown", sep->se_service); goto more; } sep->se_bi = bi; sep->se_wait = bi->bi_wait; #else syslog(LOG_ERR, "internal service %s unknown", sep->se_service); goto more; #endif } else #ifndef INETD_UNSUPPORT_BILTIN sep->se_bi = NULL #endif ; argc = 0; for (arg = skip(&cp); cp; arg = skip(&cp)) { if (argc < MAXARGV) sep->se_argv[argc++] = newstr(arg); } while (argc <= MAXARGV) sep->se_argv[argc++] = NULL; return (sep); } static void freeconfig(struct servtab *cp) { int i; free(cp->se_service); free(cp->se_proto); free(cp->se_user); /* Note: se_group is part of the newstr'ed se_user */ free(cp->se_server); for (i = 0; i < MAXARGV; i++) free(cp->se_argv[i]); } #ifndef INETD_UNSUPPORT_BILTIN static char **Argv; static char *LastArg; static void setproctitle(char *a, int s) { size_t size; char *cp; struct sockaddr_in sn; char buf[80]; cp = Argv[0]; size = sizeof(sn); if (getpeername(s, (struct sockaddr *)&sn, &size) == 0) (void) sprintf(buf, "-%s [%s]", a, inet_ntoa(sn.sin_addr)); else (void) sprintf(buf, "-%s", a); strncpy(cp, buf, LastArg - cp); cp += strlen(cp); while (cp < LastArg) *cp++ = ' '; } #endif /* INETD_UNSUPPORT_BILTIN */ static struct servtab * enter(struct servtab *cp) { struct servtab *sep; sigset_t oldmask; sep = (struct servtab *)malloc(sizeof (*sep)); if (sep == NULL) { syslog_err_and_discard_dg(SOCK_STREAM, bb_msg_memory_exhausted); } *sep = *cp; sep->se_fd = -1; sigprocmask(SIG_BLOCK, &blockmask, &oldmask); sep->se_next = servtab; servtab = sep; sigprocmask(SIG_SETMASK, &oldmask, NULL); return (sep); } static int bump_nofile(void) { #ifdef RLIMIT_NOFILE #define FD_CHUNK 32 struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) < 0) { syslog(LOG_ERR, "getrlimit: %m"); return -1; } rl.rlim_cur = MIN(rl.rlim_max, rl.rlim_cur + FD_CHUNK); if (rl.rlim_cur <= rlim_ofile_cur) { syslog(LOG_ERR, #if _FILE_OFFSET_BITS == 64 "bump_nofile: cannot extend file limit, max = %lld", #else "bump_nofile: cannot extend file limit, max = %ld", #endif rl.rlim_cur); return -1; } if (setrlimit(RLIMIT_NOFILE, &rl) < 0) { syslog(LOG_ERR, "setrlimit: %m"); return -1; } rlim_ofile_cur = rl.rlim_cur; return 0; #else syslog(LOG_ERR, "bump_nofile: cannot extend file limit"); return -1; #endif } static void setup(struct servtab *sep) { int on = 1; if ((sep->se_fd = socket(sep->se_family, sep->se_socktype, 0)) < 0) { syslog(LOG_ERR, "%s/%s: socket: %m", sep->se_service, sep->se_proto); return; } if (setsockopt(sep->se_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) < 0) syslog(LOG_ERR, "setsockopt (SO_REUSEADDR): %m"); if (bind(sep->se_fd, &sep->se_ctrladdr, sep->se_ctrladdr_size) < 0) { syslog(LOG_ERR, "%s/%s: bind: %m", sep->se_service, sep->se_proto); (void) close(sep->se_fd); sep->se_fd = -1; if (!timingout) { timingout = 1; alarm(RETRYTIME); } return; } if (sep->se_socktype == SOCK_STREAM) listen(sep->se_fd, global_queuelen); FD_SET(sep->se_fd, &allsock); nsock++; if (sep->se_fd > maxsock) { maxsock = sep->se_fd; if (maxsock > rlim_ofile_cur - FD_MARGIN) bump_nofile(); } } static void config(int signum) { struct servtab *sep, *cp, **sepp; sigset_t oldmask; unsigned n; (void)signum; if (setconfig() == NULL) return; for (sep = servtab; sep; sep = sep->se_next) sep->se_checked = 0; while ((cp = getconfigent()) != NULL) { for (sep = servtab; sep; sep = sep->se_next) if (strcmp(sep->se_service, cp->se_service) == 0 && strcmp(sep->se_proto, cp->se_proto) == 0) break; if (sep != 0) { int i; #define SWAP(type, a, b) {type c=(type)a; (type)a=(type)b; (type)b=(type)c;} sigprocmask(SIG_BLOCK, &emptymask, &oldmask); /* * sep->se_wait may be holding the pid of a daemon * that we're waiting for. If so, don't overwrite * it unless the config file explicitly says don't * wait. */ if ( #ifndef INETD_UNSUPPORT_BILTIN cp->se_bi == 0 && #endif (sep->se_wait == 1 || cp->se_wait == 0)) sep->se_wait = cp->se_wait; if (cp->se_max != sep->se_max) SWAP(int, cp->se_max, sep->se_max); if (cp->se_user) SWAP(char *, sep->se_user, cp->se_user); if (cp->se_group) SWAP(char *, sep->se_group, cp->se_group); if (cp->se_server) SWAP(char *, sep->se_server, cp->se_server); for (i = 0; i < MAXARGV; i++) SWAP(char *, sep->se_argv[i], cp->se_argv[i]); #undef SWAP sigprocmask(SIG_SETMASK, &oldmask, NULL); freeconfig(cp); } else { sep = enter(cp); } sep->se_checked = 1; switch (sep->se_family) { case AF_UNIX: if (sep->se_fd != -1) break; (void)unlink(sep->se_service); n = strlen(sep->se_service); if (n > sizeof(sep->se_ctrladdr_un.sun_path) - 1) n = sizeof(sep->se_ctrladdr_un.sun_path) - 1; strncpy(sep->se_ctrladdr_un.sun_path, sep->se_service, n); sep->se_ctrladdr_un.sun_family = AF_UNIX; sep->se_ctrladdr_size = n + sizeof sep->se_ctrladdr_un.sun_family; setup(sep); break; case AF_INET: sep->se_ctrladdr_in.sin_family = AF_INET; sep->se_ctrladdr_size = sizeof sep->se_ctrladdr_in; { u_short port = htons(atoi(sep->se_service)); if (!port) { struct servent *sp; sp = getservbyname(sep->se_service, sep->se_proto); if (sp == 0) { syslog(LOG_ERR, "%s/%s: unknown service", sep->se_service, sep->se_proto); continue; } port = sp->s_port; } if (port != sep->se_ctrladdr_in.sin_port) { sep->se_ctrladdr_in.sin_port = port; if (sep->se_fd != -1) { FD_CLR(sep->se_fd, &allsock); nsock--; (void) close(sep->se_fd); } sep->se_fd = -1; } if (sep->se_fd == -1) setup(sep); } } } if (fconfig) { (void) fclose(fconfig); fconfig = NULL; } /* * Purge anything not looked at above. */ sigprocmask(SIG_SETMASK, &blockmask, &oldmask); sepp = &servtab; while ((sep = *sepp) != NULL) { if (sep->se_checked) { sepp = &sep->se_next; continue; } *sepp = sep->se_next; if (sep->se_fd != -1) { FD_CLR(sep->se_fd, &allsock); nsock--; (void) close(sep->se_fd); } if (sep->se_family == AF_UNIX) (void)unlink(sep->se_service); freeconfig(sep); free((char *)sep); } sigprocmask(SIG_SETMASK, &oldmask, NULL); } static void reapchild(int signum) { int status; int pid; struct servtab *sep; (void)signum; for (;;) { pid = wait3(&status, WNOHANG, (struct rusage *)0); if (pid <= 0) break; for (sep = servtab; sep; sep = sep->se_next) if (sep->se_wait == pid) { if (WIFEXITED(status) && WEXITSTATUS(status)) syslog(LOG_WARNING, "%s: exit status 0x%x", sep->se_server, WEXITSTATUS(status)); else if (WIFSIGNALED(status)) syslog(LOG_WARNING, "%s: exit signal 0x%x", sep->se_server, WTERMSIG(status)); sep->se_wait = 1; FD_SET(sep->se_fd, &allsock); nsock++; } } } static void retry(int signum) { struct servtab *sep; (void)signum; timingout = 0; for (sep = servtab; sep; sep = sep->se_next) { if (sep->se_fd == -1) { switch (sep->se_family) { case AF_UNIX: case AF_INET: setup(sep); break; } } } } static void goaway(int signum) { struct servtab *sep; (void)signum; for (sep = servtab; sep; sep = sep->se_next) if (sep->se_fd != -1 && sep->se_family == AF_UNIX) (void)unlink(sep->se_service); (void)unlink(_PATH_INETDPID); exit(0); } extern int inetd_main(int argc, char *argv[]) { struct servtab *sep; struct passwd *pwd; struct group *grp = NULL; struct sigaction sa; int pid; unsigned long opt; char *sq; gid_t gid; #ifdef INETD_UNSUPPORT_BILTIN # define dofork 1 #else int dofork; extern char **environ; #endif gid = getgid(); setgroups(1, &gid); #ifndef INETD_UNSUPPORT_BILTIN Argv = argv; if (environ == 0 || *environ == 0) environ = argv; while (*environ) environ++; LastArg = environ[-1] + strlen(environ[-1]); #endif #if defined(__uClinux__) opt = bb_getopt_ulflags(argc, argv, "q:f", &sq); if (!(opt & 2)) { daemon(0, 0); /* reexec for vfork() do continue parent */ vfork_daemon_rexec(argc, argv, "-f"); } #else opt = bb_getopt_ulflags(argc, argv, "q:", &sq); daemon(0, 0); #endif /* uClinux */ if(opt & 1) { global_queuelen = atoi(optarg); if (global_queuelen < 8) global_queuelen=8; } argc -= optind; argv += optind; if (argc > 0) CONFIG = argv[0]; openlog(bb_applet_name, LOG_PID | LOG_NOWAIT, LOG_DAEMON); { FILE *fp; if ((fp = fopen(_PATH_INETDPID, "w")) != NULL) { fprintf(fp, "%u\n", getpid()); (void)fclose(fp); } } #ifdef RLIMIT_NOFILE if (getrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0) { syslog(LOG_ERR, "getrlimit: %m"); } else { rlim_ofile_cur = rlim_ofile.rlim_cur; if (rlim_ofile_cur == RLIM_INFINITY) /* ! */ rlim_ofile_cur = OPEN_MAX; } #endif config(0); sigemptyset(&emptymask); sigemptyset(&blockmask); sigaddset(&blockmask, SIGCHLD); sigaddset(&blockmask, SIGHUP); sigaddset(&blockmask, SIGALRM); memset(&sa, 0, sizeof(sa)); sa.sa_mask = blockmask; sa.sa_handler = retry; sigaction(SIGALRM, &sa, NULL); sa.sa_handler = config; sigaction(SIGHUP, &sa, NULL); sa.sa_handler = reapchild; sigaction(SIGCHLD, &sa, NULL); sa.sa_handler = goaway; sigaction(SIGTERM, &sa, NULL); sa.sa_handler = goaway; sigaction(SIGINT, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); { /* space for daemons to overwrite environment for ps */ #define DUMMYSIZE 100 char dummy[DUMMYSIZE]; (void)memset(dummy, 'x', DUMMYSIZE - 1); dummy[DUMMYSIZE - 1] = '\0'; (void)setenv("inetd_dummy", dummy, 1); } for (;;) { int n, ctrl; fd_set readable; if (nsock == 0) { sigprocmask(SIG_BLOCK, &blockmask, NULL); while (nsock == 0) sigsuspend(&emptymask); sigprocmask(SIG_SETMASK, &emptymask, NULL); } readable = allsock; if ((n = select(maxsock + 1, &readable, (fd_set *)0, (fd_set *)0, (struct timeval *)0)) <= 0) { if (n < 0 && errno != EINTR) syslog(LOG_WARNING, "select: %m"); sleep(1); continue; } for (sep = servtab; n && sep; sep = sep->se_next) if (sep->se_fd != -1 && FD_ISSET(sep->se_fd, &readable)) { n--; if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) { /* Fixed AGC */ fcntl(sep->se_fd, F_SETFL, O_NDELAY); /* --------- */ ctrl = accept(sep->se_fd, NULL, NULL); fcntl(sep->se_fd, F_SETFL, 0); if (ctrl < 0) { if (errno == EINTR || errno == EWOULDBLOCK) continue; syslog(LOG_WARNING, "accept (for %s): %m", sep->se_service); continue; } } else ctrl = sep->se_fd; sigprocmask(SIG_BLOCK, &blockmask, NULL); pid = 0; #ifndef INETD_UNSUPPORT_BILTIN dofork = (sep->se_bi == 0 || sep->se_bi->bi_fork); #endif if (dofork) { if (sep->se_count++ == 0) (void)gettimeofday(&sep->se_time, (struct timezone *)0); else if (sep->se_count >= sep->se_max) { struct timeval now; (void)gettimeofday(&now, (struct timezone *)0); if (now.tv_sec - sep->se_time.tv_sec > CNT_INTVL) { sep->se_time = now; sep->se_count = 1; } else { syslog(LOG_ERR, "%s/%s server failing (looping), service terminated", sep->se_service, sep->se_proto); FD_CLR(sep->se_fd, &allsock); (void) close(sep->se_fd); sep->se_fd = -1; sep->se_count = 0; nsock--; sigprocmask(SIG_SETMASK, &emptymask, NULL); if (!timingout) { timingout = 1; alarm(RETRYTIME); } continue; } } pid = fork(); } if (pid < 0) { syslog(LOG_ERR, "fork: %m"); if (sep->se_socktype == SOCK_STREAM) close(ctrl); sigprocmask(SIG_SETMASK, &emptymask, NULL); sleep(1); continue; } if (pid && sep->se_wait) { sep->se_wait = pid; FD_CLR(sep->se_fd, &allsock); nsock--; } sigprocmask(SIG_SETMASK, &emptymask, NULL); if (pid == 0) { #ifndef INETD_UNSUPPORT_BILTIN if (sep->se_bi) (*sep->se_bi->bi_fn)(ctrl, sep); else #endif { if ((pwd = getpwnam(sep->se_user)) == NULL) { syslog_err_and_discard_dg( sep->se_socktype, "getpwnam: %s: No such user", sep->se_user); } if (sep->se_group && (grp = getgrnam(sep->se_group)) == NULL) { syslog_err_and_discard_dg( sep->se_socktype, "getgrnam: %s: No such group", sep->se_group); } /* * Ok. There are four cases here: * 1. nonroot user, no group specified * 2. nonroot user, some group specified * 3. root user, no group specified * 4. root user, some group specified * In cases 2 and 4 we setgid to the specified * group. In cases 1 and 2 we run initgroups * to run with the groups of the given user. * In case 4 we do setgroups to run with the * given group. In case 3 we do nothing. */ if (pwd->pw_uid) { if (sep->se_group) pwd->pw_gid = grp->gr_gid; setgid((gid_t)pwd->pw_gid); initgroups(pwd->pw_name, pwd->pw_gid); setuid((uid_t)pwd->pw_uid); } else if (sep->se_group) { setgid((gid_t)grp->gr_gid); setgroups(1, &grp->gr_gid); } dup2(ctrl, 0); close(ctrl); dup2(0, 1); dup2(0, 2); #ifdef RLIMIT_NOFILE if (rlim_ofile.rlim_cur != rlim_ofile_cur) { if (setrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0) syslog(LOG_ERR,"setrlimit: %m"); } #endif for (ctrl = rlim_ofile_cur-1; --ctrl > 2; ) (void)close(ctrl); memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_DFL; sigaction(SIGPIPE, &sa, NULL); execv(sep->se_server, sep->se_argv); syslog_err_and_discard_dg(sep->se_socktype, "execv %s: %m", sep->se_server); } } if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) close(ctrl); } } } /* * Internet services provided internally by inetd: */ #define BUFSIZE 4096 #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_ECHO /* Echo service -- echo data back */ static void echo_stream(int s, struct servtab *sep) { char buffer[BUFSIZE]; int i; setproctitle(sep->se_service, s); while ((i = read(s, buffer, sizeof(buffer))) > 0 && write(s, buffer, i) > 0) ; exit(0); } /* Echo service -- echo data back */ static void echo_dg(int s, struct servtab *sep) { char buffer[BUFSIZE]; int i; size_t size; struct sockaddr sa; (void)sep; size = sizeof(sa); if ((i = recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size)) < 0) return; (void) sendto(s, buffer, i, 0, &sa, sizeof(sa)); } #endif /* CONFIG_FEATURE_INETD_SUPPORT_BILTIN_ECHO */ #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DISCARD /* Discard service -- ignore data */ static void discard_stream(int s, struct servtab *sep) { char buffer[BUFSIZE]; setproctitle(sep->se_service, s); while ((errno = 0, read(s, buffer, sizeof(buffer)) > 0) || errno == EINTR) ; exit(0); } /* Discard service -- ignore data */ static void discard_dg(int s, struct servtab *sep) { char buffer[BUFSIZE]; (void)sep; read(s, buffer, sizeof(buffer)); } #endif /* CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DISCARD */ #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_CHARGEN #include #define LINESIZ 72 static char ring[128]; static char *endring; static void initring(void) { int i; endring = ring; for (i = 0; i <= 128; ++i) if (isprint(i)) *endring++ = i; } /* Character generator */ static void chargen_stream(int s, struct servtab *sep) { char *rs; int len; char text[LINESIZ+2]; setproctitle(sep->se_service, s); if (!endring) { initring(); rs = ring; } text[LINESIZ] = '\r'; text[LINESIZ + 1] = '\n'; for (rs = ring;;) { if ((len = endring - rs) >= LINESIZ) BCOPY(rs, text, LINESIZ); else { BCOPY(rs, text, len); BCOPY(ring, text + len, LINESIZ - len); } if (++rs == endring) rs = ring; if (write(s, text, sizeof(text)) != sizeof(text)) break; } exit(0); } /* Character generator */ static void chargen_dg(int s, struct servtab *sep) { struct sockaddr sa; static char *rs; size_t len, size; char text[LINESIZ+2]; (void)sep; if (endring == 0) { initring(); rs = ring; } size = sizeof(sa); if (recvfrom(s, text, sizeof(text), 0, &sa, &size) < 0) return; if ((len = endring - rs) >= LINESIZ) BCOPY(rs, text, LINESIZ); else { BCOPY(rs, text, len); BCOPY(ring, text + len, LINESIZ - len); } if (++rs == endring) rs = ring; text[LINESIZ] = '\r'; text[LINESIZ + 1] = '\n'; (void) sendto(s, text, sizeof(text), 0, &sa, sizeof(sa)); } #endif /* CONFIG_FEATURE_INETD_SUPPORT_BILTIN_CHARGEN */ #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_TIME /* * Return a machine readable date and time, in the form of the * number of seconds since midnight, Jan 1, 1900. Since gettimeofday * returns the number of seconds since midnight, Jan 1, 1970, * we must add 2208988800 seconds to this figure to make up for * some seventy years Bell Labs was asleep. */ static long machtime(void) { struct timeval tv; if (gettimeofday(&tv, (struct timezone *)0) < 0) { fprintf(stderr, "Unable to get time of day\n"); return (0L); } return (htonl((long)tv.tv_sec + 2208988800UL)); } static void machtime_stream(int s, struct servtab *sep) { long result; (void)sep; result = machtime(); write(s, (char *) &result, sizeof(result)); } static void machtime_dg(int s, struct servtab *sep) { long result; struct sockaddr sa; size_t size; (void)sep; size = sizeof(sa); if (recvfrom(s, (char *)&result, sizeof(result), 0, &sa, &size) < 0) return; result = machtime(); (void) sendto(s, (char *) &result, sizeof(result), 0, &sa, sizeof(sa)); } #endif /* CONFIG_FEATURE_INETD_SUPPORT_BILTIN_TIME */ #ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DAYTIME /* Return human-readable time of day */ static int human_readable_time_sprintf(char *buffer) { time_t clocc = time(NULL); return sprintf(buffer, "%.24s\r\n", ctime(&clocc)); } static void daytime_stream(int s, struct servtab *sep) { char buffer[256]; size_t st = human_readable_time_sprintf(buffer); (void)sep; write(s, buffer, st); } /* Return human-readable time of day */ static void daytime_dg(int s, struct servtab *sep) { char buffer[256]; struct sockaddr sa; size_t size; (void)sep; size = sizeof(sa); if (recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size) < 0) return; size = human_readable_time_sprintf(buffer); sendto(s, buffer, size, 0, &sa, sizeof(sa)); } #endif /* CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DAYTIME */