summaryrefslogtreecommitdiff
path: root/libexec/tcpd/tcpdchk
diff options
context:
space:
mode:
authorJason Downs <downsj@cvs.openbsd.org>1997-02-26 06:17:11 +0000
committerJason Downs <downsj@cvs.openbsd.org>1997-02-26 06:17:11 +0000
commit6dc77368c66d81e37ab0b802e177c259debe3e18 (patch)
treec85e9b2759b1f3d994bd233dd3052ffe1111b4e4 /libexec/tcpd/tcpdchk
parent2274dc3b00560522f77effdb1a6a608068f06a43 (diff)
Initial integration of userland tcpd.
Diffstat (limited to 'libexec/tcpd/tcpdchk')
-rw-r--r--libexec/tcpd/tcpdchk/Makefile13
-rw-r--r--libexec/tcpd/tcpdchk/inetcf.c322
-rw-r--r--libexec/tcpd/tcpdchk/inetcf.h18
-rw-r--r--libexec/tcpd/tcpdchk/scaffold.c218
-rw-r--r--libexec/tcpd/tcpdchk/scaffold.h15
-rw-r--r--libexec/tcpd/tcpdchk/tcpdchk.867
-rw-r--r--libexec/tcpd/tcpdchk/tcpdchk.c470
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"));
+}