/*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1983, 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * 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 begin with * a space or tab. All fields must be present in each entry. * * service name must be in /etc/services * or name a tcpmux service * or specify a unix domain socket * socket type stream/dgram/raw/rdm/seqpacket * protocol tcp[4][6], udp[4][6], unix * wait/nowait single-threaded/multi-threaded * user[:group][/login-class] user/group/login-class to run daemon as * server program full path name * server program arguments maximum of MAXARGS (20) * * TCP services without official port numbers are handled with the * RFC1078-based tcpmux internal service. Tcpmux listens on port 1 for * requests. When a connection is made from a foreign host, the service * requested is passed to tcpmux, which looks it up in the servtab list * and returns the proper entry for the service. Tcpmux returns a * negative reply if the service doesn't exist, otherwise the invoked * server is expected to return the positive reply if the service type in * inetd.conf file has the prefix "tcpmux/". If the service type has the * prefix "tcpmux/+", tcpmux will return the positive reply for the * process; this is for compatibility with older server code, and also * allows you to invoke programs that use stdin/stdout without putting any * special server code in them. Services that use tcpmux are "nowait" * because they do not have a well-known port and hence cannot listen * for new requests. * * For RPC services * service name/version must be in /etc/rpc * socket type stream/dgram/raw/rdm/seqpacket * protocol rpc/tcp[4][6], rpc/udp[4][6] * wait/nowait single-threaded/multi-threaded * user[:group][/login-class] user/group/login-class to run daemon as * server program full path name * server program arguments maximum of MAXARGS * * Comment lines are indicated by a `#' in column 1. * * #ifdef IPSEC * Comment lines that start with "#@" denote IPsec policy string, as described * in ipsec_set_policy(3). This will affect all the following items in * inetd.conf(8). To reset the policy, just use "#@" line. By default, * there's no IPsec policy. * #endif */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef LIBWRAP #include #endif #include #include "inetd.h" #include "pathnames.h" #ifdef IPSEC #include #ifndef IPSEC_POLICY_IPSEC /* no ipsec support on old ipsec */ #undef IPSEC #endif #endif #ifndef LIBWRAP_ALLOW_FACILITY # define LIBWRAP_ALLOW_FACILITY LOG_AUTH #endif #ifndef LIBWRAP_ALLOW_SEVERITY # define LIBWRAP_ALLOW_SEVERITY LOG_INFO #endif #ifndef LIBWRAP_DENY_FACILITY # define LIBWRAP_DENY_FACILITY LOG_AUTH #endif #ifndef LIBWRAP_DENY_SEVERITY # define LIBWRAP_DENY_SEVERITY LOG_WARNING #endif #define ISWRAP(sep) \ ( ((wrap_ex && !(sep)->se_bi) || (wrap_bi && (sep)->se_bi)) \ && (sep->se_family == AF_INET || sep->se_family == AF_INET6) \ && ( ((sep)->se_accept && (sep)->se_socktype == SOCK_STREAM) \ || (sep)->se_socktype == SOCK_DGRAM)) #ifdef LOGIN_CAP #include /* see init.c */ #define RESOURCE_RC "daemon" #endif #ifndef MAXCHILD #define MAXCHILD -1 /* maximum number of this service < 0 = no limit */ #endif #ifndef MAXCPM #define MAXCPM -1 /* rate limit invocations from a single remote address, < 0 = no limit */ #endif #ifndef MAXPERIP #define MAXPERIP -1 /* maximum number of this service from a single remote address, < 0 = no limit */ #endif #ifndef TOOMANY #define TOOMANY 256 /* don't start more than TOOMANY */ #endif #define CNT_INTVL 60 /* servers in CNT_INTVL sec. */ #define RETRYTIME (60*10) /* retry after bind or server fail */ #define MAX_MAXCHLD 32767 /* max allowable max children */ #define SIGBLOCK (sigmask(SIGCHLD)|sigmask(SIGHUP)|sigmask(SIGALRM)) #define satosin(sa) ((struct sockaddr_in *)(void *)sa) #define csatosin(sa) ((const struct sockaddr_in *)(const void *)sa) #ifdef INET6 #define satosin6(sa) ((struct sockaddr_in6 *)(void *)sa) #define csatosin6(sa) ((const struct sockaddr_in6 *)(const void *)sa) #endif static void close_sep(struct servtab *); static void flag_signal(int); static void config(void); static int cpmip(const struct servtab *, int); static void endconfig(void); static struct servtab *enter(struct servtab *); static void freeconfig(struct servtab *); static struct servtab *getconfigent(void); static int matchservent(const char *, const char *, const char *); static char *nextline(FILE *); static void addchild(struct servtab *, int); static void reapchild(void); static void enable(struct servtab *); static void disable(struct servtab *); static void retry(void); static int setconfig(void); static void setup(struct servtab *); #ifdef IPSEC static void ipsecsetup(struct servtab *); #endif static void unregisterrpc(register struct servtab *sep); static struct conninfo *search_conn(struct servtab *sep, int ctrl); static int room_conn(struct servtab *sep, struct conninfo *conn); static void addchild_conn(struct conninfo *conn, pid_t pid); static void reapchild_conn(pid_t pid); static void free_conn(struct conninfo *conn); static void resize_conn(struct servtab *sep, int maxperip); static void free_connlist(struct servtab *sep); static void free_proc(struct procinfo *); static struct procinfo *search_proc(pid_t pid, int add); static int hashval(char *p, int len); static char *skip(char **); static char *sskip(char **); static char *newstr(const char *); static void print_service(const char *, const struct servtab *); #ifdef LIBWRAP /* tcpd.h */ int allow_severity; int deny_severity; #endif static int wrap_ex = 0; static int wrap_bi = 0; int debug = 0; static int dolog = 0; static int maxsock; /* highest-numbered descriptor */ static fd_set allsock; static int options; static int timingout; static int toomany = TOOMANY; static int maxchild = MAXCHILD; static int maxcpm = MAXCPM; static int maxperip = MAXPERIP; static struct servent *sp; static struct rpcent *rpc; static char *hostname = NULL; static struct sockaddr_in *bind_sa4; static int v4bind_ok = 0; #ifdef INET6 static struct sockaddr_in6 *bind_sa6; static int v6bind_ok = 0; #endif static int signalpipe[2]; #ifdef SANITY_CHECK static int nsock; #endif static uid_t euid; static gid_t egid; static mode_t mask; struct servtab *servtab; static const char *CONFIG = _PATH_INETDCONF; static const char *pid_file = _PATH_INETDPID; static struct pidfh *pfh = NULL; static struct netconfig *udpconf, *tcpconf, *udp6conf, *tcp6conf; static LIST_HEAD(, procinfo) proctable[PERIPSIZE]; static int getvalue(const char *arg, int *value, const char *whine) { int tmp; char *p; tmp = strtol(arg, &p, 0); if (tmp < 0 || *p) { syslog(LOG_ERR, whine, arg); return 1; /* failure */ } *value = tmp; return 0; /* success */ } #ifdef LIBWRAP static sa_family_t whichaf(struct request_info *req) { struct sockaddr *sa; sa = (struct sockaddr *)req->client->sin; if (sa == NULL) return AF_UNSPEC; #ifdef INET6 if (sa->sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&satosin6(sa)->sin6_addr)) return AF_INET; #endif return sa->sa_family; } #endif int main(int argc, char **argv) { struct servtab *sep; struct passwd *pwd; struct group *grp; struct sigaction sa, saalrm, sachld, sahup, sapipe; int ch, dofork; pid_t pid; char buf[50]; #ifdef LOGIN_CAP login_cap_t *lc = NULL; #endif #ifdef LIBWRAP struct request_info req; int denied; char *service = NULL; #endif struct sockaddr_storage peer; int i; struct addrinfo hints, *res; const char *servname; int error; struct conninfo *conn; openlog("inetd", LOG_PID | LOG_NOWAIT | LOG_PERROR, LOG_DAEMON); while ((ch = getopt(argc, argv, "dlwWR:a:c:C:p:s:")) != -1) switch(ch) { case 'd': debug = 1; options |= SO_DEBUG; break; case 'l': dolog = 1; break; case 'R': getvalue(optarg, &toomany, "-R %s: bad value for service invocation rate"); break; case 'c': getvalue(optarg, &maxchild, "-c %s: bad value for maximum children"); break; case 'C': getvalue(optarg, &maxcpm, "-C %s: bad value for maximum children/minute"); break; case 'a': hostname = optarg; break; case 'p': pid_file = optarg; break; case 's': getvalue(optarg, &maxperip, "-s %s: bad value for maximum children per source address"); break; case 'w': wrap_ex++; break; case 'W': wrap_bi++; break; case '?': default: syslog(LOG_ERR, "usage: inetd [-dlWw] [-a address] [-C rate]" " [-c maximum] [-p filename] [-R rate]" " [-s maximum] [configuration_file]"); exit(EX_USAGE); } /* * Initialize Bind Addrs. * When hostname is NULL, wild card bind addrs are obtained from * getaddrinfo(). But getaddrinfo() requires at least one of * hostname or servname is non NULL. * So when hostname is NULL, set dummy value to servname. * Since getaddrinfo() doesn't accept numeric servname, and * we doesn't use ai_socktype of struct addrinfo returned * from getaddrinfo(), we set dummy value to ai_socktype. */ servname = (hostname == NULL) ? "0" /* dummy */ : NULL; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; /* dummy */ error = getaddrinfo(hostname, servname, &hints, &res); if (error != 0) { syslog(LOG_ERR, "-a %s: %s", hostname, gai_strerror(error)); if (error == EAI_SYSTEM) syslog(LOG_ERR, "%s", strerror(errno)); exit(EX_USAGE); } do { if (res->ai_addr == NULL) { syslog(LOG_ERR, "-a %s: getaddrinfo failed", hostname); exit(EX_USAGE); } switch (res->ai_addr->sa_family) { case AF_INET: if (v4bind_ok) continue; bind_sa4 = satosin(res->ai_addr); /* init port num in case servname is dummy */ bind_sa4->sin_port = 0; v4bind_ok = 1; continue; #ifdef INET6 case AF_INET6: if (v6bind_ok) continue; bind_sa6 = satosin6(res->ai_addr); /* init port num in case servname is dummy */ bind_sa6->sin6_port = 0; v6bind_ok = 1; continue; #endif } if (v4bind_ok #ifdef INET6 && v6bind_ok #endif ) break; } while ((res = res->ai_next) != NULL); if (!v4bind_ok #ifdef INET6 && !v6bind_ok #endif ) { syslog(LOG_ERR, "-a %s: unknown address family", hostname); exit(EX_USAGE); } euid = geteuid(); egid = getegid(); umask(mask = umask(0777)); argc -= optind; argv += optind; if (argc > 0) CONFIG = argv[0]; if (access(CONFIG, R_OK) < 0) syslog(LOG_ERR, "Accessing %s: %m, continuing anyway.", CONFIG); if (debug == 0) { pid_t otherpid; pfh = pidfile_open(pid_file, 0600, &otherpid); if (pfh == NULL) { if (errno == EEXIST) { syslog(LOG_ERR, "%s already running, pid: %d", getprogname(), otherpid); exit(EX_OSERR); } syslog(LOG_WARNING, "pidfile_open() failed: %m"); } if (daemon(0, 0) < 0) { syslog(LOG_WARNING, "daemon(0,0) failed: %m"); } /* From now on we don't want syslog messages going to stderr. */ closelog(); openlog("inetd", LOG_PID | LOG_NOWAIT, LOG_DAEMON); /* * In case somebody has started inetd manually, we need to * clear the logname, so that old servers run as root do not * get the user's logname.. */ if (setlogin("") < 0) { syslog(LOG_WARNING, "cannot clear logname: %m"); /* no big deal if it fails.. */ } if (pfh != NULL && pidfile_write(pfh) == -1) { syslog(LOG_WARNING, "pidfile_write(): %m"); } } if (madvise(NULL, 0, MADV_PROTECT) != 0) syslog(LOG_WARNING, "madvise() failed: %s", strerror(errno)); for (i = 0; i < PERIPSIZE; ++i) LIST_INIT(&proctable[i]); if (v4bind_ok) { udpconf = getnetconfigent("udp"); tcpconf = getnetconfigent("tcp"); if (udpconf == NULL || tcpconf == NULL) { syslog(LOG_ERR, "unknown rpc/udp or rpc/tcp"); exit(EX_USAGE); } } #ifdef INET6 if (v6bind_ok) { udp6conf = getnetconfigent("udp6"); tcp6conf = getnetconfigent("tcp6"); if (udp6conf == NULL || tcp6conf == NULL) { syslog(LOG_ERR, "unknown rpc/udp6 or rpc/tcp6"); exit(EX_USAGE); } } #endif sa = (struct sigaction){ .sa_flags = 0, .sa_handler = flag_signal, }; sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGALRM); sigaddset(&sa.sa_mask, SIGCHLD); sigaddset(&sa.sa_mask, SIGHUP); sigaction(SIGALRM, &sa, &saalrm); config(); sigaction(SIGHUP, &sa, &sahup); sigaction(SIGCHLD, &sa, &sachld); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, &sapipe); { /* 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); } if (pipe2(signalpipe, O_CLOEXEC) != 0) { syslog(LOG_ERR, "pipe: %m"); exit(EX_OSERR); } FD_SET(signalpipe[0], &allsock); #ifdef SANITY_CHECK nsock++; #endif maxsock = MAX(MAX(maxsock, signalpipe[0]), signalpipe[1]); for (;;) { int n, ctrl; fd_set readable; #ifdef SANITY_CHECK if (nsock == 0) { syslog(LOG_ERR, "%s: nsock=0", __func__); exit(EX_SOFTWARE); } #endif 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; } /* handle any queued signal flags */ if (FD_ISSET(signalpipe[0], &readable)) { int nsig, signo; if (ioctl(signalpipe[0], FIONREAD, &nsig) != 0) { syslog(LOG_ERR, "ioctl: %m"); exit(EX_OSERR); } nsig /= sizeof(signo); while (--nsig >= 0) { size_t len; len = read(signalpipe[0], &signo, sizeof(signo)); if (len != sizeof(signo)) { syslog(LOG_ERR, "read: %m"); exit(EX_OSERR); } if (debug) warnx("handling signal flag %d", signo); switch (signo) { case SIGALRM: retry(); break; case SIGCHLD: reapchild(); break; case SIGHUP: config(); break; } } } for (sep = servtab; n && sep; sep = sep->se_next) if (sep->se_fd != -1 && FD_ISSET(sep->se_fd, &readable)) { n--; if (debug) warnx("someone wants %s", sep->se_service); dofork = !sep->se_bi || sep->se_bi->bi_fork || ISWRAP(sep); conn = NULL; if (sep->se_accept && sep->se_socktype == SOCK_STREAM) { i = 1; if (ioctl(sep->se_fd, FIONBIO, &i) < 0) syslog(LOG_ERR, "ioctl (FIONBIO, 1): %m"); ctrl = accept(sep->se_fd, (struct sockaddr *)0, (socklen_t *)0); if (debug) warnx("accept, ctrl %d", ctrl); if (ctrl < 0) { if (errno != EINTR) syslog(LOG_WARNING, "accept (for %s): %m", sep->se_service); if (sep->se_accept && sep->se_socktype == SOCK_STREAM) close(ctrl); continue; } i = 0; if (ioctl(sep->se_fd, FIONBIO, &i) < 0) syslog(LOG_ERR, "ioctl1(FIONBIO, 0): %m"); if (ioctl(ctrl, FIONBIO, &i) < 0) syslog(LOG_ERR, "ioctl2(FIONBIO, 0): %m"); if (cpmip(sep, ctrl) < 0) { close(ctrl); continue; } if (dofork && (conn = search_conn(sep, ctrl)) != NULL && !room_conn(sep, conn)) { close(ctrl); continue; } } else ctrl = sep->se_fd; if (dolog && !ISWRAP(sep)) { char pname[NI_MAXHOST] = "unknown"; socklen_t sl; sl = sizeof(peer); if (getpeername(ctrl, (struct sockaddr *) &peer, &sl)) { sl = sizeof(peer); if (recvfrom(ctrl, buf, sizeof(buf), MSG_PEEK, (struct sockaddr *)&peer, &sl) >= 0) { getnameinfo((struct sockaddr *)&peer, peer.ss_len, pname, sizeof(pname), NULL, 0, NI_NUMERICHOST); } } else { getnameinfo((struct sockaddr *)&peer, peer.ss_len, pname, sizeof(pname), NULL, 0, NI_NUMERICHOST); } syslog(LOG_INFO,"%s from %s", sep->se_service, pname); } (void) sigblock(SIGBLOCK); pid = 0; /* * Fork for all external services, builtins which need to * fork and anything we're wrapping (as wrapping might * block or use hosts_options(5) twist). */ if (dofork) { if (sep->se_count++ == 0) (void)clock_gettime(CLOCK_MONOTONIC_FAST, &sep->se_time); else if (toomany > 0 && sep->se_count >= toomany) { struct timespec now; (void)clock_gettime(CLOCK_MONOTONIC_FAST, &now); 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); if (sep->se_accept && sep->se_socktype == SOCK_STREAM) close(ctrl); close_sep(sep); free_conn(conn); sigsetmask(0L); if (!timingout) { timingout = 1; alarm(RETRYTIME); } continue; } } pid = fork(); } if (pid < 0) { syslog(LOG_ERR, "fork: %m"); if (sep->se_accept && sep->se_socktype == SOCK_STREAM) close(ctrl); free_conn(conn); sigsetmask(0L); sleep(1); continue; } if (pid) { addchild_conn(conn, pid); addchild(sep, pid); } sigsetmask(0L); if (pid == 0) { pidfile_close(pfh); if (dofork) { sigaction(SIGALRM, &saalrm, (struct sigaction *)0); sigaction(SIGCHLD, &sachld, (struct sigaction *)0); sigaction(SIGHUP, &sahup, (struct sigaction *)0); /* SIGPIPE reset before exec */ } /* * Call tcpmux to find the real service to exec. */ if (sep->se_bi && sep->se_bi->bi_fn == (bi_fn_t *) tcpmux) { sep = tcpmux(ctrl); if (sep == NULL) { close(ctrl); _exit(0); } } #ifdef LIBWRAP if (ISWRAP(sep)) { inetd_setproctitle("wrapping", ctrl); service = sep->se_server_name ? sep->se_server_name : sep->se_service; request_init(&req, RQ_DAEMON, service, RQ_FILE, ctrl, 0); fromhost(&req); deny_severity = LIBWRAP_DENY_FACILITY|LIBWRAP_DENY_SEVERITY; allow_severity = LIBWRAP_ALLOW_FACILITY|LIBWRAP_ALLOW_SEVERITY; denied = !hosts_access(&req); if (denied) { syslog(deny_severity, "refused connection from %.500s, service %s (%s%s)", eval_client(&req), service, sep->se_proto, (whichaf(&req) == AF_INET6) ? "6" : ""); if (sep->se_socktype != SOCK_STREAM) recv(ctrl, buf, sizeof (buf), 0); if (dofork) { sleep(1); _exit(0); } } if (dolog) { syslog(allow_severity, "connection from %.500s, service %s (%s%s)", eval_client(&req), service, sep->se_proto, (whichaf(&req) == AF_INET6) ? "6" : ""); } } #endif if (sep->se_bi) { (*sep->se_bi->bi_fn)(ctrl, sep); } else { if (debug) warnx("%d execl %s", getpid(), sep->se_server); /* Clear close-on-exec. */ if (fcntl(ctrl, F_SETFD, 0) < 0) { syslog(LOG_ERR, "%s/%s: fcntl (F_SETFD, 0): %m", sep->se_service, sep->se_proto); _exit(EX_OSERR); } if (ctrl != 0) { dup2(ctrl, 0); close(ctrl); } dup2(0, 1); dup2(0, 2); if ((pwd = getpwnam(sep->se_user)) == NULL) { syslog(LOG_ERR, "%s/%s: %s: no such user", sep->se_service, sep->se_proto, sep->se_user); if (sep->se_socktype != SOCK_STREAM) recv(0, buf, sizeof (buf), 0); _exit(EX_NOUSER); } grp = NULL; if ( sep->se_group != NULL && (grp = getgrnam(sep->se_group)) == NULL ) { syslog(LOG_ERR, "%s/%s: %s: no such group", sep->se_service, sep->se_proto, sep->se_group); if (sep->se_socktype != SOCK_STREAM) recv(0, buf, sizeof (buf), 0); _exit(EX_NOUSER); } if (grp != NULL) pwd->pw_gid = grp->gr_gid; #ifdef LOGIN_CAP if ((lc = login_getclass(sep->se_class)) == NULL) { /* error syslogged by getclass */ syslog(LOG_ERR, "%s/%s: %s: login class error", sep->se_service, sep->se_proto, sep->se_class); if (sep->se_socktype != SOCK_STREAM) recv(0, buf, sizeof (buf), 0); _exit(EX_NOUSER); } #endif if (setsid() < 0) { syslog(LOG_ERR, "%s: can't setsid(): %m", sep->se_service); /* _exit(EX_OSERR); not fatal yet */ } #ifdef LOGIN_CAP if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETALL & ~LOGIN_SETMAC) != 0) { syslog(LOG_ERR, "%s: can't setusercontext(..%s..): %m", sep->se_service, sep->se_user); _exit(EX_OSERR); } login_close(lc); #else if (pwd->pw_uid) { if (setlogin(sep->se_user) < 0) { syslog(LOG_ERR, "%s: can't setlogin(%s): %m", sep->se_service, sep->se_user); /* _exit(EX_OSERR); not yet */ } if (setgid(pwd->pw_gid) < 0) { syslog(LOG_ERR, "%s: can't set gid %d: %m", sep->se_service, pwd->pw_gid); _exit(EX_OSERR); } (void) initgroups(pwd->pw_name, pwd->pw_gid); if (setuid(pwd->pw_uid) < 0) { syslog(LOG_ERR, "%s: can't set uid %d: %m", sep->se_service, pwd->pw_uid); _exit(EX_OSERR); } } #endif sigaction(SIGPIPE, &sapipe, (struct sigaction *)0); execv(sep->se_server, sep->se_argv); syslog(LOG_ERR, "cannot execute %s: %m", sep->se_server); if (sep->se_socktype != SOCK_STREAM) recv(0, buf, sizeof (buf), 0); } if (dofork) _exit(0); } if (sep->se_accept && sep->se_socktype == SOCK_STREAM) close(ctrl); } } } /* * Add a signal flag to the signal flag queue for later handling */ static void flag_signal(int signo) { size_t len; len = write(signalpipe[1], &signo, sizeof(signo)); if (len != sizeof(signo)) { syslog(LOG_ERR, "write: %m"); _exit(EX_OSERR); } } /* * Record a new child pid for this service. If we've reached the * limit on children, then stop accepting incoming requests. */ static void addchild(struct servtab *sep, pid_t pid) { struct stabchild *sc; #ifdef SANITY_CHECK if (SERVTAB_EXCEEDS_LIMIT(sep)) { syslog(LOG_ERR, "%s: %d >= %d", __func__, sep->se_numchild, sep->se_maxchild); exit(EX_SOFTWARE); } #endif sc = calloc(1, sizeof(*sc)); if (sc == NULL) { syslog(LOG_ERR, "calloc: %m"); exit(EX_OSERR); } sc->sc_pid = pid; LIST_INSERT_HEAD(&sep->se_children, sc, sc_link); ++sep->se_numchild; if (SERVTAB_AT_LIMIT(sep)) disable(sep); } static void reapchild(void) { int status; pid_t pid; struct stabchild *sc; struct servtab *sep; for (;;) { pid = wait3(&status, WNOHANG, (struct rusage *)0); if (pid <= 0) break; if (debug) warnx("%d reaped, %s %u", pid, WIFEXITED(status) ? "status" : "signal", WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)); for (sep = servtab; sep; sep = sep->se_next) { LIST_FOREACH(sc, &sep->se_children, sc_link) { if (sc->sc_pid == pid) break; } if (sc == NULL) continue; if (SERVTAB_AT_LIMIT(sep)) enable(sep); LIST_REMOVE(sc, sc_link); free(sc); --sep->se_numchild; if (WIFSIGNALED(status) || WEXITSTATUS(status)) syslog(LOG_WARNING, "%s[%d]: exited, %s %u", sep->se_server, pid, WIFEXITED(status) ? "status" : "signal", WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)); break; } reapchild_conn(pid); } } static void config(void) { struct servtab *sep, *new, **sepp; long omask; int new_nomapped; #ifdef LOGIN_CAP login_cap_t *lc = NULL; #endif if (!setconfig()) { syslog(LOG_ERR, "%s: %m", CONFIG); return; } for (sep = servtab; sep; sep = sep->se_next) sep->se_checked = 0; while ((new = getconfigent())) { if (getpwnam(new->se_user) == NULL) { syslog(LOG_ERR, "%s/%s: no such user '%s', service ignored", new->se_service, new->se_proto, new->se_user); continue; } if (new->se_group && getgrnam(new->se_group) == NULL) { syslog(LOG_ERR, "%s/%s: no such group '%s', service ignored", new->se_service, new->se_proto, new->se_group); continue; } #ifdef LOGIN_CAP if ((lc = login_getclass(new->se_class)) == NULL) { /* error syslogged by getclass */ syslog(LOG_ERR, "%s/%s: %s: login class error, service ignored", new->se_service, new->se_proto, new->se_class); continue; } login_close(lc); #endif new_nomapped = new->se_nomapped; for (sep = servtab; sep; sep = sep->se_next) if (strcmp(sep->se_service, new->se_service) == 0 && strcmp(sep->se_proto, new->se_proto) == 0 && sep->se_rpc == new->se_rpc && sep->se_socktype == new->se_socktype && sep->se_family == new->se_family) break; if (sep != 0) { int i; #define SWAP(t,a, b) { t c = a; a = b; b = c; } omask = sigblock(SIGBLOCK); if (sep->se_nomapped != new->se_nomapped) { /* for rpc keep old nommaped till unregister */ if (!sep->se_rpc) sep->se_nomapped = new->se_nomapped; sep->se_reset = 1; } /* * The children tracked remain; we want numchild to * still reflect how many jobs are running so we don't * throw off our accounting. */ sep->se_maxcpm = new->se_maxcpm; sep->se_maxchild = new->se_maxchild; resize_conn(sep, new->se_maxperip); sep->se_maxperip = new->se_maxperip; sep->se_bi = new->se_bi; /* might need to turn on or off service now */ if (sep->se_fd >= 0) { if (SERVTAB_EXCEEDS_LIMIT(sep)) { if (FD_ISSET(sep->se_fd, &allsock)) disable(sep); } else { if (!FD_ISSET(sep->se_fd, &allsock)) enable(sep); } } sep->se_accept = new->se_accept; SWAP(char *, sep->se_user, new->se_user); SWAP(char *, sep->se_group, new->se_group); #ifdef LOGIN_CAP SWAP(char *, sep->se_class, new->se_class); #endif SWAP(char *, sep->se_server, new->se_server); SWAP(char *, sep->se_server_name, new->se_server_name); for (i = 0; i < MAXARGV; i++) SWAP(char *, sep->se_argv[i], new->se_argv[i]); #ifdef IPSEC SWAP(char *, sep->se_policy, new->se_policy); ipsecsetup(sep); #endif sigsetmask(omask); freeconfig(new); if (debug) print_service("REDO", sep); } else { sep = enter(new); if (debug) print_service("ADD ", sep); } sep->se_checked = 1; if (ISMUX(sep)) { sep->se_fd = -1; continue; } switch (sep->se_family) { case AF_INET: if (!v4bind_ok) { sep->se_fd = -1; continue; } break; #ifdef INET6 case AF_INET6: if (!v6bind_ok) { sep->se_fd = -1; continue; } break; #endif } if (!sep->se_rpc) { if (sep->se_family != AF_UNIX) { sp = getservbyname(sep->se_service, sep->se_proto); if (sp == 0) { syslog(LOG_ERR, "%s/%s: unknown service", sep->se_service, sep->se_proto); sep->se_checked = 0; continue; } } switch (sep->se_family) { case AF_INET: if (sp->s_port != sep->se_ctrladdr4.sin_port) { sep->se_ctrladdr4.sin_port = sp->s_port; sep->se_reset = 1; } break; #ifdef INET6 case AF_INET6: if (sp->s_port != sep->se_ctrladdr6.sin6_port) { sep->se_ctrladdr6.sin6_port = sp->s_port; sep->se_reset = 1; } break; #endif } if (sep->se_reset != 0 && sep->se_fd >= 0) close_sep(sep); } else { rpc = getrpcbyname(sep->se_service); if (rpc == 0) { syslog(LOG_ERR, "%s/%s unknown RPC service", sep->se_service, sep->se_proto); if (sep->se_fd != -1) (void) close(sep->se_fd); sep->se_fd = -1; continue; } if (sep->se_reset != 0 || rpc->r_number != sep->se_rpc_prog) { if (sep->se_rpc_prog) unregisterrpc(sep); sep->se_rpc_prog = rpc->r_number; if (sep->se_fd != -1) (void) close(sep->se_fd); sep->se_fd = -1; } sep->se_nomapped = new_nomapped; } sep->se_reset = 0; if (sep->se_fd == -1) setup(sep); } endconfig(); /* * Purge anything not looked at above. */ omask = sigblock(SIGBLOCK); sepp = &servtab; while ((sep = *sepp)) { if (sep->se_checked) { sepp = &sep->se_next; continue; } *sepp = sep->se_next; if (sep->se_fd >= 0) close_sep(sep); if (debug) print_service("FREE", sep); if (sep->se_rpc && sep->se_rpc_prog > 0) unregisterrpc(sep); freeconfig(sep); free(sep); } (void) sigsetmask(omask); } static void unregisterrpc(struct servtab *sep) { u_int i; struct servtab *sepp; long omask; struct netconfig *netid4, *netid6; omask = sigblock(SIGBLOCK); netid4 = sep->se_socktype == SOCK_DGRAM ? udpconf : tcpconf; netid6 = sep->se_socktype == SOCK_DGRAM ? udp6conf : tcp6conf; if (sep->se_family == AF_INET) netid6 = NULL; else if (sep->se_nomapped) netid4 = NULL; /* * Conflict if same prog and protocol - In that case one should look * to versions, but it is not interesting: having separate servers for * different versions does not work well. * Therefore one do not unregister if there is a conflict. * There is also transport conflict if destroying INET when INET46 * exists, or destroying INET46 when INET exists */ for (sepp = servtab; sepp; sepp = sepp->se_next) { if (sepp == sep) continue; if (sepp->se_checked == 0 || !sepp->se_rpc || strcmp(sep->se_proto, sepp->se_proto) != 0 || sep->se_rpc_prog != sepp->se_rpc_prog) continue; if (sepp->se_family == AF_INET) netid4 = NULL; if (sepp->se_family == AF_INET6) { netid6 = NULL; if (!sep->se_nomapped) netid4 = NULL; } if (netid4 == NULL && netid6 == NULL) return; } if (debug) print_service("UNREG", sep); for (i = sep->se_rpc_lowvers; i <= sep->se_rpc_highvers; i++) { if (netid4) rpcb_unset(sep->se_rpc_prog, i, netid4); if (netid6) rpcb_unset(sep->se_rpc_prog, i, netid6); } if (sep->se_fd != -1) (void) close(sep->se_fd); sep->se_fd = -1; (void) sigsetmask(omask); } static void retry(void) { struct servtab *sep; timingout = 0; for (sep = servtab; sep; sep = sep->se_next) if (sep->se_fd == -1 && !ISMUX(sep)) setup(sep); } static void setup(struct servtab *sep) { int on = 1; /* Set all listening sockets to close-on-exec. */ if ((sep->se_fd = socket(sep->se_family, sep->se_socktype | SOCK_CLOEXEC, 0)) < 0) { if (debug) warn("socket failed on %s/%s", sep->se_service, sep->se_proto); syslog(LOG_ERR, "%s/%s: socket: %m", sep->se_service, sep->se_proto); return; } #define turnon(fd, opt) \ setsockopt(fd, SOL_SOCKET, opt, (char *)&on, sizeof (on)) if (strcmp(sep->se_proto, "tcp") == 0 && (options & SO_DEBUG) && turnon(sep->se_fd, SO_DEBUG) < 0) syslog(LOG_ERR, "setsockopt (SO_DEBUG): %m"); if (turnon(sep->se_fd, SO_REUSEADDR) < 0) syslog(LOG_ERR, "setsockopt (SO_REUSEADDR): %m"); #ifdef SO_PRIVSTATE if (turnon(sep->se_fd, SO_PRIVSTATE) < 0) syslog(LOG_ERR, "setsockopt (SO_PRIVSTATE): %m"); #endif /* tftpd opens a new connection then needs more infos */ #ifdef INET6 if ((sep->se_family == AF_INET6) && (strcmp(sep->se_proto, "udp") == 0) && (sep->se_accept == 0) && (setsockopt(sep->se_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, (char *)&on, sizeof (on)) < 0)) syslog(LOG_ERR, "setsockopt (IPV6_RECVPKTINFO): %m"); if (sep->se_family == AF_INET6) { int flag = sep->se_nomapped ? 1 : 0; if (setsockopt(sep->se_fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&flag, sizeof (flag)) < 0) syslog(LOG_ERR, "setsockopt (IPV6_V6ONLY): %m"); } #endif #undef turnon #ifdef IPSEC ipsecsetup(sep); #endif if (sep->se_family == AF_UNIX) { (void) unlink(sep->se_ctrladdr_un.sun_path); umask(0777); /* Make socket with conservative permissions */ } if (bind(sep->se_fd, (struct sockaddr *)&sep->se_ctrladdr, sep->se_ctrladdr_size) < 0) { if (debug) warn("bind failed on %s/%s", sep->se_service, sep->se_proto); 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); } if (sep->se_family == AF_UNIX) umask(mask); return; } if (sep->se_family == AF_UNIX) { /* Ick - fch{own,mod} don't work on Unix domain sockets */ if (chown(sep->se_service, sep->se_sockuid, sep->se_sockgid) < 0) syslog(LOG_ERR, "chown socket: %m"); if (chmod(sep->se_service, sep->se_sockmode) < 0) syslog(LOG_ERR, "chmod socket: %m"); umask(mask); } if (sep->se_rpc) { u_int i; socklen_t len = sep->se_ctrladdr_size; struct netconfig *netid, *netid2 = NULL; #ifdef INET6 struct sockaddr_in sock; #endif struct netbuf nbuf, nbuf2; if (getsockname(sep->se_fd, (struct sockaddr*)&sep->se_ctrladdr, &len) < 0){ syslog(LOG_ERR, "%s/%s: getsockname: %m", sep->se_service, sep->se_proto); (void) close(sep->se_fd); sep->se_fd = -1; return; } nbuf.buf = &sep->se_ctrladdr; nbuf.len = sep->se_ctrladdr.sa_len; if (sep->se_family == AF_INET) netid = sep->se_socktype==SOCK_DGRAM? udpconf:tcpconf; #ifdef INET6 else { netid = sep->se_socktype==SOCK_DGRAM? udp6conf:tcp6conf; if (!sep->se_nomapped) { /* INET and INET6 */ netid2 = netid==udp6conf? udpconf:tcpconf; memset(&sock, 0, sizeof sock); /* ADDR_ANY */ nbuf2.buf = &sock; nbuf2.len = sock.sin_len = sizeof sock; sock.sin_family = AF_INET; sock.sin_port = sep->se_ctrladdr6.sin6_port; } } #else else { syslog(LOG_ERR, "%s/%s: inetd compiled without inet6 support\n", sep->se_service, sep->se_proto); (void) close(sep->se_fd); sep->se_fd = -1; return; } #endif if (debug) print_service("REG ", sep); for (i = sep->se_rpc_lowvers; i <= sep->se_rpc_highvers; i++) { rpcb_unset(sep->se_rpc_prog, i, netid); rpcb_set(sep->se_rpc_prog, i, netid, &nbuf); if (netid2) { rpcb_unset(sep->se_rpc_prog, i, netid2); rpcb_set(sep->se_rpc_prog, i, netid2, &nbuf2); } } } if (sep->se_socktype == SOCK_STREAM) listen(sep->se_fd, -1); enable(sep); if (debug) { warnx("registered %s on %d", sep->se_server, sep->se_fd); } } #ifdef IPSEC static void ipsecsetup(struct servtab *sep) { char *buf; char *policy_in = NULL; char *policy_out = NULL; int level; int opt; switch (sep->se_family) { case AF_INET: level = IPPROTO_IP; opt = IP_IPSEC_POLICY; break; #ifdef INET6 case AF_INET6: level = IPPROTO_IPV6; opt = IPV6_IPSEC_POLICY; break; #endif default: return; } if (!sep->se_policy || sep->se_policy[0] == '\0') { static char def_in[] = "in entrust", def_out[] = "out entrust"; policy_in = def_in; policy_out = def_out; } else { if (!strncmp("in", sep->se_policy, 2)) policy_in = sep->se_policy; else if (!strncmp("out", sep->se_policy, 3)) policy_out = sep->se_policy; else { syslog(LOG_ERR, "invalid security policy \"%s\"", sep->se_policy); return; } } if (policy_in != NULL) { buf = ipsec_set_policy(policy_in, strlen(policy_in)); if (buf != NULL) { if (setsockopt(sep->se_fd, level, opt, buf, ipsec_get_policylen(buf)) < 0 && debug != 0) warnx("%s/%s: ipsec initialization failed; %s", sep->se_service, sep->se_proto, policy_in); free(buf); } else syslog(LOG_ERR, "invalid security policy \"%s\"", policy_in); } if (policy_out != NULL) { buf = ipsec_set_policy(policy_out, strlen(policy_out)); if (buf != NULL) { if (setsockopt(sep->se_fd, level, opt, buf, ipsec_get_policylen(buf)) < 0 && debug != 0) warnx("%s/%s: ipsec initialization failed; %s", sep->se_service, sep->se_proto, policy_out); free(buf); } else syslog(LOG_ERR, "invalid security policy \"%s\"", policy_out); } } #endif /* * Finish with a service and its socket. */ static void close_sep(struct servtab *sep) { if (sep->se_fd >= 0) { if (FD_ISSET(sep->se_fd, &allsock)) disable(sep); (void) close(sep->se_fd); sep->se_fd = -1; } sep->se_count = 0; sep->se_numchild = 0; /* forget about any existing children */ } static int matchservent(const char *name1, const char *name2, const char *proto) { char **alias, *p; struct servent *se; if (strcmp(proto, "unix") == 0) { if ((p = strrchr(name1, '/')) != NULL) name1 = p + 1; if ((p = strrchr(name2, '/')) != NULL) name2 = p + 1; } if (strcmp(name1, name2) == 0) return(1); if ((se = getservbyname(name1, proto)) != NULL) { if (strcmp(name2, se->s_name) == 0) return(1); for (alias = se->s_aliases; *alias; alias++) if (strcmp(name2, *alias) == 0) return(1); } return(0); } static struct servtab * enter(struct servtab *cp) { struct servtab *sep; long omask; sep = malloc(sizeof(*sep)); if (sep == NULL) { syslog(LOG_ERR, "malloc: %m"); exit(EX_OSERR); } *sep = *cp; sep->se_fd = -1; omask = sigblock(SIGBLOCK); sep->se_next = servtab; servtab = sep; sigsetmask(omask); return (sep); } static void enable(struct servtab *sep) { if (debug) warnx( "enabling %s, fd %d", sep->se_service, sep->se_fd); #ifdef SANITY_CHECK if (sep->se_fd < 0) { syslog(LOG_ERR, "%s: %s: bad fd", __func__, sep->se_service); exit(EX_SOFTWARE); } if (ISMUX(sep)) { syslog(LOG_ERR, "%s: %s: is mux", __func__, sep->se_service); exit(EX_SOFTWARE); } if (FD_ISSET(sep->se_fd, &allsock)) { syslog(LOG_ERR, "%s: %s: not off", __func__, sep->se_service); exit(EX_SOFTWARE); } nsock++; #endif FD_SET(sep->se_fd, &allsock); maxsock = MAX(maxsock, sep->se_fd); } static void disable(struct servtab *sep) { if (debug) warnx( "disabling %s, fd %d", sep->se_service, sep->se_fd); #ifdef SANITY_CHECK if (sep->se_fd < 0) { syslog(LOG_ERR, "%s: %s: bad fd", __func__, sep->se_service); exit(EX_SOFTWARE); } if (ISMUX(sep)) { syslog(LOG_ERR, "%s: %s: is mux", __func__, sep->se_service); exit(EX_SOFTWARE); } if (!FD_ISSET(sep->se_fd, &allsock)) { syslog(LOG_ERR, "%s: %s: not on", __func__, sep->se_service); exit(EX_SOFTWARE); } if (nsock == 0) { syslog(LOG_ERR, "%s: nsock=0", __func__); exit(EX_SOFTWARE); } nsock--; #endif FD_CLR(sep->se_fd, &allsock); if (sep->se_fd == maxsock) maxsock--; } static FILE *fconfig = NULL; static struct servtab serv; static char line[LINE_MAX]; static int setconfig(void) { if (fconfig != NULL) { fseek(fconfig, 0L, SEEK_SET); return (1); } fconfig = fopen(CONFIG, "r"); return (fconfig != NULL); } static void endconfig(void) { if (fconfig) { (void) fclose(fconfig); fconfig = NULL; } } static struct servtab * getconfigent(void) { struct servtab *sep = &serv; int argc; char *cp, *arg, *s; char *versp; static char TCPMUX_TOKEN[] = "tcpmux/"; #define MUX_LEN (sizeof(TCPMUX_TOKEN)-1) #ifdef IPSEC char *policy; #endif #ifdef INET6 int v4bind; int v6bind; #endif int i; #ifdef IPSEC policy = NULL; #endif more: #ifdef INET6 v4bind = 0; v6bind = 0; #endif while ((cp = nextline(fconfig)) != NULL) { #ifdef IPSEC /* lines starting with #@ is not a comment, but the policy */ if (cp[0] == '#' && cp[1] == '@') { char *p; for (p = cp + 2; p && *p && isspace(*p); p++) ; if (*p == '\0') { free(policy); policy = NULL; } else if (ipsec_get_policylen(p) >= 0) { free(policy); policy = newstr(p); } else { syslog(LOG_ERR, "%s: invalid ipsec policy \"%s\"", CONFIG, p); exit(EX_CONFIG); } } #endif if (*cp == '#' || *cp == '\0') continue; break; } if (cp == NULL) { #ifdef IPSEC free(policy); #endif return (NULL); } /* * clear the static buffer, since some fields (se_ctrladdr, * for example) don't get initialized here. */ memset(sep, 0, sizeof *sep); arg = skip(&cp); if (cp == NULL) { /* got an empty line containing just blanks/tabs. */ goto more; } if (arg[0] == ':') { /* :user:group:perm: */ char *user, *group, *perm; struct passwd *pw; struct group *gr; user = arg+1; if ((group = strchr(user, ':')) == NULL) { syslog(LOG_ERR, "no group after user '%s'", user); goto more; } *group++ = '\0'; if ((perm = strchr(group, ':')) == NULL) { syslog(LOG_ERR, "no mode after group '%s'", group); goto more; } *perm++ = '\0'; if ((pw = getpwnam(user)) == NULL) { syslog(LOG_ERR, "no such user '%s'", user); goto more; } sep->se_sockuid = pw->pw_uid; if ((gr = getgrnam(group)) == NULL) { syslog(LOG_ERR, "no such user '%s'", group); goto more; } sep->se_sockgid = gr->gr_gid; sep->se_sockmode = strtol(perm, &arg, 8); if (*arg != ':') { syslog(LOG_ERR, "bad mode '%s'", perm); goto more; } *arg++ = '\0'; } else { sep->se_sockuid = euid; sep->se_sockgid = egid; sep->se_sockmode = 0200; } if (strncmp(arg, TCPMUX_TOKEN, MUX_LEN) == 0) { char *c = arg + MUX_LEN; if (*c == '+') { sep->se_type = MUXPLUS_TYPE; c++; } else sep->se_type = MUX_TYPE; sep->se_service = newstr(c); } else { sep->se_service = newstr(arg); sep->se_type = NORM_TYPE; } arg = sskip(&cp); 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; arg = sskip(&cp); if (strncmp(arg, "tcp", 3) == 0) { sep->se_proto = newstr(strsep(&arg, "/")); if (arg != NULL && (strcmp(arg, "faith") == 0)) { syslog(LOG_ERR, "faith has been deprecated"); goto more; } } else { if (sep->se_type == NORM_TYPE && strncmp(arg, "faith/", 6) == 0) { syslog(LOG_ERR, "faith has been deprecated"); goto more; } sep->se_proto = newstr(arg); } if (strncmp(sep->se_proto, "rpc/", 4) == 0) { memmove(sep->se_proto, sep->se_proto + 4, strlen(sep->se_proto) + 1 - 4); sep->se_rpc = 1; sep->se_rpc_prog = sep->se_rpc_lowvers = sep->se_rpc_highvers = 0; if ((versp = strrchr(sep->se_service, '/'))) { *versp++ = '\0'; switch (sscanf(versp, "%u-%u", &sep->se_rpc_lowvers, &sep->se_rpc_highvers)) { case 2: break; case 1: sep->se_rpc_highvers = sep->se_rpc_lowvers; break; default: syslog(LOG_ERR, "bad RPC version specifier; %s", sep->se_service); freeconfig(sep); goto more; } } else { sep->se_rpc_lowvers = sep->se_rpc_highvers = 1; } } sep->se_nomapped = 0; if (strcmp(sep->se_proto, "unix") == 0) { sep->se_family = AF_UNIX; } else { while (isdigit(sep->se_proto[strlen(sep->se_proto) - 1])) { #ifdef INET6 if (sep->se_proto[strlen(sep->se_proto) - 1] == '6') { sep->se_proto[strlen(sep->se_proto) - 1] = '\0'; v6bind = 1; continue; } #endif if (sep->se_proto[strlen(sep->se_proto) - 1] == '4') { sep->se_proto[strlen(sep->se_proto) - 1] = '\0'; #ifdef INET6 v4bind = 1; #endif continue; } /* illegal version num */ syslog(LOG_ERR, "bad IP version for %s", sep->se_proto); freeconfig(sep); goto more; } #ifdef INET6 if (v6bind && !v6bind_ok) { syslog(LOG_INFO, "IPv6 bind is ignored for %s", sep->se_service); if (v4bind && v4bind_ok) v6bind = 0; else { freeconfig(sep); goto more; } } if (v6bind) { sep->se_family = AF_INET6; if (!v4bind || !v4bind_ok) sep->se_nomapped = 1; } else #endif { /* default to v4 bind if not v6 bind */ if (!v4bind_ok) { syslog(LOG_NOTICE, "IPv4 bind is ignored for %s", sep->se_service); freeconfig(sep); goto more; } sep->se_family = AF_INET; } } /* init ctladdr */ switch(sep->se_family) { case AF_INET: memcpy(&sep->se_ctrladdr4, bind_sa4, sizeof(sep->se_ctrladdr4)); sep->se_ctrladdr_size = sizeof(sep->se_ctrladdr4); break; #ifdef INET6 case AF_INET6: memcpy(&sep->se_ctrladdr6, bind_sa6, sizeof(sep->se_ctrladdr6)); sep->se_ctrladdr_size = sizeof(sep->se_ctrladdr6); break; #endif case AF_UNIX: #define SUN_PATH_MAXSIZE sizeof(sep->se_ctrladdr_un.sun_path) memset(&sep->se_ctrladdr, 0, sizeof(sep->se_ctrladdr)); sep->se_ctrladdr_un.sun_family = sep->se_family; if (strlcpy(sep->se_ctrladdr_un.sun_path, sep->se_service, SUN_PATH_MAXSIZE) >= SUN_PATH_MAXSIZE) { syslog(LOG_ERR, "domain socket pathname too long for service %s", sep->se_service); goto more; } #undef SUN_PATH_MAXSIZE sep->se_ctrladdr_size = sep->se_ctrladdr_un.sun_len = SUN_LEN(&sep->se_ctrladdr_un); } arg = sskip(&cp); if (!strncmp(arg, "wait", 4)) sep->se_accept = 0; else if (!strncmp(arg, "nowait", 6)) sep->se_accept = 1; else { syslog(LOG_ERR, "%s: bad wait/nowait for service %s", CONFIG, sep->se_service); goto more; } sep->se_maxchild = -1; sep->se_maxcpm = -1; sep->se_maxperip = -1; if ((s = strchr(arg, '/')) != NULL) { char *eptr; u_long val; val = strtoul(s + 1, &eptr, 10); if (eptr == s + 1 || val > MAX_MAXCHLD) { syslog(LOG_ERR, "%s: bad max-child for service %s", CONFIG, sep->se_service); goto more; } if (debug) if (!sep->se_accept && val != 1) warnx("maxchild=%lu for wait service %s" " not recommended", val, sep->se_service); sep->se_maxchild = val; if (*eptr == '/') sep->se_maxcpm = strtol(eptr + 1, &eptr, 10); if (*eptr == '/') sep->se_maxperip = strtol(eptr + 1, &eptr, 10); /* * explicitly do not check for \0 for future expansion / * backwards compatibility */ } if (ISMUX(sep)) { /* * Silently enforce "nowait" mode for TCPMUX services * since they don't have an assigned port to listen on. */ sep->se_accept = 1; if (strcmp(sep->se_proto, "tcp")) { syslog(LOG_ERR, "%s: bad protocol for tcpmux service %s", CONFIG, sep->se_service); goto more; } if (sep->se_socktype != SOCK_STREAM) { syslog(LOG_ERR, "%s: bad socket type for tcpmux service %s", CONFIG, sep->se_service); goto more; } } sep->se_user = newstr(sskip(&cp)); #ifdef LOGIN_CAP if ((s = strrchr(sep->se_user, '/')) != NULL) { *s = '\0'; sep->se_class = newstr(s + 1); } else sep->se_class = newstr(RESOURCE_RC); #endif if ((s = strrchr(sep->se_user, ':')) != NULL) { *s = '\0'; sep->se_group = newstr(s + 1); } else sep->se_group = NULL; sep->se_server = newstr(sskip(&cp)); if ((sep->se_server_name = strrchr(sep->se_server, '/'))) sep->se_server_name++; if (strcmp(sep->se_server, "internal") == 0) { struct biltin *bi; for (bi = biltins; bi->bi_service; bi++) if (bi->bi_socktype == sep->se_socktype && matchservent(bi->bi_service, sep->se_service, sep->se_proto)) break; if (bi->bi_service == 0) { syslog(LOG_ERR, "internal service %s unknown", sep->se_service); goto more; } sep->se_accept = 1; /* force accept mode for built-ins */ sep->se_bi = bi; } else sep->se_bi = NULL; if (sep->se_maxperip < 0) sep->se_maxperip = maxperip; if (sep->se_maxcpm < 0) sep->se_maxcpm = maxcpm; if (sep->se_maxchild < 0) { /* apply default max-children */ if (sep->se_bi && sep->se_bi->bi_maxchild >= 0) sep->se_maxchild = sep->se_bi->bi_maxchild; else if (sep->se_accept) sep->se_maxchild = MAX(maxchild, 0); else sep->se_maxchild = 1; } LIST_INIT(&sep->se_children); argc = 0; for (arg = skip(&cp); cp; arg = skip(&cp)) if (argc < MAXARGV) { sep->se_argv[argc++] = newstr(arg); } else { syslog(LOG_ERR, "%s: too many arguments for service %s", CONFIG, sep->se_service); goto more; } while (argc <= MAXARGV) sep->se_argv[argc++] = NULL; for (i = 0; i < PERIPSIZE; ++i) LIST_INIT(&sep->se_conn[i]); #ifdef IPSEC sep->se_policy = policy ? newstr(policy) : NULL; free(policy); #endif return (sep); } static void freeconfig(struct servtab *cp) { struct stabchild *sc; int i; free(cp->se_service); free(cp->se_proto); free(cp->se_user); free(cp->se_group); #ifdef LOGIN_CAP free(cp->se_class); #endif free(cp->se_server); while (!LIST_EMPTY(&cp->se_children)) { sc = LIST_FIRST(&cp->se_children); LIST_REMOVE(sc, sc_link); free(sc); } for (i = 0; i < MAXARGV; i++) if (cp->se_argv[i]) free(cp->se_argv[i]); free_connlist(cp); #ifdef IPSEC free(cp->se_policy); #endif } /* * Safe skip - if skip returns null, log a syntax error in the * configuration file and exit. */ static char * sskip(char **cpp) { char *cp; cp = skip(cpp); if (cp == NULL) { syslog(LOG_ERR, "%s: syntax error", CONFIG); exit(EX_DATAERR); } return (cp); } static char * skip(char **cpp) { char *cp = *cpp; char *start; char quote = '\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(fconfig))) goto again; *cpp = (char *)0; return ((char *)0); } if (*cp == '"' || *cp == '\'') quote = *cp++; start = cp; if (quote) while (*cp && *cp != quote) cp++; else while (*cp && *cp != ' ' && *cp != '\t') cp++; if (*cp != '\0') *cp++ = '\0'; *cpp = cp; return (start); } static char * nextline(FILE *fd) { char *cp; if (fgets(line, sizeof (line), fd) == NULL) return ((char *)0); cp = strchr(line, '\n'); if (cp) *cp = '\0'; return (line); } static char * newstr(const char *cp) { char *cr; if ((cr = strdup(cp != NULL ? cp : ""))) return (cr); syslog(LOG_ERR, "strdup: %m"); exit(EX_OSERR); } void inetd_setproctitle(const char *a, int s) { socklen_t size; struct sockaddr_storage ss; char buf[80], pbuf[NI_MAXHOST]; size = sizeof(ss); if (getpeername(s, (struct sockaddr *)&ss, &size) == 0) { getnameinfo((struct sockaddr *)&ss, size, pbuf, sizeof(pbuf), NULL, 0, NI_NUMERICHOST); (void) sprintf(buf, "%s [%s]", a, pbuf); } else (void) sprintf(buf, "%s", a); setproctitle("%s", buf); } int check_loop(const struct sockaddr *sa, const struct servtab *sep) { struct servtab *se2; char pname[NI_MAXHOST]; for (se2 = servtab; se2; se2 = se2->se_next) { if (!se2->se_bi || se2->se_socktype != SOCK_DGRAM) continue; switch (se2->se_family) { case AF_INET: if (csatosin(sa)->sin_port == se2->se_ctrladdr4.sin_port) goto isloop; continue; #ifdef INET6 case AF_INET6: if (csatosin6(sa)->sin6_port == se2->se_ctrladdr6.sin6_port) goto isloop; continue; #endif default: continue; } isloop: getnameinfo(sa, sa->sa_len, pname, sizeof(pname), NULL, 0, NI_NUMERICHOST); syslog(LOG_WARNING, "%s/%s:%s/%s loop request REFUSED from %s", sep->se_service, sep->se_proto, se2->se_service, se2->se_proto, pname); return 1; } return 0; } /* * print_service: * Dump relevant information to stderr */ static void print_service(const char *action, const struct servtab *sep) { fprintf(stderr, "%s: %s proto=%s accept=%d max=%d user=%s group=%s" #ifdef LOGIN_CAP "class=%s" #endif " builtin=%p server=%s" #ifdef IPSEC " policy=\"%s\"" #endif "\n", action, sep->se_service, sep->se_proto, sep->se_accept, sep->se_maxchild, sep->se_user, sep->se_group, #ifdef LOGIN_CAP sep->se_class, #endif (void *) sep->se_bi, sep->se_server #ifdef IPSEC , (sep->se_policy ? sep->se_policy : "") #endif ); } #define CPMHSIZE 256 #define CPMHMASK (CPMHSIZE-1) #define CHTGRAN 10 #define CHTSIZE 6 typedef struct CTime { unsigned long ct_Ticks; int ct_Count; } CTime; typedef struct CHash { union { struct in_addr c4_Addr; struct in6_addr c6_Addr; } cu_Addr; #define ch_Addr4 cu_Addr.c4_Addr #define ch_Addr6 cu_Addr.c6_Addr int ch_Family; time_t ch_LTime; char *ch_Service; CTime ch_Times[CHTSIZE]; } CHash; static CHash CHashAry[CPMHSIZE]; static int cpmip(const struct servtab *sep, int ctrl) { struct sockaddr_storage rss; socklen_t rssLen = sizeof(rss); int r = 0; /* * If getpeername() fails, just let it through (if logging is * enabled the condition is caught elsewhere) */ if (sep->se_maxcpm > 0 && (sep->se_family == AF_INET || sep->se_family == AF_INET6) && getpeername(ctrl, (struct sockaddr *)&rss, &rssLen) == 0 ) { time_t t = time(NULL); unsigned int hv = 0xABC3D20F; int i; int cnt = 0; CHash *chBest = NULL; unsigned int ticks = t / CHTGRAN; struct sockaddr_in *sin4; #ifdef INET6 struct sockaddr_in6 *sin6; #endif sin4 = (struct sockaddr_in *)&rss; #ifdef INET6 sin6 = (struct sockaddr_in6 *)&rss; #endif { char *p; int addrlen; switch (rss.ss_family) { case AF_INET: p = (char *)&sin4->sin_addr; addrlen = sizeof(struct in_addr); break; #ifdef INET6 case AF_INET6: p = (char *)&sin6->sin6_addr; addrlen = sizeof(struct in6_addr); break; #endif default: /* should not happen */ return -1; } for (i = 0; i < addrlen; ++i, ++p) { hv = (hv << 5) ^ (hv >> 23) ^ *p; } hv = (hv ^ (hv >> 16)); } for (i = 0; i < 5; ++i) { CHash *ch = &CHashAry[(hv + i) & CPMHMASK]; if (rss.ss_family == AF_INET && ch->ch_Family == AF_INET && sin4->sin_addr.s_addr == ch->ch_Addr4.s_addr && ch->ch_Service && strcmp(sep->se_service, ch->ch_Service) == 0) { chBest = ch; break; } #ifdef INET6 if (rss.ss_family == AF_INET6 && ch->ch_Family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &ch->ch_Addr6) != 0 && ch->ch_Service && strcmp(sep->se_service, ch->ch_Service) == 0) { chBest = ch; break; } #endif if (chBest == NULL || ch->ch_LTime == 0 || ch->ch_LTime < chBest->ch_LTime) { chBest = ch; } } if ((rss.ss_family == AF_INET && (chBest->ch_Family != AF_INET || sin4->sin_addr.s_addr != chBest->ch_Addr4.s_addr)) || chBest->ch_Service == NULL || strcmp(sep->se_service, chBest->ch_Service) != 0) { chBest->ch_Family = sin4->sin_family; chBest->ch_Addr4 = sin4->sin_addr; free(chBest->ch_Service); chBest->ch_Service = strdup(sep->se_service); memset(chBest->ch_Times, 0, sizeof(chBest->ch_Times)); } #ifdef INET6 if ((rss.ss_family == AF_INET6 && (chBest->ch_Family != AF_INET6 || IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &chBest->ch_Addr6) == 0)) || chBest->ch_Service == NULL || strcmp(sep->se_service, chBest->ch_Service) != 0) { chBest->ch_Family = sin6->sin6_family; chBest->ch_Addr6 = sin6->sin6_addr; free(chBest->ch_Service); chBest->ch_Service = strdup(sep->se_service); memset(chBest->ch_Times, 0, sizeof(chBest->ch_Times)); } #endif chBest->ch_LTime = t; { CTime *ct = &chBest->ch_Times[ticks % CHTSIZE]; if (ct->ct_Ticks != ticks) { ct->ct_Ticks = ticks; ct->ct_Count = 0; } ++ct->ct_Count; } for (i = 0; i < CHTSIZE; ++i) { CTime *ct = &chBest->ch_Times[i]; if (ct->ct_Ticks <= ticks && ct->ct_Ticks >= ticks - CHTSIZE) { cnt += ct->ct_Count; } } if ((cnt * 60) / (CHTSIZE * CHTGRAN) > sep->se_maxcpm) { char pname[NI_MAXHOST]; getnameinfo((struct sockaddr *)&rss, ((struct sockaddr *)&rss)->sa_len, pname, sizeof(pname), NULL, 0, NI_NUMERICHOST); r = -1; syslog(LOG_ERR, "%s from %s exceeded counts/min (limit %d/min)", sep->se_service, pname, sep->se_maxcpm); } } return(r); } static struct conninfo * search_conn(struct servtab *sep, int ctrl) { struct sockaddr_storage ss; socklen_t sslen = sizeof(ss); struct conninfo *conn; int hv; char pname[NI_MAXHOST], pname2[NI_MAXHOST]; if (sep->se_maxperip <= 0) return NULL; /* * If getpeername() fails, just let it through (if logging is * enabled the condition is caught elsewhere) */ if (getpeername(ctrl, (struct sockaddr *)&ss, &sslen) != 0) return NULL; switch (ss.ss_family) { case AF_INET: hv = hashval((char *)&((struct sockaddr_in *)&ss)->sin_addr, sizeof(struct in_addr)); break; #ifdef INET6 case AF_INET6: hv = hashval((char *)&((struct sockaddr_in6 *)&ss)->sin6_addr, sizeof(struct in6_addr)); break; #endif default: /* * Since we only support AF_INET and AF_INET6, just * let other than AF_INET and AF_INET6 through. */ return NULL; } if (getnameinfo((struct sockaddr *)&ss, sslen, pname, sizeof(pname), NULL, 0, NI_NUMERICHOST) != 0) return NULL; LIST_FOREACH(conn, &sep->se_conn[hv], co_link) { if (getnameinfo((struct sockaddr *)&conn->co_addr, conn->co_addr.ss_len, pname2, sizeof(pname2), NULL, 0, NI_NUMERICHOST) == 0 && strcmp(pname, pname2) == 0) break; } if (conn == NULL) { if ((conn = malloc(sizeof(struct conninfo))) == NULL) { syslog(LOG_ERR, "malloc: %m"); exit(EX_OSERR); } conn->co_proc = reallocarray(NULL, sep->se_maxperip, sizeof(*conn->co_proc)); if (conn->co_proc == NULL) { syslog(LOG_ERR, "reallocarray: %m"); exit(EX_OSERR); } memcpy(&conn->co_addr, (struct sockaddr *)&ss, sslen); conn->co_numchild = 0; LIST_INSERT_HEAD(&sep->se_conn[hv], conn, co_link); } /* * Since a child process is not invoked yet, we cannot * determine a pid of a child. So, co_proc and co_numchild * should be filled leter. */ return conn; } static int room_conn(struct servtab *sep, struct conninfo *conn) { char pname[NI_MAXHOST]; if (conn->co_numchild >= sep->se_maxperip) { getnameinfo((struct sockaddr *)&conn->co_addr, conn->co_addr.ss_len, pname, sizeof(pname), NULL, 0, NI_NUMERICHOST); syslog(LOG_ERR, "%s from %s exceeded counts (limit %d)", sep->se_service, pname, sep->se_maxperip); return 0; } return 1; } static void addchild_conn(struct conninfo *conn, pid_t pid) { struct procinfo *proc; if (conn == NULL) return; if ((proc = search_proc(pid, 1)) != NULL) { if (proc->pr_conn != NULL) { syslog(LOG_ERR, "addchild_conn: child already on process list"); exit(EX_OSERR); } proc->pr_conn = conn; } conn->co_proc[conn->co_numchild++] = proc; } static void reapchild_conn(pid_t pid) { struct procinfo *proc; struct conninfo *conn; int i; if ((proc = search_proc(pid, 0)) == NULL) return; if ((conn = proc->pr_conn) == NULL) return; for (i = 0; i < conn->co_numchild; ++i) if (conn->co_proc[i] == proc) { conn->co_proc[i] = conn->co_proc[--conn->co_numchild]; break; } free_proc(proc); free_conn(conn); } static void resize_conn(struct servtab *sep, int maxpip) { struct conninfo *conn; int i, j; if (sep->se_maxperip <= 0) return; if (maxpip <= 0) { free_connlist(sep); return; } for (i = 0; i < PERIPSIZE; ++i) { LIST_FOREACH(conn, &sep->se_conn[i], co_link) { for (j = maxpip; j < conn->co_numchild; ++j) free_proc(conn->co_proc[j]); conn->co_proc = reallocarray(conn->co_proc, maxpip, sizeof(*conn->co_proc)); if (conn->co_proc == NULL) { syslog(LOG_ERR, "reallocarray: %m"); exit(EX_OSERR); } if (conn->co_numchild > maxpip) conn->co_numchild = maxpip; } } } static void free_connlist(struct servtab *sep) { struct conninfo *conn, *conn_temp; int i, j; for (i = 0; i < PERIPSIZE; ++i) { LIST_FOREACH_SAFE(conn, &sep->se_conn[i], co_link, conn_temp) { if (conn == NULL) { LIST_REMOVE(conn, co_link); continue; } for (j = 0; j < conn->co_numchild; ++j) free_proc(conn->co_proc[j]); conn->co_numchild = 0; free_conn(conn); } } } static void free_conn(struct conninfo *conn) { if (conn == NULL) return; if (conn->co_numchild <= 0) { LIST_REMOVE(conn, co_link); free(conn->co_proc); free(conn); } } static struct procinfo * search_proc(pid_t pid, int add) { struct procinfo *proc; int hv; hv = hashval((char *)&pid, sizeof(pid)); LIST_FOREACH(proc, &proctable[hv], pr_link) { if (proc->pr_pid == pid) break; } if (proc == NULL && add) { if ((proc = malloc(sizeof(struct procinfo))) == NULL) { syslog(LOG_ERR, "malloc: %m"); exit(EX_OSERR); } proc->pr_pid = pid; proc->pr_conn = NULL; LIST_INSERT_HEAD(&proctable[hv], proc, pr_link); } return proc; } static void free_proc(struct procinfo *proc) { if (proc == NULL) return; LIST_REMOVE(proc, pr_link); free(proc); } static int hashval(char *p, int len) { unsigned int hv = 0xABC3D20F; int i; for (i = 0; i < len; ++i, ++p) hv = (hv << 5) ^ (hv >> 23) ^ *p; hv = (hv ^ (hv >> 16)) & (PERIPSIZE - 1); return hv; }