# 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
--- /dev/null
+# $OpenBSD: Makefile,v 1.1 1997/02/26 06:17:02 downsj Exp $
+
+PROG= safe_finger
+NOMAN= yes
+
+.include <bsd.prog.mk>
--- /dev/null
+/* $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);
+ }
+}
--- /dev/null
+# $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>
--- /dev/null
+.\" $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
--- /dev/null
+/* $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 */
+}
--- /dev/null
+# $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>
--- /dev/null
+/* $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);
+}
--- /dev/null
+/* $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 */
--- /dev/null
+/* $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);
+}
--- /dev/null
+/* $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
--- /dev/null
+.\" $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
--- /dev/null
+/* $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"));
+}
--- /dev/null
+# $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>
--- /dev/null
+.\" $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
--- /dev/null
+/* $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");
+}