/* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, * advertising materials, and other materials related to such * distribution and use acknowledge that the software was developed * by the University of California, Berkeley. The name of the * University may not be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #ifndef lint char copyright[] = "@(#) Copyright (c) 1983 Regents of the University of California.\n\ All rights reserved.\n"; #endif /* not lint */ #ifndef lint static char sccsid[] = "@(#)inetd.c 5.14 (Berkeley) 1/23/89"; #endif /* not lint */ /* * 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 must * name a tcpmux or rpc service * socket type stream/dgram/raw/rdm/seqpacket * protocol must be in /etc/protocols unless * rpc service * wait/nowait single-threaded/multi-threaded * user user to run daemon as * server program full path name * server program arguments maximum of MAXARGS (10+1) * * 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. * * Services based on RPC also constitute a special case. These services * need not be listed in /etc/services, because their port numbers are * dynamically bound by the portmapper. The portmapper protocol specifies * a service with the triple (program number, version min, version max). * The format for RPC service entries begins as follows: * * service-name/version-set service-name must be in /etc/rpc * version-set has the form 1-3,7,10 * socket type dgram or stream * rpc/protocol protocol is udp or tcp * etc. as above * * Comment lines are indicated by a `#' in column 1. * * Inetd normally logs an error to the syslog if a server program * is missing. Placing a `?' at the beginning of the server program * path (for example, ?/usr/etc/podd) will suppress the error message. * * The following options are supported when inetd runs on a kernel that * supports IP Security Options, and silently ignored otherwise: * * The string "/lc" (label-cognizant) after "wait" or "nowait" causes * inetd to invoke the server at the default process label and to pass a * wildcard socket to the server. Otherwise, the server is invoked with a * socket and process label corresponding with that of the client. * * The string "/rcv" after the user name causes inetd to invoke the server * with the uid of the client. This is useful for servers that * communicate with Xsgi. */ #define _BSD_SIGNALS #include #include #include #include #include #include #include #include #include /* includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TOOMANY 2000 /* don't start more than TOOMANY */ #define LISTEN_QLEN 255 /* Maximum length of list queue */ #define CNT_INTVL 60 /* servers in CNT_INTVL sec. */ #define RETRYTIME (60*10) /* retry after bind or server fail */ #define SIGBLOCK (sigmask(SIGCHLD)|sigmask(SIGHUP)|sigmask(SIGALRM)| \ sigmask(SIGPIPE)) #define TCPMUX_TIMEOUT 120 static int default_qlen = LISTEN_QLEN; static int debug = 0; static int nsock, maxsock; static fd_set allsock; static int options; static int timingout; static int toomany = TOOMANY; struct rpcvers { u_long rv_low; /* low version number */ u_long rv_high; /* same as low or high if range */ struct rpcvers *rv_more; /* more numbers or ranges */ }; static struct servtab { char *se_service; /* name of service */ int se_socktype; /* type of socket to use */ char *se_proto; /* protocol used */ short se_wait; /* single threaded server */ short se_checked; /* looked at during merge */ char *se_user; /* user name to run as */ struct biltin *se_bi; /* if built-in, description */ char *se_server; /* server program */ pid_t se_pid; /* daemon process id */ #define MAXARGV 11 char *se_argv[MAXARGV+1]; /* program arguments */ int se_fd; /* open descriptor */ int se_type; /* discriminant for se_un */ union { struct sockaddr_in ctrladdr; struct { u_long prog; /* RPC program number */ u_short prot; /* IP protocol number */ u_short port; /* port in net byte order */ struct rpcvers vers; } rpcinfo; } se_un; int se_count; /* number started since se_time */ struct timeval se_time; /* start of se_count */ struct servtab *se_aliases; /* head of alias list */ struct servtab *se_nextalias; /* alias forward linkage */ struct servtab *se_next; int se_flags; /* extended service attributes */ cap_t se_cap; } *servtab; #define se_ctrladdr se_un.ctrladdr #define se_rpcinfo se_un.rpcinfo #define se_prog se_rpcinfo.prog #define se_prot se_rpcinfo.prot #define se_port se_rpcinfo.port #define se_vers se_rpcinfo.vers #define NORM_TYPE 0 #define RPC_TYPE 1 #define MUX_TYPE 2 #define MUXPLUS_TYPE 3 #define ISRPC(sep) ((sep)->se_type == RPC_TYPE) #define ISMUX(sep) (((sep)->se_type == MUX_TYPE) || \ ((sep)->se_type == MUXPLUS_TYPE)) #define ISMUXPLUS(sep) ((sep)->se_type == MUXPLUS_TYPE) /* se_flags: */ #define SE_MISSING_OK 0x00000001 /* Don't log error if missing */ #define IS_MISSING_OK(sep) ((sep)->se_flags & SE_MISSING_OK) #define SE_LBLCOG 0x00000002 /* Leave socket label wildcard */ #define IS_LBLCOG(sep) ((sep)->se_flags & SE_LBLCOG) #define SE_RCVUID 0x00000008 /* Use received (client) uid */ #define IS_RCVUID(sep) ((sep)->se_flags & SE_RCVUID) #define XE_NONE 0x000 #define XE_CAP 0x001 /* feature types */ static struct { char *name; int type; } xe_names[] = { {"cap", XE_CAP}, {(char *) NULL, XE_NONE}, }; typedef struct xe_feature { int xe_type; char *xe_value; } xe_feature; struct xe_spec { char *xe_service; /* service name */ xe_feature *xe_fset[MAXARGV+1]; /* feature type/value pairs */ }; struct conf_file { FILE *fptr; char *name; int line_no; } conf, xconf; struct cl_opt { int specified; char *val; }; #define SETCLOPT(x,v) { (x).specified = 1; (x).val =(v); } #define INITCLOPT(x) { memset(&(x), 0, sizeof(struct cl_opt)); } #define CLOPT(x) (x).specified #define CLOPT_ARG(x) (x).val static void print_service(char *, const struct servtab *); static void setup(struct servtab *); static void rpcsetup(struct servtab *); static void dupaliasfd(struct servtab *, int); static int eqrpcvers(const struct rpcvers *, const struct rpcvers *); static void close_sep(struct servtab *); static void pmap_reset_aliases(struct servtab *, int); static int getrpcsock(struct servtab *); static int getrpcvers(char *, struct rpcvers *); static void freerpcvers(struct servtab *); static void setsrvcap(const struct servtab *); /* parse CONFIG */ static void config(int); static int setconfig(void); static void endconfig(void); static void freeconfig(struct servtab *); static struct servtab *getconfigent(void); /* parse XCONFIG */ static void xconfig(struct servtab *); /* signal handlers */ static void reapchild(int); static void sigpipe(int); static void sigterm(int); static void sig_ign(int); static void retry(int); static struct servtab *tcpmux(int); static struct servtab *enter(struct servtab *); /* builtin service functions */ static void echo_stream(int, struct servtab *); static void echo_dg(int, struct servtab *); static void discard_stream(int, struct servtab *); static void discard_dg(int, struct servtab *); static void machtime_stream(int, struct servtab *); static void machtime_dg(int, struct servtab *); static void daytime_stream(int, struct servtab *); static void daytime_dg(int, struct servtab *); static void chargen_stream(int, struct servtab *); static void chargen_dg(int, struct servtab *); /* internal utility functions */ static int check_loop(struct sockaddr_in *); static int police_fork_rate(struct servtab *); static void _w_syslog(int, char *, ...); static void init_cfile(struct conf_file *, char *); typedef void (*bi_fn_t)(int, struct servtab *); static struct biltin { 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 */ bi_fn_t bi_fn; /* 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, (bi_fn_t) tcpmux, 0 }; static int havemac = 0; /* kernel supports Mandatory Access Control */ static int havecipso = 0; /* kernel supports IP secopts & socket ACL */ static int havecap = 0; /* kernel supports Capabilities */ static int dryrun = 0; /* are we in dry run mode */ static int dr_errors = 0; /* number of error msgs counted in dry-run */ static char *progname = 0; /* name of this program */ static void check_clearance(const struct clearance *, const struct servtab *); static char *CONFIG = "/etc/inetd.conf"; static char *XCONFIG = "/etc/inetd.conf.sec"; extern char **environ; static char timez[256] = { "TZ=" }; static char *env[] = { timez, NULL, }; #define tmalloc(type) ((type *) emalloc(sizeof(type))) static void * emalloc(size_t size) { void *new; new = malloc(size); if (new == 0) { _w_syslog(LOG_ERR, "Out of memory."); exit(1); } return new; } static char * mystrdup(const char *cp) { if (cp == NULL) cp = ""; return strcpy((char *) emalloc(strlen(cp) + 1), cp); } /* * Check that we aren't looping. If we are looping, logs this fact, * terminates the service and schedules a retry alarm. */ int police_fork_rate(register struct servtab *sep) { if (sep->se_count++ == 0) (void) gettimeofday(&sep->se_time, NULL); else if (sep->se_count >= toomany) { struct timeval now; (void) gettimeofday(&now, NULL); if (now.tv_sec - sep->se_time.tv_sec > CNT_INTVL) { sep->se_time = now; sep->se_count = 1; } else { _w_syslog(LOG_CRIT, "%s/%s server failing (looping), service terminated. " "Use -R to increase maximum rate if this is undesired", sep->se_service, sep->se_proto); close_sep(sep); sigsetmask(0L); if (!timingout) { timingout = 1; alarm(RETRYTIME); } return -1; } } return 0; } /* * _w_syslog is a wrapper for the syslog function. In normal operation * it makes a syslog entry and in dry-run mode it reports an error to stderr. */ void _w_syslog(int pri, char *str, ...) { va_list ap; va_start(ap, str); if(dryrun) { fprintf(stderr,"%s: ", progname); vfprintf(stderr, str, ap); fputc('\n', stderr); dr_errors++; } else { vsyslog(pri, str, ap); } va_end(ap); } static void init_cfile(struct conf_file *cf, char *name) { memset(cf, 0, sizeof(struct conf_file)); cf->name = name; } int main(int argc, char *argv[]) { register struct servtab *sep, *next; register int tmp; struct sigvec sv; int opt, in_child; pid_t pid; char buf[50]; char *tp; mac_t original_label; struct cl_opt opt_l, opt_R, opt_ERR; /* * First record the options; how we handle the initialisation * depends if we are running in dry-run mode or not. */ progname = *argv; opterr = 0; INITCLOPT(opt_l); INITCLOPT(opt_R); INITCLOPT(opt_ERR); while ((opt = getopt(argc, argv, "dfl:sR:X:")) != EOF) switch (opt) { case 'd': debug = 1; #if 0 options |= SO_DEBUG; #endif break; case 'f': /* * This option is no longer supported (it never * worked in 6.5 anyway). */ _w_syslog(LOG_WARNING, "usage: -f option no longer supported; ignoring."); break; case 'l': SETCLOPT(opt_l, optarg); break; case 's': dryrun = 1; break; case 'R': /* invocation rate */ SETCLOPT(opt_R, optarg); break; case 'X': /* eXtended configuration */ XCONFIG = optarg; break; default: SETCLOPT(opt_ERR, 0); break; } if(!dryrun) { openlog("inetd", LOG_PID | LOG_NOWAIT, LOG_DAEMON); if (getuid()) { fprintf(stderr, "inetd: must be started by super-user\n"); _w_syslog(LOG_ERR, "usage: inetd must be started by super-user."); exit(1); } havemac = (sysconf(_SC_MAC) > 0); havecipso = (sysconf(_SC_IP_SECOPTS) > 0); havecap = (sysconf(_SC_CAP) > 0); if (havemac) { original_label = mac_get_proc (); if (original_label == NULL) { _w_syslog(LOG_ERR, "%s: mac_get_proc failed: %m"); _exit(1); } } } /* * Now process the options. Do these after having read all * the options so we know where to send any errors. */ if(CLOPT(opt_ERR)) { _w_syslog(LOG_ERR, "usage: inetd [-ds] [-l qlen] [-R rate] " "[-X xconf-file] [conf-file]"); exit(1); } if(CLOPT(opt_l)) { tmp = atoi(CLOPT_ARG(opt_l)); if (tmp < 1) { _w_syslog(LOG_ERR, "-l %d: bad value for listen queue length", tmp); } else { default_qlen = tmp; } } if(CLOPT(opt_R)) { tmp = atoi(CLOPT_ARG(opt_R)); if (tmp < 1) _w_syslog(LOG_ERR, "-R %d: bad value for service invocation rate", tmp); else toomany = tmp; } argc -= optind; argv += optind; if (argc > 0) CONFIG = argv[0]; init_cfile(&conf, CONFIG); init_cfile(&xconf, XCONFIG); /* * In dry-run mode, just read and check the config file, then exit */ if(dryrun) { fprintf(stderr,"Checking \"%s\"\n", conf.name); config(0); fprintf(stderr,"%d error%s found\n", dr_errors, dr_errors != 1 ? "s" : ""); exit(dr_errors > 0 ? 1 : 0); } /* disable debugging if invoked from startup script */ if (debug && !isatty(0)) debug = 0; if (debug == 0) { if (_daemonize(0, -1, -1, -1) < 0) exit(1); /* _daemonize does a closelog() */ openlog("inetd", LOG_PID | LOG_NOWAIT, LOG_DAEMON); } if ((tp = getenv("TZ")) != NULL) strncat(timez,tp,sizeof(timez)-3); else env[0] = NULL; environ = env; memset((void *) &sv, '\0', sizeof (sv)); sv.sv_mask = SIGBLOCK; sv.sv_handler = retry; sigvec(SIGALRM, &sv, (struct sigvec *)0); config(0); sv.sv_handler = config; sigvec(SIGHUP, &sv, (struct sigvec *)0); sv.sv_handler = reapchild; sigvec(SIGCHLD, &sv, (struct sigvec *)0); sv.sv_handler = sigpipe; sigvec(SIGPIPE, &sv, (struct sigvec *)0); sv.sv_handler = sigterm; sigvec(SIGTERM, &sv, (struct sigvec *)0); /* * Ignore any other signals that may kill the process */ sv.sv_handler = sig_ign; sigvec(SIGINT, &sv, (struct sigvec *)0); sigvec(SIGUSR1, &sv, (struct sigvec *)0); sigvec(SIGUSR2, &sv, (struct sigvec *)0); sigvec(SIGPOLL, &sv, (struct sigvec *)0); sigvec(SIGIO, &sv, (struct sigvec *)0); sigvec(SIGVTALRM, &sv, (struct sigvec *)0); sigvec(SIGPROF, &sv, (struct sigvec *)0); sigvec(SIGRTMIN, &sv, (struct sigvec *)0); sigvec(SIGRTMAX, &sv, (struct sigvec *)0); for (;;) { int n, ctrl; fd_set readable; if (nsock == 0) { (void) sigblock(SIGBLOCK); while (nsock == 0) sigpause(0L); (void) sigsetmask(0L); } readable = allsock; if ((n = select(maxsock + 1, &readable, (fd_set *)0, (fd_set *)0, (struct timeval *)0)) <= 0) { if (n < 0 && errno != EINTR){ _w_syslog(LOG_WARNING, "select: %m\n"); sginap(HZ); } else { /* Came here due to a signal while waiting on select * Most often, it's due to SIGCHLD - Child process * started by inetd created another one to do the * work and dies. Allows inetd to start next instance * of a "wait" type server. If inetd sleeps for * a second in each such case, we end up allowing * these servers to start at max rate of 1/sec. * So sginap(1) should improve this rate.. But * what about child death due to some server being * removed ?? -> Let '-R' handle it. */ sginap(1); } continue; } for (sep = servtab; n && sep; sep = next) { struct servtab *alias; mac_t socket_label = NULL; uid_t socket_uid = -2; /* make default uid invalid */ cap_t ocap; int s; next = sep->se_next; if (sep->se_fd < 0 || !FD_ISSET(sep->se_fd, &readable)) continue; n--; if (debug) fprintf(stderr, "someone wants %s on fd %d\n", sep->se_service, sep->se_fd); /* * If we have a request for tcpmux, then fork now rather * than later and set the in_child flag to rember that * we are in the child process. */ in_child = 0; if (sep->se_socktype == SOCK_STREAM) { if (!sep->se_wait) { ctrl = accept(sep->se_fd, (struct sockaddr *) NULL, (int *) NULL); if (debug) fprintf(stderr, "accept, ctrl %d\n", ctrl); if (ctrl == -1) { if (errno != EINTR) _w_syslog(LOG_WARNING, "accept: %m"); continue; } /* * Call tcpmux to find the real service to * exec. */ if (sep->se_bi && sep->se_bi->bi_fn == (bi_fn_t) tcpmux) { /* * Fork now rather than later to * avoid having the inetd parent * process block here. */ (void) sigblock(SIGBLOCK); if(police_fork_rate(sep) < 0) { (void)close(ctrl); continue; } if(pid = fork()) { if(pid > 0) close(ctrl); goto tcpmux_fork; } /* in child */ in_child = 1; sep = tcpmux(ctrl); if (sep == NULL) { close(ctrl); exit(1); } } } else { ctrl = sep->se_fd; } } else { /* SOCK_DGRAM */ ctrl = sep->se_fd; } if (!IS_LBLCOG(sep)) { s = tsix_get_uid (ctrl, &socket_uid); if (s != -1) s = tsix_get_mac (ctrl, &socket_label); if (s == -1) { _w_syslog(LOG_ERR, "%s/%s: cipso setup failed: %m", sep->se_service, sep->se_proto); (void)close(sep->se_fd); if (ctrl != sep->se_fd) (void)close(ctrl); FD_CLR(sep->se_fd, &allsock); nsock--; sep->se_fd = -1; continue; } } /* * Unless we already forked, fork now if necessary */ if(!in_child) { (void) sigblock(SIGBLOCK); pid = 0; if(sep->se_bi == 0 || sep->se_bi->bi_fork) { if(police_fork_rate(sep) < 0) { (void)close(ctrl); mac_free(socket_label); continue; } if(!(pid = fork())) in_child = 1; } } tcpmux_fork: if (pid < 0) { if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) close(ctrl); sigsetmask(0L); sleep(1); mac_free(socket_label); continue; } if (pid && sep->se_wait) { alias = sep->se_aliases; if (alias == NULL) { sep->se_pid=pid; FD_CLR(sep->se_fd, &allsock); nsock--; } else { do { alias->se_pid = pid; FD_CLR(alias->se_fd, &allsock); nsock--; alias = alias->se_nextalias; } while (alias != NULL); } } sigsetmask(0L); if (pid == 0) { const cap_value_t cmrs = CAP_MAC_RELABEL_SUBJ; endpwent(); if (debug && in_child) if (setsid() < 0) _w_syslog(LOG_ERR, "setsid failed: %m"); /* * MAC: set the label unless this server * is label-cognizant (e.g. mountd). */ if (!IS_LBLCOG(sep) && havecipso && havemac) { ocap = cap_acquire (1, &cmrs); s = mac_set_proc (socket_label); cap_surrender (ocap); if (s == -1) { _w_syslog(LOG_ERR, "%s/%s: mac_set_proc: %m", sep->se_service, sep->se_proto); if (in_child) /* child */ exit(1); if (ctrl != sep->se_fd) close(ctrl); mac_free(socket_label); continue; } s = tsix_set_solabel (ctrl, socket_label); if (s == -1) { _w_syslog(LOG_ERR, "%s/%s: tsix_set_solabel: %m", sep->se_service, sep->se_proto); if (in_child) /* child */ exit(1); if (ctrl != sep->se_fd) close(ctrl); mac_free(socket_label); continue; } s = tsix_off(ctrl); if (s == -1) { _w_syslog(LOG_ERR, "%s/%s: tsix_off: %m", sep->se_service, sep->se_proto); if (in_child) /* child */ exit(1); if (ctrl != sep->se_fd) close(ctrl); mac_free(socket_label); continue; } } if (sep->se_bi) { (*sep->se_bi->bi_fn)(ctrl, sep); if (!IS_LBLCOG(sep) && havecipso && havemac) { ocap = cap_acquire (1, &cmrs); s = mac_set_proc (original_label); cap_surrender (ocap); if (s == -1) { _w_syslog(LOG_ERR, "%s/%s: mac_set_proc: %m", sep->se_service, sep->se_proto); mac_free(original_label); exit(1); } } } else { int lastfd; struct clearance *clp; struct passwd *pwd; char *usename, audit_rationale[256]; cap_value_t cap_audit_write = CAP_AUDIT_WRITE; if (debug) { fprintf(stderr, "%d execl %s\n", (int) getpid(), sep->se_server); } alias = sep->se_aliases; if (alias == NULL) { dup2(ctrl, 0); dup2(0, 1); dup2(0, 2); lastfd = 2; } else { for (lastfd = 0; ; lastfd++) { dupaliasfd(alias, lastfd); alias = alias->se_nextalias; if (alias == NULL) break; } } /* * This server will be run with * client's uid. (Used to restrict * dgld's X server access.) */ if (IS_RCVUID(sep)) pwd = getpwuid(socket_uid); else pwd = getpwnam(sep->se_user); if (pwd == NULL) { _w_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); } usename = pwd->pw_name; /* * Check user clearance; audit the event. */ if (havemac) { clp = sgi_getclearancebyname (usename); if (clp == NULL) { _w_syslog(LOG_ERR, "%s/%s: sgi_getclearancebyname: %s: No such user", sep->se_service, sep->se_proto, usename); if (sep->se_socktype != SOCK_STREAM) recv(0, buf, sizeof (buf), 0); _exit(1); } check_clearance(clp, sep); } /* * Audit */ sprintf(audit_rationale, "Remote %s service granted.", sep->se_service); ocap = cap_acquire(1, &cap_audit_write); (void) ia_audit("inetd", usename, 1, audit_rationale); cap_surrender(ocap); if (pwd->pw_uid) { cap_value_t cap_setuid = CAP_SETUID; cap_value_t cap_setgid = CAP_SETGID; ocap = cap_acquire (1, &cap_setgid); if (setgid(pwd->pw_gid) == -1) { _w_syslog(LOG_ERR, "%s: can't set gid %d: %m", sep->se_service, pwd->pw_gid); cap_surrender (ocap); _exit(1); } (void) initgroups(usename, pwd->pw_gid); cap_surrender (ocap); ocap = cap_acquire (1, &cap_setuid); if (setuid(pwd->pw_uid) == -1) { _w_syslog(LOG_ERR, "%s: can't set uid %d: %m", sep->se_service, pwd->pw_uid); cap_surrender (ocap); _exit(1); } cap_surrender (ocap); } /* * Close all of the sockets opened by NIS to * do the getpwnam() */ for (tmp = getdtablehi(); --tmp > lastfd; ) (void)close(tmp); setsrvcap(sep); execv(sep->se_server, sep->se_argv); if (sep->se_socktype != SOCK_STREAM) recv(0, buf, sizeof (buf), 0); _w_syslog(LOG_ERR, "cannot execute %s: %m", sep->se_server); _exit(1); } } if (ctrl != sep->se_fd) { close(ctrl); } if (pid > 0 && !IS_LBLCOG(sep) && havecipso && havemac && (sep->se_wait || sep->se_socktype == SOCK_DGRAM)) { close(sep->se_fd); FD_CLR(sep->se_fd, &allsock); nsock--; sep->se_fd = -1; if (ISRPC(sep)) rpcsetup(sep); else setup(sep); } mac_free(socket_label); } } } static void dupaliasfd(struct servtab *alias, int tofd) { struct servtab *sep; if (alias->se_fd == tofd) return; for (sep = alias->se_aliases; sep != NULL; sep = sep->se_nextalias) if (sep != alias && sep->se_fd == tofd) { sep->se_fd = dup(tofd); if (sep->se_fd < 0) _w_syslog(LOG_ERR, "%s: %m", sep->se_server); } dup2(alias->se_fd, tofd); alias->se_fd = tofd; } /* ARGSUSED */ static void reapchild(int sig) { int status; pid_t pid; register struct servtab *sep; for (;;) { pid = waitpid((pid_t) -1, &status, WNOHANG); if (pid <= 0) break; if (debug) fprintf(stderr, "%d reaped, status %#x\n", (int) pid, (int) WEXITSTATUS(status)); for (sep = servtab; sep; sep = sep->se_next) if (sep->se_pid == pid) { if (WIFEXITED(status) && WEXITSTATUS(status)) _w_syslog(LOG_WARNING, "%s: exit status 0x%x", sep->se_server, (int) WEXITSTATUS(status)); else if (WIFSIGNALED(status)) _w_syslog(LOG_WARNING, "%s: exit signal 0x%x", sep->se_server, (int) WTERMSIG(status)); if (debug) fprintf(stderr, "restored %s, fd %d\n", sep->se_service, sep->se_fd); if (sep->se_fd >= 0) { FD_SET(sep->se_fd, &allsock); nsock++; } sep->se_pid = 1; } } } /* * TCP Port-scanning inetd casued it to die mysteriously (see bug 627767) * This was due to error messages being written to phantom connections * causing the server to receive a SIGPIPE. Here we capture the SIGPIPE * to avoid this problem. (sm@engr) */ void sigpipe(int sig) { syslog(LOG_ERR,"received SIGPIPE signal (possible port-scan attack)\n"); } void sigterm(int sig) { syslog(LOG_DAEMON, "inetd received SIGTERM; terminating."); exit(0); } /* * Dummy signal handler; does nothing. */ void sig_ign(int sig) { } /* * Remove a servtab entry from its lists of aliases, if it's on any. * We call this before calling freeconfig, so that we don't end up * having pointers to free memory on alias lists. */ static void removealias(struct servtab *alias) { struct servtab *sp, **app; for (sp = servtab; sp; sp = sp->se_next) { app = &sp->se_aliases; while (*app) { if (*app == alias) { *app = (*app)->se_nextalias; } if (*app) { app = &(*app)->se_nextalias; } } } } static void config(int sig) { register struct servtab *sep, *sep_found, *cp, **sepp; long omask; struct servtab *alias; struct servent *sp; int svccnt = 0; int res; #ifdef sgi char cbuffer[1024]; extern int _pw_stayopen, _getpwent_no_shadow; #endif if (sig == SIGHUP) { syslog(LOG_INFO, "received SIGHUP: reconfiguring"); } if (!setconfig()) { _w_syslog(LOG_ERR, "%s: %m", CONFIG); return; } #ifdef sgi setbuffer(conf.fptr, cbuffer, sizeof cbuffer); _pw_stayopen = _getpwent_no_shadow = 1; #endif for (sep = servtab; sep; sep = sep->se_next) sep->se_checked = 0; while (cp = getconfigent()) { /* skip non-existent servers */ if (cp->se_bi == 0 && access(cp->se_server, EX_OK) < 0) { if (errno != ENOENT || !IS_MISSING_OK(cp)) _w_syslog(LOG_ERR, "%s: %m", cp->se_server); continue; } if ( (getpwnam(cp->se_user) == NULL) && !IS_RCVUID(cp) ) { _w_syslog(LOG_ERR, "%s/%s: No such user '%s', service ignored", cp->se_service, cp->se_proto, cp->se_user); continue; } alias = 0; for (sep = servtab; sep; sep = sep->se_next) { if (strcmp(sep->se_server, cp->se_server) != 0) continue; if (strcmp(sep->se_service, cp->se_service) == 0 && strcmp(sep->se_proto, cp->se_proto) == 0) break; if (sep->se_wait && strcmp(sep->se_user, cp->se_user) == 0) alias = sep; } sep_found = sep; if (sep != 0) { int i; omask = sigblock(SIGBLOCK); /* * If the RPC service info has changed, close * the existing socket. Replace the version * set with cp's set, which we hide from * freeconfig. */ if (ISRPC(sep) && sep->se_fd >= 0 && (sep->se_prog != cp->se_prog || !eqrpcvers(&sep->se_vers, &cp->se_vers))) { close_sep(sep); freerpcvers(sep); sep->se_rpcinfo = cp->se_rpcinfo; cp->se_vers.rv_more = NULL; } /* * sep->se_wait may be holding the process id * of a running daemon we are awaiting. If so, * don't lose it unless we are instructed now * not to wait. */ if (cp->se_bi == 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); for (i = 0; i < MAXARGV; i++) SWAP(sep->se_argv[i], cp->se_argv[i]); sigsetmask(omask); /* * We don't have to call removealias here, * because there's no way that cp can be on an * alias list. We just modified sep, which * will stay on any list it's already on. */ freeconfig(cp); if (debug) print_service("REDO", sep); } else { sep = enter(cp); if (alias) { /* * Sort sep into the alias list in canonical * service and protocol order (reuse cp). */ if (alias->se_aliases == NULL) alias->se_aliases = alias; for (sepp = &alias->se_aliases; (cp = *sepp) != NULL && ((res = strcmp(cp->se_service, sep->se_service)) <= 0 || (res == 0 && strcmp(cp->se_proto, sep->se_proto) < 0)); sepp = &cp->se_nextalias) continue; sep->se_aliases = alias->se_aliases; sep->se_nextalias = cp; *sepp = sep; } if (debug) print_service("ADD ", sep); } sep->se_checked = 1; if (ISMUX(sep)) { sep->se_fd = -1; continue; } if (ISRPC(sep)) { /* * Don't remap RPC services if they haven't changed. */ if (sep->se_fd < 0 && !dryrun) rpcsetup(sep); continue; } sp = getservbyname(sep->se_service, sep->se_proto); if (sp == 0) { _w_syslog(LOG_ERR, "%s/%s: unknown service", sep->se_service, sep->se_proto); sep->se_checked = 0; continue; } if(!dryrun) { if(!sep_found) { /* * Didn't find the service we wanted among * the list of existing services. This could * be because the server field has changed, so * check for anything that is listening on * the port we are going to listen on and * close it (reuse cp for the search). */ for(sepp = &servtab; cp = *sepp; sepp = &cp->se_next) { if(!cp->se_checked && cp->se_fd >= 0 && cp->se_ctrladdr.sin_port == sp->s_port) { /* * Close conflicting service */ *sepp = cp->se_next; close_sep(cp); if (debug) print_service("FREE", cp); removealias(cp); freeconfig(cp); free((char *)cp); break; } } } if (sp->s_port != sep->se_ctrladdr.sin_port) { sep->se_ctrladdr.sin_port = sp->s_port; if (sep->se_fd >= 0) close_sep(sep); } if (sep->se_fd < 0) setup(sep); } } endconfig(); #ifdef sgi _pw_stayopen = 0; endpwent(); #endif /* * Purge anything not looked at above. */ omask = sigblock(SIGBLOCK); sepp = &servtab; while (sep = *sepp) { if (sep->se_checked) { sepp = &sep->se_next; svccnt++; continue; } *sepp = sep->se_next; if (sep->se_fd >= 0) { close_sep(sep); } if (debug) print_service("FREE", sep); removealias(sep); freeconfig(sep); free((char *)sep); } (void) sigsetmask(omask); /* * Add 2 fd's for accept and getpwnam calls. */ if (svccnt+2 >= getdtablesize()) _w_syslog(LOG_ERR, "too many services: open-file limit reached."); if(!dryrun) xconfig(servtab); } static int eqrpcvers(const struct rpcvers *rv1, const struct rpcvers *rv2) { if (rv1->rv_low == rv2->rv_low && rv1->rv_high == rv2->rv_high) { if (rv1->rv_more && rv2->rv_more) return eqrpcvers(rv1->rv_more, rv2->rv_more); if (rv1->rv_more || rv2->rv_more) return 0; return 1; } return 0; } /* ARGSUSED */ static void retry(int sig) { register struct servtab *sep; timingout = 0; for (sep = servtab; sep; sep = sep->se_next) { if (sep->se_fd < 0) { if (ISRPC(sep)) rpcsetup(sep); else setup(sep); } } } int turnon( int fd, int opt) { int on = 1; return (setsockopt(fd, SOL_SOCKET, opt, (char *) &on, sizeof on)); } static void setup(sep) register struct servtab *sep; { if (sep->se_socktype == SOCK_STREAM || sep->se_socktype == SOCK_DGRAM) sep->se_fd = socket(AF_INET, sep->se_socktype, 0); else sep->se_fd = cap_socket(AF_INET, sep->se_socktype, 0); if (sep->se_fd < 0) { _w_syslog(LOG_ERR, "%s/%s: socket: %m", sep->se_service, sep->se_proto); return; } if (tsix_on(sep->se_fd) == -1) { _w_syslog(LOG_ERR, "%s/%s: tsix_on: %m", sep->se_service, sep->se_proto); return; } if (strcmp(sep->se_proto, "tcp") == 0 && debug && turnon(sep->se_fd, SO_DEBUG) == -1) _w_syslog(LOG_ERR, "setsockopt (SO_DEBUG): %m"); if (turnon(sep->se_fd, SO_REUSEADDR) == -1) _w_syslog(LOG_ERR, "setsockopt (SO_REUSEADDR): %m"); sep->se_ctrladdr.sin_family = AF_INET; if (cap_bind(sep->se_fd, (struct sockaddr *) &sep->se_ctrladdr, sizeof (sep->se_ctrladdr)) < 0) { _w_syslog(LOG_ERR, "%s/%s: bind: %m", sep->se_service, sep->se_proto); (void) close(sep->se_fd); sep->se_fd = -1; if (!timingout) { timingout = 1; alarm(RETRYTIME); } return; } if (sep->se_socktype == SOCK_STREAM) listen(sep->se_fd, default_qlen); 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. */ static void close_sep(struct servtab *sep) { if (sep->se_fd >= 0) { nsock--; FD_CLR(sep->se_fd, &allsock); (void) close(sep->se_fd); sep->se_fd = -1; } /* * Don't keep the pid of this running daemon: when reapchild() * reaps this pid, it would erroneously increment nsock. */ if (sep->se_pid > 1){ sep->se_pid=1; } sep->se_count = 0; if (ISRPC(sep)) { struct rpcvers *rvp; u_long vers; rvp = &sep->se_vers; do { for (vers = rvp->rv_low; vers <= rvp->rv_high; vers++) pmap_unset(sep->se_prog, vers); rvp = rvp->rv_more; } while (rvp); if (sep->se_aliases != NULL) pmap_reset_aliases(sep, 1); } } /* * Thanks to Sun's unorthogonal pmap_unset, which unsets all portmappings * for prog&vers, we must restore other protocols' mappings when unmapping * rpcsep's port. */ static void pmap_reset_aliases(rpcsep, skipit) struct servtab *rpcsep; int skipit; { struct servtab *sep; struct rpcvers *rvp; u_long vers; for (sep = rpcsep->se_aliases; sep; sep = sep->se_nextalias) { /* Restore just the other services' mappings, not rcpsep's */ if ((skipit && sep == rpcsep) || (sep->se_checked == 0)) continue; rvp = &sep->se_vers; do { for (vers = rvp->rv_low; vers <= rvp->rv_high; vers++) { pmap_set(sep->se_prog, vers, sep->se_prot, sep->se_port); } rvp = rvp->rv_more; } while (rvp); } } static void rpcsetup(sep) register struct servtab *sep; { register int rpcsock; if (sep->se_fd >= 0) close_sep(sep); rpcsock = getrpcsock(sep); if (rpcsock < 0) { _w_syslog(LOG_ERR, "rpc/%s socket creation problem: %m", sep->se_proto); sep->se_checked = 0; return; } sep->se_fd = rpcsock; FD_SET(rpcsock, &allsock); nsock++; if (rpcsock > maxsock) maxsock = rpcsock; if (debug) { fprintf(stderr, "registered %s on %d\n", sep->se_server, rpcsock); } } static int getrpcsock(struct servtab *sep) { int s, len; struct sockaddr_in addr; struct rpcvers *rvp; int registrations = 0; int port; if (sep->se_socktype == SOCK_STREAM || sep->se_socktype == SOCK_DGRAM) s = socket(AF_INET, sep->se_socktype, 0); else s = cap_socket(AF_INET, sep->se_socktype, 0); if (s < 0) { _w_syslog(LOG_ERR, "socket create failed: type %d", sep->se_socktype); return -1; } if (tsix_on(s) == -1) { _w_syslog(LOG_ERR, "%s/%s: tsix_on: %m", sep->se_service, sep->se_proto); return -1; } addr.sin_family = AF_INET; addr.sin_port = 0; addr.sin_addr.s_addr = INADDR_ANY; if (bind(s, &addr, sizeof(addr)) < 0) { _w_syslog(LOG_ERR, "bind failed"); (void)close(s); return -1; } len = sizeof(struct sockaddr_in); if (getsockname(s, &addr, &len) != 0) { _w_syslog(LOG_ERR, "getsockname failed"); (void)close(s); return -1; } sep->se_prot = (sep->se_socktype == SOCK_DGRAM) ? IPPROTO_UDP : IPPROTO_TCP; sep->se_port = htons(addr.sin_port); rvp = &sep->se_vers; do { register u_long vers; for (vers = rvp->rv_low; vers <= rvp->rv_high; vers++) { if (debug) { fprintf(stderr, "%s: %d/%d/%d port = %d\n", sep->se_service, sep->se_prog, vers, sep->se_prot, sep->se_port); } if (!pmap_set(sep->se_prog, vers, sep->se_prot, sep->se_port)) { if (pmap_unset(sep->se_prog, vers)) { if (pmap_set(sep->se_prog, vers, sep->se_prot, sep->se_port)) { registrations++; } if (sep->se_aliases) { pmap_reset_aliases(sep, 0); } } } else registrations++; } rvp = rvp->rv_more; } while (rvp); if (registrations == 0) { /* * all attempts to register this service failed! * forget this port number */ _w_syslog(LOG_ERR, "%s/%s: failed to register service", sep->se_service, sep->se_proto); sep->se_port = 0; close(s); return -1; } skip_registration: if (sep->se_socktype == SOCK_STREAM) listen(s, default_qlen); return s; } static struct servtab * enter(struct servtab *cp) { register struct servtab *sep; long omask; sep = tmalloc(struct servtab); *sep = *cp; sep->se_fd = -1; omask = sigblock(SIGBLOCK); sep->se_next = servtab; servtab = sep; sigsetmask(omask); return (sep); } static struct servtab serv; #define MAX_LINE_LEN 512 static char line[MAX_LINE_LEN]; static char *fskip(char **, struct conf_file *); static char *p_skip(char **); static char *skip(char **); static char *nextline(struct conf_file *); static int setconfig(void) { conf.line_no = 0; if (conf.fptr != NULL) { fseek(conf.fptr, 0L, L_SET); return (1); } conf.fptr = fopen(conf.name, "r"); return (conf.fptr != NULL); } static void endconfig(void) { if (conf.fptr) { (void) fclose(conf.fptr); conf.fptr = NULL; } } static struct servtab * getconfigent(void) { register struct servtab *sep = &serv; char *cp, *arg, *usrp, *tmpstr, *usrnm; int argc; static char TCPMUX_TOKEN[] = "tcpmux/"; #define MUX_LEN (sizeof(TCPMUX_TOKEN)-1) more: while ((cp = nextline(&conf)) && (*cp == '#' || *cp == '\0')) ; if (cp == NULL) return ((struct servtab *)0); /* * clear the static buffer, since some fields (se_ctrladdr, * for example) don't get initialized here. */ memset((void *) sep, '\0', sizeof(*sep)); if((arg = p_skip(&cp)) == NULL) goto more; if (cp == NULL) { /* got an empty line containing just blanks/tabs. */ goto more; } 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 = mystrdup(c); } else { sep->se_service = mystrdup(arg); sep->se_type = NORM_TYPE; } if((arg = p_skip(&cp)) == NULL) goto more; if (strcmp(arg, "stream") == 0) sep->se_socktype = SOCK_STREAM; else if (strcmp(arg, "dgram") == 0) sep->se_socktype = SOCK_DGRAM; else if (strcmp(arg, "rdm") == 0) sep->se_socktype = SOCK_RDM; else if (strcmp(arg, "seqpacket") == 0) sep->se_socktype = SOCK_SEQPACKET; else if (strcmp(arg, "raw") == 0) sep->se_socktype = SOCK_RAW; else sep->se_socktype = -1; if((arg = p_skip(&cp)) == NULL) goto more; if (strncmp(arg, "rpc/", 4) != 0) { sep->se_proto = mystrdup(arg); } else { register struct rpcent *rp; sep->se_proto = mystrdup(arg + 4); sep->se_type = RPC_TYPE; arg = strchr(sep->se_service, '/'); if (arg == NULL) { _w_syslog(LOG_ERR, "%s: missing rpc version for %s", CONFIG, sep->se_service); goto more; } *arg++ = '\0'; rp = getrpcbyname(sep->se_service); if (rp == NULL) { _w_syslog(LOG_ERR, "%s: unknown rpc service %s", CONFIG, sep->se_service); goto more; } sep->se_prog = rp->r_number; if (!getrpcvers(arg, &sep->se_vers)) { _w_syslog(LOG_ERR, "%s: malformed rpc version in %s", CONFIG, sep->se_service); goto more; } } if((arg = p_skip(&cp)) == NULL) goto more; if (strcmp(arg, "wait/lc") == 0 || strcmp(arg, "nowait/lc") == 0) sep->se_flags |= SE_LBLCOG; sep->se_wait = (strncmp(arg, "wait", 4) == 0); 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 (strcmp(sep->se_proto, "tcp")) { _w_syslog(LOG_ERR, "%s: bad protocol for tcpmux service %s", CONFIG, sep->se_service); goto more; } if (sep->se_socktype != SOCK_STREAM) { _w_syslog(LOG_ERR, "%s: bad socket type for tcpmux service %s", CONFIG, sep->se_service); goto more; } } if((usrnm = p_skip(&cp)) == NULL) goto more; sep->se_user = mystrdup(usrnm); if ((usrp = strstr(sep->se_user, "/rcv")) != 0) { *usrp = 0; if (havecipso) sep->se_flags |= SE_RCVUID; } if((tmpstr = p_skip(&cp)) == NULL) goto more; if (*tmpstr == '?') { sep->se_flags |= SE_MISSING_OK; sep->se_server = mystrdup(tmpstr+1); } else sep->se_server = mystrdup(tmpstr); if (strcmp(sep->se_server, "internal") == 0) { register 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) { _w_syslog(LOG_ERR, "internal service %s unknown", sep->se_service); goto more; } sep->se_bi = bi; sep->se_wait = bi->bi_wait; } else sep->se_bi = NULL; if (havecipso || havemac) { /* additional sanity checks */ if (sep->se_socktype == SOCK_STREAM && strcmp(sep->se_proto, "tcp") || sep->se_socktype == SOCK_DGRAM && strcmp(sep->se_proto, "udp") || sep->se_type == RPC_TYPE && sep->se_bi) { _w_syslog(LOG_ERR, "%s: bad configuration combination for service %s", CONFIG, sep->se_service); goto more; } } argc = 0; for (arg = skip(&cp); cp; arg = skip(&cp)) if (argc < MAXARGV) sep->se_argv[argc++] = mystrdup(arg); while (argc <= MAXARGV) sep->se_argv[argc++] = NULL; sep->se_aliases = sep->se_nextalias = NULL; /* extended service attributes */ sep->se_cap = (havecap ? cap_init() : NULL); return (sep); } static int setxconfig(void) { if (xconf.fptr != NULL) { fseek(xconf.fptr, 0L, SEEK_SET); return (1); } xconf.fptr = fopen(xconf.name, "r"); return (xconf.fptr != NULL); } static void endxconfig(void) { if (xconf.fptr) { (void) fclose(xconf.fptr); xconf.fptr = NULL; } } static int getxent(struct xe_spec *xe) { char *cp, *arg; int i = 0; xe->xe_service = NULL; xe->xe_fset[i] = NULL; more: /* skip comments and empty lines */ while ((cp = nextline(&xconf)) && (*cp == '#' || *cp == '\0')) ; if (cp == NULL) return (0); arg = fskip(&cp, &xconf); if (cp == NULL) goto more; /* empty line containing just blanks/tabs */ /* copy service name */ xe->xe_service = mystrdup(arg); if (debug) fprintf(stderr, "service name: %s\n", xe->xe_service); /* copy each extended feature spec */ while ((arg = fskip(&cp, &xconf)) != NULL && i < MAXARGV) { char *equ, *name, *value; int j; /* must have '=' in entry */ if ((equ = strchr(arg, '=')) == NULL) continue; /* split into name/value pair */ name = arg; *equ = '\0'; value = equ + 1; /* find matching feature name */ for (j = 0; xe_names[j].name != NULL; j++) if (strcmp(xe_names[j].name, name) == 0) break; /* if it matches, save it */ if (xe_names[j].name != NULL) { xe->xe_fset[i] = tmalloc (struct xe_feature); xe->xe_fset[i]->xe_type = xe_names[j].type; xe->xe_fset[i]->xe_value = mystrdup(value); xe->xe_fset[++i] = NULL; } if (debug) { fprintf(stderr, "feature name: %s\n", name); fprintf(stderr, "feature value: %s\n", value); } } return(1); } static void xe_spec_free(struct xe_spec *xe) { int i; free(xe->xe_service); for (i = 0; xe->xe_fset[i] != NULL; i++) { free(xe->xe_fset[i]->xe_value); free(xe->xe_fset[i]); } } static void update_xent(struct servtab *sep, struct xe_spec *xe) { int i; /* operate according to attribute type */ for (i = 0; xe->xe_fset[i] != NULL; i++) { switch (xe->xe_fset[i]->xe_type) { case XE_CAP: if (!havecap) break; cap_free(sep->se_cap); sep->se_cap = cap_from_text(xe->xe_fset[i]->xe_value); if (sep->se_cap == NULL) _w_syslog(LOG_ERR, "%s: invalid capability set", xe->xe_fset[i]->xe_value); break; } } } static void xconfig(struct servtab *tab) { struct xe_spec xe; if (!setxconfig()) { if (debug) perror(XCONFIG); _w_syslog(LOG_ERR, "%s: %m", XCONFIG); return; } while (getxent(&xe)) { struct servtab *sep, *alias; /* find matching entry in service table */ for (sep = tab; sep; sep = sep->se_next) { /* check primary service */ if (strcmp(sep->se_service, xe.xe_service) == 0) update_xent(sep, &xe); /* check alias list */ for (alias = sep->se_aliases; alias != NULL; alias = alias->se_nextalias) { if (strcmp(alias->se_service, xe.xe_service) == 0) update_xent(alias, &xe); } } xe_spec_free(&xe); } endxconfig(); } static int getrpcvers(char *buf, struct rpcvers *rvp) { char *cp; rvp->rv_low = strtoul(buf, &cp, 0); if (cp == buf) return 0; if (*cp == '-') { buf = cp + 1; rvp->rv_high = strtoul(buf, &cp, 0); if (cp == buf) return 0; } else rvp->rv_high = rvp->rv_low; if (*cp == ',') { rvp->rv_more = tmalloc(struct rpcvers); if (!getrpcvers(cp + 1, rvp->rv_more)) { free((char *) rvp->rv_more); return 0; } return 1; } rvp->rv_more = 0; return (*cp == '\0'); } static void freeconfig(cp) register struct servtab *cp; { int i; if (cp->se_service && !ISRPC(cp)) 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); if (cp->se_cap) cap_free(cp->se_cap); for (i = 0; i < MAXARGV; i++) if (cp->se_argv[i]) free(cp->se_argv[i]); if (ISRPC(cp)) freerpcvers(cp); } static void freerpcvers(sep) struct servtab *sep; { struct rpcvers *rvp, *more; for (rvp = sep->se_vers.rv_more; rvp; rvp = more) { more = rvp->rv_more; free((char *) rvp); } } /* * Safe skip - if skip returns null, log a syntax error in the * configuration file and exit. */ static char * p_skip(char **cpp) { char *cp = skip(cpp); if (cp == NULL) { _w_syslog(LOG_ERR, "%s: syntax error on line %d; ignoring line", CONFIG, conf.line_no); } return cp; } /* * fskip returns a pointer to the next symbol in the configuration * file *fp, or NULL if the end of the file has been reached. */ static char * fskip(char **cpp, struct conf_file *fp) { register char *cp = *cpp; char *start; again: while (*cp == ' ' || *cp == '\t') cp++; if (*cp == '\0') { int c; c = getc(fp->fptr); (void) ungetc(c, fp->fptr); if (c == ' ' || c == '\t') if (cp = nextline(fp)) goto again; *cpp = (char *)0; return ((char *)0); } start = cp; while (*cp && *cp != ' ' && *cp != '\t') cp++; if (*cp != '\0') *cp++ = '\0'; *cpp = cp; return (start); } static char * skip(char **cpp) { return(fskip(cpp, &conf)); } static char * nextline(struct conf_file *flptr) { char *cp; if (fgets(line, sizeof (line), flptr->fptr) == NULL) return ((char *)0); cp = strchr(line, '\n'); if (cp) *cp = '\0'; flptr->line_no++; return (line); } /* * Internet services provided internally by inetd: */ #define BUFSIZE (16 * 1024) /* ARGSUSED */ static void echo_stream(s, sep) /* Echo service -- echo data back */ int s; struct servtab *sep; { char buffer[BUFSIZE]; int i; while ((i = read(s, buffer, sizeof(buffer))) > 0 && write(s, buffer, i) > 0) ; exit(0); } /* ARGSUSED */ static void echo_dg(s, sep) /* Echo service -- echo data back */ int s; struct servtab *sep; { char buffer[BUFSIZE]; int i, size; struct sockaddr_in sin; size = sizeof(sin); if ((i = recvfrom(s, buffer, sizeof(buffer), 0, &sin, &size)) < 0) return; if (!check_loop(&sin)) return; (void) sendto(s, buffer, i, 0, &sin, sizeof(sin)); } /* ARGSUSED */ static void discard_stream(s, sep) /* Discard service -- ignore data */ int s; struct servtab *sep; { char buffer[BUFSIZE]; errno = 0; while (1) { while (read(s, buffer, sizeof(buffer)) > 0) ; if (errno != EINTR) break; } exit(0); } /* ARGSUSED */ static void discard_dg(s, sep) /* Discard service -- ignore data */ int s; struct servtab *sep; { char buffer[BUFSIZE]; (void) read(s, buffer, sizeof(buffer)); } #include #define LINESIZ 72 static char ring[128]; static char *endring; static void initring(void) { register int i; endring = ring; for (i = 0; i <= 128; ++i) if (isprint(i)) *endring++ = i; } /* ARGSUSED */ static void chargen_stream(s, sep) /* Character generator */ int s; struct servtab *sep; { register char *rs; int len; char text[LINESIZ+2]; if (!endring) { initring(); rs = ring; } text[LINESIZ] = '\r'; text[LINESIZ + 1] = '\n'; for (rs = ring;;) { if ((len = endring - rs) >= LINESIZ) memcpy(text, rs, LINESIZ); else { memcpy(text, rs, len); memcpy(text + len, ring, LINESIZ - len); } if (++rs == endring) rs = ring; if (write(s, text, sizeof(text)) != sizeof(text)) break; } exit(0); } /* ARGSUSED */ static void chargen_dg(s, sep) /* Character generator */ int s; struct servtab *sep; { struct sockaddr_in sin; static char *rs; int len, size; char text[LINESIZ+2]; if (endring == 0) { initring(); rs = ring; } size = sizeof(sin); if (recvfrom(s, text, sizeof(text), 0, &sin, &size) < 0) return; if (!check_loop(&sin)) return; if ((len = endring - rs) >= LINESIZ) memcpy(text, rs, LINESIZ); else { memcpy(text, rs, len); memcpy(text + len, ring, LINESIZ - len); } if (++rs == endring) rs = ring; text[LINESIZ] = '\r'; text[LINESIZ + 1] = '\n'; (void) sendto(s, text, sizeof(text), 0, &sin, sizeof(sin)); } /* * Return a machine readable date and time, in the form of the * number of seconds since midnight, Jan 1, 1900. Since gettimeofday * returns the number of seconds since midnight, Jan 1, 1970, * we must add 2208988800 seconds to this figure to make up for * some seventy years Bell Labs was asleep. */ static long machtime(void) { struct timeval tv; if (gettimeofday(&tv, 0) < 0) { if (debug) fprintf(stderr, "Unable to get time of day\n"); return (0L); } return (htonl((long)tv.tv_sec + 2208988800)); } /* ARGSUSED */ static void machtime_stream(s, sep) int s; struct servtab *sep; { long result; result = machtime(); (void) write(s, (char *) &result, sizeof(result)); } /* ARGSUSED */ static void machtime_dg(s, sep) int s; struct servtab *sep; { long result; struct sockaddr_in sin; int size; size = sizeof(sin); if (recvfrom(s, &result, sizeof(result), 0, &sin, &size) < 0) return; if (!check_loop(&sin)) return; result = machtime(); (void) sendto(s, &result, sizeof(result), 0, &sin, sizeof(sin)); } /* ARGSUSED */ static void daytime_stream(s, sep) /* Return human-readable time of day */ int s; struct servtab *sep; { char buffer[256]; time_t clock; clock = time(0); (void) sprintf(buffer, "%.24s\r\n", ctime(&clock)); (void) write(s, buffer, strlen(buffer)); } /* ARGSUSED */ static void daytime_dg(int s, struct servtab *sep) { char buffer[256]; time_t clock; struct sockaddr_in sin; int size; clock = time(0); size = sizeof(sin); if (recvfrom(s, buffer, sizeof(buffer), 0, &sin, &size) < 0) return; if (!check_loop(&sin)) return; (void) sprintf(buffer, "%.24s\r\n", ctime(&clock)); (void) sendto(s, buffer, strlen(buffer), 0, &sin, sizeof(sin)); } /* * print_service: * Dump relevant information to stderr */ static void print_service(action, sep) char *action; const struct servtab *sep; { fprintf(stderr, "%s: %s proto=%s, wait=%d, user=%s builtin=%x server=%s", action, sep->se_service, sep->se_proto, sep->se_wait, sep->se_user, (int) sep->se_bi, sep->se_server); if (sep->se_cap) { char *s = cap_to_text(sep->se_cap, (size_t *) NULL); if (s != NULL) { fprintf(stderr, ", cap=%s", s); cap_free(s); } } fprintf(stderr, "\n"); } /* * Based on TCPMUX.C by Mark K. Lottor November 1988 * sri-nic::ps:tcpmux.c */ static int /* # of characters upto \r,\n or \0 */ getline(int fd, char *buf, int len) { int count = 0, n, sr; struct timeval timo; struct timeval gl_stop; fd_set fdr; do { FD_ZERO(&fdr); FD_SET(fd, &fdr); (void) gettimeofday(&gl_stop, NULL); gl_stop.tv_sec += TCPMUX_TIMEOUT; do { (void) gettimeofday(&timo, NULL); if(timo.tv_sec >= gl_stop.tv_sec) break; timo.tv_sec = gl_stop.tv_sec - timo.tv_sec; timo.tv_usec = 0; sr = select(fd+1, &fdr, (fd_set *)0, (fd_set *)0, &timo); } while(sr < 0 && errno == EINTR); if(sr <= 0) /* * If sr == 0, we timed out. */ return 0; n = read(fd, buf, len-count); if (n == 0) return count; if (n < 0) { if (debug) fprintf(stderr, "getline: read: %s", strerror(errno)); 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 (MAX_LINE_LEN+2) /* 2 bytes for \r\n */ #define strwrite(fd, buf) (void) write(fd, buf, sizeof(buf)-1) static struct servtab * tcpmux(int s) { register struct servtab *sep; char service[MAX_SERV_LEN+1]; int len; /* Get requested service name */ if ((len = getline(s, service, MAX_SERV_LEN)) < 0) { strwrite(s, "-Error reading service name\r\n"); return(NULL); } if(len == 0) { /* * Read from connection failed due to error or timeout */ return NULL; } 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; (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); } /* Check that an internal datagram service is not being abused in * an echo-loop */ static int /* 0=dump it */ check_loop(struct sockaddr_in *sinp) { /* * Reject some likely abused ports. */ switch (sinp->sin_port) { case 7: /* echo */ case 11: /* systat */ case 13: /* daytime */ case 17: /* qotd */ case 19: /* chargen */ case 37: /* time */ case 53: /* DNS */ return 0; } /* Drop an occasional packet in case the other port is * an unfamiliar echo port. */ if ((random() & 0x1f) == 0) return 0; return 1; } /* Check user clearance for current process label. */ static void check_clearance(const struct clearance *clp, const struct servtab *sep) { const cap_value_t cap_audit_write = CAP_AUDIT_WRITE; cap_t ocap; mac_t proc_label = mac_get_proc (); if (mac_clearedlbl (clp, proc_label) != MAC_CLEARED) { char *labelname; labelname = mac_to_text (proc_label, (size_t *) NULL); mac_free (proc_label); _w_syslog (LOG_ERR | LOG_AUTH, "%s not cleared for %s at label %.40s", clp->cl_name, sep->se_service, labelname ? labelname : "** INVALID LABEL **"); mac_free (labelname); ocap = cap_acquire(1, &cap_audit_write); (void) ia_audit ("inetd", clp->cl_name, 0, "Not cleared for label."); cap_surrender(ocap); _exit (1); } mac_free (proc_label); ocap = cap_acquire(1, &cap_audit_write); (void) ia_audit ("inetd", clp->cl_name, 1, "Cleared for label."); cap_surrender(ocap); } /* * Execute a server with an appropriate capability set. * * If a server has no capability set defined, it is * executed with an empty capability set. * * If inetd cannot set its capability set, it does not * execute the server. */ static void setsrvcap (const struct servtab *sep) { cap_t ocap; const cap_value_t cap_setpcap = CAP_SETPCAP; char buf[50]; if (!havecap) return; ocap = cap_acquire (1, &cap_setpcap); if (cap_set_proc (sep->se_cap) == -1) { _w_syslog(LOG_ERR, "cannot set capability state for %s: %m", sep->se_server); cap_free(ocap); if (sep->se_socktype != SOCK_STREAM) recv(0, buf, sizeof (buf), 0); _exit(1); } cap_free(ocap); }