2544 lines
59 KiB
C
2544 lines
59 KiB
C
/*
|
|
* 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 <sys/param.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/file.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/time.h>
|
|
#include <sys/resource.h>
|
|
|
|
#include <rpc/rpc.h> /* includes <netinet/in.h> */
|
|
#include <rpc/pmap_prot.h>
|
|
#include <arpa/inet.h>
|
|
#include <rpc/pmap_clnt.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <netdb.h>
|
|
#include <syslog.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <getopt.h>
|
|
#include <net/soioctl.h>
|
|
#include <sys/mac_label.h>
|
|
#include <clearance.h>
|
|
#include <sys/types.h>
|
|
#include <sys/mac.h>
|
|
#include <sys/capability.h>
|
|
#include <t6net.h>
|
|
#include <cap_net.h>
|
|
|
|
#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 <ctype.h>
|
|
#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:<mkl>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);
|
|
}
|