From: downsj Date: Wed, 26 Feb 1997 06:17:01 +0000 (+0000) Subject: Initial integration of userland tcpd. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=58c9c4d4a1255cd4c57e204837f9897d6b7a6e7d;p=openbsd Initial integration of userland tcpd. --- diff --git a/libexec/Makefile b/libexec/Makefile index 50f425c3671..f65fe082ad7 100644 --- a/libexec/Makefile +++ b/libexec/Makefile @@ -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 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 index 00000000000..f3df4b49ef4 --- /dev/null +++ b/libexec/tcpd/safe_finger/Makefile @@ -0,0 +1,6 @@ +# $OpenBSD: Makefile,v 1.1 1997/02/26 06:17:02 downsj Exp $ + +PROG= safe_finger +NOMAN= yes + +.include diff --git a/libexec/tcpd/safe_finger/safe_finger.c b/libexec/tcpd/safe_finger/safe_finger.c new file mode 100644 index 00000000000..64ad57d0c8d --- /dev/null +++ b/libexec/tcpd/safe_finger/safe_finger.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 ). + */ + 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 index 00000000000..e802c7e9b92 --- /dev/null +++ b/libexec/tcpd/tcpd/Makefile @@ -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 diff --git a/libexec/tcpd/tcpd/tcpd.8 b/libexec/tcpd/tcpd/tcpd.8 new file mode 100644 index 00000000000..c916f36efd4 --- /dev/null +++ b/libexec/tcpd/tcpd/tcpd.8 @@ -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 index 00000000000..d91d9487411 --- /dev/null +++ b/libexec/tcpd/tcpd/tcpd.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 index 00000000000..4a24a0f8962 --- /dev/null +++ b/libexec/tcpd/tcpdchk/Makefile @@ -0,0 +1,13 @@ +# $OpenBSD: Makefile,v 1.1 1997/02/26 06:17:06 downsj Exp $ + +PROG= tcpdchk +MAN= tcpdchk.8 + +SRCS= inetcf.c scaffold.c tcpdchk.c + +DPADD= ${LIBWRAP} +LDADD= -lwrap + +BINDIR= /usr/sbin + +.include diff --git a/libexec/tcpd/tcpdchk/inetcf.c b/libexec/tcpd/tcpdchk/inetcf.c new file mode 100644 index 00000000000..c2ebf5307ea --- /dev/null +++ b/libexec/tcpd/tcpdchk/inetcf.c @@ -0,0 +1,322 @@ +/* $OpenBSD: inetcf.c,v 1.1 1997/02/26 06:17:07 downsj Exp $ */ + + /* + * Routines to parse an inetd.conf or tlid.conf file. This would be a great + * job for a PERL script. + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#) inetcf.c 1.7 97/02/12 02:13:23"; +#else +static char rcsid[] = "$OpenBSD: inetcf.c,v 1.1 1997/02/26 06:17:07 downsj Exp $"; +#endif +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#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 . + */ + if ((path = strtok((char *) 0, whitespace)) == 0) + continue; + } + if ((arg0 = strtok((char *) 0, whitespace)) == 0) { + tcpd_warn("incomplete line"); + continue; + } + if (arg0[strspn(arg0, "0123456789")] == 0) { + + /* + * We're reading a tlid.conf file, the format is: + * + * ...stuff... path arg_count arguments mod_count modules + */ + if ((arg0 = strtok((char *) 0, whitespace)) == 0) { + tcpd_warn("incomplete line"); + continue; + } + } + if ((arg1 = strtok((char *) 0, whitespace)) == 0) + arg1 = ""; + + inet_chk(protocol, path, arg0, arg1); + } + fclose(fp); + tcpd_context = saved_context; + return (conf); +} + +/* inet_chk - examine one inetd.conf (tlid.conf?) entry */ + +static void inet_chk(protocol, path, arg0, arg1) +char *protocol; +char *path; +char *arg0; +char *arg1; +{ + char daemon[BUFSIZ]; + struct stat st; + int wrap_status = WR_MAYBE; + char *base_name_path = base_name(path); + char *tcpd_proc_name = (arg0[0] == '/' ? base_name(arg0) : arg0); + + /* + * Always warn when the executable does not exist or when it is not + * executable. + */ + if (check_path(path, &st) < 0) { + tcpd_warn("%s: not found: %m", path); + } else if ((st.st_mode & 0100) == 0) { + tcpd_warn("%s: not executable", path); + } + + /* + * Cheat on the miscd tests, nobody uses it anymore. + */ + if (STR_EQ(base_name_path, "miscd")) { + inet_set(arg0, WR_YES); + return; + } + + /* + * While we are here... + */ + if (STR_EQ(tcpd_proc_name, "rexd") || STR_EQ(tcpd_proc_name, "rpc.rexd")) + tcpd_warn("%s may be an insecure service", tcpd_proc_name); + + /* + * The tcpd program gets most of the attention. + */ + if (STR_EQ(base_name_path, "tcpd")) { + + if (STR_EQ(tcpd_proc_name, "tcpd")) + tcpd_warn("%s is recursively calling itself", tcpd_proc_name); + + wrap_status = WR_YES; + + /* + * Check: some sites install the wrapper set-uid. + */ + if ((st.st_mode & 06000) != 0) + tcpd_warn("%s: file is set-uid or set-gid", path); + + /* + * Check: some sites insert tcpd in inetd.conf, instead of replacing + * the daemon pathname. + */ + if (arg0[0] == '/' && STR_EQ(tcpd_proc_name, base_name(arg1))) + tcpd_warn("%s inserted before %s", path, arg0); + + /* + * Check: make sure files exist and are executable. On some systems + * the network daemons are set-uid so we cannot complain. Note that + * tcpd takes the basename only in case of absolute pathnames. + */ + if (arg0[0] == '/') { /* absolute path */ + if (check_path(arg0, &st) < 0) { + tcpd_warn("%s: not found: %m", arg0); + } else if ((st.st_mode & 0100) == 0) { + tcpd_warn("%s: not executable", arg0); + } + } else { /* look in REAL_DAEMON_DIR */ + sprintf(daemon, "%s/%s", REAL_DAEMON_DIR, arg0); + if (check_path(daemon, &st) < 0) { + tcpd_warn("%s: not found in %s: %m", + arg0, REAL_DAEMON_DIR); + } else if ((st.st_mode & 0100) == 0) { + tcpd_warn("%s: not executable", daemon); + } + } + + } else { + + /* + * No tcpd program found. Perhaps they used the "simple installation" + * recipe. Look for a file with the same basename in REAL_DAEMON_DIR. + * Draw some conservative conclusions when a distinct file is found. + */ + sprintf(daemon, "%s/%s", REAL_DAEMON_DIR, arg0); + if (STR_EQ(path, daemon)) { + wrap_status = WR_NOT; + } else if (check_path(daemon, &st) >= 0) { + wrap_status = WR_MAYBE; + } else if (errno == ENOENT) { + wrap_status = WR_NOT; + } else { + tcpd_warn("%s: file lookup: %m", daemon); + wrap_status = WR_MAYBE; + } + } + + /* + * Alas, we cannot wrap rpc/tcp services. + */ + if (wrap_status == WR_YES && STR_EQ(protocol, "rpc/tcp")) + tcpd_warn("%s: cannot wrap rpc/tcp services", tcpd_proc_name); + + inet_set(tcpd_proc_name, wrap_status); +} + +/* inet_set - remember service status */ + +void inet_set(name, type) +char *name; +int type; +{ + struct inet_ent *ip = + (struct inet_ent *) malloc(sizeof(struct inet_ent) + strlen(name)); + + if (ip == 0) { + fprintf(stderr, "out of memory\n"); + exit(1); + } + ip->next = inet_list; + strcpy(ip->name, name); + ip->type = type; + inet_list = ip; +} + +/* inet_get - look up service status */ + +int inet_get(name) +char *name; +{ + struct inet_ent *ip; + + if (inet_list == 0) + return (WR_MAYBE); + + for (ip = inet_list; ip; ip = ip->next) + if (STR_EQ(ip->name, name)) + return (ip->type); + + return (-1); +} + +/* base_name - compute last pathname component */ + +static char *base_name(path) +char *path; +{ + char *cp; + + if ((cp = strrchr(path, '/')) != 0) + path = cp + 1; + return (path); +} diff --git a/libexec/tcpd/tcpdchk/inetcf.h b/libexec/tcpd/tcpdchk/inetcf.h new file mode 100644 index 00000000000..c07ef60ecac --- /dev/null +++ b/libexec/tcpd/tcpdchk/inetcf.h @@ -0,0 +1,18 @@ +/* $OpenBSD: inetcf.h,v 1.1 1997/02/26 06:17:07 downsj Exp $ */ + + /* + * @(#) inetcf.h 1.1 94/12/28 17:42:30 + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ + +#include + +extern char *inet_cfg __P((char *)); +extern void inet_set __P((char *, int)); +extern int inet_get __P((char *)); + +#define WR_UNKNOWN (-1) /* service unknown */ +#define WR_NOT 1 /* may not be wrapped */ +#define WR_MAYBE 2 /* may be wrapped */ +#define WR_YES 3 /* service is wrapped */ diff --git a/libexec/tcpd/tcpdchk/scaffold.c b/libexec/tcpd/tcpdchk/scaffold.c new file mode 100644 index 00000000000..4d8c62b9d98 --- /dev/null +++ b/libexec/tcpd/tcpdchk/scaffold.c @@ -0,0 +1,218 @@ +/* $OpenBSD: scaffold.c,v 1.1 1997/02/26 06:17:07 downsj Exp $ */ + + /* + * Routines for testing only. Not really industrial strength. + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ + +#ifndef lint +#if 0 +static char sccs_id[] = "@(#) scaffold.c 1.5 95/01/03 09:13:48"; +#else +static char rcsid[] = "$OpenBSD: scaffold.c,v 1.1 1997/02/26 06:17:07 downsj Exp $"; +#endif +#endif + +/* System libraries. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifndef INADDR_NONE +#define INADDR_NONE (-1) /* XXX should be 0xffffffff */ +#endif + +/* Application-specific. */ + +#include "scaffold.h" + + /* + * These are referenced by the options module and by rfc931.c. + */ +int allow_severity = SEVERITY; +int deny_severity = LOG_WARNING; +int rfc931_timeout = RFC931_TIMEOUT; + +/* dup_hostent - create hostent in one memory block */ + +static struct hostent *dup_hostent(hp) +struct hostent *hp; +{ + struct hostent_block { + struct hostent host; + char *addr_list[1]; + }; + struct hostent_block *hb; + int count; + char *data; + char *addr; + + for (count = 0; hp->h_addr_list[count] != 0; count++) + /* void */ ; + + if ((hb = (struct hostent_block *) malloc(sizeof(struct hostent_block) + + (hp->h_length + sizeof(char *)) * count)) == 0) { + fprintf(stderr, "Sorry, out of memory\n"); + exit(1); + } + memset((char *) &hb->host, 0, sizeof(hb->host)); + hb->host.h_length = hp->h_length; + hb->host.h_addr_list = hb->addr_list; + hb->host.h_addr_list[count] = 0; + data = (char *) (hb->host.h_addr_list + count + 1); + + for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) { + hb->host.h_addr_list[count] = data + hp->h_length * count; + memcpy(hb->host.h_addr_list[count], addr, hp->h_length); + } + return (&hb->host); +} + +/* find_inet_addr - find all addresses for this host, result to free() */ + +struct hostent *find_inet_addr(host) +char *host; +{ + struct in_addr addr; + struct hostent *hp; + static struct hostent h; + static char *addr_list[2]; + + /* + * Host address: translate it to internal form. + */ + if ((addr.s_addr = dot_quad_addr(host)) != INADDR_NONE) { + h.h_addr_list = addr_list; + h.h_addr_list[0] = (char *) &addr; + h.h_length = sizeof(addr); + return (dup_hostent(&h)); + } + + /* + * Map host name to a series of addresses. Watch out for non-internet + * forms or aliases. The NOT_INADDR() is here in case gethostbyname() has + * been "enhanced" to accept numeric addresses. Make a copy of the + * address list so that later gethostbyXXX() calls will not clobber it. + */ + if (NOT_INADDR(host) == 0) { + tcpd_warn("%s: not an internet address", host); + return (0); + } + if ((hp = gethostbyname(host)) == 0) { + tcpd_warn("%s: host not found", host); + return (0); + } + if (hp->h_addrtype != AF_INET) { + tcpd_warn("%d: not an internet host", hp->h_addrtype); + return (0); + } + if (STR_NE(host, hp->h_name)) { + tcpd_warn("%s: hostname alias", host); + tcpd_warn("(official name: %s)", hp->h_name); + } + return (dup_hostent(hp)); +} + +/* check_dns - give each address thorough workout, return address count */ + +int check_dns(host) +char *host; +{ + struct request_info request; + struct sockaddr_in sin; + struct hostent *hp; + int count; + char *addr; + + if ((hp = find_inet_addr(host)) == 0) + return (0); + request_init(&request, RQ_CLIENT_SIN, &sin, 0); + sock_methods(&request); + memset((char *) &sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + + for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) { + memcpy((char *) &sin.sin_addr, addr, sizeof(sin.sin_addr)); + + /* + * Force host name and address conversions. Use the request structure + * as a cache. Detect hostname lookup problems. Any name/name or + * name/address conflicts will be reported while eval_hostname() does + * its job. + */ + request_set(&request, RQ_CLIENT_ADDR, "", RQ_CLIENT_NAME, "", 0); + if (STR_EQ(eval_hostname(request.client), unknown)) + tcpd_warn("host address %s->name lookup failed", + eval_hostaddr(request.client)); + } + free((char *) hp); + return (count); +} + +/* dummy function to intercept the real shell_cmd() */ + +/* ARGSUSED */ + +void shell_cmd(command) +char *command; +{ + if (hosts_access_verbose) + printf("command: %s", command); +} + +/* dummy function to intercept the real clean_exit() */ + +/* ARGSUSED */ + +void clean_exit(request) +struct request_info *request; +{ + exit(0); +} + +/* dummy function to intercept the real rfc931() */ + +/* ARGSUSED */ +void rfc931(a1, a2, d1) +struct sockaddr_in *a1, *a2; +char *d1; +{ +} + +/* check_path - examine accessibility */ + +int check_path(path, st) +char *path; +struct stat *st; +{ + struct stat stbuf; + char buf[BUFSIZ]; + + if (stat(path, st) < 0) + return (-1); +#ifdef notdef + if (st->st_uid != 0) + tcpd_warn("%s: not owned by root", path); + if (st->st_mode & 020) + tcpd_warn("%s: group writable", path); +#endif + if (st->st_mode & 002) + tcpd_warn("%s: world writable", path); + if (path[0] == '/' && path[1] != 0) { + strrchr(strcpy(buf, path), '/')[0] = 0; + (void) check_path(buf[0] ? buf : "/", &stbuf); + } + return (0); +} diff --git a/libexec/tcpd/tcpdchk/scaffold.h b/libexec/tcpd/tcpdchk/scaffold.h new file mode 100644 index 00000000000..ca845c11e44 --- /dev/null +++ b/libexec/tcpd/tcpdchk/scaffold.h @@ -0,0 +1,15 @@ +/* $OpenBSD: scaffold.h,v 1.1 1997/02/26 06:17:08 downsj Exp $ */ + + /* + * @(#) scaffold.h 1.3 94/12/31 18:19:19 + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ + +#include + +__BEGIN_DECLS +extern struct hostent *find_inet_addr __P((char *)); +extern int check_dns __P((char *)); +extern int check_path __P((char *, struct stat *)); +__END_DECLS diff --git a/libexec/tcpd/tcpdchk/tcpdchk.8 b/libexec/tcpd/tcpdchk/tcpdchk.8 new file mode 100644 index 00000000000..736f39e4260 --- /dev/null +++ b/libexec/tcpd/tcpdchk/tcpdchk.8 @@ -0,0 +1,67 @@ +.\" $OpenBSD: tcpdchk.8,v 1.1 1997/02/26 06:17:08 downsj Exp $ +.TH TCPDCHK 8 +.SH NAME +tcpdchk \- tcp wrapper configuration checker +.SH SYNOPSYS +tcpdchk [-a] [-d] [-i inet_conf] [-v] +.SH DESCRIPTION +.PP +\fItcpdchk\fR examines your tcp wrapper configuration and reports all +potential and real problems it can find. The program examines the +\fItcpd\fR access control files (by default, these are +\fI/etc/hosts.allow\fR and \fI/etc/hosts.deny\fR), and compares the +entries in these files against entries in the \fIinetd\fR or \fItlid\fR +network configuration files. +.PP +\fItcpdchk\fR reports problems such as non-existent pathnames; services +that appear in \fItcpd\fR access control rules, but are not controlled +by \fItcpd\fR; services that should not be wrapped; non-existent host +names or non-internet address forms; occurrences of host aliases +instead of official host names; hosts with a name/address conflict; +inappropriate use of wildcard patterns; inappropriate use of NIS +netgroups or references to non-existent NIS netgroups; references to +non-existent options; invalid arguments to options; and so on. +.PP +Where possible, \fItcpdchk\fR provides a helpful suggestion to fix the +problem. +.SH OPTIONS +.IP -a +Report access control rules that permit access without an explicit +ALLOW keyword. This applies only when the extended access control +language is enabled (build with -DPROCESS_OPTIONS). +.IP -d +Examine \fIhosts.allow\fR and \fIhosts.deny\fR files in the current +directory instead of the default ones. +.IP "-i inet_conf" +Specify this option when \fItcpdchk\fR is unable to find your +\fIinetd.conf\fR or \fItlid.conf\fR network configuration file, or when +you suspect that the program uses the wrong one. +.IP -v +Display the contents of each access control rule. Daemon lists, client +lists, shell commands and options are shown in a pretty-printed format; +this makes it easier for you to spot any discrepancies between what you +want and what the program understands. +.SH FILES +.PP +The default locations of the \fItcpd\fR access control tables are: +.PP +/etc/hosts.allow +.br +/etc/hosts.deny +.SH SEE ALSO +.na +.nf +tcpdmatch(8), explain what tcpd would do in specific cases. +hosts_access(5), format of the tcpd access control tables. +hosts_options(5), format of the language extensions. +inetd.conf(5), format of the inetd control file. +tlid.conf(5), format of the tlid control file. +.SH AUTHORS +.na +.nf +Wietse Venema (wietse@wzv.win.tue.nl), +Department of Mathematics and Computing Science, +Eindhoven University of Technology +Den Dolech 2, P.O. Box 513, +5600 MB Eindhoven, The Netherlands +\" @(#) tcpdchk.8 1.3 95/01/08 17:00:30 diff --git a/libexec/tcpd/tcpdchk/tcpdchk.c b/libexec/tcpd/tcpdchk/tcpdchk.c new file mode 100644 index 00000000000..c15a735c779 --- /dev/null +++ b/libexec/tcpd/tcpdchk/tcpdchk.c @@ -0,0 +1,470 @@ +/* $OpenBSD: tcpdchk.c,v 1.1 1997/02/26 06:17:09 downsj Exp $ */ + + /* + * tcpdchk - examine all tcpd access control rules and inetd.conf entries + * + * Usage: tcpdchk [-a] [-d] [-i inet_conf] [-v] + * + * -a: complain about implicit "allow" at end of rule. + * + * -d: rules in current directory. + * + * -i: location of inetd.conf file. + * + * -v: show all rules. + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#) tcpdchk.c 1.8 97/02/12 02:13:25"; +#else +static char rcsid[] = "$OpenBSD: tcpdchk.c,v 1.1 1997/02/26 06:17:09 downsj Exp $"; +#endif +#endif + +/* System libraries. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef NETGROUP +#include +#endif + +#include + +#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 index 00000000000..f601a980d2b --- /dev/null +++ b/libexec/tcpd/tcpdmatch/Makefile @@ -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 diff --git a/libexec/tcpd/tcpdmatch/tcpdmatch.8 b/libexec/tcpd/tcpdmatch/tcpdmatch.8 new file mode 100644 index 00000000000..416d0d902a0 --- /dev/null +++ b/libexec/tcpd/tcpdmatch/tcpdmatch.8 @@ -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 index 00000000000..fb4fd1c242f --- /dev/null +++ b/libexec/tcpd/tcpdmatch/tcpdmatch.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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"); +}