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