144743Smarkm /*
244743Smarkm  * tcpdmatch - explain what tcpd would do in a specific case
344743Smarkm  *
444743Smarkm  * usage: tcpdmatch [-d] [-i inet_conf] daemon[@host] [user@]host
544743Smarkm  *
644743Smarkm  * -d: use the access control tables in the current directory.
744743Smarkm  *
844743Smarkm  * -i: location of inetd.conf file.
944743Smarkm  *
1044743Smarkm  * All errors are reported to the standard error stream, including the errors
1144743Smarkm  * that would normally be reported via the syslog daemon.
1244743Smarkm  *
1344743Smarkm  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
1456977Sshin  *
1556977Sshin  * $FreeBSD: releng/10.3/contrib/tcp_wrappers/tcpdmatch.c 63158 2000-07-14 17:15:34Z ume $
1644743Smarkm  */
1744743Smarkm
1844743Smarkm#ifndef lint
1944743Smarkmstatic char sccsid[] = "@(#) tcpdmatch.c 1.5 96/02/11 17:01:36";
2044743Smarkm#endif
2144743Smarkm
2244743Smarkm/* System libraries. */
2344743Smarkm
2444743Smarkm#include <sys/types.h>
2544743Smarkm#include <sys/stat.h>
2644743Smarkm#include <sys/socket.h>
2744743Smarkm#include <netinet/in.h>
2844743Smarkm#include <arpa/inet.h>
2944743Smarkm#include <netdb.h>
3044743Smarkm#include <stdio.h>
3144743Smarkm#include <syslog.h>
3244743Smarkm#include <setjmp.h>
3344743Smarkm#include <string.h>
3444743Smarkm
3544743Smarkmextern void exit();
3644743Smarkmextern int optind;
3744743Smarkmextern char *optarg;
3844743Smarkm
3944743Smarkm#ifndef	INADDR_NONE
4044743Smarkm#define	INADDR_NONE	(-1)		/* XXX should be 0xffffffff */
4144743Smarkm#endif
4244743Smarkm
4344743Smarkm#ifndef S_ISDIR
4444743Smarkm#define S_ISDIR(m)	(((m) & S_IFMT) == S_IFDIR)
4544743Smarkm#endif
4644743Smarkm
4744743Smarkm/* Application-specific. */
4844743Smarkm
4944743Smarkm#include "tcpd.h"
5044743Smarkm#include "inetcf.h"
5144743Smarkm#include "scaffold.h"
5244743Smarkm
5344743Smarkmstatic void usage();
5444743Smarkmstatic void tcpdmatch();
5544743Smarkm
5644743Smarkm/* The main program */
5744743Smarkm
5844743Smarkmint     main(argc, argv)
5944743Smarkmint     argc;
6044743Smarkmchar  **argv;
6144743Smarkm{
6263158Sume#ifdef INET6
6363158Sume    struct addrinfo hints, *hp, *res;
6463158Sume#else
6544743Smarkm    struct hostent *hp;
6663158Sume#endif
6744743Smarkm    char   *myname = argv[0];
6844743Smarkm    char   *client;
6944743Smarkm    char   *server;
7044743Smarkm    char   *addr;
7144743Smarkm    char   *user;
7244743Smarkm    char   *daemon;
7344743Smarkm    struct request_info request;
7444743Smarkm    int     ch;
7544743Smarkm    char   *inetcf = 0;
7644743Smarkm    int     count;
7756977Sshin#ifdef INET6
7856977Sshin    struct sockaddr_storage server_sin;
7956977Sshin    struct sockaddr_storage client_sin;
8056977Sshin#else
8144743Smarkm    struct sockaddr_in server_sin;
8244743Smarkm    struct sockaddr_in client_sin;
8356977Sshin#endif
8444743Smarkm    struct stat st;
8544743Smarkm
8644743Smarkm    /*
8744743Smarkm     * Show what rule actually matched.
8844743Smarkm     */
8944743Smarkm    hosts_access_verbose = 2;
9044743Smarkm
9144743Smarkm    /*
9244743Smarkm     * Parse the JCL.
9344743Smarkm     */
9444743Smarkm    while ((ch = getopt(argc, argv, "di:")) != EOF) {
9544743Smarkm	switch (ch) {
9644743Smarkm	case 'd':
9744743Smarkm	    hosts_allow_table = "hosts.allow";
9844743Smarkm	    hosts_deny_table = "hosts.deny";
9944743Smarkm	    break;
10044743Smarkm	case 'i':
10144743Smarkm	    inetcf = optarg;
10244743Smarkm	    break;
10344743Smarkm	default:
10444743Smarkm	    usage(myname);
10544743Smarkm	    /* NOTREACHED */
10644743Smarkm	}
10744743Smarkm    }
10844743Smarkm    if (argc != optind + 2)
10944743Smarkm	usage(myname);
11044743Smarkm
11144743Smarkm    /*
11244743Smarkm     * When confusion really strikes...
11344743Smarkm     */
11444743Smarkm    if (check_path(REAL_DAEMON_DIR, &st) < 0) {
11544743Smarkm	tcpd_warn("REAL_DAEMON_DIR %s: %m", REAL_DAEMON_DIR);
11644743Smarkm    } else if (!S_ISDIR(st.st_mode)) {
11744743Smarkm	tcpd_warn("REAL_DAEMON_DIR %s is not a directory", REAL_DAEMON_DIR);
11844743Smarkm    }
11944743Smarkm
12044743Smarkm    /*
12144743Smarkm     * Default is to specify a daemon process name. When daemon@host is
12244743Smarkm     * specified, separate the two parts.
12344743Smarkm     */
12444743Smarkm    if ((server = split_at(argv[optind], '@')) == 0)
12544743Smarkm	server = unknown;
12644743Smarkm    if (argv[optind][0] == '/') {
12744743Smarkm	daemon = strrchr(argv[optind], '/') + 1;
12844743Smarkm	tcpd_warn("%s: daemon name normalized to: %s", argv[optind], daemon);
12944743Smarkm    } else {
13044743Smarkm	daemon = argv[optind];
13144743Smarkm    }
13244743Smarkm
13344743Smarkm    /*
13444743Smarkm     * Default is to specify a client hostname or address. When user@host is
13544743Smarkm     * specified, separate the two parts.
13644743Smarkm     */
13744743Smarkm    if ((client = split_at(argv[optind + 1], '@')) != 0) {
13844743Smarkm	user = argv[optind + 1];
13944743Smarkm    } else {
14044743Smarkm	client = argv[optind + 1];
14144743Smarkm	user = unknown;
14244743Smarkm    }
14344743Smarkm
14444743Smarkm    /*
14544743Smarkm     * Analyze the inetd (or tlid) configuration file, so that we can warn
14644743Smarkm     * the user about services that may not be wrapped, services that are not
14744743Smarkm     * configured, or services that are wrapped in an incorrect manner. Allow
14844743Smarkm     * for services that are not run from inetd, or that have tcpd access
14944743Smarkm     * control built into them.
15044743Smarkm     */
15144743Smarkm    inetcf = inet_cfg(inetcf);
15244743Smarkm    inet_set("portmap", WR_NOT);
15344743Smarkm    inet_set("rpcbind", WR_NOT);
15444743Smarkm    switch (inet_get(daemon)) {
15544743Smarkm    case WR_UNKNOWN:
15644743Smarkm	tcpd_warn("%s: no such process name in %s", daemon, inetcf);
15744743Smarkm	break;
15844743Smarkm    case WR_NOT:
15944743Smarkm	tcpd_warn("%s: service possibly not wrapped", daemon);
16044743Smarkm	break;
16144743Smarkm    }
16244743Smarkm
16344743Smarkm    /*
16444743Smarkm     * Check accessibility of access control files.
16544743Smarkm     */
16644743Smarkm    (void) check_path(hosts_allow_table, &st);
16744743Smarkm    (void) check_path(hosts_deny_table, &st);
16844743Smarkm
16944743Smarkm    /*
17044743Smarkm     * Fill in what we have figured out sofar. Use socket and DNS routines
17144743Smarkm     * for address and name conversions. We attach stdout to the request so
17244743Smarkm     * that banner messages will become visible.
17344743Smarkm     */
17444743Smarkm    request_init(&request, RQ_DAEMON, daemon, RQ_USER, user, RQ_FILE, 1, 0);
17544743Smarkm    sock_methods(&request);
17644743Smarkm
17744743Smarkm    /*
17844743Smarkm     * If a server hostname is specified, insist that the name maps to at
17944743Smarkm     * most one address. eval_hostname() warns the user about name server
18044743Smarkm     * problems, while using the request.server structure as a cache for host
18144743Smarkm     * address and name conversion results.
18244743Smarkm     */
18344743Smarkm    if (NOT_INADDR(server) == 0 || HOSTNAME_KNOWN(server)) {
18444743Smarkm	if ((hp = find_inet_addr(server)) == 0)
18544743Smarkm	    exit(1);
18663158Sume#ifndef INET6
18744743Smarkm	memset((char *) &server_sin, 0, sizeof(server_sin));
18844743Smarkm	server_sin.sin_family = AF_INET;
18956977Sshin#endif
19044743Smarkm	request_set(&request, RQ_SERVER_SIN, &server_sin, 0);
19144743Smarkm
19256977Sshin#ifdef INET6
19363158Sume	for (res = hp, count = 0; res; res = res->ai_next, count++) {
19463158Sume	    memcpy(&server_sin, res->ai_addr, res->ai_addrlen);
19556977Sshin#else
19663158Sume	for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) {
19744743Smarkm	    memcpy((char *) &server_sin.sin_addr, addr,
19844743Smarkm		   sizeof(server_sin.sin_addr));
19956977Sshin#endif
20044743Smarkm
20144743Smarkm	    /*
20244743Smarkm	     * Force evaluation of server host name and address. Host name
20344743Smarkm	     * conflicts will be reported while eval_hostname() does its job.
20444743Smarkm	     */
20544743Smarkm	    request_set(&request, RQ_SERVER_NAME, "", RQ_SERVER_ADDR, "", 0);
20644743Smarkm	    if (STR_EQ(eval_hostname(request.server), unknown))
20744743Smarkm		tcpd_warn("host address %s->name lookup failed",
20844743Smarkm			  eval_hostaddr(request.server));
20944743Smarkm	}
21044743Smarkm	if (count > 1) {
21144743Smarkm	    fprintf(stderr, "Error: %s has more than one address\n", server);
21244743Smarkm	    fprintf(stderr, "Please specify an address instead\n");
21344743Smarkm	    exit(1);
21444743Smarkm	}
21563158Sume#ifdef INET6
21663158Sume	freeaddrinfo(hp);
21763158Sume#else
21844743Smarkm	free((char *) hp);
21963158Sume#endif
22044743Smarkm    } else {
22144743Smarkm	request_set(&request, RQ_SERVER_NAME, server, 0);
22244743Smarkm    }
22344743Smarkm
22444743Smarkm    /*
22544743Smarkm     * If a client address is specified, we simulate the effect of client
22644743Smarkm     * hostname lookup failure.
22744743Smarkm     */
22844743Smarkm    if (dot_quad_addr(client) != INADDR_NONE) {
22944743Smarkm	request_set(&request, RQ_CLIENT_ADDR, client, 0);
23044743Smarkm	tcpdmatch(&request);
23144743Smarkm	exit(0);
23244743Smarkm    }
23363158Sume#ifdef INET6
23463158Sume    memset(&hints, 0, sizeof(hints));
23563158Sume    hints.ai_family = AF_INET6;
23663158Sume    hints.ai_socktype = SOCK_STREAM;
23763158Sume    hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
23863158Sume    if (getaddrinfo(client, NULL, &hints, &res) == 0) {
23963158Sume	freeaddrinfo(res);
24063158Sume	request_set(&request, RQ_CLIENT_ADDR, client, 0);
24163158Sume	tcpdmatch(&request);
24263158Sume	exit(0);
24363158Sume    }
24463158Sume#endif
24544743Smarkm
24644743Smarkm    /*
24744743Smarkm     * Perhaps they are testing special client hostname patterns that aren't
24844743Smarkm     * really host names at all.
24944743Smarkm     */
25044743Smarkm    if (NOT_INADDR(client) && HOSTNAME_KNOWN(client) == 0) {
25144743Smarkm	request_set(&request, RQ_CLIENT_NAME, client, 0);
25244743Smarkm	tcpdmatch(&request);
25344743Smarkm	exit(0);
25444743Smarkm    }
25544743Smarkm
25644743Smarkm    /*
25744743Smarkm     * Otherwise, assume that a client hostname is specified, and insist that
25844743Smarkm     * the address can be looked up. The reason for this requirement is that
25944743Smarkm     * in real life the client address is available (at least with IP). Let
26044743Smarkm     * eval_hostname() figure out if this host is properly registered, while
26144743Smarkm     * using the request.client structure as a cache for host name and
26244743Smarkm     * address conversion results.
26344743Smarkm     */
26444743Smarkm    if ((hp = find_inet_addr(client)) == 0)
26544743Smarkm	exit(1);
26656977Sshin#ifdef INET6
26763158Sume    request_set(&request, RQ_CLIENT_SIN, &client_sin, 0);
26863158Sume
26963158Sume    for (res = hp, count = 0; res; res = res->ai_next, count++) {
27063158Sume	memcpy(&client_sin, res->ai_addr, res->ai_addrlen);
27163158Sume
27263158Sume	/*
27363158Sume	 * getnameinfo() doesn't do reverse lookup against link-local
27463158Sume	 * address.  So, we pass through host name evaluation against
27563158Sume	 * such addresses.
27663158Sume	 */
27763158Sume	if (res->ai_family != AF_INET6 ||
27863158Sume	    !IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)res->ai_addr)->sin6_addr)) {
27963158Sume	    /*
28063158Sume	     * Force evaluation of client host name and address. Host name
28163158Sume	     * conflicts will be reported while eval_hostname() does its job.
28263158Sume	     */
28363158Sume	    request_set(&request, RQ_CLIENT_NAME, "", RQ_CLIENT_ADDR, "", 0);
28463158Sume	    if (STR_EQ(eval_hostname(request.client), unknown))
28563158Sume		tcpd_warn("host address %s->name lookup failed",
28663158Sume			  eval_hostaddr(request.client));
28763158Sume	}
28863158Sume	tcpdmatch(&request);
28963158Sume	if (res->ai_next)
29063158Sume	    printf("\n");
29156977Sshin    }
29263158Sume    freeaddrinfo(hp);
29356977Sshin#else
29463158Sume    memset((char *) &client_sin, 0, sizeof(client_sin));
29544743Smarkm    client_sin.sin_family = AF_INET;
29644743Smarkm    request_set(&request, RQ_CLIENT_SIN, &client_sin, 0);
29744743Smarkm
29844743Smarkm    for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) {
29944743Smarkm	memcpy((char *) &client_sin.sin_addr, addr,
30044743Smarkm	       sizeof(client_sin.sin_addr));
30144743Smarkm
30244743Smarkm	/*
30344743Smarkm	 * Force evaluation of client host name and address. Host name
30444743Smarkm	 * conflicts will be reported while eval_hostname() does its job.
30544743Smarkm	 */
30644743Smarkm	request_set(&request, RQ_CLIENT_NAME, "", RQ_CLIENT_ADDR, "", 0);
30744743Smarkm	if (STR_EQ(eval_hostname(request.client), unknown))
30844743Smarkm	    tcpd_warn("host address %s->name lookup failed",
30944743Smarkm		      eval_hostaddr(request.client));
31044743Smarkm	tcpdmatch(&request);
31144743Smarkm	if (hp->h_addr_list[count + 1])
31244743Smarkm	    printf("\n");
31344743Smarkm    }
31444743Smarkm    free((char *) hp);
31563158Sume#endif
31644743Smarkm    exit(0);
31744743Smarkm}
31844743Smarkm
31944743Smarkm/* Explain how to use this program */
32044743Smarkm
32144743Smarkmstatic void usage(myname)
32244743Smarkmchar   *myname;
32344743Smarkm{
32444743Smarkm    fprintf(stderr, "usage: %s [-d] [-i inet_conf] daemon[@host] [user@]host\n",
32544743Smarkm	    myname);
32644743Smarkm    fprintf(stderr, "	-d: use allow/deny files in current directory\n");
32744743Smarkm    fprintf(stderr, "	-i: location of inetd.conf file\n");
32844743Smarkm    exit(1);
32944743Smarkm}
33044743Smarkm
33144743Smarkm/* Print interesting expansions */
33244743Smarkm
33344743Smarkmstatic void expand(text, pattern, request)
33444743Smarkmchar   *text;
33544743Smarkmchar   *pattern;
33644743Smarkmstruct request_info *request;
33744743Smarkm{
33844743Smarkm    char    buf[BUFSIZ];
33944743Smarkm
34044743Smarkm    if (STR_NE(percent_x(buf, sizeof(buf), pattern, request), unknown))
34144743Smarkm	printf("%s %s\n", text, buf);
34244743Smarkm}
34344743Smarkm
34444743Smarkm/* Try out a (server,client) pair */
34544743Smarkm
34644743Smarkmstatic void tcpdmatch(request)
34744743Smarkmstruct request_info *request;
34844743Smarkm{
34944743Smarkm    int     verdict;
35044743Smarkm
35144743Smarkm    /*
35244743Smarkm     * Show what we really know. Suppress uninteresting noise.
35344743Smarkm     */
35444743Smarkm    expand("client:   hostname", "%n", request);
35544743Smarkm    expand("client:   address ", "%a", request);
35644743Smarkm    expand("client:   username", "%u", request);
35744743Smarkm    expand("server:   hostname", "%N", request);
35844743Smarkm    expand("server:   address ", "%A", request);
35944743Smarkm    expand("server:   process ", "%d", request);
36044743Smarkm
36144743Smarkm    /*
36244743Smarkm     * Reset stuff that might be changed by options handlers. In dry-run
36344743Smarkm     * mode, extension language routines that would not return should inform
36444743Smarkm     * us of their plan, by clearing the dry_run flag. This is a bit clumsy
36544743Smarkm     * but we must be able to verify hosts with more than one network
36644743Smarkm     * address.
36744743Smarkm     */
36844743Smarkm    rfc931_timeout = RFC931_TIMEOUT;
36944743Smarkm    allow_severity = SEVERITY;
37044743Smarkm    deny_severity = LOG_WARNING;
37144743Smarkm    dry_run = 1;
37244743Smarkm
37344743Smarkm    /*
37444743Smarkm     * When paranoid mode is enabled, access is rejected no matter what the
37544743Smarkm     * access control rules say.
37644743Smarkm     */
37744743Smarkm#ifdef PARANOID
37844743Smarkm    if (STR_EQ(eval_hostname(request->client), paranoid)) {
37944743Smarkm	printf("access:   denied (PARANOID mode)\n\n");
38044743Smarkm	return;
38144743Smarkm    }
38244743Smarkm#endif
38344743Smarkm
38444743Smarkm    /*
38544743Smarkm     * Report the access control verdict.
38644743Smarkm     */
38744743Smarkm    verdict = hosts_access(request);
38844743Smarkm    printf("access:   %s\n",
38944743Smarkm	   dry_run == 0 ? "delegated" :
39044743Smarkm	   verdict ? "granted" : "denied");
39144743Smarkm}
392