Initial integration of userland tcpd.
authordownsj <downsj@openbsd.org>
Wed, 26 Feb 1997 06:17:01 +0000 (06:17 +0000)
committerdownsj <downsj@openbsd.org>
Wed, 26 Feb 1997 06:17:01 +0000 (06:17 +0000)
16 files changed:
libexec/Makefile
libexec/tcpd/safe_finger/Makefile [new file with mode: 0644]
libexec/tcpd/safe_finger/safe_finger.c [new file with mode: 0644]
libexec/tcpd/tcpd/Makefile [new file with mode: 0644]
libexec/tcpd/tcpd/tcpd.8 [new file with mode: 0644]
libexec/tcpd/tcpd/tcpd.c [new file with mode: 0644]
libexec/tcpd/tcpdchk/Makefile [new file with mode: 0644]
libexec/tcpd/tcpdchk/inetcf.c [new file with mode: 0644]
libexec/tcpd/tcpdchk/inetcf.h [new file with mode: 0644]
libexec/tcpd/tcpdchk/scaffold.c [new file with mode: 0644]
libexec/tcpd/tcpdchk/scaffold.h [new file with mode: 0644]
libexec/tcpd/tcpdchk/tcpdchk.8 [new file with mode: 0644]
libexec/tcpd/tcpdchk/tcpdchk.c [new file with mode: 0644]
libexec/tcpd/tcpdmatch/Makefile [new file with mode: 0644]
libexec/tcpd/tcpdmatch/tcpdmatch.8 [new file with mode: 0644]
libexec/tcpd/tcpdmatch/tcpdmatch.c [new file with mode: 0644]

index 50f425c..f65fe08 100644 (file)
@@ -1,12 +1,12 @@
 #      from: @(#)Makefile      5.7 (Berkeley) 4/1/91
-#      $OpenBSD: Makefile,v 1.9 1996/11/11 05:45:57 tholo Exp $
+#      $OpenBSD: Makefile,v 1.10 1997/02/26 06:17:01 downsj Exp $
 
 .include <bsd.own.mk>
 
 SUBDIR=        atrun comsat fingerd ftpd getNAME getty identd \
        lfs_cleanerd mail.local makewhatis rexecd rlogind rshd rpc.lockd \
        rpc.rquotad rpc.rstatd rpc.rusersd rpc.rwalld rpc.sprayd \
-       talkd telnetd tftpd uucpd
+       talkd tcpd telnetd tftpd uucpd
 
 .if defined(YP)
 SUBDIR+=rpc.yppasswdd
diff --git a/libexec/tcpd/safe_finger/Makefile b/libexec/tcpd/safe_finger/Makefile
new file mode 100644 (file)
index 0000000..f3df4b4
--- /dev/null
@@ -0,0 +1,6 @@
+#      $OpenBSD: Makefile,v 1.1 1997/02/26 06:17:02 downsj Exp $
+
+PROG=  safe_finger
+NOMAN= yes
+
+.include <bsd.prog.mk>
diff --git a/libexec/tcpd/safe_finger/safe_finger.c b/libexec/tcpd/safe_finger/safe_finger.c
new file mode 100644 (file)
index 0000000..64ad57d
--- /dev/null
@@ -0,0 +1,206 @@
+/*     $OpenBSD: safe_finger.c,v 1.1 1997/02/26 06:17:03 downsj Exp $  */
+
+ /*
+  * safe_finger - finger client wrapper that protects against nasty stuff
+  * from finger servers. Use this program for automatic reverse finger
+  * probes, not the raw finger command.
+  * 
+  * Build with: cc -o safe_finger safe_finger.c
+  * 
+  * The problem: some programs may react to stuff in the first column. Other
+  * programs may get upset by thrash anywhere on a line. File systems may
+  * fill up as the finger server keeps sending data. Text editors may bomb
+  * out on extremely long lines. The finger server may take forever because
+  * it is somehow wedged. The code below takes care of all this badness.
+  * 
+  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+  */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#) safe_finger.c 1.4 94/12/28 17:42:41";
+#else
+static char rcsid[] = "$OpenBSD: safe_finger.c,v 1.1 1997/02/26 06:17:03 downsj Exp $";
+#endif
+#endif
+
+/* System libraries */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <pwd.h>
+
+/* Local stuff */
+
+char    path[] = "PATH=/bin:/usr/bin:/usr/sbin:/sbin";
+
+#define        TIME_LIMIT      60              /* Do not keep listinging forever */
+#define        INPUT_LENGTH    100000          /* Do not keep listinging forever */
+#define        LINE_LENGTH     128             /* Editors can choke on long lines */
+#define        FINGER_PROGRAM  "finger"        /* Most, if not all, UNIX systems */
+#define        UNPRIV_NAME     "nobody"        /* Preferred privilege level */
+#define        UNPRIV_UGID     32767           /* Default uid and gid */
+
+int     finger_pid;
+
+int    pipe_stdin __P((char **));
+
+void    cleanup(sig)
+int     sig;
+{
+    kill(finger_pid, SIGKILL);
+    exit(0);
+}
+
+int main(argc, argv)
+int     argc;
+char  **argv;
+{
+    int     c;
+    int     line_length = 0;
+    int     finger_status;
+    int     wait_pid;
+    int     input_count = 0;
+    struct passwd *pwd;
+
+    /*
+     * First of all, let's don't run with superuser privileges.
+     */
+    if (getuid() == 0 || geteuid() == 0) {
+       if ((pwd = getpwnam(UNPRIV_NAME)) && pwd->pw_uid > 0) {
+           setgid(pwd->pw_gid);
+           setuid(pwd->pw_uid);
+       } else {
+           setgid(UNPRIV_UGID);
+           setuid(UNPRIV_UGID);
+       }
+    }
+
+    /*
+     * Redirect our standard input through the raw finger command.
+     */
+    if (putenv(path)) {
+       fprintf(stderr, "%s: putenv: out of memory", argv[0]);
+       exit(1);
+    }
+    argv[0] = FINGER_PROGRAM;
+    finger_pid = pipe_stdin(argv);
+
+    /*
+     * Don't wait forever (Peter Wemm <peter@gecko.DIALix.oz.au>).
+     */
+    signal(SIGALRM, cleanup);
+    (void) alarm(TIME_LIMIT);
+
+    /*
+     * Main filter loop.
+     */
+    while ((c = getchar()) != EOF) {
+       if (input_count++ >= INPUT_LENGTH) {    /* don't listen forever */
+           fclose(stdin);
+           printf("\n\n Input truncated to %d bytes...\n", input_count - 1);
+           break;
+       }
+       if (c == '\n') {                        /* good: end of line */
+           putchar(c);
+           line_length = 0;
+       } else {
+           if (line_length >= LINE_LENGTH) {   /* force end of line */
+               printf("\\\n");
+               line_length = 0;
+           }
+           if (line_length == 0) {             /* protect left margin */
+               putchar(' ');
+               line_length++;
+           }
+           if (isascii(c) && (isprint(c) || isspace(c))) {     /* text */
+               if (c == '\\') {
+                   putchar(c);
+                   line_length++;
+               }
+               putchar(c);
+               line_length++;
+           } else {                            /* quote all other thash */
+               printf("\\%03o", c & 0377);
+               line_length += 4;
+           }
+       }
+    }
+
+    /*
+     * Wait until the finger child process has terminated and account for its
+     * exit status. Which will always be zero on most systems.
+     */
+    while ((wait_pid = wait(&finger_status)) != -1 && wait_pid != finger_pid)
+        /* void */ ;
+    return (wait_pid != finger_pid || finger_status != 0);
+}
+
+/* perror_exit - report system error text and terminate */
+
+void    perror_exit(text)
+char   *text;
+{
+    perror(text);
+    exit(1);
+}
+
+/* pipe_stdin - pipe stdin through program (from my ANSI to OLD C converter) */
+
+int     pipe_stdin(argv)
+char  **argv;
+{
+    int     pipefds[2];
+    int     pid;
+    int     i;
+    struct stat st;
+
+    /*
+     * The code that sets up the pipe requires that file descriptors 0,1,2
+     * are already open. All kinds of mysterious things will happen if that
+     * is not the case. The following loops makes sure that descriptors 0,1,2
+     * are set up properly.
+     */
+
+    for (i = 0; i < 3; i++) {
+       if (fstat(i, &st) == -1 && open("/dev/null", O_RDWR) != i)
+           perror_exit("open /dev/null");
+    }
+
+    /*
+     * Set up the pipe that interposes the command into our standard input
+     * stream.
+     */
+
+    if (pipe(pipefds))
+       perror_exit("pipe");
+
+    switch (pid = fork()) {
+    case -1:                                   /* error */
+       perror_exit("fork");
+       /* NOTREACHED */
+    case 0:                                    /* child */
+       (void) close(pipefds[0]);               /* close reading end */
+       (void) close(1);                        /* connect stdout to pipe */
+       if (dup(pipefds[1]) != 1)
+           perror_exit("dup");
+       (void) close(pipefds[1]);               /* close redundant fd */
+       (void) execvp(argv[0], argv);
+       perror_exit(argv[0]);
+       /* NOTREACHED */
+    default:                                   /* parent */
+       (void) close(pipefds[1]);               /* close writing end */
+       (void) close(0);                        /* connect stdin to pipe */
+       if (dup(pipefds[0]) != 0)
+           perror_exit("dup");
+       (void) close(pipefds[0]);               /* close redundant fd */
+       return (pid);
+    }
+}
diff --git a/libexec/tcpd/tcpd/Makefile b/libexec/tcpd/tcpd/Makefile
new file mode 100644 (file)
index 0000000..e802c7e
--- /dev/null
@@ -0,0 +1,9 @@
+#      $OpenBSD: Makefile,v 1.1 1997/02/26 06:17:04 downsj Exp $
+
+PROG=  tcpd
+MAN=   tcpd.8
+
+DPADD= ${LIBWRAP}
+LDADD= -lwrap
+
+.include <bsd.prog.mk>
diff --git a/libexec/tcpd/tcpd/tcpd.8 b/libexec/tcpd/tcpd/tcpd.8
new file mode 100644 (file)
index 0000000..c916f36
--- /dev/null
@@ -0,0 +1,179 @@
+.\"    $OpenBSD: tcpd.8,v 1.1 1997/02/26 06:17:05 downsj Exp $
+.TH TCPD 8
+.SH NAME
+tcpd \- access control facility for internet services
+.SH DESCRIPTION
+.PP
+The \fItcpd\fR program can be set up to monitor incoming requests for
+\fItelnet\fR, \fIfinger\fR, \fIftp\fR, \fIexec\fR, \fIrsh\fR,
+\fIrlogin\fR, \fItftp\fR, \fItalk\fR, \fIcomsat\fR and other services
+that have a one-to-one mapping onto executable files.
+.PP
+.\" The program supports both 4.3BSD-style sockets and System V.4-style
+.\" TLI.  Functionality may be limited when the protocol underneath TLI is
+.\" not an internet protocol.
+.\" .PP
+Operation is as follows: whenever a request for service arrives, the
+\fIinetd\fP daemon is tricked into running the \fItcpd\fP program
+instead of the desired server. \fItcpd\fP logs the request and does
+some additional checks. When all is well, \fItcpd\fP runs the
+appropriate server program and goes away.
+.PP
+Optional features are: pattern-based access control, client username
+lookups with the RFC 931 etc. protocol, protection against hosts that
+pretend to have someone elses host name, and protection against hosts
+that pretend to have someone elses network address.
+.SH LOGGING
+Connections that are monitored by
+.I tcpd
+are reported through the \fIsyslog\fR(3) facility. Each record contains
+a time stamp, the client host name and the name of the requested
+service.  The information can be useful to detect unwanted activities,
+especially when logfile information from several hosts is merged.
+.PP
+In order to find out where your logs are going, examine the syslog
+configuration file, usually /etc/syslog.conf.
+.SH ACCESS CONTROL
+Optionally,
+.I tcpd
+supports a simple form of access control that is based on pattern
+matching.  The access-control software provides hooks for the execution
+of shell commands when a pattern fires.  For details, see the
+\fIhosts_access\fR(5) manual page.
+.SH HOST NAME VERIFICATION
+The authentication scheme of some protocols (\fIrlogin, rsh\fR) relies
+on host names. Some implementations believe the host name that they get
+from any random name server; other implementations are more careful but
+use a flawed algorithm.
+.PP
+.I tcpd
+verifies the client host name that is returned by the address->name DNS
+server by looking at the host name and address that are returned by the
+name->address DNS server.  If any discrepancy is detected,
+.I tcpd
+concludes that it is dealing with a host that pretends to have someone
+elses host name.
+.PP
+If the sources are compiled with -DPARANOID,
+.I tcpd
+will drop the connection in case of a host name/address mismatch.
+Otherwise, the hostname can be matched with the \fIPARANOID\fR wildcard,
+after which suitable action can be taken.
+.SH HOST ADDRESS SPOOFING
+Optionally,
+.I tcpd
+disables source-routing socket options on every connection that it
+deals with. This will take care of most attacks from hosts that pretend
+to have an address that belongs to someone elses network. UDP services
+do not benefit from this protection. This feature must be turned on
+at compile time.
+.SH RFC 931
+When RFC 931 etc. lookups are enabled (compile-time option) \fItcpd\fR
+will attempt to establish the name of the client user. This will
+succeed only if the client host runs an RFC 931-compliant daemon.
+Client user name lookups will not work for datagram-oriented
+connections, and may cause noticeable delays in the case of connections
+from PCs.
+.SH EXAMPLES
+The details of using \fItcpd\fR depend on pathname information that was
+compiled into the program.
+.SH EXAMPLE 1
+This example applies when \fItcpd\fR expects that the original network
+daemons will be moved to an "other" place.
+.PP
+In order to monitor access to the \fIfinger\fR service, move the
+original finger daemon to the "other" place and install tcpd in the
+place of the original finger daemon. No changes are required to
+configuration files.
+.nf
+.sp
+.in +5
+# mkdir /other/place
+# mv /usr/etc/in.fingerd /other/place
+# cp tcpd /usr/etc/in.fingerd
+.fi
+.PP
+The example assumes that the network daemons live in /usr/etc. On some
+systems, network daemons live in /usr/sbin or in /usr/libexec, or have
+no `in.\' prefix to their name.
+.SH EXAMPLE 2
+This example applies when \fItcpd\fR expects that the network daemons
+are left in their original place.
+.PP
+In order to monitor access to the \fIfinger\fR service, perform the
+following edits on the \fIinetd\fR configuration file (usually 
+\fI/etc/inetd.conf\fR or \fI/etc/inet/inetd.conf\fR):
+.nf
+.sp
+.ti +5
+finger  stream  tcp  nowait  nobody  /usr/etc/in.fingerd  in.fingerd
+.sp
+becomes:
+.sp
+.ti +5
+finger  stream  tcp  nowait  nobody  /some/where/tcpd     in.fingerd
+.sp
+.fi
+.PP
+The example assumes that the network daemons live in /usr/etc. On some
+systems, network daemons live in /usr/sbin or in /usr/libexec, the
+daemons have no `in.\' prefix to their name, or there is no userid
+field in the inetd configuration file.
+.PP
+Similar changes will be needed for the other services that are to be
+covered by \fItcpd\fR.  Send a `kill -HUP\' to the \fIinetd\fR(8)
+process to make the changes effective. AIX users may also have to
+execute the `inetimp\' command.
+.SH EXAMPLE 3
+In the case of daemons that do not live in a common directory ("secret"
+or otherwise), edit the \fIinetd\fR configuration file so that it
+specifies an absolute path name for the process name field. For example:
+.nf
+.sp
+    ntalk  dgram  udp  wait  root  /some/where/tcpd  /usr/local/lib/ntalkd
+.sp
+.fi
+.PP
+Only the last component (ntalkd) of the pathname will be used for
+access control and logging.
+.SH BUGS
+Some UDP (and RPC) daemons linger around for a while after they have
+finished their work, in case another request comes in.  In the inetd
+configuration file these services are registered with the \fIwait\fR
+option. Only the request that started such a daemon will be logged.
+.PP
+The program does not work with RPC services over TCP. These services
+are registered as \fIrpc/tcp\fR in the inetd configuration file. The
+only non-trivial service that is affected by this limitation is
+\fIrexd\fR, which is used by the \fIon(1)\fR command. This is no great
+loss.  On most systems, \fIrexd\fR is less secure than a wildcard in
+/etc/hosts.equiv.
+.PP
+RPC broadcast requests (for example: \fIrwall, rup, rusers\fR) always
+appear to come from the responding host. What happens is that the
+client broadcasts the request to all \fIportmap\fR daemons on its
+network; each \fIportmap\fR daemon forwards the request to a local
+daemon. As far as the \fIrwall\fR etc.  daemons know, the request comes
+from the local host.
+.SH FILES
+.PP
+The default locations of the host access control tables are:
+.PP
+/etc/hosts.allow
+.br
+/etc/hosts.deny
+.SH SEE ALSO
+.na
+.nf
+hosts_access(5), format of the tcpd access control tables.
+syslog.conf(5), format of the syslogd control file.
+inetd.conf(5), format of the inetd 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
+\" @(#) tcpd.8 1.5 96/02/21 16:39:16
diff --git a/libexec/tcpd/tcpd/tcpd.c b/libexec/tcpd/tcpd/tcpd.c
new file mode 100644 (file)
index 0000000..d91d948
--- /dev/null
@@ -0,0 +1,135 @@
+/*     $OpenBSD: tcpd.c,v 1.1 1997/02/26 06:17:05 downsj Exp $ */
+
+ /*
+  * General front end for stream and datagram IP services. This program logs
+  * the remote host name and then invokes the real daemon. For example,
+  * install as /usr/etc/{tftpd,fingerd,telnetd,ftpd,rlogind,rshd,rexecd},
+  * after saving the real daemons in the directory specified with the
+  * REAL_DAEMON_DIR macro. This arrangement requires that the network daemons
+  * are started by inetd or something similar. Connections and diagnostics
+  * are logged through syslog(3).
+  * 
+  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+  */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#) tcpd.c 1.10 96/02/11 17:01:32";
+#else
+static char rcsid[] = "$OpenBSD: tcpd.c,v 1.1 1997/02/26 06:17:05 downsj Exp $";
+#endif
+#endif
+
+/* System libraries. */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <tcpd.h>
+
+#ifndef MAXPATHNAMELEN
+#define MAXPATHNAMELEN BUFSIZ
+#endif
+
+#ifndef STDIN_FILENO
+#define STDIN_FILENO   0
+#endif
+
+/* Local stuff. */
+
+int     allow_severity = SEVERITY;     /* run-time adjustable */
+int     deny_severity = LOG_WARNING;   /* ditto */
+
+int main(argc, argv)
+int     argc;
+char  **argv;
+{
+    struct request_info request;
+    char    path[MAXPATHNAMELEN];
+
+    /* Attempt to prevent the creation of world-writable files. */
+
+#ifdef DAEMON_UMASK
+    umask(DAEMON_UMASK);
+#endif
+
+    /*
+     * If argv[0] is an absolute path name, ignore REAL_DAEMON_DIR, and strip
+     * argv[0] to its basename.
+     */
+
+    if (argv[0][0] == '/') {
+       strcpy(path, argv[0]);
+       argv[0] = strrchr(argv[0], '/') + 1;
+    } else {
+       sprintf(path, "%s/%s", REAL_DAEMON_DIR, argv[0]);
+    }
+
+    /*
+     * Open a channel to the syslog daemon. Older versions of openlog()
+     * require only two arguments.
+     */
+
+#ifdef LOG_MAIL
+    (void) openlog(argv[0], LOG_PID, FACILITY);
+#else
+    (void) openlog(argv[0], LOG_PID);
+#endif
+
+    /*
+     * Find out the endpoint addresses of this conversation. Host name
+     * lookups and double checks will be done on demand.
+     */
+
+    request_init(&request, RQ_DAEMON, argv[0], RQ_FILE, STDIN_FILENO, 0);
+    fromhost(&request);
+
+    /*
+     * Optionally look up and double check the remote host name. Sites
+     * concerned with security may choose to refuse connections from hosts
+     * that pretend to have someone elses host name.
+     */
+
+#ifdef PARANOID
+    if (STR_EQ(eval_hostname(request.client), paranoid))
+       refuse(&request);
+#endif
+
+    /*
+     * The BSD rlogin and rsh daemons that came out after 4.3 BSD disallow
+     * socket options at the IP level. They do so for a good reason.
+     * Unfortunately, we cannot use this with SunOS 4.1.x because the
+     * getsockopt() system call can panic the system.
+     */
+
+#ifdef KILL_IP_OPTIONS
+    fix_options(&request);
+#endif
+
+    /*
+     * Check whether this host can access the service in argv[0]. The
+     * access-control code invokes optional shell commands as specified in
+     * the access-control tables.
+     */
+
+#ifdef HOSTS_ACCESS
+    if (!hosts_access(&request))
+       refuse(&request);
+#endif
+
+    /* Report request and invoke the real daemon program. */
+
+    syslog(allow_severity, "connect from %s", eval_client(&request));
+    closelog();
+    (void) execv(path, argv);
+    syslog(LOG_ERR, "error: cannot execute %s: %m", path);
+    clean_exit(&request);
+    /* NOTREACHED */
+}
diff --git a/libexec/tcpd/tcpdchk/Makefile b/libexec/tcpd/tcpdchk/Makefile
new file mode 100644 (file)
index 0000000..4a24a0f
--- /dev/null
@@ -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 (file)
index 0000000..c2ebf53
--- /dev/null
@@ -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 (file)
index 0000000..c07ef60
--- /dev/null
@@ -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 (file)
index 0000000..4d8c62b
--- /dev/null
@@ -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 (file)
index 0000000..ca845c1
--- /dev/null
@@ -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 (file)
index 0000000..736f39e
--- /dev/null
@@ -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 (file)
index 0000000..c15a735
--- /dev/null
@@ -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"));
+}
diff --git a/libexec/tcpd/tcpdmatch/Makefile b/libexec/tcpd/tcpdmatch/Makefile
new file mode 100644 (file)
index 0000000..f601a98
--- /dev/null
@@ -0,0 +1,16 @@
+#      $OpenBSD: Makefile,v 1.1 1997/02/26 06:17:10 downsj Exp $
+
+.PATH: ${.CURDIR}/../tcpdchk
+CFLAGS+=-I${.CURDIR}/../tcpdchk
+
+PROG=  tcpdmatch
+MAN=   tcpdmatch.8
+
+SRCS=  inetcf.c scaffold.c tcpdmatch.c
+
+DPADD= ${LIBWRAP}
+LDADD= -lwrap
+
+BINDIR=        /usr/sbin
+
+.include <bsd.prog.mk>
diff --git a/libexec/tcpd/tcpdmatch/tcpdmatch.8 b/libexec/tcpd/tcpdmatch/tcpdmatch.8
new file mode 100644 (file)
index 0000000..416d0d9
--- /dev/null
@@ -0,0 +1,99 @@
+.\"    $OpenBSD: tcpdmatch.8,v 1.1 1997/02/26 06:17:10 downsj Exp $
+.TH TCPDMATCH 8
+.SH NAME
+tcpdmatch \- tcp wrapper oracle
+.SH SYNOPSYS
+tcpdmatch [-d] [-i inet_conf] daemon client
+.sp
+tcpdmatch [-d] [-i inet_conf] daemon[@server] [user@]client
+.SH DESCRIPTION
+.PP
+\fItcpdmatch\fR predicts how the tcp wrapper would handle a specific
+request for service.  Examples are given below.
+.PP
+The program examines the \fItcpd\fR access control tables (default
+\fI/etc/hosts.allow\fR and \fI/etc/hosts.deny\fR) and prints its
+conclusion.  For maximal accuracy, it extracts additional information
+from your \fIinetd\fR or \fItlid\fR network configuration file.
+.PP
+When \fItcpdmatch\fR finds a match in the access control tables, it
+identifies the matched rule. In addition, it displays the optional
+shell commands or options 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 ARGUMENTS
+The following two arguments are always required:
+.IP daemon
+A daemon process name. Typically, the last component of a daemon
+executable pathname.
+.IP client
+A host name or network address, or one of the `unknown' or `paranoid'
+wildcard patterns.
+.sp
+When a client host name is specified, \fItcpdmatch\fR gives a
+prediction for each address listed for that client.
+.sp
+When a client address is specified, \fItcpdmatch\fR predicts what
+\fItcpd\fR would do when client name lookup fails.
+.PP
+Optional information specified with the \fIdaemon@server\fR form:
+.IP server
+A host name or network address, or one of the `unknown' or `paranoid'
+wildcard patterns. The default server name is `unknown'.
+.PP
+Optional information specified with the \fIuser@client\fR form:
+.IP user
+A client user identifier. Typically, a login name or a numeric userid.
+The default user name is `unknown'.
+.SH 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 \fItcpdmatch\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.
+.SH EXAMPLES
+To predict how \fItcpd\fR would handle a telnet request from the local
+system:
+.sp
+.ti +5
+tcpdmatch in.telnetd localhost
+.PP
+The same request, pretending that hostname lookup failed:
+.sp
+.ti +5
+tcpdmatch in.telnetd 127.0.0.1
+.PP
+To predict what tcpd would do when the client name does not match the
+client address:
+.sp
+.ti +5
+tcpdmatch in.telnetd paranoid
+.PP
+On some systems, daemon names have no `in.' prefix, or \fItcpdmatch\fR
+may need some help to locate the inetd configuration file.
+.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
+tcpdchk(8), tcpd configuration checker
+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
+\" @(#) tcpdmatch.8 1.5 96/02/11 17:01:35
diff --git a/libexec/tcpd/tcpdmatch/tcpdmatch.c b/libexec/tcpd/tcpdmatch/tcpdmatch.c
new file mode 100644 (file)
index 0000000..fb4fd1c
--- /dev/null
@@ -0,0 +1,333 @@
+/*     $OpenBSD: tcpdmatch.c,v 1.1 1997/02/26 06:17:10 downsj Exp $    */
+
+ /*
+  * tcpdmatch - explain what tcpd would do in a specific case
+  * 
+  * usage: tcpdmatch [-d] [-i inet_conf] daemon[@host] [user@]host
+  * 
+  * -d: use the access control tables in the current directory.
+  * 
+  * -i: location of inetd.conf file.
+  * 
+  * All errors are reported to the standard error stream, including the errors
+  * that would normally be reported via the syslog daemon.
+  * 
+  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+  */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#) tcpdmatch.c 1.5 96/02/11 17:01:36";
+#else
+static char rcsid[] = "$OpenBSD: tcpdmatch.c,v 1.1 1997/02/26 06:17:10 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 <unistd.h>
+#include <stdlib.h>
+
+#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"
+
+static void usage();
+static void tcpdmatch();
+
+/* The main program */
+
+int     main(argc, argv)
+int     argc;
+char  **argv;
+{
+    struct hostent *hp;
+    char   *myname = argv[0];
+    char   *client;
+    char   *server;
+    char   *addr;
+    char   *user;
+    char   *daemon;
+    struct request_info request;
+    int     ch;
+    char   *inetcf = 0;
+    int     count;
+    struct sockaddr_in server_sin;
+    struct sockaddr_in client_sin;
+    struct stat st;
+
+    /*
+     * Show what rule actually matched.
+     */
+    hosts_access_verbose = 2;
+
+    /*
+     * Parse the JCL.
+     */
+    while ((ch = getopt(argc, argv, "di:")) != EOF) {
+       switch (ch) {
+       case 'd':
+           hosts_allow_table = "hosts.allow";
+           hosts_deny_table = "hosts.deny";
+           break;
+       case 'i':
+           inetcf = optarg;
+           break;
+       default:
+           usage(myname);
+           /* NOTREACHED */
+       }
+    }
+    if (argc != optind + 2)
+       usage(myname);
+
+    /*
+     * 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);
+    }
+
+    /*
+     * Default is to specify a daemon process name. When daemon@host is
+     * specified, separate the two parts.
+     */
+    if ((server = split_at(argv[optind], '@')) == 0)
+       server = unknown;
+    if (argv[optind][0] == '/') {
+       daemon = strrchr(argv[optind], '/') + 1;
+       tcpd_warn("%s: daemon name normalized to: %s", argv[optind], daemon);
+    } else {
+       daemon = argv[optind];
+    }
+
+    /*
+     * Default is to specify a client hostname or address. When user@host is
+     * specified, separate the two parts.
+     */
+    if ((client = split_at(argv[optind + 1], '@')) != 0) {
+       user = argv[optind + 1];
+    } else {
+       client = argv[optind + 1];
+       user = unknown;
+    }
+
+    /*
+     * Analyze the inetd (or tlid) configuration file, so that we can warn
+     * the user about services that may not be wrapped, services that are not
+     * configured, or services that are wrapped in an incorrect manner. Allow
+     * for services that are not run from inetd, or that have tcpd access
+     * control built into them.
+     */
+    inetcf = inet_cfg(inetcf);
+    inet_set("portmap", WR_NOT);
+    inet_set("rpcbind", WR_NOT);
+    switch (inet_get(daemon)) {
+    case WR_UNKNOWN:
+       tcpd_warn("%s: no such process name in %s", daemon, inetcf);
+       break;
+    case WR_NOT:
+       tcpd_warn("%s: service possibly not wrapped", daemon);
+       break;
+    }
+
+    /*
+     * Check accessibility of access control files.
+     */
+    (void) check_path(hosts_allow_table, &st);
+    (void) check_path(hosts_deny_table, &st);
+
+    /*
+     * Fill in what we have figured out sofar. Use socket and DNS routines
+     * for address and name conversions. We attach stdout to the request so
+     * that banner messages will become visible.
+     */
+    request_init(&request, RQ_DAEMON, daemon, RQ_USER, user, RQ_FILE, 1, 0);
+    sock_methods(&request);
+
+    /*
+     * If a server hostname is specified, insist that the name maps to at
+     * most one address. eval_hostname() warns the user about name server
+     * problems, while using the request.server structure as a cache for host
+     * address and name conversion results.
+     */
+    if (NOT_INADDR(server) == 0 || HOSTNAME_KNOWN(server)) {
+       if ((hp = find_inet_addr(server)) == 0)
+           exit(1);
+       memset((char *) &server_sin, 0, sizeof(server_sin));
+       server_sin.sin_family = AF_INET;
+       request_set(&request, RQ_SERVER_SIN, &server_sin, 0);
+
+       for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) {
+           memcpy((char *) &server_sin.sin_addr, addr,
+                  sizeof(server_sin.sin_addr));
+
+           /*
+            * Force evaluation of server host name and address. Host name
+            * conflicts will be reported while eval_hostname() does its job.
+            */
+           request_set(&request, RQ_SERVER_NAME, "", RQ_SERVER_ADDR, "", 0);
+           if (STR_EQ(eval_hostname(request.server), unknown))
+               tcpd_warn("host address %s->name lookup failed",
+                         eval_hostaddr(request.server));
+       }
+       if (count > 1) {
+           fprintf(stderr, "Error: %s has more than one address\n", server);
+           fprintf(stderr, "Please specify an address instead\n");
+           exit(1);
+       }
+       free((char *) hp);
+    } else {
+       request_set(&request, RQ_SERVER_NAME, server, 0);
+    }
+
+    /*
+     * If a client address is specified, we simulate the effect of client
+     * hostname lookup failure.
+     */
+    if (dot_quad_addr(client) != INADDR_NONE) {
+       request_set(&request, RQ_CLIENT_ADDR, client, 0);
+       tcpdmatch(&request);
+       exit(0);
+    }
+
+    /*
+     * Perhaps they are testing special client hostname patterns that aren't
+     * really host names at all.
+     */
+    if (NOT_INADDR(client) && HOSTNAME_KNOWN(client) == 0) {
+       request_set(&request, RQ_CLIENT_NAME, client, 0);
+       tcpdmatch(&request);
+       exit(0);
+    }
+
+    /*
+     * Otherwise, assume that a client hostname is specified, and insist that
+     * the address can be looked up. The reason for this requirement is that
+     * in real life the client address is available (at least with IP). Let
+     * eval_hostname() figure out if this host is properly registered, while
+     * using the request.client structure as a cache for host name and
+     * address conversion results.
+     */
+    if ((hp = find_inet_addr(client)) == 0)
+       exit(1);
+    memset((char *) &client_sin, 0, sizeof(client_sin));
+    client_sin.sin_family = AF_INET;
+    request_set(&request, RQ_CLIENT_SIN, &client_sin, 0);
+
+    for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) {
+       memcpy((char *) &client_sin.sin_addr, addr,
+              sizeof(client_sin.sin_addr));
+
+       /*
+        * Force evaluation of client host name and address. Host name
+        * conflicts will be reported while eval_hostname() does its job.
+        */
+       request_set(&request, RQ_CLIENT_NAME, "", RQ_CLIENT_ADDR, "", 0);
+       if (STR_EQ(eval_hostname(request.client), unknown))
+           tcpd_warn("host address %s->name lookup failed",
+                     eval_hostaddr(request.client));
+       tcpdmatch(&request);
+       if (hp->h_addr_list[count + 1])
+           printf("\n");
+    }
+    free((char *) hp);
+    exit(0);
+}
+
+/* Explain how to use this program */
+
+static void usage(myname)
+char   *myname;
+{
+    fprintf(stderr, "usage: %s [-d] [-i inet_conf] daemon[@host] [user@]host\n",
+           myname);
+    fprintf(stderr, "  -d: use allow/deny files in current directory\n");
+    fprintf(stderr, "  -i: location of inetd.conf file\n");
+    exit(1);
+}
+
+/* Print interesting expansions */
+
+static void expand(text, pattern, request)
+char   *text;
+char   *pattern;
+struct request_info *request;
+{
+    char    buf[BUFSIZ];
+
+    if (STR_NE(percent_x(buf, sizeof(buf), pattern, request), unknown))
+       printf("%s %s\n", text, buf);
+}
+
+/* Try out a (server,client) pair */
+
+static void tcpdmatch(request)
+struct request_info *request;
+{
+    int     verdict;
+
+    /*
+     * Show what we really know. Suppress uninteresting noise.
+     */
+    expand("client:   hostname", "%n", request);
+    expand("client:   address ", "%a", request);
+    expand("client:   username", "%u", request);
+    expand("server:   hostname", "%N", request);
+    expand("server:   address ", "%A", request);
+    expand("server:   process ", "%d", request);
+
+    /*
+     * Reset stuff that might be changed by options handlers. In dry-run
+     * mode, extension language routines that would not return should inform
+     * us of their plan, by clearing the dry_run flag. This is a bit clumsy
+     * but we must be able to verify hosts with more than one network
+     * address.
+     */
+    rfc931_timeout = RFC931_TIMEOUT;
+    allow_severity = SEVERITY;
+    deny_severity = LOG_WARNING;
+    dry_run = 1;
+
+    /*
+     * When paranoid mode is enabled, access is rejected no matter what the
+     * access control rules say.
+     */
+#ifdef PARANOID
+    if (STR_EQ(eval_hostname(request->client), paranoid)) {
+       printf("access:   denied (PARANOID mode)\n\n");
+       return;
+    }
+#endif
+
+    /*
+     * Report the access control verdict.
+     */
+    verdict = hosts_access(request);
+    printf("access:   %s\n",
+          dry_run == 0 ? "delegated" :
+          verdict ? "granted" : "denied");
+}