tcpdmatch.c revision 56977
190792Sgshapiro /*
2261370Sgshapiro  * tcpdmatch - explain what tcpd would do in a specific case
390792Sgshapiro  *
490792Sgshapiro  * usage: tcpdmatch [-d] [-i inet_conf] daemon[@host] [user@]host
590792Sgshapiro  *
690792Sgshapiro  * -d: use the access control tables in the current directory.
790792Sgshapiro  *
890792Sgshapiro  * -i: location of inetd.conf file.
990792Sgshapiro  *
1090792Sgshapiro  * All errors are reported to the standard error stream, including the errors
11261370Sgshapiro  * that would normally be reported via the syslog daemon.
1290792Sgshapiro  *
1390792Sgshapiro  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
1490792Sgshapiro  *
1590792Sgshapiro  * $FreeBSD: head/contrib/tcp_wrappers/tcpdmatch.c 56977 2000-02-03 10:27:03Z shin $
16  */
17
18#ifndef lint
19static char sccsid[] = "@(#) tcpdmatch.c 1.5 96/02/11 17:01:36";
20#endif
21
22/* System libraries. */
23
24#include <sys/types.h>
25#include <sys/stat.h>
26#include <sys/socket.h>
27#include <netinet/in.h>
28#include <arpa/inet.h>
29#include <netdb.h>
30#include <stdio.h>
31#include <syslog.h>
32#include <setjmp.h>
33#include <string.h>
34
35extern void exit();
36extern int optind;
37extern char *optarg;
38
39#ifndef	INADDR_NONE
40#define	INADDR_NONE	(-1)		/* XXX should be 0xffffffff */
41#endif
42
43#ifndef S_ISDIR
44#define S_ISDIR(m)	(((m) & S_IFMT) == S_IFDIR)
45#endif
46
47/* Application-specific. */
48
49#include "tcpd.h"
50#include "inetcf.h"
51#include "scaffold.h"
52
53static void usage();
54static void tcpdmatch();
55
56/* The main program */
57
58int     main(argc, argv)
59int     argc;
60char  **argv;
61{
62    struct hostent *hp;
63    char   *myname = argv[0];
64    char   *client;
65    char   *server;
66    char   *addr;
67    char   *user;
68    char   *daemon;
69    struct request_info request;
70    int     ch;
71    char   *inetcf = 0;
72    int     count;
73#ifdef INET6
74    struct sockaddr_storage server_sin;
75    struct sockaddr_storage client_sin;
76    char *ap;
77    int alen;
78#else
79    struct sockaddr_in server_sin;
80    struct sockaddr_in client_sin;
81#endif
82    struct stat st;
83
84    /*
85     * Show what rule actually matched.
86     */
87    hosts_access_verbose = 2;
88
89    /*
90     * Parse the JCL.
91     */
92    while ((ch = getopt(argc, argv, "di:")) != EOF) {
93	switch (ch) {
94	case 'd':
95	    hosts_allow_table = "hosts.allow";
96	    hosts_deny_table = "hosts.deny";
97	    break;
98	case 'i':
99	    inetcf = optarg;
100	    break;
101	default:
102	    usage(myname);
103	    /* NOTREACHED */
104	}
105    }
106    if (argc != optind + 2)
107	usage(myname);
108
109    /*
110     * When confusion really strikes...
111     */
112    if (check_path(REAL_DAEMON_DIR, &st) < 0) {
113	tcpd_warn("REAL_DAEMON_DIR %s: %m", REAL_DAEMON_DIR);
114    } else if (!S_ISDIR(st.st_mode)) {
115	tcpd_warn("REAL_DAEMON_DIR %s is not a directory", REAL_DAEMON_DIR);
116    }
117
118    /*
119     * Default is to specify a daemon process name. When daemon@host is
120     * specified, separate the two parts.
121     */
122    if ((server = split_at(argv[optind], '@')) == 0)
123	server = unknown;
124    if (argv[optind][0] == '/') {
125	daemon = strrchr(argv[optind], '/') + 1;
126	tcpd_warn("%s: daemon name normalized to: %s", argv[optind], daemon);
127    } else {
128	daemon = argv[optind];
129    }
130
131    /*
132     * Default is to specify a client hostname or address. When user@host is
133     * specified, separate the two parts.
134     */
135    if ((client = split_at(argv[optind + 1], '@')) != 0) {
136	user = argv[optind + 1];
137    } else {
138	client = argv[optind + 1];
139	user = unknown;
140    }
141
142    /*
143     * Analyze the inetd (or tlid) configuration file, so that we can warn
144     * the user about services that may not be wrapped, services that are not
145     * configured, or services that are wrapped in an incorrect manner. Allow
146     * for services that are not run from inetd, or that have tcpd access
147     * control built into them.
148     */
149    inetcf = inet_cfg(inetcf);
150    inet_set("portmap", WR_NOT);
151    inet_set("rpcbind", WR_NOT);
152    switch (inet_get(daemon)) {
153    case WR_UNKNOWN:
154	tcpd_warn("%s: no such process name in %s", daemon, inetcf);
155	break;
156    case WR_NOT:
157	tcpd_warn("%s: service possibly not wrapped", daemon);
158	break;
159    }
160
161    /*
162     * Check accessibility of access control files.
163     */
164    (void) check_path(hosts_allow_table, &st);
165    (void) check_path(hosts_deny_table, &st);
166
167    /*
168     * Fill in what we have figured out sofar. Use socket and DNS routines
169     * for address and name conversions. We attach stdout to the request so
170     * that banner messages will become visible.
171     */
172    request_init(&request, RQ_DAEMON, daemon, RQ_USER, user, RQ_FILE, 1, 0);
173    sock_methods(&request);
174
175    /*
176     * If a server hostname is specified, insist that the name maps to at
177     * most one address. eval_hostname() warns the user about name server
178     * problems, while using the request.server structure as a cache for host
179     * address and name conversion results.
180     */
181    if (NOT_INADDR(server) == 0 || HOSTNAME_KNOWN(server)) {
182	if ((hp = find_inet_addr(server)) == 0)
183	    exit(1);
184	memset((char *) &server_sin, 0, sizeof(server_sin));
185#ifdef INET6
186	server_sin.ss_family = hp->h_addrtype;
187	switch (hp->h_addrtype) {
188	case AF_INET:
189	    ap = (char *)&((struct sockaddr_in *)&server_sin)->sin_addr;
190	    alen = sizeof(struct sockaddr_in);
191	    break;
192	case AF_INET6:
193	    ap = (char *)&((struct sockaddr_in6 *)&server_sin)->sin6_addr;
194	    alen = sizeof(struct sockaddr_in6);
195	    break;
196	default:
197	    exit(1);
198	}
199#ifdef SIN6_LEN
200	server_sin.ss_len = alen;
201#endif
202#else
203	server_sin.sin_family = AF_INET;
204#endif
205	request_set(&request, RQ_SERVER_SIN, &server_sin, 0);
206
207	for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) {
208#ifdef INET6
209	    memcpy(ap, addr, alen);
210#else
211	    memcpy((char *) &server_sin.sin_addr, addr,
212		   sizeof(server_sin.sin_addr));
213#endif
214
215	    /*
216	     * Force evaluation of server host name and address. Host name
217	     * conflicts will be reported while eval_hostname() does its job.
218	     */
219	    request_set(&request, RQ_SERVER_NAME, "", RQ_SERVER_ADDR, "", 0);
220	    if (STR_EQ(eval_hostname(request.server), unknown))
221		tcpd_warn("host address %s->name lookup failed",
222			  eval_hostaddr(request.server));
223	}
224	if (count > 1) {
225	    fprintf(stderr, "Error: %s has more than one address\n", server);
226	    fprintf(stderr, "Please specify an address instead\n");
227	    exit(1);
228	}
229	free((char *) hp);
230    } else {
231	request_set(&request, RQ_SERVER_NAME, server, 0);
232    }
233
234    /*
235     * If a client address is specified, we simulate the effect of client
236     * hostname lookup failure.
237     */
238    if (dot_quad_addr(client) != INADDR_NONE) {
239	request_set(&request, RQ_CLIENT_ADDR, client, 0);
240	tcpdmatch(&request);
241	exit(0);
242    }
243
244    /*
245     * Perhaps they are testing special client hostname patterns that aren't
246     * really host names at all.
247     */
248    if (NOT_INADDR(client) && HOSTNAME_KNOWN(client) == 0) {
249	request_set(&request, RQ_CLIENT_NAME, client, 0);
250	tcpdmatch(&request);
251	exit(0);
252    }
253
254    /*
255     * Otherwise, assume that a client hostname is specified, and insist that
256     * the address can be looked up. The reason for this requirement is that
257     * in real life the client address is available (at least with IP). Let
258     * eval_hostname() figure out if this host is properly registered, while
259     * using the request.client structure as a cache for host name and
260     * address conversion results.
261     */
262    if ((hp = find_inet_addr(client)) == 0)
263	exit(1);
264    memset((char *) &client_sin, 0, sizeof(client_sin));
265#ifdef INET6
266    client_sin.ss_family = hp->h_addrtype;
267    switch (hp->h_addrtype) {
268    case AF_INET:
269	ap = (char *)&((struct sockaddr_in *)&client_sin)->sin_addr;
270	alen = sizeof(struct sockaddr_in);
271	break;
272    case AF_INET6:
273	ap = (char *)&((struct sockaddr_in6 *)&client_sin)->sin6_addr;
274	alen = sizeof(struct sockaddr_in6);
275	break;
276    default:
277	exit(1);
278    }
279#ifdef SIN6_LEN
280    client_sin.ss_len = alen;
281#endif
282#else
283    client_sin.sin_family = AF_INET;
284#endif
285    request_set(&request, RQ_CLIENT_SIN, &client_sin, 0);
286
287    for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) {
288#ifdef INET6
289	memcpy(ap, addr, alen);
290#else
291	memcpy((char *) &client_sin.sin_addr, addr,
292	       sizeof(client_sin.sin_addr));
293#endif
294
295	/*
296	 * Force evaluation of client host name and address. Host name
297	 * conflicts will be reported while eval_hostname() does its job.
298	 */
299	request_set(&request, RQ_CLIENT_NAME, "", RQ_CLIENT_ADDR, "", 0);
300	if (STR_EQ(eval_hostname(request.client), unknown))
301	    tcpd_warn("host address %s->name lookup failed",
302		      eval_hostaddr(request.client));
303	tcpdmatch(&request);
304	if (hp->h_addr_list[count + 1])
305	    printf("\n");
306    }
307    free((char *) hp);
308    exit(0);
309}
310
311/* Explain how to use this program */
312
313static void usage(myname)
314char   *myname;
315{
316    fprintf(stderr, "usage: %s [-d] [-i inet_conf] daemon[@host] [user@]host\n",
317	    myname);
318    fprintf(stderr, "	-d: use allow/deny files in current directory\n");
319    fprintf(stderr, "	-i: location of inetd.conf file\n");
320    exit(1);
321}
322
323/* Print interesting expansions */
324
325static void expand(text, pattern, request)
326char   *text;
327char   *pattern;
328struct request_info *request;
329{
330    char    buf[BUFSIZ];
331
332    if (STR_NE(percent_x(buf, sizeof(buf), pattern, request), unknown))
333	printf("%s %s\n", text, buf);
334}
335
336/* Try out a (server,client) pair */
337
338static void tcpdmatch(request)
339struct request_info *request;
340{
341    int     verdict;
342
343    /*
344     * Show what we really know. Suppress uninteresting noise.
345     */
346    expand("client:   hostname", "%n", request);
347    expand("client:   address ", "%a", request);
348    expand("client:   username", "%u", request);
349    expand("server:   hostname", "%N", request);
350    expand("server:   address ", "%A", request);
351    expand("server:   process ", "%d", request);
352
353    /*
354     * Reset stuff that might be changed by options handlers. In dry-run
355     * mode, extension language routines that would not return should inform
356     * us of their plan, by clearing the dry_run flag. This is a bit clumsy
357     * but we must be able to verify hosts with more than one network
358     * address.
359     */
360    rfc931_timeout = RFC931_TIMEOUT;
361    allow_severity = SEVERITY;
362    deny_severity = LOG_WARNING;
363    dry_run = 1;
364
365    /*
366     * When paranoid mode is enabled, access is rejected no matter what the
367     * access control rules say.
368     */
369#ifdef PARANOID
370    if (STR_EQ(eval_hostname(request->client), paranoid)) {
371	printf("access:   denied (PARANOID mode)\n\n");
372	return;
373    }
374#endif
375
376    /*
377     * Report the access control verdict.
378     */
379    verdict = hosts_access(request);
380    printf("access:   %s\n",
381	   dry_run == 0 ? "delegated" :
382	   verdict ? "granted" : "denied");
383}
384