Index: head/usr.sbin/inetd/builtins.c =================================================================== --- head/usr.sbin/inetd/builtins.c (revision 310920) +++ head/usr.sbin/inetd/builtins.c (revision 310921) @@ -1,814 +1,813 @@ /*- * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "inetd.h" -void chargen_dg(int, struct servtab *); -void chargen_stream(int, struct servtab *); -void daytime_dg(int, struct servtab *); -void daytime_stream(int, struct servtab *); -void discard_dg(int, struct servtab *); -void discard_stream(int, struct servtab *); -void echo_dg(int, struct servtab *); -void echo_stream(int, struct servtab *); +static void chargen_dg(int, struct servtab *); +static void chargen_stream(int, struct servtab *); +static void daytime_dg(int, struct servtab *); +static void daytime_stream(int, struct servtab *); +static void discard_dg(int, struct servtab *); +static void discard_stream(int, struct servtab *); +static void echo_dg(int, struct servtab *); +static void echo_stream(int, struct servtab *); static int get_line(int, char *, int); -void iderror(int, int, int, const char *); -void ident_stream(int, struct servtab *); -void initring(void); -uint32_t machtime(void); -void machtime_dg(int, struct servtab *); -void machtime_stream(int, struct servtab *); +static void iderror(int, int, int, const char *); +static void ident_stream(int, struct servtab *); +static void initring(void); +static uint32_t machtime(void); +static void machtime_dg(int, struct servtab *); +static void machtime_stream(int, struct servtab *); -char ring[128]; -char *endring; +static char ring[128]; +static char *endring; - struct biltin biltins[] = { /* Echo received data */ { "echo", SOCK_STREAM, 1, -1, echo_stream }, { "echo", SOCK_DGRAM, 0, 1, echo_dg }, /* Internet /dev/null */ { "discard", SOCK_STREAM, 1, -1, discard_stream }, { "discard", SOCK_DGRAM, 0, 1, discard_dg }, /* Return 32 bit time since 1900 */ { "time", SOCK_STREAM, 0, -1, machtime_stream }, { "time", SOCK_DGRAM, 0, 1, machtime_dg }, /* Return human-readable time */ { "daytime", SOCK_STREAM, 0, -1, daytime_stream }, { "daytime", SOCK_DGRAM, 0, 1, daytime_dg }, /* Familiar character generator */ { "chargen", SOCK_STREAM, 1, -1, chargen_stream }, { "chargen", SOCK_DGRAM, 0, 1, chargen_dg }, { "tcpmux", SOCK_STREAM, 1, -1, (bi_fn_t *)tcpmux }, { "auth", SOCK_STREAM, 1, -1, ident_stream }, { NULL, 0, 0, 0, NULL } }; /* * RFC864 Character Generator Protocol. Generates character data without * any regard for input. */ -void +static void initring(void) { int i; endring = ring; for (i = 0; i <= 128; ++i) if (isprint(i)) *endring++ = i; } /* Character generator * The RFC says that we should send back a random number of * characters chosen from the range 0 to 512. We send LINESIZ+2. */ /* ARGSUSED */ -void +static void chargen_dg(int s, struct servtab *sep) { struct sockaddr_storage ss; static char *rs; int len; socklen_t size; char text[LINESIZ+2]; if (endring == 0) { initring(); rs = ring; } size = sizeof(ss); if (recvfrom(s, text, sizeof(text), 0, (struct sockaddr *)&ss, &size) < 0) return; if (check_loop((struct sockaddr *)&ss, sep)) return; if ((len = endring - rs) >= LINESIZ) memmove(text, rs, LINESIZ); else { memmove(text, rs, len); memmove(text + len, ring, LINESIZ - len); } if (++rs == endring) rs = ring; text[LINESIZ] = '\r'; text[LINESIZ + 1] = '\n'; (void) sendto(s, text, sizeof(text), 0, (struct sockaddr *)&ss, size); } /* Character generator */ /* ARGSUSED */ -void +static void chargen_stream(int s, struct servtab *sep) { int len; char *rs, text[LINESIZ+2]; inetd_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) memmove(text, rs, LINESIZ); else { memmove(text, rs, len); memmove(text + len, ring, LINESIZ - len); } if (++rs == endring) rs = ring; if (write(s, text, sizeof(text)) != sizeof(text)) break; } exit(0); } /* * RFC867 Daytime Protocol. Sends the current date and time as an ascii * character string without any regard for input. */ /* Return human-readable time of day */ /* ARGSUSED */ -void +static void daytime_dg(int s, struct servtab *sep) { char buffer[256]; time_t now; struct sockaddr_storage ss; socklen_t size; now = time((time_t *) 0); size = sizeof(ss); if (recvfrom(s, buffer, sizeof(buffer), 0, (struct sockaddr *)&ss, &size) < 0) return; if (check_loop((struct sockaddr *)&ss, sep)) return; (void) sprintf(buffer, "%.24s\r\n", ctime(&now)); (void) sendto(s, buffer, strlen(buffer), 0, (struct sockaddr *)&ss, size); } /* Return human-readable time of day */ /* ARGSUSED */ -void +static void daytime_stream(int s, struct servtab *sep __unused) { char buffer[256]; time_t now; now = time((time_t *) 0); (void) sprintf(buffer, "%.24s\r\n", ctime(&now)); (void) send(s, buffer, strlen(buffer), MSG_EOF); } /* * RFC863 Discard Protocol. Any data received is thrown away and no response * is sent. */ /* Discard service -- ignore data */ /* ARGSUSED */ -void +static void discard_dg(int s, struct servtab *sep __unused) { char buffer[BUFSIZE]; (void) read(s, buffer, sizeof(buffer)); } /* Discard service -- ignore data */ /* ARGSUSED */ -void +static void discard_stream(int s, struct servtab *sep) { int ret; char buffer[BUFSIZE]; inetd_setproctitle(sep->se_service, s); while (1) { while ((ret = read(s, buffer, sizeof(buffer))) > 0) ; if (ret == 0 || errno != EINTR) break; } exit(0); } /* * RFC862 Echo Protocol. Any data received is sent back to the sender as * received. */ /* Echo service -- echo data back */ /* ARGSUSED */ -void +static void echo_dg(int s, struct servtab *sep) { char buffer[65536]; /* Should be sizeof(max datagram). */ int i; socklen_t size; struct sockaddr_storage ss; size = sizeof(ss); if ((i = recvfrom(s, buffer, sizeof(buffer), 0, (struct sockaddr *)&ss, &size)) < 0) return; if (check_loop((struct sockaddr *)&ss, sep)) return; (void) sendto(s, buffer, i, 0, (struct sockaddr *)&ss, size); } /* Echo service -- echo data back */ /* ARGSUSED */ -void +static void echo_stream(int s, struct servtab *sep) { char buffer[BUFSIZE]; int i; inetd_setproctitle(sep->se_service, s); while ((i = read(s, buffer, sizeof(buffer))) > 0 && write(s, buffer, i) > 0) ; exit(0); } /* * RFC1413 Identification Protocol. Given a TCP port number pair, return a * character string which identifies the owner of that connection on the * server's system. Extended to allow for ~/.fakeid support and ~/.noident * support. */ /* RFC 1413 says the following are the only errors you can return. */ #define ID_INVALID "INVALID-PORT" /* Port number improperly specified. */ #define ID_NOUSER "NO-USER" /* Port not in use/not identifable. */ #define ID_HIDDEN "HIDDEN-USER" /* Hiden at user's request. */ #define ID_UNKNOWN "UNKNOWN-ERROR" /* Everything else. */ /* Generic ident_stream error-sending func */ /* ARGSUSED */ -void +static void iderror(int lport, int fport, int s, const char *er) { char *p; asprintf(&p, "%d , %d : ERROR : %s\r\n", lport, fport, er); if (p == NULL) { syslog(LOG_ERR, "asprintf: %m"); exit(EX_OSERR); } send(s, p, strlen(p), MSG_EOF); free(p); exit(0); } /* Ident service (AKA "auth") */ /* ARGSUSED */ -void +static void ident_stream(int s, struct servtab *sep) { struct utsname un; struct stat sb; struct sockaddr_in sin4[2]; #ifdef INET6 struct sockaddr_in6 sin6[2]; #endif struct sockaddr_storage ss[2]; struct xucred uc; struct timeval tv = { 10, 0 }, to; struct passwd *pw = NULL; fd_set fdset; char buf[BUFSIZE], *p, **av, *osname = NULL, e; char idbuf[MAXLOGNAME] = ""; /* Big enough to hold uid in decimal. */ socklen_t socklen; ssize_t ssize; size_t size, bufsiz; int c, fflag = 0, nflag = 0, rflag = 0, argc = 0; int gflag = 0, iflag = 0, Fflag = 0, getcredfail = 0, onreadlen; u_short lport, fport; inetd_setproctitle(sep->se_service, s); /* * Reset getopt() since we are a fork() but not an exec() from * a parent which used getopt() already. */ optind = 1; optreset = 1; /* * Take the internal argument vector and count it out to make an * argument count for getopt. This can be used for any internal * service to read arguments and use getopt() easily. */ for (av = sep->se_argv; *av; av++) argc++; if (argc) { int sec, usec; size_t i; u_int32_t rnd32; while ((c = getopt(argc, sep->se_argv, "d:fFgino:rt:")) != -1) switch (c) { case 'd': if (!gflag) strlcpy(idbuf, optarg, sizeof(idbuf)); break; case 'f': fflag = 1; break; case 'F': fflag = 1; Fflag=1; break; case 'g': gflag = 1; rnd32 = 0; /* Shush, compiler. */ /* * The number of bits in "rnd32" divided * by the number of bits needed per iteration * gives a more optimal way to reload the * random number only when necessary. * * 32 bits from arc4random corresponds to * about 6 base-36 digits, so we reseed evey 6. */ for (i = 0; i < sizeof(idbuf) - 1; i++) { static const char *const base36 = "0123456789" "abcdefghijklmnopqrstuvwxyz"; if (i % 6 == 0) rnd32 = arc4random(); idbuf[i] = base36[rnd32 % 36]; rnd32 /= 36; } idbuf[i] = '\0'; break; case 'i': iflag = 1; break; case 'n': nflag = 1; break; case 'o': osname = optarg; break; case 'r': rflag = 1; break; case 't': switch (sscanf(optarg, "%d.%d", &sec, &usec)) { case 2: tv.tv_usec = usec; /* FALLTHROUGH */ case 1: tv.tv_sec = sec; break; default: if (debug) warnx("bad -t argument"); break; } break; default: break; } } if (osname == NULL) { if (uname(&un) == -1) iderror(0, 0, s, ID_UNKNOWN); osname = un.sysname; } /* * We're going to prepare for and execute reception of a * packet of data from the user. The data is in the format * "local_port , foreign_port\r\n" (with local being the * server's port and foreign being the client's.) */ gettimeofday(&to, NULL); to.tv_sec += tv.tv_sec; to.tv_usec += tv.tv_usec; if (to.tv_usec >= 1000000) { to.tv_usec -= 1000000; to.tv_sec++; } size = 0; bufsiz = sizeof(buf) - 1; FD_ZERO(&fdset); while (bufsiz > 0) { gettimeofday(&tv, NULL); tv.tv_sec = to.tv_sec - tv.tv_sec; tv.tv_usec = to.tv_usec - tv.tv_usec; if (tv.tv_usec < 0) { tv.tv_usec += 1000000; tv.tv_sec--; } if (tv.tv_sec < 0) break; FD_SET(s, &fdset); if (select(s + 1, &fdset, NULL, NULL, &tv) == -1) iderror(0, 0, s, ID_UNKNOWN); if (ioctl(s, FIONREAD, &onreadlen) == -1) iderror(0, 0, s, ID_UNKNOWN); if ((size_t)onreadlen > bufsiz) onreadlen = bufsiz; ssize = read(s, &buf[size], (size_t)onreadlen); if (ssize == -1) iderror(0, 0, s, ID_UNKNOWN); else if (ssize == 0) break; bufsiz -= ssize; size += ssize; if (memchr(&buf[size - ssize], '\n', ssize) != NULL) break; } buf[size] = '\0'; /* Read two characters, and check for a delimiting character */ if (sscanf(buf, "%hu , %hu%c", &lport, &fport, &e) != 3 || isdigit(e)) iderror(0, 0, s, ID_INVALID); /* Send garbage? */ if (gflag) goto printit; /* * If not "real" (-r), send a HIDDEN-USER error for everything. * If -d is used to set a fallback username, this is used to * override it, and the fallback is returned instead. */ if (!rflag) { if (*idbuf == '\0') iderror(lport, fport, s, ID_HIDDEN); goto printit; } /* * We take the input and construct an array of two sockaddr_ins * which contain the local address information and foreign * address information, respectively, used to look up the * credentials for the socket (which are returned by the * sysctl "net.inet.tcp.getcred" when we call it.) */ socklen = sizeof(ss[0]); if (getsockname(s, (struct sockaddr *)&ss[0], &socklen) == -1) iderror(lport, fport, s, ID_UNKNOWN); socklen = sizeof(ss[1]); if (getpeername(s, (struct sockaddr *)&ss[1], &socklen) == -1) iderror(lport, fport, s, ID_UNKNOWN); if (ss[0].ss_family != ss[1].ss_family) iderror(lport, fport, s, ID_UNKNOWN); size = sizeof(uc); switch (ss[0].ss_family) { case AF_INET: sin4[0] = *(struct sockaddr_in *)&ss[0]; sin4[0].sin_port = htons(lport); sin4[1] = *(struct sockaddr_in *)&ss[1]; sin4[1].sin_port = htons(fport); if (sysctlbyname("net.inet.tcp.getcred", &uc, &size, sin4, sizeof(sin4)) == -1) getcredfail = errno; break; #ifdef INET6 case AF_INET6: sin6[0] = *(struct sockaddr_in6 *)&ss[0]; sin6[0].sin6_port = htons(lport); sin6[1] = *(struct sockaddr_in6 *)&ss[1]; sin6[1].sin6_port = htons(fport); if (sysctlbyname("net.inet6.tcp6.getcred", &uc, &size, sin6, sizeof(sin6)) == -1) getcredfail = errno; break; #endif default: /* should not reach here */ getcredfail = EAFNOSUPPORT; break; } if (getcredfail != 0 || uc.cr_version != XUCRED_VERSION) { if (*idbuf == '\0') iderror(lport, fport, s, getcredfail == ENOENT ? ID_NOUSER : ID_UNKNOWN); goto printit; } /* Look up the pw to get the username and home directory*/ errno = 0; pw = getpwuid(uc.cr_uid); if (pw == NULL) iderror(lport, fport, s, errno == 0 ? ID_NOUSER : ID_UNKNOWN); if (iflag) snprintf(idbuf, sizeof(idbuf), "%u", (unsigned)pw->pw_uid); else strlcpy(idbuf, pw->pw_name, sizeof(idbuf)); /* * If enabled, we check for a file named ".noident" in the user's * home directory. If found, we return HIDDEN-USER. */ if (nflag) { if (asprintf(&p, "%s/.noident", pw->pw_dir) == -1) iderror(lport, fport, s, ID_UNKNOWN); if (lstat(p, &sb) == 0) { free(p); iderror(lport, fport, s, ID_HIDDEN); } free(p); } /* * Here, if enabled, we read a user's ".fakeid" file in their * home directory. It consists of a line containing the name * they want. */ if (fflag) { int fakeid_fd; /* * Here we set ourself to effectively be the user, so we don't * open any files we have no permission to open, especially * symbolic links to sensitive root-owned files or devices. */ if (initgroups(pw->pw_name, pw->pw_gid) == -1) iderror(lport, fport, s, ID_UNKNOWN); if (seteuid(pw->pw_uid) == -1) iderror(lport, fport, s, ID_UNKNOWN); /* * We can't stat() here since that would be a race * condition. * Therefore, we open the file we have permissions to open * and if it's not a regular file, we close it and end up * returning the user's real username. */ if (asprintf(&p, "%s/.fakeid", pw->pw_dir) == -1) iderror(lport, fport, s, ID_UNKNOWN); fakeid_fd = open(p, O_RDONLY | O_NONBLOCK); free(p); if (fakeid_fd == -1 || fstat(fakeid_fd, &sb) == -1 || !S_ISREG(sb.st_mode)) goto fakeid_fail; if ((ssize = read(fakeid_fd, buf, sizeof(buf) - 1)) < 0) goto fakeid_fail; buf[ssize] = '\0'; /* * Usually, the file will have the desired identity * in the form "identity\n". Allow for leading white * space and trailing white space/end of line. */ p = buf; p += strspn(p, " \t"); p[strcspn(p, " \t\r\n")] = '\0'; if (strlen(p) > MAXLOGNAME - 1) /* Too long (including nul)? */ p[MAXLOGNAME - 1] = '\0'; /* * If the name is a zero-length string or matches it * the id or name of another user (unless permitted by -F) * then it is invalid. */ if (*p == '\0') goto fakeid_fail; if (!Fflag) { if (iflag) { if (p[strspn(p, "0123456789")] == '\0' && getpwuid(atoi(p)) != NULL) goto fakeid_fail; } else { if (getpwnam(p) != NULL) goto fakeid_fail; } } strlcpy(idbuf, p, sizeof(idbuf)); fakeid_fail: if (fakeid_fd != -1) close(fakeid_fd); } printit: /* Finally, we make and send the reply. */ if (asprintf(&p, "%d , %d : USERID : %s : %s\r\n", lport, fport, osname, idbuf) == -1) { syslog(LOG_ERR, "asprintf: %m"); exit(EX_OSERR); } send(s, p, strlen(p), MSG_EOF); free(p); exit(0); } /* * RFC738/868 Time Server. * 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. */ -uint32_t +static uint32_t machtime(void) { #define OFFSET ((uint32_t)25567 * 24*60*60) return (htonl((uint32_t)(time(NULL) + OFFSET))); #undef OFFSET } /* ARGSUSED */ -void +static void machtime_dg(int s, struct servtab *sep) { uint32_t result; struct sockaddr_storage ss; socklen_t size; size = sizeof(ss); if (recvfrom(s, (char *)&result, sizeof(result), 0, (struct sockaddr *)&ss, &size) < 0) return; if (check_loop((struct sockaddr *)&ss, sep)) return; result = machtime(); (void) sendto(s, (char *) &result, sizeof(result), 0, (struct sockaddr *)&ss, size); } /* ARGSUSED */ -void +static void machtime_stream(int s, struct servtab *sep __unused) { uint32_t result; result = machtime(); (void) send(s, (char *) &result, sizeof(result), MSG_EOF); } /* * RFC1078 TCP Port Service Multiplexer (TCPMUX). Service connections to * services based on the service name sent. * * Based on TCPMUX.C by Mark K. Lottor November 1988 * sri-nic::ps:tcpmux.c */ #define MAX_SERV_LEN (256+2) /* 2 bytes for \r\n */ #define strwrite(fd, buf) (void) write(fd, buf, sizeof(buf)-1) static int /* # of characters up to \r,\n or \0 */ get_line(int fd, char *buf, int len) { int count = 0, n; struct sigaction sa; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_DFL; sigaction(SIGALRM, &sa, (struct sigaction *)0); do { alarm(10); n = read(fd, buf, len-count); alarm(0); if (n == 0) return (count); if (n < 0) return (-1); while (--n >= 0) { if (*buf == '\r' || *buf == '\n' || *buf == '\0') return (count); count++; buf++; } } while (count < len); return (count); } struct servtab * tcpmux(int s) { struct servtab *sep; char service[MAX_SERV_LEN+1]; int len; /* Get requested service name */ if ((len = get_line(s, service, MAX_SERV_LEN)) < 0) { strwrite(s, "-Error reading service name\r\n"); return (NULL); } service[len] = '\0'; if (debug) warnx("tcpmux: someone wants %s", service); /* * Help is a required command, and lists available services, * one per line. */ if (!strcasecmp(service, "help")) { for (sep = servtab; sep; sep = sep->se_next) { if (!ISMUX(sep)) continue; (void)write(s,sep->se_service,strlen(sep->se_service)); strwrite(s, "\r\n"); } return (NULL); } /* Try matching a service in inetd.conf with the request */ for (sep = servtab; sep; sep = sep->se_next) { if (!ISMUX(sep)) continue; if (!strcasecmp(service, sep->se_service)) { if (ISMUXPLUS(sep)) { strwrite(s, "+Go\r\n"); } return (sep); } } strwrite(s, "-Service not available\r\n"); return (NULL); } Index: head/usr.sbin/inetd/inetd.c =================================================================== --- head/usr.sbin/inetd/inetd.c (revision 310920) +++ head/usr.sbin/inetd/inetd.c (revision 310921) @@ -1,2555 +1,2565 @@ /* * 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. * 4. 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. */ #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1983, 1991, 1993, 1994\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)from: inetd.c 8.4 (Berkeley) 4/13/94"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); /* * 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 #include #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)) -void close_sep(struct servtab *); -void flag_signal(int); -void flag_config(int); -void config(void); -int cpmip(const struct servtab *, int); -void endconfig(void); -struct servtab *enter(struct servtab *); -void freeconfig(struct servtab *); -struct servtab *getconfigent(void); -int matchservent(const char *, const char *, const char *); -char *nextline(FILE *); -void addchild(struct servtab *, int); -void flag_reapchild(int); -void reapchild(void); -void enable(struct servtab *); -void disable(struct servtab *); -void flag_retry(int); -void retry(void); -int setconfig(void); -void setup(struct servtab *); +#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 flag_config(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 flag_reapchild(int); +static void reapchild(void); +static void enable(struct servtab *); +static void disable(struct servtab *); +static void flag_retry(int); +static void retry(void); +static int setconfig(void); +static void setup(struct servtab *); #ifdef IPSEC -void ipsecsetup(struct servtab *); +static void ipsecsetup(struct servtab *); #endif -void unregisterrpc(register struct servtab *sep); +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 *); +/* tcpd.h */ int allow_severity; int deny_severity; -int wrap_ex = 0; -int wrap_bi = 0; + +static int wrap_ex = 0; +static int wrap_bi = 0; int debug = 0; -int dolog = 0; -int maxsock; /* highest-numbered descriptor */ -fd_set allsock; -int options; -int timingout; -int toomany = TOOMANY; -int maxchild = MAXCHILD; -int maxcpm = MAXCPM; -int maxperip = MAXPERIP; -struct servent *sp; -struct rpcent *rpc; -char *hostname = NULL; -struct sockaddr_in *bind_sa4; -int v4bind_ok = 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 -struct sockaddr_in6 *bind_sa6; -int v6bind_ok = 0; +static struct sockaddr_in6 *bind_sa6; +static int v6bind_ok = 0; #endif -int signalpipe[2]; +static int signalpipe[2]; #ifdef SANITY_CHECK -int nsock; +static int nsock; #endif -uid_t euid; -gid_t egid; -mode_t mask; +static uid_t euid; +static gid_t egid; +static mode_t mask; -struct servtab *servtab; +struct servtab *servtab; -extern struct biltin biltins[]; +static const char *CONFIG = _PATH_INETDCONF; +static const char *pid_file = _PATH_INETDPID; +static struct pidfh *pfh = NULL; -const char *CONFIG = _PATH_INETDCONF; -const char *pid_file = _PATH_INETDPID; -struct pidfh *pfh = NULL; +static struct netconfig *udpconf, *tcpconf, *udp6conf, *tcp6conf; -struct netconfig *udpconf, *tcpconf, *udp6conf, *tcp6conf; - static LIST_HEAD(, procinfo) proctable[PERIPSIZE]; -int +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 */ } static sa_family_t whichaf(struct request_info *req) { struct sockaddr *sa; sa = (struct sockaddr *)req->client->sin; if (sa == NULL) return AF_UNSPEC; if (sa->sa_family == AF_INET6 && - IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)sa)->sin6_addr)) + IN6_IS_ADDR_V4MAPPED(&satosin6(sa)->sin6_addr)) return AF_INET; return sa->sa_family; } 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 struct request_info req; int denied; char *service = NULL; 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] [-R rate]" " [-c maximum] [-C rate]" " [-p pidfile] [conf-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; bzero(&hints, 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 = (struct sockaddr_in *)res->ai_addr; + 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 = (struct sockaddr_in6 *)res->ai_addr; + 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.sa_flags = 0; sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGALRM); sigaddset(&sa.sa_mask, SIGCHLD); sigaddset(&sa.sa_mask, SIGHUP); sa.sa_handler = flag_retry; sigaction(SIGALRM, &sa, &saalrm); config(); sa.sa_handler = flag_config; sigaction(SIGHUP, &sa, &sahup); sa.sa_handler = flag_reapchild; 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 if (signalpipe[0] > maxsock) maxsock = signalpipe[0]; if (signalpipe[1] > maxsock) maxsock = 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; if (ioctl(signalpipe[0], FIONREAD, &nsig) != 0) { syslog(LOG_ERR, "ioctl: %m"); exit(EX_OSERR); } while (--nsig >= 0) { char c; if (read(signalpipe[0], &c, 1) != 1) { syslog(LOG_ERR, "read: %m"); exit(EX_OSERR); } if (debug) warnx("handling signal flag %c", c); switch(c) { case 'A': /* sigalrm */ retry(); break; case 'C': /* sigchld */ reapchild(); break; case 'H': /* 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); } } 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" : ""); } } 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 */ -void +static void flag_signal(int c) { char ch = c; if (write(signalpipe[1], &ch, 1) != 1) { 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. */ -void +static void addchild(struct servtab *sep, pid_t pid) { if (sep->se_maxchild <= 0) return; #ifdef SANITY_CHECK if (sep->se_numchild >= sep->se_maxchild) { syslog(LOG_ERR, "%s: %d >= %d", __func__, sep->se_numchild, sep->se_maxchild); exit(EX_SOFTWARE); } #endif sep->se_pids[sep->se_numchild++] = pid; if (sep->se_numchild == sep->se_maxchild) disable(sep); } /* * Some child process has exited. See if it's on somebody's list. */ -void +static void flag_reapchild(int signo __unused) { flag_signal('C'); } -void +static void reapchild(void) { int k, status; pid_t pid; 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) { for (k = 0; k < sep->se_numchild; k++) if (sep->se_pids[k] == pid) break; if (k == sep->se_numchild) continue; if (sep->se_numchild == sep->se_maxchild) enable(sep); sep->se_pids[k] = sep->se_pids[--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); } } -void +static void flag_config(int signo __unused) { flag_signal('H'); } -void +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; } /* copy over outstanding child pids */ if (sep->se_maxchild > 0 && new->se_maxchild > 0) { new->se_numchild = sep->se_numchild; if (new->se_numchild > new->se_maxchild) new->se_numchild = new->se_maxchild; memcpy(new->se_pids, sep->se_pids, new->se_numchild * sizeof(*new->se_pids)); } SWAP(pid_t *, sep->se_pids, new->se_pids); sep->se_maxchild = new->se_maxchild; sep->se_numchild = new->se_numchild; sep->se_maxcpm = new->se_maxcpm; 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 (sep->se_maxchild > 0 && sep->se_numchild == sep->se_maxchild) { 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); } -void +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); } -void +static void flag_retry(int signo __unused) { flag_signal('A'); } -void +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); } -void +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 */ 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"); } #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; struct sockaddr_in sock; 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; 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; } } 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 -void +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. */ -void +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 */ } -int +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); } -struct servtab * +static struct servtab * enter(struct servtab *cp) { struct servtab *sep; long omask; sep = (struct servtab *)malloc(sizeof (*sep)); if (sep == (struct servtab *)0) { 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); } -void +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); if (sep->se_fd > maxsock) maxsock = sep->se_fd; } -void +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--; } -FILE *fconfig = NULL; -struct servtab serv; -char line[LINE_MAX]; +static FILE *fconfig = NULL; +static struct servtab serv; +static char line[LINE_MAX]; -int +static int setconfig(void) { if (fconfig != NULL) { fseek(fconfig, 0L, SEEK_SET); return (1); } fconfig = fopen(CONFIG, "r"); return (fconfig != NULL); } -void +static void endconfig(void) { if (fconfig) { (void) fclose(fconfig); fconfig = NULL; } } -struct servtab * +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 int v4bind; #ifdef INET6 int v6bind; #endif int i; #ifdef IPSEC policy = NULL; #endif more: v4bind = 0; #ifdef INET6 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') { if (policy) free(policy); policy = NULL; } else if (ipsec_get_policylen(p) >= 0) { if (policy) 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) return ((struct servtab *)0); /* * 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'; v4bind = 1; 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: if (strlen(sep->se_service) >= sizeof(sep->se_ctrladdr_un.sun_path)) { syslog(LOG_ERR, "domain socket pathname too long for service %s", sep->se_service); goto more; } memset(&sep->se_ctrladdr, 0, sizeof(sep->se_ctrladdr)); sep->se_ctrladdr_un.sun_family = sep->se_family; sep->se_ctrladdr_un.sun_len = strlen(sep->se_service); strcpy(sep->se_ctrladdr_un.sun_path, sep->se_service); sep->se_ctrladdr_size = 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; } if (sep->se_maxchild > 0) { sep->se_pids = malloc(sep->se_maxchild * sizeof(*sep->se_pids)); if (sep->se_pids == NULL) { syslog(LOG_ERR, "malloc: %m"); exit(EX_OSERR); } } 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; #endif return (sep); } -void +static void freeconfig(struct servtab *cp) { int i; if (cp->se_service) free(cp->se_service); if (cp->se_proto) free(cp->se_proto); if (cp->se_user) free(cp->se_user); if (cp->se_group) free(cp->se_group); #ifdef LOGIN_CAP if (cp->se_class) free(cp->se_class); #endif if (cp->se_server) free(cp->se_server); if (cp->se_pids) free(cp->se_pids); for (i = 0; i < MAXARGV; i++) if (cp->se_argv[i]) free(cp->se_argv[i]); free_connlist(cp); #ifdef IPSEC if (cp->se_policy) free(cp->se_policy); #endif } /* * Safe skip - if skip returns null, log a syntax error in the * configuration file and exit. */ -char * +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); } -char * +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); } -char * +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); } -char * +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 (((const struct sockaddr_in *)sa)->sin_port == + if (csatosin(sa)->sin_port == se2->se_ctrladdr4.sin_port) goto isloop; continue; #ifdef INET6 case AF_INET6: - if (((const struct sockaddr_in6 *)sa)->sin6_port == + 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 */ -void +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; -CHash CHashAry[CPMHSIZE]; +static CHash CHashAry[CPMHSIZE]; -int +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); 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; if (chBest->ch_Service) free(chBest->ch_Service); chBest->ch_Service = strdup(sep->se_service); bzero(chBest->ch_Times, 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; if (chBest->ch_Service) free(chBest->ch_Service); chBest->ch_Service = strdup(sep->se_service); bzero(chBest->ch_Times, 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 = malloc(sep->se_maxperip * sizeof(*conn->co_proc)); if (conn->co_proc == NULL) { syslog(LOG_ERR, "malloc: %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 = realloc(conn->co_proc, maxpip * sizeof(*conn->co_proc)); if (conn->co_proc == NULL) { syslog(LOG_ERR, "realloc: %m"); exit(EX_OSERR); } if (conn->co_numchild > maxpip) conn->co_numchild = maxpip; } } } static void free_connlist(struct servtab *sep) { struct conninfo *conn; int i, j; for (i = 0; i < PERIPSIZE; ++i) { while ((conn = LIST_FIRST(&sep->se_conn[i])) != NULL) { 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) { int i, hv = 0xABC3D20F; for (i = 0; i < len; ++i, ++p) hv = (hv << 5) ^ (hv >> 23) ^ *p; hv = (hv ^ (hv >> 16)) & (PERIPSIZE - 1); return hv; } Index: head/usr.sbin/inetd/inetd.h =================================================================== --- head/usr.sbin/inetd/inetd.h (revision 310920) +++ head/usr.sbin/inetd/inetd.h (revision 310921) @@ -1,145 +1,141 @@ /* * 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. * 4. 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. * * $FreeBSD$ */ #include #include #include #include #include #include #define BUFSIZE 8192 #define LINESIZ 72 #define NORM_TYPE 0 #define MUX_TYPE 1 #define MUXPLUS_TYPE 2 #define FAITH_TYPE 4 #define ISMUX(sep) (((sep)->se_type == MUX_TYPE) || \ ((sep)->se_type == MUXPLUS_TYPE)) #define ISMUXPLUS(sep) ((sep)->se_type == MUXPLUS_TYPE) struct procinfo { LIST_ENTRY(procinfo) pr_link; pid_t pr_pid; /* child pid */ struct conninfo *pr_conn; }; struct conninfo { LIST_ENTRY(conninfo) co_link; struct sockaddr_storage co_addr; /* source address */ int co_numchild; /* current number of children */ struct procinfo **co_proc; /* array of child proc entry */ }; #define PERIPSIZE 256 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 */ int se_maxchild; /* max number of children */ int se_maxcpm; /* max connects per IP per minute */ int se_numchild; /* current number of children */ pid_t *se_pids; /* array of child pids */ char *se_user; /* user name to run as */ char *se_group; /* group name to run as */ #ifdef LOGIN_CAP char *se_class; /* login class name to run with */ #endif struct biltin *se_bi; /* if built-in, description */ char *se_server; /* server program */ char *se_server_name; /* server program without path */ #define MAXARGV 20 char *se_argv[MAXARGV+1]; /* program arguments */ #ifdef IPSEC char *se_policy; /* IPsec policy string */ #endif int se_fd; /* open descriptor */ union { /* bound address */ struct sockaddr se_un_ctrladdr; struct sockaddr_in se_un_ctrladdr4; struct sockaddr_in6 se_un_ctrladdr6; struct sockaddr_un se_un_ctrladdr_un; } se_un; #define se_ctrladdr se_un.se_un_ctrladdr #define se_ctrladdr4 se_un.se_un_ctrladdr4 #define se_ctrladdr6 se_un.se_un_ctrladdr6 #define se_ctrladdr_un se_un.se_un_ctrladdr_un socklen_t se_ctrladdr_size; uid_t se_sockuid; /* Owner for unix domain socket */ gid_t se_sockgid; /* Group for unix domain socket */ mode_t se_sockmode; /* Mode for unix domain socket */ u_char se_type; /* type: normal, mux, or mux+ */ u_char se_checked; /* looked at during merge */ u_char se_accept; /* i.e., wait/nowait mode */ u_char se_rpc; /* ==1 if RPC service */ int se_rpc_prog; /* RPC program number */ u_int se_rpc_lowvers; /* RPC low version */ u_int se_rpc_highvers; /* RPC high version */ int se_count; /* number started since se_time */ struct timespec se_time; /* start of se_count */ struct servtab *se_next; struct se_flags { u_int se_nomapped : 1; u_int se_reset : 1; } se_flags; int se_maxperip; /* max number of children per src */ LIST_HEAD(, conninfo) se_conn[PERIPSIZE]; }; #define se_nomapped se_flags.se_nomapped #define se_reset se_flags.se_reset int check_loop(const struct sockaddr *, const struct servtab *sep); -int getvalue(const char *, int *, const char *); -char *newstr(const char *); void inetd_setproctitle(const char *, int); -void print_service(const char *, const struct servtab *); -char *sskip(char **); -char *skip(char **); struct servtab *tcpmux(int); extern int debug; extern struct servtab *servtab; typedef void (bi_fn_t)(int, struct servtab *); 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 */ int bi_maxchild; /* max number of children, -1=default */ bi_fn_t *bi_fn; /* function which performs it */ }; +extern struct biltin biltins[];