/* Copyright (C) 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. This file is part of GNU Inetutils. GNU Inetutils is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU Inetutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Inetutils; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * 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. */ #if 0 static char sccsid[] = "@(#)inetd.c 8.4 (Berkeley) 4/13/94"; #endif /* * Inetd - Internet super-server * * This program invokes all internet services as needed. Connection-oriented * services are invoked each time a connection is made, by creating a process. * This process is passed the connection as file descriptor 0 and is expected * to do a getpeername to find out the source host and port. * * Datagram oriented services are invoked when a datagram * arrives; a process is created and passed a pending message * on file descriptor 0. Datagram servers may either connect * to their peer, freeing up the original socket for inetd * to receive further messages on, or ``take over the socket'', * processing all arriving datagrams and, eventually, timing * out. The first type of server is said to be ``multi-threaded''; * the second type of server ``single-threaded''. * * Inetd uses a configuration file which is read at startup * and, possibly, at some later time in response to a hangup signal. * The configuration file is ``free format'' with fields given in the * order shown below. Continuation lines for an entry must being with * a space or tab. All fields must be present in each entry. * * service name must be in /etc/services or must * name a tcpmux service * socket type stream/dgram/raw/rdm/seqpacket * protocol must be in /etc/protocols * wait/nowait single-threaded/multi-threaded * user user to run daemon as * server program full path name * server program arguments arguments starting with argv[0] * * 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. * * Comment lines are indicated by a `#' in column 1. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef TIME_WITH_SYS_TIME # include # include #else # ifdef HAVE_SYS_TIME_H # include # else # include # endif #endif #ifdef HAVE_SYS_RESOURCE_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #define TOOMANY 40 /* don't start more than TOOMANY */ #define CNT_INTVL 60 /* servers in CNT_INTVL sec. */ #define RETRYTIME (60*10) /* retry after bind or server fail */ #ifndef SIGCHLD #define SIGCHLD SIGCLD #endif #define SIGBLOCK (sigmask(SIGCHLD)|sigmask(SIGHUP)|sigmask(SIGALRM)) extern char *__progname; int debug = 0; int nsock, maxsock; fd_set allsock; int options; int timingout; int toomany = TOOMANY; struct servtab { char *se_service; /* name of service */ int se_socktype; /* type of socket to use */ char *se_proto; /* protocol used */ pid_t se_wait; /* single threaded server */ short se_checked; /* looked at during merge */ char *se_user; /* user name to run as */ struct biltin *se_bi; /* if built-in, description */ char *se_server; /* server program */ char **se_argv; /* program arguments */ size_t se_argc; /* number of arguments */ int se_fd; /* open descriptor */ int se_type; /* type */ sa_family_t se_family; /* address family of the socket */ char se_v4mapped; /* 1 = accept v4mapped connection, 0 = don't */ #ifdef IPV6 struct sockaddr_storage se_ctrladdr;/* bound address */ #else struct sockaddr_in se_ctrladdr;/* bound address */ #endif int se_count; /* number started since se_time */ struct timeval se_time; /* start of se_count */ struct servtab *se_next; } *servtab; #define NORM_TYPE 0 #define MUX_TYPE 1 #define MUXPLUS_TYPE 2 #define ISMUX(sep) (((sep)->se_type == MUX_TYPE) || \ ((sep)->se_type == MUXPLUS_TYPE)) #define ISMUXPLUS(sep) ((sep)->se_type == MUXPLUS_TYPE) void chargen_dg __P((int, struct servtab *)); void chargen_stream __P((int, struct servtab *)); void close_sep __P((struct servtab *)); void config __P((int)); void daytime_dg __P((int, struct servtab *)); void daytime_stream __P((int, struct servtab *)); void discard_dg __P((int, struct servtab *)); void discard_stream __P((int, struct servtab *)); void echo_dg __P((int, struct servtab *)); void echo_stream __P((int, struct servtab *)); void endconfig __P((FILE *)); struct servtab *enter __P((struct servtab *)); void freeconfig __P((struct servtab *)); struct servtab *getconfigent __P((FILE *, const char *, size_t *)); void machtime_dg __P((int, struct servtab *)); void machtime_stream __P((int, struct servtab *)); char *newstr __P((const char *)); char *nextline __P((FILE *)); void nextconfig __P((const char *)); void print_service __P((const char *, const char *, struct servtab *)); void reapchild __P((int)); void retry __P((int)); FILE *setconfig __P((const char *)); void setup __P((struct servtab *)); void tcpmux __P((int s, struct servtab *sep)); void set_proc_title __P ((char *, int)); void initring __P((void)); long machtime __P((void)); void run_service __P ((int ctrl, struct servtab *sep)); struct biltin { const char *bi_service; /* internally provided service name */ int bi_socktype; /* type of socket supported */ short bi_fork; /* 1 if should fork before call */ short bi_wait; /* 1 if should wait for child */ void (*bi_fn) __P((int s, struct servtab *)); /*function which performs it */ } biltins[] = { /* Echo received data */ { "echo", SOCK_STREAM, 1, 0, echo_stream }, { "echo", SOCK_DGRAM, 0, 0, echo_dg }, /* Internet /dev/null */ { "discard", SOCK_STREAM, 1, 0, discard_stream }, { "discard", SOCK_DGRAM, 0, 0, discard_dg }, /* Return 32 bit time since 1900 */ { "time", SOCK_STREAM, 0, 0, machtime_stream }, { "time", SOCK_DGRAM, 0, 0, machtime_dg }, /* Return human-readable time */ { "daytime", SOCK_STREAM, 0, 0, daytime_stream }, { "daytime", SOCK_DGRAM, 0, 0, daytime_dg }, /* Familiar character generator */ { "chargen", SOCK_STREAM, 1, 0, chargen_stream }, { "chargen", SOCK_DGRAM, 0, 0, chargen_dg }, { "tcpmux", SOCK_STREAM, 1, 0, tcpmux }, { NULL, 0, 0, 0, NULL } }; #define NUMINT (sizeof(intab) / sizeof(struct inent)) char **Argv; char *LastArg; char **config_files; /* Signal handling */ #if defined(HAVE_SIGACTION) # define SIGSTATUS sigset_t #else # define SIGSTATUS long #endif void signal_set_handler (int signo, RETSIGTYPE (*handler) ()) { #if defined(HAVE_SIGACTION) struct sigaction sa; memset ((char *)&sa, 0, sizeof(sa)); sigemptyset (&sa.sa_mask); sigaddset (&sa.sa_mask, signo); #ifdef SA_RESTART sa.sa_flags = SA_RESTART; #endif sa.sa_handler = handler; sigaction (signo, &sa, NULL); #elif defined(HAVE_SIGVEC) struct sigvec sv; memset (&sv, 0, sizeof(sv)); sv.sv_mask = SIGBLOCK; sv.sv_handler = handler; sigvec (signo, &sv, NULL); #else /* !HAVE_SIGVEC */ signal (signo, handler); #endif /* HAVE_SIGACTION */ } void signal_block (SIGSTATUS *old_status) { #ifdef HAVE_SIGACTION sigset_t sigs; sigemptyset (&sigs); sigaddset (&sigs, SIGCHLD); sigaddset (&sigs, SIGHUP); sigaddset (&sigs, SIGALRM); sigprocmask (SIG_BLOCK, &sigs, old_status); #else long omask = sigblock (SIGBLOCK); if (old_status) *old_status = omask; #endif } void signal_unblock (SIGSTATUS *status) { #ifdef HAVE_SIGACTION if (status) sigprocmask (SIG_SETMASK, status, 0); else { sigset_t empty; sigemptyset (&empty); sigprocmask (SIG_SETMASK, &empty, 0); } #else sigsetmask (status ? *status : 0); #endif } static void usage (int err) { if (err != 0) { fprintf (stderr, "Usage: %s [OPTION...] [CONF-FILE [CONF-DIR]] ...\n", __progname); fprintf (stderr, "Try `%s --help' for more information.\n", __progname); } else { fprintf (stdout, "Usage: %s [OPTION...] [CONF-FILE [CONF-DIR]] ...\n", __progname); puts ("Internet super-server.\n\n\ -d, --debug Debug mode\n\ -R, --rate NUMBER Maximum invocation rate (per second)\n\ --help Display this help and exit\n\ -V, --version Output version information and exit"); fprintf (stdout, "\nSubmit bug reports to %s.\n", PACKAGE_BUGREPORT); } exit (err); } static const char *short_options = "dR:V"; static struct option long_options[] = { { "debug", no_argument, 0, 'd' }, { "rate", required_argument, 0, 'R' }, { "help", no_argument, 0, '&' }, { "version", no_argument, 0, 'V' }, { 0, 0, 0, 0 } }; int main (int argc, char *argv[], char *envp[]) { int option; struct servtab *sep; int dofork; pid_t pid; #ifndef HAVE___PROGNAME __progname = argv[0]; #endif Argv = argv; if (envp == 0 || *envp == 0) envp = argv; while (*envp) envp++; LastArg = envp[-1] + strlen (envp[-1]); while ((option = getopt_long (argc, argv, short_options, long_options, 0)) != EOF) { switch (option) { case 'd': /* Debug. */ debug = 1; options |= SO_DEBUG; break; case 'R': /* Invocation rate. */ { char *p; int number; number = strtol (optarg, &p, 0); if (number < 1 || *p) syslog (LOG_ERR, "-R %s: bad value for service invocation rate", optarg); else toomany = number; break; } case '&': /* Usage. */ usage (0); /* Not reached. */ case 'V': /* Version. */ printf ("inetd (%s) %s\n", PACKAGE_NAME, PACKAGE_VERSION); exit (0); /* Not reached. */ case '?': default: usage (1); /* Not reached. */ } } if (optind < argc) { int i; config_files = calloc (argc - optind + 1, sizeof (*config_files)); for (i = 0; optind < argc; optind++, i++) { config_files[i] = strdup (argv[optind]); } } else { config_files = calloc (3, sizeof (*config_files)); config_files[0] = newstr (PATH_INETDCONF); config_files[1] = newstr (PATH_INETDDIR); } if (debug == 0) { daemon (0, 0); } openlog ("inetd", LOG_PID | LOG_NOWAIT, LOG_DAEMON); { FILE *fp = fopen (PATH_INETDPID, "w"); if (fp != NULL) { fprintf (fp, "%d\n", getpid ()); (void) fclose (fp); } else syslog (LOG_CRIT, "can't open %s: %s\n", PATH_INETDPID, strerror (errno)); } signal_set_handler (SIGALRM, retry); config (0); signal_set_handler (SIGHUP, config); signal_set_handler (SIGCHLD, reapchild); signal_set_handler (SIGPIPE, SIG_IGN); { /* space for daemons to overwrite environment for ps */ #define DUMMYSIZE 100 char dummy[DUMMYSIZE]; memset(dummy, 'x', DUMMYSIZE - 1); dummy[DUMMYSIZE - 1] = '\0'; setenv("inetd_dummy", dummy, 1); } for (;;) { int n, ctrl; fd_set readable; if (nsock == 0) { signal_block (NULL); while (nsock == 0) sigpause (0L); signal_unblock (NULL); } readable = allsock; if ((n = select (maxsock + 1, &readable, NULL, NULL, NULL)) <= 0) { if (n < 0 && errno != EINTR) syslog (LOG_WARNING, "select: %m"); sleep (1); continue; } for (sep = servtab; n && sep; sep = sep->se_next) if (sep->se_fd != -1 && FD_ISSET (sep->se_fd, &readable)) { n--; if (debug) fprintf (stderr, "someone wants %s\n", sep->se_service); if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) { ctrl = accept (sep->se_fd, NULL, NULL); if (debug) fprintf (stderr, "accept, ctrl %d\n", ctrl); if (ctrl < 0) { if (errno != EINTR) syslog (LOG_WARNING, "accept (for %s): %m", sep->se_service); continue; } } else ctrl = sep->se_fd; signal_block (NULL); pid = 0; dofork = (sep->se_bi == 0 || sep->se_bi->bi_fork); if (dofork) { if (sep->se_count++ == 0) gettimeofday (&sep->se_time, NULL); else if (sep->se_count >= toomany) { struct timeval now; gettimeofday (&now, NULL); 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); close_sep (sep); signal_unblock (NULL); if (!timingout) { timingout = 1; alarm (RETRYTIME); } continue; } } pid = fork (); } if (pid < 0) { syslog (LOG_ERR, "fork: %m"); if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) close (ctrl); signal_unblock (NULL); sleep (1); continue; } if (pid && sep->se_wait) { sep->se_wait = pid; if (sep->se_fd >= 0) { FD_CLR (sep->se_fd, &allsock); nsock--; } } signal_unblock (NULL); if (pid == 0) { if (debug && dofork) setsid (); if (dofork) { int sock; if (debug) fprintf (stderr, "+ Closing from %d\n", maxsock); for (sock = maxsock; sock > 2; sock--) if (sock != ctrl) close (sock); } run_service (ctrl, sep); } if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) close (ctrl); } } } void run_service (int ctrl, struct servtab *sep) { struct passwd *pwd; char buf[50]; if (sep->se_bi) { (*sep->se_bi->bi_fn) (ctrl, sep); } else { if (debug) fprintf (stderr, "%d execl %s\n", getpid (), sep->se_server); 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 (1); } if (pwd->pw_uid) { if (setgid (pwd->pw_gid) < 0) { syslog (LOG_ERR, "%s: can't set gid %d: %m", sep->se_service, pwd->pw_gid); _exit (1); } #ifdef HAVE_INITGROUPS initgroups (pwd->pw_name, pwd->pw_gid); #endif if (setuid (pwd->pw_uid) < 0) { syslog (LOG_ERR, "%s: can't set uid %d: %m", sep->se_service, pwd->pw_uid); _exit (1); } } execv (sep->se_server, sep->se_argv); if (sep->se_socktype != SOCK_STREAM) recv (0, buf, sizeof buf, 0); syslog (LOG_ERR, "cannot execute %s: %m", sep->se_server); _exit (1); } } void reapchild (int signo) { int status; pid_t pid; struct servtab *sep; (void)signo; /* shutup gcc */ for (;;) { #ifdef HAVE_WAIT3 pid = wait3 (&status, WNOHANG, NULL); #else pid = wait (&status); #endif if (pid <= 0) break; if (debug) fprintf (stderr, "%d reaped, status %#x\n", pid, status); for (sep = servtab; sep; sep = sep->se_next) if (sep->se_wait == pid) { if (status) syslog (LOG_WARNING, "%s: exit status 0x%x", sep->se_server, status); if (debug) fprintf (stderr, "restored %s, fd %d\n", sep->se_service, sep->se_fd); FD_SET (sep->se_fd, &allsock); nsock++; sep->se_wait = 1; } } } void config (int signo) { int i; struct stat stats; struct servtab *sep; (void)signo; /* Shutup gcc. */ for (sep = servtab; sep; sep = sep->se_next) sep->se_checked = 0; for (i = 0; config_files[i]; i++) { struct stat statbuf; if (stat (config_files[i], &statbuf) == 0) { if (S_ISDIR (statbuf.st_mode)) { DIR *dirp = opendir (config_files[i]); if (dirp) { struct dirent *dp; while ((dp = readdir (dirp)) != NULL) { char *path = calloc (strlen (config_files[i]) + strlen (dp->d_name) + 2, 1); if (path) { sprintf (path,"%s/%s", config_files[i], dp->d_name); if (stat (path, &stats) == 0 && S_ISREG(stats.st_mode)) { nextconfig (path); } free (path); } } closedir (dirp); } } else if (S_ISREG (statbuf.st_mode)) { nextconfig (config_files[i]); } } else { if (signo == 0) fprintf (stderr, "inetd: %s, %s\n", config_files[i], strerror(errno)); else syslog (LOG_ERR, "%s: %m", config_files[i]); } } } void nextconfig (const char *file) { #ifndef IPV6 struct servent *sp; #endif struct servtab *sep, *cp, **sepp; struct passwd *pwd; FILE * fconfig; SIGSTATUS sigstatus; size_t line = 0; fconfig = setconfig (file); if (!fconfig) { syslog (LOG_ERR, "%s: %m", file); return; } while ((cp = getconfigent (fconfig, file, &line))) { if ((pwd = getpwnam (cp->se_user)) == NULL) { syslog(LOG_ERR, "%s/%s: No such user '%s', service ignored", cp->se_service, cp->se_proto, cp->se_user); continue; } /* Checking/Removing duplicates */ for (sep = servtab; sep; sep = sep->se_next) if (strcmp (sep->se_service, cp->se_service) == 0 && strcmp (sep->se_proto, cp->se_proto) == 0 && ISMUX(sep) == ISMUX (cp)) break; if (sep != 0) { signal_block (&sigstatus); /* * sep->se_wait may be holding the pid of a daemon * that we're waiting for. If so, don't overwrite * it unless the config file explicitly says don't * wait. */ if (cp->se_bi == 0 && (sep->se_wait == 1 || cp->se_wait == 0)) sep->se_wait = cp->se_wait; #define SWAP(a, b) { char *c = a; a = b; b = c; } if (cp->se_user) SWAP(sep->se_user, cp->se_user); if (cp->se_server) SWAP(sep->se_server, cp->se_server); argcv_free(sep->se_argc, sep->se_argv); sep->se_argc = cp->se_argc; sep->se_argv = cp->se_argv; cp->se_argc = 0; cp->se_argv = NULL; signal_unblock (&sigstatus); freeconfig (cp); if (debug) print_service (file, "REDO", sep); } else { sep = enter (cp); if (debug) print_service (file, "ADD ", sep); } sep->se_checked = 1; if (ISMUX (sep)) { sep->se_fd = -1; continue; } #ifndef IPV6 /* This code is moved to setup() for IPV6. */ 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; } if (sp->s_port != sep->se_ctrladdr.sin_port) { sep->se_ctrladdr.sin_family = AF_INET; sep->se_ctrladdr.sin_port = sp->s_port; if (sep->se_fd >= 0) close_sep (sep); } #endif if (sep->se_fd == -1) setup (sep); } endconfig (fconfig); /* * Purge anything not looked at above. */ signal_block (&sigstatus); 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 (file, "FREE", sep); freeconfig (sep); free ((char *)sep); } signal_unblock (&sigstatus); } void retry (int signo) { struct servtab *sep; (void)signo; /* shutup gcc */ timingout = 0; for (sep = servtab; sep; sep = sep->se_next) if (sep->se_fd == -1 && !ISMUX (sep)) setup (sep); } void setup (struct servtab *sep) { int err; const int on = 1; #ifdef IPV6 const int off = 0; struct addrinfo *result, hints; struct protoent *proto; tryagain: #endif sep->se_fd = socket (sep->se_family, sep->se_socktype, 0); if (sep->se_fd < 0) { #ifdef IPV6 /* If we don't support creating AF_INET6 sockets, create AF_INET sockets. */ if (errno == EAFNOSUPPORT && sep->se_family == AF_INET6 && sep->se_v4mapped) { /* Fall back to IPv6 silently. */ sep->se_family = AF_INET; goto tryagain; } #endif if (debug) fprintf (stderr, "socket failed on %s/%s: %s\n", sep->se_service, sep->se_proto, strerror (errno)); syslog(LOG_ERR, "%s/%s: socket: %m", sep->se_service, sep->se_proto); return; } #ifdef IPV6 /* Make sure that tcp6 etc also work. */ if (strncmp (sep->se_proto, "tcp", 3) == 0) proto = getprotobyname ("tcp"); else if (strncmp (sep->se_proto, "udp", 3) == 0) proto = getprotobyname ("udp"); else proto = getprotobyname (sep->se_proto); if (!proto) { syslog (LOG_ERR, "%s: Unknown protocol", sep->se_proto); close (sep->se_fd); sep->se_fd = -1; return; } memset (&hints, 0, sizeof (hints)); hints.ai_flags = AI_PASSIVE; hints.ai_family = sep->se_family; hints.ai_socktype = sep->se_socktype; hints.ai_protocol = proto->p_proto; err = getaddrinfo (NULL, sep->se_service, &hints, &result); if (err) { const char *errmsg; if (err == EAI_FAMILY && sep->se_family == AF_INET6 && sep->se_v4mapped) { /* Fall back to IPv6 silently. */ sep->se_family = AF_INET; close (sep->se_fd); goto tryagain; } if (err == EAI_SYSTEM) errmsg = strerror (errno); else errmsg = gai_strerror (err); syslog (LOG_ERR, "%s/%s: getaddrinfo: %s", sep->se_service, sep->se_proto, errmsg); close (sep->se_fd); sep->se_fd = -1; return; } memcpy (&sep->se_ctrladdr, result->ai_addr, result->ai_addrlen); freeaddrinfo (result); if (sep->se_family == AF_INET6) { if (sep->se_v4mapped) err = setsockopt (sep->se_fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&off, sizeof(off)); else err = setsockopt (sep->se_fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&on, sizeof(on)); if (err < 0) syslog (LOG_ERR, "setsockopt (IPV6_V6ONLY): %m"); } #endif if (strncmp (sep->se_proto, "tcp", 3) == 0 && (options & SO_DEBUG)) { err = setsockopt(sep->se_fd, SOL_SOCKET, SO_DEBUG, (char *)&on, sizeof (on)); if (err < 0) syslog (LOG_ERR, "setsockopt (SO_DEBUG): %m"); } err = setsockopt(sep->se_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof (on)); if (err < 0) syslog (LOG_ERR, "setsockopt (SO_REUSEADDR): %m"); err = bind (sep->se_fd, (struct sockaddr *)&sep->se_ctrladdr, sizeof (sep->se_ctrladdr)); if (err < 0) { #ifdef IPV6 /* If we can't bind with AF_INET6 try again with AF_INET. */ if ((errno == EADDRNOTAVAIL || errno == EAFNOSUPPORT) && sep->se_family == AF_INET6 && sep->se_v4mapped) { /* Fall back to IPv6 silently. */ sep->se_family = AF_INET; close (sep->se_fd); goto tryagain; } #endif if (debug) fprintf (stderr, "bind failed on %s/%s: %s\n", sep->se_service, sep->se_proto, strerror (errno)); syslog(LOG_ERR, "%s/%s: bind: %m", sep->se_service, sep->se_proto); close (sep->se_fd); sep->se_fd = -1; if (!timingout) { timingout = 1; alarm (RETRYTIME); } return; } if (sep->se_socktype == SOCK_STREAM) listen (sep->se_fd, 10); FD_SET (sep->se_fd, &allsock); nsock++; if (sep->se_fd > maxsock) maxsock = sep->se_fd; if (debug) { fprintf(stderr, "registered %s on %d\n", sep->se_server, sep->se_fd); } } /* * Finish with a service and its socket. */ void close_sep (struct servtab *sep) { if (sep->se_fd >= 0) { nsock--; FD_CLR (sep->se_fd, &allsock); close (sep->se_fd); sep->se_fd = -1; } sep->se_count = 0; /* * Don't keep the pid of this running deamon: when reapchild() * reaps this pid, it would erroneously increment nsock. */ if (sep->se_wait > 1) sep->se_wait = 1; } struct servtab * enter (struct servtab *cp) { struct servtab *sep; SIGSTATUS sigstatus; sep = (struct servtab *)malloc (sizeof (*sep)); if (sep == NULL) { syslog (LOG_ERR, "Out of memory."); exit (-1); } *sep = *cp; sep->se_fd = -1; signal_block (&sigstatus); sep->se_next = servtab; servtab = sep; signal_unblock (&sigstatus); return sep; } struct servtab serv; #ifdef LINE_MAX char line[LINE_MAX]; #else char line[2048]; #endif FILE * setconfig (const char *file) { return fopen (file, "r"); } void endconfig (FILE *fconfig) { if (fconfig) { fclose (fconfig); } } #define INETD_SERVICE 0 /* service name */ #define INETD_SOCKET 1 /* socket type */ #define INETD_PROTOCOL 2 /* protocol */ #define INETD_WAIT 3 /* wait/nowait */ #define INETD_USER 4 /* user */ #define INETD_SERVER_PATH 5 /* server program path */ #define INETD_SERVER_ARGS 6 /* server program arguments */ #define INETD_FIELDS_MIN 6 /* Minimum number of fields in entry */ struct servtab * getconfigent (FILE *fconfig, const char *file, size_t *line) { struct servtab *sep = &serv; size_t argc = 0, i; char **argv = NULL; char *cp; static char TCPMUX_TOKEN[] = "tcpmux/"; #define MUX_LEN (sizeof(TCPMUX_TOKEN)-1) memset ((caddr_t)sep, 0, sizeof *sep); while (1) { argcv_free (argc, argv); freeconfig (sep); memset ((caddr_t)sep, 0, sizeof *sep); while ((cp = nextline (fconfig)) && (*cp == '#' || *cp == '\0')) ++*line; ++*line; if (cp == NULL) return NULL; if (argcv_get (cp, "", &argc, &argv)) continue; if (argc < INETD_FIELDS_MIN) { syslog (LOG_ERR, "%s:%lu: not enough fields", file, (unsigned long) *line); continue; } if (strncmp (argv[INETD_SERVICE], TCPMUX_TOKEN, MUX_LEN) == 0) { char *c = argv[INETD_SERVICE] + 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 (argv[INETD_SERVICE]); sep->se_type = NORM_TYPE; } if (strcmp (argv[INETD_SOCKET], "stream") == 0) sep->se_socktype = SOCK_STREAM; else if (strcmp (argv[INETD_SOCKET], "dgram") == 0) sep->se_socktype = SOCK_DGRAM; else if (strcmp (argv[INETD_SOCKET], "rdm") == 0) sep->se_socktype = SOCK_RDM; else if (strcmp (argv[INETD_SOCKET], "seqpacket") == 0) sep->se_socktype = SOCK_SEQPACKET; else if (strcmp (argv[INETD_SOCKET], "raw") == 0) sep->se_socktype = SOCK_RAW; else { syslog (LOG_WARNING, "%s:%lu: bad socket type", file, (unsigned long) *line); sep->se_socktype = -1; } sep->se_proto = newstr (argv[INETD_PROTOCOL]); #ifdef IPV6 /* We default to IPv6, in setup() we'll fall back to IPv4 if it doesn't work. */ sep->se_family = AF_INET6; sep->se_v4mapped = 1; if ((strncmp (sep->se_proto, "tcp", 3) == 0) || (strncmp (sep->se_proto, "udp", 3) == 0)) { if (sep->se_proto[3] == '6') { sep->se_family = AF_INET6; sep->se_v4mapped = 0; } else if (sep->se_proto[3] == '4') { sep->se_family = AF_INET; } } #else if ((strncmp (sep->se_proto, "tcp6", 4) == 0) || (strncmp (sep->se_proto, "udp6", 4) == 0)) { syslog (LOG_ERR, "%s:%lu: %s: IPv6 support isn't eneabled", file, (unsigned long) *line, sep->se_proto); continue; } sep->se_family = AF_INET; #endif if (strcmp (argv[INETD_WAIT], "wait") == 0) sep->se_wait = 1; else if (strcmp (argv[INETD_WAIT], "nowait") == 0) sep->se_wait = 0; else { syslog (LOG_WARNING, "%s:%lu: bad wait type", file, (unsigned long) *line); } if (ISMUX (sep)) { /* * Silently enforce "nowait" for TCPMUX services since * they don't have an assigned port to listen on. */ sep->se_wait = 0; if (strncmp (sep->se_proto, "tcp", 3)) { syslog (LOG_ERR, "%s:%lu: bad protocol for tcpmux service %s", file, (unsigned long) *line, sep->se_service); continue; } if (sep->se_socktype != SOCK_STREAM) { syslog (LOG_ERR, "%s:%lu: bad socket type for tcpmux service %s", file, (unsigned long) *line, sep->se_service); continue; } } sep->se_user = newstr (argv[INETD_USER]); sep->se_server = newstr (argv[INETD_SERVER_PATH]); if (strcmp (sep->se_server, "internal") == 0) { struct biltin *bi; for (bi = biltins; bi->bi_service; bi++) if (bi->bi_socktype == sep->se_socktype && strcmp (bi->bi_service, sep->se_service) == 0) break; if (bi->bi_service == 0) { syslog (LOG_ERR, "%s:%lu: internal service %s unknown", file, (unsigned long) *line, sep->se_service); continue; } sep->se_bi = bi; sep->se_wait = bi->bi_wait; } else sep->se_bi = NULL; sep->se_argc = argc - INETD_FIELDS_MIN + 1; sep->se_argv = calloc (sep->se_argc + 1, sizeof sep->se_argv[0]); if (!sep->se_argv) { syslog (LOG_ERR, "%s:%lu: Out of memory.", file, (unsigned long) *line); exit (-1); } for (i = 0; i < sep->se_argc; i++) { sep->se_argv[i] = argv[INETD_SERVER_ARGS + i]; argv[INETD_SERVER_ARGS + i] = 0; } sep->se_argv[i] = NULL; break; } argcv_free (argc, argv); return sep; } void freeconfig (struct servtab *cp) { 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_server) free (cp->se_server); argcv_free(cp->se_argc, cp->se_argv); } char * nextline (FILE *fd) { char *cp; if (fgets (line, sizeof line, fd) == NULL) return NULL; cp = strchr (line, '\n'); if (cp) *cp = '\0'; return line; } char * newstr (const char *cp) { char *s; if ((s = strdup (cp ? cp : ""))) return s; syslog (LOG_ERR, "strdup: %m"); exit (-1); } void set_proc_title (char *a, int s) { int size; char *cp; #ifdef IPV6 struct sockaddr_storage saddr; #else struct sockaddr_in saddr; #endif char buf[80]; cp = Argv[0]; size = sizeof saddr; if (getpeername (s, (struct sockaddr *)&saddr, &size) == 0) { #ifdef IPV6 int err; char buf2[80]; err = getnameinfo ((struct sockaddr *) &saddr, sizeof (saddr), buf2, sizeof (buf2), NULL, 0, NI_NUMERICHOST); if (!err) snprintf (buf, sizeof buf, "-%s [%s]", a, buf2); else snprintf (buf, sizeof buf, "-%s", a); #else snprintf (buf, sizeof buf, "-%s [%s]", a, inet_ntoa (saddr.sin_addr)); #endif } else snprintf (buf, sizeof buf, "-%s", a); strncpy (cp, buf, LastArg - cp); cp += strlen (cp); while (cp < LastArg) *cp++ = ' '; } /* * Internet services provided internally by inetd: */ #define BUFSIZE 8192 /* ARGSUSED */ /* Echo service -- echo data back */ void echo_stream (int s, struct servtab *sep) { char buffer[BUFSIZE]; int i; set_proc_title (sep->se_service, s); while ((i = read (s, buffer, sizeof buffer)) > 0 && write (s, buffer, i) > 0) ; exit (0); } /* ARGSUSED */ /* Echo service -- echo data back */ void echo_dg (int s, struct servtab *sep) { char buffer[BUFSIZE]; int i, size; #ifdef IPV6 struct sockaddr_storage sa; #else struct sockaddr sa; #endif (void)sep; size = sizeof sa; i = recvfrom (s, buffer, sizeof buffer, 0, (struct sockaddr *)&sa, &size); if (i < 0) return; sendto (s, buffer, i, 0, (struct sockaddr *) &sa, sizeof sa); } /* ARGSUSED */ /* Discard service -- ignore data */ void discard_stream (int s, struct servtab *sep) { int ret; char buffer[BUFSIZE]; set_proc_title (sep->se_service, s); while (1) { while ((ret = read (s, buffer, sizeof buffer)) > 0) ; if (ret == 0 || errno != EINTR) break; } exit (0); } /* ARGSUSED */ void /* Discard service -- ignore data */ discard_dg (int s, struct servtab *sep) { char buffer[BUFSIZE]; (void)sep; /* shutup gcc */ read (s, buffer, sizeof buffer); } #include #define LINESIZ 72 char ring[128]; char *endring; void initring (void) { int i; endring = ring; for (i = 0; i <= 128; ++i) if (isprint (i)) *endring++ = i; } /* ARGSUSED */ /* Character generator */ void chargen_stream (int s, struct servtab *sep) { int len; char *rs, text[LINESIZ+2]; set_proc_title (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); } /* ARGSUSED */ /* Character generator */ void chargen_dg (int s, struct servtab *sep) { #ifdef IPV6 struct sockaddr_storage sa; #else struct sockaddr sa; #endif static char *rs; int len, size; char text[LINESIZ+2]; (void)sep; /* shutup gcc */ if (endring == 0) { initring (); rs = ring; } size = sizeof sa; if (recvfrom (s, text, sizeof text, 0, (struct sockaddr *)&sa, &size) < 0) 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'; sendto (s, text, sizeof text, 0, (struct sockaddr *)&sa, sizeof sa); } /* * 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. */ long machtime (void) { struct timeval tv; if (gettimeofday (&tv, NULL) < 0) { if (debug) fprintf (stderr, "Unable to get time of day\n"); return 0L; } #define OFFSET ((u_long)25567 * 24*60*60) return (htonl ((long)(tv.tv_sec + OFFSET))); #undef OFFSET } /* ARGSUSED */ void machtime_stream (int s, struct servtab *sep) { long result; (void)sep; /* shutup gcc */ result = machtime (); write (s, (char *) &result, sizeof result); } /* ARGSUSED */ void machtime_dg (int s, struct servtab *sep) { long result; #ifdef IPV6 struct sockaddr_storage sa; #else struct sockaddr sa; #endif int size; (void)sep; /* shutup gcc */ size = sizeof sa; if (recvfrom (s, (char *)&result, sizeof result, 0, (struct sockaddr *)&sa, &size) < 0) return; result = machtime (); sendto (s, (char *) &result, sizeof result, 0, (struct sockaddr *)&sa, sizeof sa); } /* ARGSUSED */ void /* Return human-readable time of day */ daytime_stream (int s, struct servtab *sep) { char buffer[256]; time_t lclock; (void)sep; /*shutup gcc*/ lclock = time ((time_t *) 0); sprintf (buffer, "%.24s\r\n", ctime(&lclock)); write (s, buffer, strlen(buffer)); } /* ARGSUSED */ /* Return human-readable time of day */ void daytime_dg(int s, struct servtab *sep) { char buffer[256]; time_t lclock; #ifdef IPV6 struct sockaddr_storage sa; #else struct sockaddr sa; #endif int size; (void)sep; /* shutup gcc */ lclock = time ((time_t *) 0); size = sizeof sa; if (recvfrom (s, buffer, sizeof buffer, 0, (struct sockaddr *)&sa, &size) < 0) return; sprintf (buffer, "%.24s\r\n", ctime (&lclock)); sendto (s, buffer, strlen(buffer), 0, (struct sockaddr *)&sa, sizeof sa); } /* * print_service: * Dump relevant information to stderr */ void print_service (const char *file, const char *action, struct servtab *sep) { fprintf (stderr, "%s:%s: %s proto=%s, wait=%d, user=%s builtin=%lx server=%s\n", file, action, sep->se_service, sep->se_proto, sep->se_wait, sep->se_user, (long)sep->se_bi, sep->se_server); } /* * Based on TCPMUX.C by Mark K. Lottor November 1988 * sri-nic::ps:tcpmux.c */ /* # of characters upto \r,\n or \0 */ static int fd_getline (int fd, char *buf, int len) { int count = 0, n; do { n = read (fd, buf, len-count); 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; } #define MAX_SERV_LEN (256+2) /* 2 bytes for \r\n */ #define strwrite(fd, buf) write(fd, buf, sizeof(buf)-1) void tcpmux(int s, struct servtab *sep) { char service[MAX_SERV_LEN+1]; int len; /* Get requested service name */ if ((len = fd_getline (s, service, MAX_SERV_LEN)) < 0) { strwrite (s, "-Error reading service name\r\n"); _exit (1); } service[len] = '\0'; if (debug) fprintf (stderr, "tcpmux: someone wants %s\n", 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; write (s, sep->se_service, strlen (sep->se_service)); strwrite (s, "\r\n"); } _exit (1); } /* 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"); } run_service (s, sep); return; } } strwrite (s, "-Service not available\r\n"); exit (1); }