diff options
author | Jason Downs <downsj@cvs.openbsd.org> | 1997-02-26 06:17:11 +0000 |
---|---|---|
committer | Jason Downs <downsj@cvs.openbsd.org> | 1997-02-26 06:17:11 +0000 |
commit | 6dc77368c66d81e37ab0b802e177c259debe3e18 (patch) | |
tree | c85e9b2759b1f3d994bd233dd3052ffe1111b4e4 /libexec/tcpd/tcpdchk | |
parent | 2274dc3b00560522f77effdb1a6a608068f06a43 (diff) |
Initial integration of userland tcpd.
Diffstat (limited to 'libexec/tcpd/tcpdchk')
-rw-r--r-- | libexec/tcpd/tcpdchk/Makefile | 13 | ||||
-rw-r--r-- | libexec/tcpd/tcpdchk/inetcf.c | 322 | ||||
-rw-r--r-- | libexec/tcpd/tcpdchk/inetcf.h | 18 | ||||
-rw-r--r-- | libexec/tcpd/tcpdchk/scaffold.c | 218 | ||||
-rw-r--r-- | libexec/tcpd/tcpdchk/scaffold.h | 15 | ||||
-rw-r--r-- | libexec/tcpd/tcpdchk/tcpdchk.8 | 67 | ||||
-rw-r--r-- | libexec/tcpd/tcpdchk/tcpdchk.c | 470 |
7 files changed, 1123 insertions, 0 deletions
diff --git a/libexec/tcpd/tcpdchk/Makefile b/libexec/tcpd/tcpdchk/Makefile new file mode 100644 index 00000000000..4a24a0f8962 --- /dev/null +++ b/libexec/tcpd/tcpdchk/Makefile @@ -0,0 +1,13 @@ +# $OpenBSD: Makefile,v 1.1 1997/02/26 06:17:06 downsj Exp $ + +PROG= tcpdchk +MAN= tcpdchk.8 + +SRCS= inetcf.c scaffold.c tcpdchk.c + +DPADD= ${LIBWRAP} +LDADD= -lwrap + +BINDIR= /usr/sbin + +.include <bsd.prog.mk> diff --git a/libexec/tcpd/tcpdchk/inetcf.c b/libexec/tcpd/tcpdchk/inetcf.c new file mode 100644 index 00000000000..c2ebf5307ea --- /dev/null +++ b/libexec/tcpd/tcpdchk/inetcf.c @@ -0,0 +1,322 @@ +/* $OpenBSD: inetcf.c,v 1.1 1997/02/26 06:17:07 downsj Exp $ */ + + /* + * Routines to parse an inetd.conf or tlid.conf file. This would be a great + * job for a PERL script. + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#) inetcf.c 1.7 97/02/12 02:13:23"; +#else +static char rcsid[] = "$OpenBSD: inetcf.c,v 1.1 1997/02/26 06:17:07 downsj Exp $"; +#endif +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +#include <tcpd.h> + +#include "inetcf.h" +#include "scaffold.h" + + /* + * Network configuration files may live in unusual places. Here are some + * guesses. Shorter names follow longer ones. + */ +char *inet_files[] = { + "/private/etc/inetd.conf", /* NEXT */ + "/etc/inet/inetd.conf", /* SYSV4 */ + "/usr/etc/inetd.conf", /* IRIX?? */ + "/etc/inetd.conf", /* BSD */ + "/etc/net/tlid.conf", /* SYSV4?? */ + "/etc/saf/tlid.conf", /* SYSV4?? */ + "/etc/tlid.conf", /* SYSV4?? */ + 0, +}; + +static void inet_chk(); +static char *base_name(); + + /* + * Structure with everything we know about a service. + */ +struct inet_ent { + struct inet_ent *next; + int type; + char name[1]; +}; + +static struct inet_ent *inet_list = 0; + +static char whitespace[] = " \t\r\n"; + +/* inet_conf - read in and examine inetd.conf (or tlid.conf) entries */ + +char *inet_cfg(conf) +char *conf; +{ + char buf[BUFSIZ]; + FILE *fp = (FILE *)NULL; + char *service; + char *protocol; + char *user; + char *path; + char *arg0; + char *arg1; + struct tcpd_context saved_context; + int i; + struct stat st; + + saved_context = tcpd_context; + + /* + * The inetd.conf (or tlid.conf) information is so useful that we insist + * on its availability. When no file is given run a series of educated + * guesses. + */ + if (conf != 0) { + if ((fp = fopen(conf, "r")) == (FILE *)NULL) { + fprintf(stderr, percent_m(buf, "open %s: %m\n"), conf); + exit(1); + } + } else { + for (i = 0; inet_files[i] && (fp = fopen(inet_files[i], "r")) == 0; i++) + /* void */ ; + if (fp == (FILE *)NULL) { + fprintf(stderr, "Cannot find your inetd.conf or tlid.conf file.\n"); + fprintf(stderr, "Please specify its location.\n"); + exit(1); + } + conf = inet_files[i]; + check_path(conf, &st); + } + + /* + * Process the file. After the 7.0 wrapper release it became clear that + * there are many more inetd.conf formats than the 8 systems that I had + * studied. EP/IX uses a two-line specification for rpc services; HP-UX + * permits long lines to be broken with backslash-newline. + */ + tcpd_context.file = conf; + tcpd_context.line = 0; + while (xgets(buf, sizeof(buf), fp)) { + service = strtok(buf, whitespace); /* service */ + if (service == 0 || *service == '#') + continue; + if (STR_NE(service, "stream") && STR_NE(service, "dgram")) + strtok((char *) 0, whitespace); /* endpoint */ + protocol = strtok((char *) 0, whitespace); + (void) strtok((char *) 0, whitespace); /* wait */ + if ((user = strtok((char *) 0, whitespace)) == 0) + continue; + if (user[0] == '/') { /* user */ + path = user; + } else { /* path */ + if ((path = strtok((char *) 0, whitespace)) == 0) + continue; + } + if (path[0] == '?') /* IRIX optional service */ + path++; + if (STR_EQ(path, "internal")) + continue; + if (path[strspn(path, "-0123456789")] == 0) { + + /* + * ConvexOS puts RPC version numbers before path names. Jukka + * Ukkonen <ukkonen@csc.fi>. + */ + if ((path = strtok((char *) 0, whitespace)) == 0) + continue; + } + if ((arg0 = strtok((char *) 0, whitespace)) == 0) { + tcpd_warn("incomplete line"); + continue; + } + if (arg0[strspn(arg0, "0123456789")] == 0) { + + /* + * We're reading a tlid.conf file, the format is: + * + * ...stuff... path arg_count arguments mod_count modules + */ + if ((arg0 = strtok((char *) 0, whitespace)) == 0) { + tcpd_warn("incomplete line"); + continue; + } + } + if ((arg1 = strtok((char *) 0, whitespace)) == 0) + arg1 = ""; + + inet_chk(protocol, path, arg0, arg1); + } + fclose(fp); + tcpd_context = saved_context; + return (conf); +} + +/* inet_chk - examine one inetd.conf (tlid.conf?) entry */ + +static void inet_chk(protocol, path, arg0, arg1) +char *protocol; +char *path; +char *arg0; +char *arg1; +{ + char daemon[BUFSIZ]; + struct stat st; + int wrap_status = WR_MAYBE; + char *base_name_path = base_name(path); + char *tcpd_proc_name = (arg0[0] == '/' ? base_name(arg0) : arg0); + + /* + * Always warn when the executable does not exist or when it is not + * executable. + */ + if (check_path(path, &st) < 0) { + tcpd_warn("%s: not found: %m", path); + } else if ((st.st_mode & 0100) == 0) { + tcpd_warn("%s: not executable", path); + } + + /* + * Cheat on the miscd tests, nobody uses it anymore. + */ + if (STR_EQ(base_name_path, "miscd")) { + inet_set(arg0, WR_YES); + return; + } + + /* + * While we are here... + */ + if (STR_EQ(tcpd_proc_name, "rexd") || STR_EQ(tcpd_proc_name, "rpc.rexd")) + tcpd_warn("%s may be an insecure service", tcpd_proc_name); + + /* + * The tcpd program gets most of the attention. + */ + if (STR_EQ(base_name_path, "tcpd")) { + + if (STR_EQ(tcpd_proc_name, "tcpd")) + tcpd_warn("%s is recursively calling itself", tcpd_proc_name); + + wrap_status = WR_YES; + + /* + * Check: some sites install the wrapper set-uid. + */ + if ((st.st_mode & 06000) != 0) + tcpd_warn("%s: file is set-uid or set-gid", path); + + /* + * Check: some sites insert tcpd in inetd.conf, instead of replacing + * the daemon pathname. + */ + if (arg0[0] == '/' && STR_EQ(tcpd_proc_name, base_name(arg1))) + tcpd_warn("%s inserted before %s", path, arg0); + + /* + * Check: make sure files exist and are executable. On some systems + * the network daemons are set-uid so we cannot complain. Note that + * tcpd takes the basename only in case of absolute pathnames. + */ + if (arg0[0] == '/') { /* absolute path */ + if (check_path(arg0, &st) < 0) { + tcpd_warn("%s: not found: %m", arg0); + } else if ((st.st_mode & 0100) == 0) { + tcpd_warn("%s: not executable", arg0); + } + } else { /* look in REAL_DAEMON_DIR */ + sprintf(daemon, "%s/%s", REAL_DAEMON_DIR, arg0); + if (check_path(daemon, &st) < 0) { + tcpd_warn("%s: not found in %s: %m", + arg0, REAL_DAEMON_DIR); + } else if ((st.st_mode & 0100) == 0) { + tcpd_warn("%s: not executable", daemon); + } + } + + } else { + + /* + * No tcpd program found. Perhaps they used the "simple installation" + * recipe. Look for a file with the same basename in REAL_DAEMON_DIR. + * Draw some conservative conclusions when a distinct file is found. + */ + sprintf(daemon, "%s/%s", REAL_DAEMON_DIR, arg0); + if (STR_EQ(path, daemon)) { + wrap_status = WR_NOT; + } else if (check_path(daemon, &st) >= 0) { + wrap_status = WR_MAYBE; + } else if (errno == ENOENT) { + wrap_status = WR_NOT; + } else { + tcpd_warn("%s: file lookup: %m", daemon); + wrap_status = WR_MAYBE; + } + } + + /* + * Alas, we cannot wrap rpc/tcp services. + */ + if (wrap_status == WR_YES && STR_EQ(protocol, "rpc/tcp")) + tcpd_warn("%s: cannot wrap rpc/tcp services", tcpd_proc_name); + + inet_set(tcpd_proc_name, wrap_status); +} + +/* inet_set - remember service status */ + +void inet_set(name, type) +char *name; +int type; +{ + struct inet_ent *ip = + (struct inet_ent *) malloc(sizeof(struct inet_ent) + strlen(name)); + + if (ip == 0) { + fprintf(stderr, "out of memory\n"); + exit(1); + } + ip->next = inet_list; + strcpy(ip->name, name); + ip->type = type; + inet_list = ip; +} + +/* inet_get - look up service status */ + +int inet_get(name) +char *name; +{ + struct inet_ent *ip; + + if (inet_list == 0) + return (WR_MAYBE); + + for (ip = inet_list; ip; ip = ip->next) + if (STR_EQ(ip->name, name)) + return (ip->type); + + return (-1); +} + +/* base_name - compute last pathname component */ + +static char *base_name(path) +char *path; +{ + char *cp; + + if ((cp = strrchr(path, '/')) != 0) + path = cp + 1; + return (path); +} diff --git a/libexec/tcpd/tcpdchk/inetcf.h b/libexec/tcpd/tcpdchk/inetcf.h new file mode 100644 index 00000000000..c07ef60ecac --- /dev/null +++ b/libexec/tcpd/tcpdchk/inetcf.h @@ -0,0 +1,18 @@ +/* $OpenBSD: inetcf.h,v 1.1 1997/02/26 06:17:07 downsj Exp $ */ + + /* + * @(#) inetcf.h 1.1 94/12/28 17:42:30 + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ + +#include <sys/cdefs.h> + +extern char *inet_cfg __P((char *)); +extern void inet_set __P((char *, int)); +extern int inet_get __P((char *)); + +#define WR_UNKNOWN (-1) /* service unknown */ +#define WR_NOT 1 /* may not be wrapped */ +#define WR_MAYBE 2 /* may be wrapped */ +#define WR_YES 3 /* service is wrapped */ diff --git a/libexec/tcpd/tcpdchk/scaffold.c b/libexec/tcpd/tcpdchk/scaffold.c new file mode 100644 index 00000000000..4d8c62b9d98 --- /dev/null +++ b/libexec/tcpd/tcpdchk/scaffold.c @@ -0,0 +1,218 @@ +/* $OpenBSD: scaffold.c,v 1.1 1997/02/26 06:17:07 downsj Exp $ */ + + /* + * Routines for testing only. Not really industrial strength. + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ + +#ifndef lint +#if 0 +static char sccs_id[] = "@(#) scaffold.c 1.5 95/01/03 09:13:48"; +#else +static char rcsid[] = "$OpenBSD: scaffold.c,v 1.1 1997/02/26 06:17:07 downsj Exp $"; +#endif +#endif + +/* System libraries. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <stdio.h> +#include <syslog.h> +#include <setjmp.h> +#include <string.h> +#include <stdlib.h> + +#include <tcpd.h> + +#ifndef INADDR_NONE +#define INADDR_NONE (-1) /* XXX should be 0xffffffff */ +#endif + +/* Application-specific. */ + +#include "scaffold.h" + + /* + * These are referenced by the options module and by rfc931.c. + */ +int allow_severity = SEVERITY; +int deny_severity = LOG_WARNING; +int rfc931_timeout = RFC931_TIMEOUT; + +/* dup_hostent - create hostent in one memory block */ + +static struct hostent *dup_hostent(hp) +struct hostent *hp; +{ + struct hostent_block { + struct hostent host; + char *addr_list[1]; + }; + struct hostent_block *hb; + int count; + char *data; + char *addr; + + for (count = 0; hp->h_addr_list[count] != 0; count++) + /* void */ ; + + if ((hb = (struct hostent_block *) malloc(sizeof(struct hostent_block) + + (hp->h_length + sizeof(char *)) * count)) == 0) { + fprintf(stderr, "Sorry, out of memory\n"); + exit(1); + } + memset((char *) &hb->host, 0, sizeof(hb->host)); + hb->host.h_length = hp->h_length; + hb->host.h_addr_list = hb->addr_list; + hb->host.h_addr_list[count] = 0; + data = (char *) (hb->host.h_addr_list + count + 1); + + for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) { + hb->host.h_addr_list[count] = data + hp->h_length * count; + memcpy(hb->host.h_addr_list[count], addr, hp->h_length); + } + return (&hb->host); +} + +/* find_inet_addr - find all addresses for this host, result to free() */ + +struct hostent *find_inet_addr(host) +char *host; +{ + struct in_addr addr; + struct hostent *hp; + static struct hostent h; + static char *addr_list[2]; + + /* + * Host address: translate it to internal form. + */ + if ((addr.s_addr = dot_quad_addr(host)) != INADDR_NONE) { + h.h_addr_list = addr_list; + h.h_addr_list[0] = (char *) &addr; + h.h_length = sizeof(addr); + return (dup_hostent(&h)); + } + + /* + * Map host name to a series of addresses. Watch out for non-internet + * forms or aliases. The NOT_INADDR() is here in case gethostbyname() has + * been "enhanced" to accept numeric addresses. Make a copy of the + * address list so that later gethostbyXXX() calls will not clobber it. + */ + if (NOT_INADDR(host) == 0) { + tcpd_warn("%s: not an internet address", host); + return (0); + } + if ((hp = gethostbyname(host)) == 0) { + tcpd_warn("%s: host not found", host); + return (0); + } + if (hp->h_addrtype != AF_INET) { + tcpd_warn("%d: not an internet host", hp->h_addrtype); + return (0); + } + if (STR_NE(host, hp->h_name)) { + tcpd_warn("%s: hostname alias", host); + tcpd_warn("(official name: %s)", hp->h_name); + } + return (dup_hostent(hp)); +} + +/* check_dns - give each address thorough workout, return address count */ + +int check_dns(host) +char *host; +{ + struct request_info request; + struct sockaddr_in sin; + struct hostent *hp; + int count; + char *addr; + + if ((hp = find_inet_addr(host)) == 0) + return (0); + request_init(&request, RQ_CLIENT_SIN, &sin, 0); + sock_methods(&request); + memset((char *) &sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + + for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) { + memcpy((char *) &sin.sin_addr, addr, sizeof(sin.sin_addr)); + + /* + * Force host name and address conversions. Use the request structure + * as a cache. Detect hostname lookup problems. Any name/name or + * name/address conflicts will be reported while eval_hostname() does + * its job. + */ + request_set(&request, RQ_CLIENT_ADDR, "", RQ_CLIENT_NAME, "", 0); + if (STR_EQ(eval_hostname(request.client), unknown)) + tcpd_warn("host address %s->name lookup failed", + eval_hostaddr(request.client)); + } + free((char *) hp); + return (count); +} + +/* dummy function to intercept the real shell_cmd() */ + +/* ARGSUSED */ + +void shell_cmd(command) +char *command; +{ + if (hosts_access_verbose) + printf("command: %s", command); +} + +/* dummy function to intercept the real clean_exit() */ + +/* ARGSUSED */ + +void clean_exit(request) +struct request_info *request; +{ + exit(0); +} + +/* dummy function to intercept the real rfc931() */ + +/* ARGSUSED */ +void rfc931(a1, a2, d1) +struct sockaddr_in *a1, *a2; +char *d1; +{ +} + +/* check_path - examine accessibility */ + +int check_path(path, st) +char *path; +struct stat *st; +{ + struct stat stbuf; + char buf[BUFSIZ]; + + if (stat(path, st) < 0) + return (-1); +#ifdef notdef + if (st->st_uid != 0) + tcpd_warn("%s: not owned by root", path); + if (st->st_mode & 020) + tcpd_warn("%s: group writable", path); +#endif + if (st->st_mode & 002) + tcpd_warn("%s: world writable", path); + if (path[0] == '/' && path[1] != 0) { + strrchr(strcpy(buf, path), '/')[0] = 0; + (void) check_path(buf[0] ? buf : "/", &stbuf); + } + return (0); +} diff --git a/libexec/tcpd/tcpdchk/scaffold.h b/libexec/tcpd/tcpdchk/scaffold.h new file mode 100644 index 00000000000..ca845c11e44 --- /dev/null +++ b/libexec/tcpd/tcpdchk/scaffold.h @@ -0,0 +1,15 @@ +/* $OpenBSD: scaffold.h,v 1.1 1997/02/26 06:17:08 downsj Exp $ */ + + /* + * @(#) scaffold.h 1.3 94/12/31 18:19:19 + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ + +#include <sys/cdefs.h> + +__BEGIN_DECLS +extern struct hostent *find_inet_addr __P((char *)); +extern int check_dns __P((char *)); +extern int check_path __P((char *, struct stat *)); +__END_DECLS diff --git a/libexec/tcpd/tcpdchk/tcpdchk.8 b/libexec/tcpd/tcpdchk/tcpdchk.8 new file mode 100644 index 00000000000..736f39e4260 --- /dev/null +++ b/libexec/tcpd/tcpdchk/tcpdchk.8 @@ -0,0 +1,67 @@ +.\" $OpenBSD: tcpdchk.8,v 1.1 1997/02/26 06:17:08 downsj Exp $ +.TH TCPDCHK 8 +.SH NAME +tcpdchk \- tcp wrapper configuration checker +.SH SYNOPSYS +tcpdchk [-a] [-d] [-i inet_conf] [-v] +.SH DESCRIPTION +.PP +\fItcpdchk\fR examines your tcp wrapper configuration and reports all +potential and real problems it can find. The program examines the +\fItcpd\fR access control files (by default, these are +\fI/etc/hosts.allow\fR and \fI/etc/hosts.deny\fR), and compares the +entries in these files against entries in the \fIinetd\fR or \fItlid\fR +network configuration files. +.PP +\fItcpdchk\fR reports problems such as non-existent pathnames; services +that appear in \fItcpd\fR access control rules, but are not controlled +by \fItcpd\fR; services that should not be wrapped; non-existent host +names or non-internet address forms; occurrences of host aliases +instead of official host names; hosts with a name/address conflict; +inappropriate use of wildcard patterns; inappropriate use of NIS +netgroups or references to non-existent NIS netgroups; references to +non-existent options; invalid arguments to options; and so on. +.PP +Where possible, \fItcpdchk\fR provides a helpful suggestion to fix the +problem. +.SH OPTIONS +.IP -a +Report access control rules that permit access without an explicit +ALLOW keyword. This applies only when the extended access control +language is enabled (build with -DPROCESS_OPTIONS). +.IP -d +Examine \fIhosts.allow\fR and \fIhosts.deny\fR files in the current +directory instead of the default ones. +.IP "-i inet_conf" +Specify this option when \fItcpdchk\fR is unable to find your +\fIinetd.conf\fR or \fItlid.conf\fR network configuration file, or when +you suspect that the program uses the wrong one. +.IP -v +Display the contents of each access control rule. Daemon lists, client +lists, shell commands and options are shown in a pretty-printed format; +this makes it easier for you to spot any discrepancies between what you +want and what the program understands. +.SH FILES +.PP +The default locations of the \fItcpd\fR access control tables are: +.PP +/etc/hosts.allow +.br +/etc/hosts.deny +.SH SEE ALSO +.na +.nf +tcpdmatch(8), explain what tcpd would do in specific cases. +hosts_access(5), format of the tcpd access control tables. +hosts_options(5), format of the language extensions. +inetd.conf(5), format of the inetd control file. +tlid.conf(5), format of the tlid control file. +.SH AUTHORS +.na +.nf +Wietse Venema (wietse@wzv.win.tue.nl), +Department of Mathematics and Computing Science, +Eindhoven University of Technology +Den Dolech 2, P.O. Box 513, +5600 MB Eindhoven, The Netherlands +\" @(#) tcpdchk.8 1.3 95/01/08 17:00:30 diff --git a/libexec/tcpd/tcpdchk/tcpdchk.c b/libexec/tcpd/tcpdchk/tcpdchk.c new file mode 100644 index 00000000000..c15a735c779 --- /dev/null +++ b/libexec/tcpd/tcpdchk/tcpdchk.c @@ -0,0 +1,470 @@ +/* $OpenBSD: tcpdchk.c,v 1.1 1997/02/26 06:17:09 downsj Exp $ */ + + /* + * tcpdchk - examine all tcpd access control rules and inetd.conf entries + * + * Usage: tcpdchk [-a] [-d] [-i inet_conf] [-v] + * + * -a: complain about implicit "allow" at end of rule. + * + * -d: rules in current directory. + * + * -i: location of inetd.conf file. + * + * -v: show all rules. + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#) tcpdchk.c 1.8 97/02/12 02:13:25"; +#else +static char rcsid[] = "$OpenBSD: tcpdchk.c,v 1.1 1997/02/26 06:17:09 downsj Exp $"; +#endif +#endif + +/* System libraries. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <syslog.h> +#include <setjmp.h> +#include <errno.h> +#include <netdb.h> +#include <string.h> +#include <unistd.h> +#ifdef NETGROUP +#include <netgroup.h> +#endif + +#include <tcpd.h> + +#ifndef INADDR_NONE +#define INADDR_NONE (-1) /* XXX should be 0xffffffff */ +#endif + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif + +/* Application-specific. */ + +#include "inetcf.h" +#include "scaffold.h" + + /* + * Stolen from hosts_access.c... + */ +static char sep[] = ", \t\n"; + +#define BUFLEN 2048 + +int resident = 0; +int hosts_access_verbose = 0; +char *hosts_allow_table = HOSTS_ALLOW; +char *hosts_deny_table = HOSTS_DENY; +extern jmp_buf tcpd_buf; + + /* + * Local stuff. + */ +static void usage(); +static void parse_table(); +static void print_list(); +static void check_daemon_list(); +static void check_client_list(); +static void check_daemon(); +static void check_user(); +static int check_host(); +static int reserved_name(); + +#define PERMIT 1 +#define DENY 0 + +#define YES 1 +#define NO 0 + +static int defl_verdict; +static char *myname; +static int allow_check; +static char *inetcf; + +int main(argc, argv) +int argc; +char **argv; +{ + struct request_info request; + struct stat st; + int c; + + myname = argv[0]; + + /* + * Parse the JCL. + */ + while ((c = getopt(argc, argv, "adi:v")) != EOF) { + switch (c) { + case 'a': + allow_check = 1; + break; + case 'd': + hosts_allow_table = "hosts.allow"; + hosts_deny_table = "hosts.deny"; + break; + case 'i': + inetcf = optarg; + break; + case 'v': + hosts_access_verbose++; + break; + default: + usage(); + /* NOTREACHED */ + } + } + if (argc != optind) + usage(); + + /* + * When confusion really strikes... + */ + if (check_path(REAL_DAEMON_DIR, &st) < 0) { + tcpd_warn("REAL_DAEMON_DIR %s: %m", REAL_DAEMON_DIR); + } else if (!S_ISDIR(st.st_mode)) { + tcpd_warn("REAL_DAEMON_DIR %s is not a directory", REAL_DAEMON_DIR); + } + + /* + * Process the inet configuration file (or its moral equivalent). This + * information is used later to find references in hosts.allow/deny to + * unwrapped services, and other possible problems. + */ + inetcf = inet_cfg(inetcf); + if (hosts_access_verbose) + printf("Using network configuration file: %s\n", inetcf); + + /* + * These are not run from inetd but may have built-in access control. + */ + inet_set("portmap", WR_NOT); + inet_set("rpcbind", WR_NOT); + + /* + * Check accessibility of access control files. + */ + (void) check_path(hosts_allow_table, &st); + (void) check_path(hosts_deny_table, &st); + + /* + * Fake up an arbitrary service request. + */ + request_init(&request, + RQ_DAEMON, "daemon_name", + RQ_SERVER_NAME, "server_hostname", + RQ_SERVER_ADDR, "server_addr", + RQ_USER, "user_name", + RQ_CLIENT_NAME, "client_hostname", + RQ_CLIENT_ADDR, "client_addr", + RQ_FILE, 1, + 0); + + /* + * Examine all access-control rules. + */ + defl_verdict = PERMIT; + parse_table(hosts_allow_table, &request); + defl_verdict = DENY; + parse_table(hosts_deny_table, &request); + return (0); +} + +/* usage - explain */ + +static void usage() +{ + fprintf(stderr, "usage: %s [-a] [-d] [-i inet_conf] [-v]\n", myname); + fprintf(stderr, " -a: report rules with implicit \"ALLOW\" at end\n"); + fprintf(stderr, " -d: use allow/deny files in current directory\n"); + fprintf(stderr, " -i: location of inetd.conf file\n"); + fprintf(stderr, " -v: list all rules\n"); + exit(1); +} + +/* parse_table - like table_match(), but examines _all_ entries */ + +static void parse_table(table, request) +char *table; +struct request_info *request; +{ + FILE *fp; + int real_verdict; + char sv_list[BUFLEN]; /* becomes list of daemons */ + char *cl_list; /* becomes list of requests */ + char *sh_cmd; /* becomes optional shell command */ +#ifndef PROCESS_OPTIONS + char buf[BUFSIZ]; +#endif + int verdict; + struct tcpd_context saved_context; + + saved_context = tcpd_context; /* stupid compilers */ + + if ((fp = fopen(table, "r")) != (FILE *)NULL) { + tcpd_context.file = table; + tcpd_context.line = 0; + while (xgets(sv_list, sizeof(sv_list), fp)) { + if (sv_list[strlen(sv_list) - 1] != '\n') { + tcpd_warn("missing newline or line too long"); + continue; + } + if (sv_list[0] == '#' || sv_list[strspn(sv_list, " \t\r\n")] == 0) + continue; + if ((cl_list = split_at(sv_list, ':')) == 0) { + tcpd_warn("missing \":\" separator"); + continue; + } + sh_cmd = split_at(cl_list, ':'); + + if (hosts_access_verbose) + printf("\n>>> Rule %s line %d:\n", + tcpd_context.file, tcpd_context.line); + + if (hosts_access_verbose) + print_list("daemons: ", sv_list); + check_daemon_list(sv_list); + + if (hosts_access_verbose) + print_list("clients: ", cl_list); + check_client_list(cl_list); + +#ifdef PROCESS_OPTIONS + real_verdict = defl_verdict; + if (sh_cmd) { + verdict = setjmp(tcpd_buf); + if (verdict != 0) { + real_verdict = (verdict == AC_PERMIT); + } else { + dry_run = 1; + process_options(sh_cmd, request); + if (dry_run == 1 && real_verdict && allow_check) + tcpd_warn("implicit \"allow\" at end of rule"); + } + } else if (defl_verdict && allow_check) { + tcpd_warn("implicit \"allow\" at end of rule"); + } + if (hosts_access_verbose) + printf("access: %s\n", real_verdict ? "granted" : "denied"); +#else + if (sh_cmd) + shell_cmd(percent_x(buf, sizeof(buf), sh_cmd, request)); + if (hosts_access_verbose) + printf("access: %s\n", defl_verdict ? "granted" : "denied"); +#endif + } + (void) fclose(fp); + } else if (errno != ENOENT) { + tcpd_warn("cannot open %s: %m", table); + } + tcpd_context = saved_context; +} + +/* print_list - pretty-print a list */ + +static void print_list(title, list) +char *title; +char *list; +{ + char buf[BUFLEN]; + char *cp; + char *next; + + fputs(title, stdout); + strcpy(buf, list); + + for (cp = strtok(buf, sep); cp != 0; cp = next) { + fputs(cp, stdout); + next = strtok((char *) 0, sep); + if (next != 0) + fputs(" ", stdout); + } + fputs("\n", stdout); +} + +/* check_daemon_list - criticize daemon list */ + +static void check_daemon_list(list) +char *list; +{ + char buf[BUFLEN]; + char *cp; + char *host; + int daemons = 0; + + strcpy(buf, list); + + for (cp = strtok(buf, sep); cp != 0; cp = strtok((char *) 0, sep)) { + if (STR_EQ(cp, "EXCEPT")) { + daemons = 0; + } else { + daemons++; + if ((host = split_at(cp + 1, '@')) != 0 && check_host(host) > 1) { + tcpd_warn("host %s has more than one address", host); + tcpd_warn("(consider using an address instead)"); + } + check_daemon(cp); + } + } + if (daemons == 0) + tcpd_warn("daemon list is empty or ends in EXCEPT"); +} + +/* check_client_list - criticize client list */ + +static void check_client_list(list) +char *list; +{ + char buf[BUFLEN]; + char *cp; + char *host; + int clients = 0; + + strcpy(buf, list); + + for (cp = strtok(buf, sep); cp != 0; cp = strtok((char *) 0, sep)) { + if (STR_EQ(cp, "EXCEPT")) { + clients = 0; + } else { + clients++; + if ((host = split_at(cp + 1, '@'))) { /* user@host */ + check_user(cp); + check_host(host); + } else { + check_host(cp); + } + } + } + if (clients == 0) + tcpd_warn("client list is empty or ends in EXCEPT"); +} + +/* check_daemon - criticize daemon pattern */ + +static void check_daemon(pat) +char *pat; +{ + if (pat[0] == '@') { + tcpd_warn("%s: daemon name begins with \"@\"", pat); + } else if (pat[0] == '.') { + tcpd_warn("%s: daemon name begins with dot", pat); + } else if (pat[strlen(pat) - 1] == '.') { + tcpd_warn("%s: daemon name ends in dot", pat); + } else if (STR_EQ(pat, "ALL") || STR_EQ(pat, unknown)) { + /* void */ ; + } else if (STR_EQ(pat, "FAIL")) { /* obsolete */ + tcpd_warn("FAIL is no longer recognized"); + tcpd_warn("(use EXCEPT or DENY instead)"); + } else if (reserved_name(pat)) { + tcpd_warn("%s: daemon name may be reserved word", pat); + } else { + switch (inet_get(pat)) { + case WR_UNKNOWN: + tcpd_warn("%s: no such process name in %s", pat, inetcf); + inet_set(pat, WR_YES); /* shut up next time */ + break; + case WR_NOT: + tcpd_warn("%s: service possibly not wrapped", pat); + inet_set(pat, WR_YES); + break; + } + } +} + +/* check_user - criticize user pattern */ + +static void check_user(pat) +char *pat; +{ + if (pat[0] == '@') { /* @netgroup */ + tcpd_warn("%s: user name begins with \"@\"", pat); + } else if (pat[0] == '.') { + tcpd_warn("%s: user name begins with dot", pat); + } else if (pat[strlen(pat) - 1] == '.') { + tcpd_warn("%s: user name ends in dot", pat); + } else if (STR_EQ(pat, "ALL") || STR_EQ(pat, unknown) + || STR_EQ(pat, "KNOWN")) { + /* void */ ; + } else if (STR_EQ(pat, "FAIL")) { /* obsolete */ + tcpd_warn("FAIL is no longer recognized"); + tcpd_warn("(use EXCEPT or DENY instead)"); + } else if (reserved_name(pat)) { + tcpd_warn("%s: user name may be reserved word", pat); + } +} + +/* check_host - criticize host pattern */ + +static int check_host(pat) +char *pat; +{ + char *mask; + int addr_count = 1; + + if (pat[0] == '@') { /* @netgroup */ +#ifdef NO_NETGRENT + /* SCO has no *netgrent() support */ +#else +#ifdef NETGROUP + const char *machinep; + const char *userp; + const char *domainp; + + setnetgrent(pat + 1); + if (getnetgrent(&machinep, &userp, &domainp) == 0) + tcpd_warn("%s: unknown or empty netgroup", pat + 1); + endnetgrent(); +#else + tcpd_warn("netgroup support disabled"); +#endif +#endif + } else if ((mask = split_at(pat, '/'))) { /* network/netmask */ + if (dot_quad_addr(pat) == INADDR_NONE + || dot_quad_addr(mask) == INADDR_NONE) + tcpd_warn("%s/%s: bad net/mask pattern", pat, mask); + } else if (STR_EQ(pat, "FAIL")) { /* obsolete */ + tcpd_warn("FAIL is no longer recognized"); + tcpd_warn("(use EXCEPT or DENY instead)"); + } else if (reserved_name(pat)) { /* other reserved */ + /* void */ ; + } else if (NOT_INADDR(pat)) { /* internet name */ + if (pat[strlen(pat) - 1] == '.') { + tcpd_warn("%s: domain or host name ends in dot", pat); + } else if (pat[0] != '.') { + addr_count = check_dns(pat); + } + } else { /* numeric form */ + if (STR_EQ(pat, "0.0.0.0") || STR_EQ(pat, "255.255.255.255")) { + /* void */ ; + } else if (pat[0] == '.') { + tcpd_warn("%s: network number begins with dot", pat); + } else if (pat[strlen(pat) - 1] != '.') { + check_dns(pat); + } + } + return (addr_count); +} + +/* reserved_name - determine if name is reserved */ + +static int reserved_name(pat) +char *pat; +{ + return (STR_EQ(pat, unknown) + || STR_EQ(pat, "KNOWN") + || STR_EQ(pat, paranoid) + || STR_EQ(pat, "ALL") + || STR_EQ(pat, "LOCAL")); +} |