tcpdchk.c revision 51495
144743Smarkm /*
244743Smarkm  * tcpdchk - examine all tcpd access control rules and inetd.conf entries
344743Smarkm  *
444743Smarkm  * Usage: tcpdchk [-a] [-d] [-i inet_conf] [-v]
544743Smarkm  *
644743Smarkm  * -a: complain about implicit "allow" at end of rule.
744743Smarkm  *
844743Smarkm  * -d: rules in current directory.
944743Smarkm  *
1044743Smarkm  * -i: location of inetd.conf file.
1144743Smarkm  *
1244743Smarkm  * -v: show all rules.
1344743Smarkm  *
1444743Smarkm  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
1551495Ssheldonh  *
1651495Ssheldonh  * $FreeBSD: head/contrib/tcp_wrappers/tcpdchk.c 51495 1999-09-21 09:09:57Z sheldonh $
1744743Smarkm  */
1844743Smarkm
1944743Smarkm#ifndef lint
2044743Smarkmstatic char sccsid[] = "@(#) tcpdchk.c 1.8 97/02/12 02:13:25";
2144743Smarkm#endif
2244743Smarkm
2344743Smarkm/* System libraries. */
2444743Smarkm
2544743Smarkm#include <sys/types.h>
2644743Smarkm#include <sys/stat.h>
2744743Smarkm#include <netinet/in.h>
2844743Smarkm#include <arpa/inet.h>
2944743Smarkm#include <stdio.h>
3044743Smarkm#include <syslog.h>
3144743Smarkm#include <setjmp.h>
3244743Smarkm#include <errno.h>
3344743Smarkm#include <netdb.h>
3444743Smarkm#include <string.h>
3544743Smarkm
3644743Smarkmextern int errno;
3744743Smarkmextern void exit();
3844743Smarkmextern int optind;
3944743Smarkmextern char *optarg;
4044743Smarkm
4144743Smarkm#ifndef INADDR_NONE
4244743Smarkm#define INADDR_NONE     (-1)		/* XXX should be 0xffffffff */
4344743Smarkm#endif
4444743Smarkm
4544743Smarkm#ifndef S_ISDIR
4644743Smarkm#define S_ISDIR(m)	(((m) & S_IFMT) == S_IFDIR)
4744743Smarkm#endif
4844743Smarkm
4944743Smarkm/* Application-specific. */
5044743Smarkm
5144743Smarkm#include "tcpd.h"
5244743Smarkm#include "inetcf.h"
5344743Smarkm#include "scaffold.h"
5444743Smarkm
5544743Smarkm /*
5644743Smarkm  * Stolen from hosts_access.c...
5744743Smarkm  */
5844743Smarkmstatic char sep[] = ", \t\n";
5944743Smarkm
6044743Smarkm#define	BUFLEN 2048
6144743Smarkm
6244743Smarkmint     resident = 0;
6344743Smarkmint     hosts_access_verbose = 0;
6444743Smarkmchar   *hosts_allow_table = HOSTS_ALLOW;
6544743Smarkmchar   *hosts_deny_table = HOSTS_DENY;
6644743Smarkmextern jmp_buf tcpd_buf;
6744743Smarkm
6844743Smarkm /*
6944743Smarkm  * Local stuff.
7044743Smarkm  */
7144743Smarkmstatic void usage();
7244743Smarkmstatic void parse_table();
7344743Smarkmstatic void print_list();
7444743Smarkmstatic void check_daemon_list();
7544743Smarkmstatic void check_client_list();
7644743Smarkmstatic void check_daemon();
7744743Smarkmstatic void check_user();
7844743Smarkmstatic int check_host();
7944743Smarkmstatic int reserved_name();
8044743Smarkm
8144743Smarkm#define PERMIT	1
8244743Smarkm#define DENY	0
8344743Smarkm
8444743Smarkm#define YES	1
8544743Smarkm#define	NO	0
8644743Smarkm
8744743Smarkmstatic int defl_verdict;
8844743Smarkmstatic char *myname;
8944743Smarkmstatic int allow_check;
9044743Smarkmstatic char *inetcf;
9144743Smarkm
9244743Smarkmint     main(argc, argv)
9344743Smarkmint     argc;
9444743Smarkmchar  **argv;
9544743Smarkm{
9644743Smarkm    struct request_info request;
9744743Smarkm    struct stat st;
9844743Smarkm    int     c;
9944743Smarkm
10044743Smarkm    myname = argv[0];
10144743Smarkm
10244743Smarkm    /*
10344743Smarkm     * Parse the JCL.
10444743Smarkm     */
10544743Smarkm    while ((c = getopt(argc, argv, "adi:v")) != EOF) {
10644743Smarkm	switch (c) {
10744743Smarkm	case 'a':
10844743Smarkm	    allow_check = 1;
10944743Smarkm	    break;
11044743Smarkm	case 'd':
11144743Smarkm	    hosts_allow_table = "hosts.allow";
11244743Smarkm	    hosts_deny_table = "hosts.deny";
11344743Smarkm	    break;
11444743Smarkm	case 'i':
11544743Smarkm	    inetcf = optarg;
11644743Smarkm	    break;
11744743Smarkm	case 'v':
11844743Smarkm	    hosts_access_verbose++;
11944743Smarkm	    break;
12044743Smarkm	default:
12144743Smarkm	    usage();
12244743Smarkm	    /* NOTREACHED */
12344743Smarkm	}
12444743Smarkm    }
12544743Smarkm    if (argc != optind)
12644743Smarkm	usage();
12744743Smarkm
12844743Smarkm    /*
12944743Smarkm     * When confusion really strikes...
13044743Smarkm     */
13144743Smarkm    if (check_path(REAL_DAEMON_DIR, &st) < 0) {
13244743Smarkm	tcpd_warn("REAL_DAEMON_DIR %s: %m", REAL_DAEMON_DIR);
13344743Smarkm    } else if (!S_ISDIR(st.st_mode)) {
13444743Smarkm	tcpd_warn("REAL_DAEMON_DIR %s is not a directory", REAL_DAEMON_DIR);
13544743Smarkm    }
13644743Smarkm
13744743Smarkm    /*
13844743Smarkm     * Process the inet configuration file (or its moral equivalent). This
13944743Smarkm     * information is used later to find references in hosts.allow/deny to
14044743Smarkm     * unwrapped services, and other possible problems.
14144743Smarkm     */
14244743Smarkm    inetcf = inet_cfg(inetcf);
14344743Smarkm    if (hosts_access_verbose)
14444743Smarkm	printf("Using network configuration file: %s\n", inetcf);
14544743Smarkm
14644743Smarkm    /*
14744743Smarkm     * These are not run from inetd but may have built-in access control.
14844743Smarkm     */
14944743Smarkm    inet_set("portmap", WR_NOT);
15044743Smarkm    inet_set("rpcbind", WR_NOT);
15144743Smarkm
15244743Smarkm    /*
15344743Smarkm     * Check accessibility of access control files.
15444743Smarkm     */
15544743Smarkm    (void) check_path(hosts_allow_table, &st);
15644743Smarkm    (void) check_path(hosts_deny_table, &st);
15744743Smarkm
15844743Smarkm    /*
15944743Smarkm     * Fake up an arbitrary service request.
16044743Smarkm     */
16144743Smarkm    request_init(&request,
16244743Smarkm		 RQ_DAEMON, "daemon_name",
16344743Smarkm		 RQ_SERVER_NAME, "server_hostname",
16444743Smarkm		 RQ_SERVER_ADDR, "server_addr",
16544743Smarkm		 RQ_USER, "user_name",
16644743Smarkm		 RQ_CLIENT_NAME, "client_hostname",
16744743Smarkm		 RQ_CLIENT_ADDR, "client_addr",
16844743Smarkm		 RQ_FILE, 1,
16944743Smarkm		 0);
17044743Smarkm
17144743Smarkm    /*
17244743Smarkm     * Examine all access-control rules.
17344743Smarkm     */
17444743Smarkm    defl_verdict = PERMIT;
17544743Smarkm    parse_table(hosts_allow_table, &request);
17644743Smarkm    defl_verdict = DENY;
17744743Smarkm    parse_table(hosts_deny_table, &request);
17844743Smarkm    return (0);
17944743Smarkm}
18044743Smarkm
18144743Smarkm/* usage - explain */
18244743Smarkm
18344743Smarkmstatic void usage()
18444743Smarkm{
18544743Smarkm    fprintf(stderr, "usage: %s [-a] [-d] [-i inet_conf] [-v]\n", myname);
18644743Smarkm    fprintf(stderr, "	-a: report rules with implicit \"ALLOW\" at end\n");
18744743Smarkm    fprintf(stderr, "	-d: use allow/deny files in current directory\n");
18844743Smarkm    fprintf(stderr, "	-i: location of inetd.conf file\n");
18944743Smarkm    fprintf(stderr, "	-v: list all rules\n");
19044743Smarkm    exit(1);
19144743Smarkm}
19244743Smarkm
19344743Smarkm/* parse_table - like table_match(), but examines _all_ entries */
19444743Smarkm
19544743Smarkmstatic void parse_table(table, request)
19644743Smarkmchar   *table;
19744743Smarkmstruct request_info *request;
19844743Smarkm{
19944743Smarkm    FILE   *fp;
20044743Smarkm    int     real_verdict;
20144743Smarkm    char    sv_list[BUFLEN];		/* becomes list of daemons */
20244743Smarkm    char   *cl_list;			/* becomes list of requests */
20344743Smarkm    char   *sh_cmd;			/* becomes optional shell command */
20444743Smarkm    char    buf[BUFSIZ];
20544743Smarkm    int     verdict;
20644743Smarkm    struct tcpd_context saved_context;
20744743Smarkm
20844743Smarkm    saved_context = tcpd_context;		/* stupid compilers */
20944743Smarkm
21044743Smarkm    if (fp = fopen(table, "r")) {
21144743Smarkm	tcpd_context.file = table;
21244743Smarkm	tcpd_context.line = 0;
21344743Smarkm	while (xgets(sv_list, sizeof(sv_list), fp)) {
21444743Smarkm	    if (sv_list[strlen(sv_list) - 1] != '\n') {
21544743Smarkm		tcpd_warn("missing newline or line too long");
21644743Smarkm		continue;
21744743Smarkm	    }
21844743Smarkm	    if (sv_list[0] == '#' || sv_list[strspn(sv_list, " \t\r\n")] == 0)
21944743Smarkm		continue;
22044743Smarkm	    if ((cl_list = split_at(sv_list, ':')) == 0) {
22144743Smarkm		tcpd_warn("missing \":\" separator");
22244743Smarkm		continue;
22344743Smarkm	    }
22444743Smarkm	    sh_cmd = split_at(cl_list, ':');
22544743Smarkm
22644743Smarkm	    if (hosts_access_verbose)
22744743Smarkm		printf("\n>>> Rule %s line %d:\n",
22844743Smarkm		       tcpd_context.file, tcpd_context.line);
22944743Smarkm
23044743Smarkm	    if (hosts_access_verbose)
23144743Smarkm		print_list("daemons:  ", sv_list);
23244743Smarkm	    check_daemon_list(sv_list);
23344743Smarkm
23444743Smarkm	    if (hosts_access_verbose)
23544743Smarkm		print_list("clients:  ", cl_list);
23644743Smarkm	    check_client_list(cl_list);
23744743Smarkm
23844743Smarkm#ifdef PROCESS_OPTIONS
23944743Smarkm	    real_verdict = defl_verdict;
24044743Smarkm	    if (sh_cmd) {
24144743Smarkm		verdict = setjmp(tcpd_buf);
24244743Smarkm		if (verdict != 0) {
24344743Smarkm		    real_verdict = (verdict == AC_PERMIT);
24444743Smarkm		} else {
24544743Smarkm		    dry_run = 1;
24644743Smarkm		    process_options(sh_cmd, request);
24744743Smarkm		    if (dry_run == 1 && real_verdict && allow_check)
24844743Smarkm			tcpd_warn("implicit \"allow\" at end of rule");
24944743Smarkm		}
25044743Smarkm	    } else if (defl_verdict && allow_check) {
25144743Smarkm		tcpd_warn("implicit \"allow\" at end of rule");
25244743Smarkm	    }
25344743Smarkm	    if (hosts_access_verbose)
25444743Smarkm		printf("access:   %s\n", real_verdict ? "granted" : "denied");
25544743Smarkm#else
25644743Smarkm	    if (sh_cmd)
25744743Smarkm		shell_cmd(percent_x(buf, sizeof(buf), sh_cmd, request));
25844743Smarkm	    if (hosts_access_verbose)
25944743Smarkm		printf("access:   %s\n", defl_verdict ? "granted" : "denied");
26044743Smarkm#endif
26144743Smarkm	}
26244743Smarkm	(void) fclose(fp);
26344743Smarkm    } else if (errno != ENOENT) {
26444743Smarkm	tcpd_warn("cannot open %s: %m", table);
26544743Smarkm    }
26644743Smarkm    tcpd_context = saved_context;
26744743Smarkm}
26844743Smarkm
26944743Smarkm/* print_list - pretty-print a list */
27044743Smarkm
27144743Smarkmstatic void print_list(title, list)
27244743Smarkmchar   *title;
27344743Smarkmchar   *list;
27444743Smarkm{
27544743Smarkm    char    buf[BUFLEN];
27644743Smarkm    char   *cp;
27744743Smarkm    char   *next;
27844743Smarkm
27944743Smarkm    fputs(title, stdout);
28044743Smarkm    strcpy(buf, list);
28144743Smarkm
28244743Smarkm    for (cp = strtok(buf, sep); cp != 0; cp = next) {
28344743Smarkm	fputs(cp, stdout);
28444743Smarkm	next = strtok((char *) 0, sep);
28544743Smarkm	if (next != 0)
28644743Smarkm	    fputs(" ", stdout);
28744743Smarkm    }
28844743Smarkm    fputs("\n", stdout);
28944743Smarkm}
29044743Smarkm
29144743Smarkm/* check_daemon_list - criticize daemon list */
29244743Smarkm
29344743Smarkmstatic void check_daemon_list(list)
29444743Smarkmchar   *list;
29544743Smarkm{
29644743Smarkm    char    buf[BUFLEN];
29744743Smarkm    char   *cp;
29844743Smarkm    char   *host;
29944743Smarkm    int     daemons = 0;
30044743Smarkm
30144743Smarkm    strcpy(buf, list);
30244743Smarkm
30344743Smarkm    for (cp = strtok(buf, sep); cp != 0; cp = strtok((char *) 0, sep)) {
30444743Smarkm	if (STR_EQ(cp, "EXCEPT")) {
30544743Smarkm	    daemons = 0;
30644743Smarkm	} else {
30744743Smarkm	    daemons++;
30844743Smarkm	    if ((host = split_at(cp + 1, '@')) != 0 && check_host(host) > 1) {
30944743Smarkm		tcpd_warn("host %s has more than one address", host);
31044743Smarkm		tcpd_warn("(consider using an address instead)");
31144743Smarkm	    }
31244743Smarkm	    check_daemon(cp);
31344743Smarkm	}
31444743Smarkm    }
31544743Smarkm    if (daemons == 0)
31644743Smarkm	tcpd_warn("daemon list is empty or ends in EXCEPT");
31744743Smarkm}
31844743Smarkm
31944743Smarkm/* check_client_list - criticize client list */
32044743Smarkm
32144743Smarkmstatic void check_client_list(list)
32244743Smarkmchar   *list;
32344743Smarkm{
32444743Smarkm    char    buf[BUFLEN];
32544743Smarkm    char   *cp;
32644743Smarkm    char   *host;
32744743Smarkm    int     clients = 0;
32844743Smarkm
32944743Smarkm    strcpy(buf, list);
33044743Smarkm
33144743Smarkm    for (cp = strtok(buf, sep); cp != 0; cp = strtok((char *) 0, sep)) {
33244743Smarkm	if (STR_EQ(cp, "EXCEPT")) {
33344743Smarkm	    clients = 0;
33444743Smarkm	} else {
33544743Smarkm	    clients++;
33644743Smarkm	    if (host = split_at(cp + 1, '@')) {	/* user@host */
33744743Smarkm		check_user(cp);
33844743Smarkm		check_host(host);
33944743Smarkm	    } else {
34044743Smarkm		check_host(cp);
34144743Smarkm	    }
34244743Smarkm	}
34344743Smarkm    }
34444743Smarkm    if (clients == 0)
34544743Smarkm	tcpd_warn("client list is empty or ends in EXCEPT");
34644743Smarkm}
34744743Smarkm
34844743Smarkm/* check_daemon - criticize daemon pattern */
34944743Smarkm
35044743Smarkmstatic void check_daemon(pat)
35144743Smarkmchar   *pat;
35244743Smarkm{
35344743Smarkm    if (pat[0] == '@') {
35444743Smarkm	tcpd_warn("%s: daemon name begins with \"@\"", pat);
35551495Ssheldonh    } else if (pat[0] == '/') {
35651495Ssheldonh	tcpd_warn("%s: daemon name begins with \"/\"", pat);
35744743Smarkm    } else if (pat[0] == '.') {
35844743Smarkm	tcpd_warn("%s: daemon name begins with dot", pat);
35944743Smarkm    } else if (pat[strlen(pat) - 1] == '.') {
36044743Smarkm	tcpd_warn("%s: daemon name ends in dot", pat);
36144743Smarkm    } else if (STR_EQ(pat, "ALL") || STR_EQ(pat, unknown)) {
36244743Smarkm	 /* void */ ;
36344743Smarkm    } else if (STR_EQ(pat, "FAIL")) {		/* obsolete */
36444743Smarkm	tcpd_warn("FAIL is no longer recognized");
36544743Smarkm	tcpd_warn("(use EXCEPT or DENY instead)");
36644743Smarkm    } else if (reserved_name(pat)) {
36744743Smarkm	tcpd_warn("%s: daemon name may be reserved word", pat);
36844743Smarkm    } else {
36944743Smarkm	switch (inet_get(pat)) {
37044743Smarkm	case WR_UNKNOWN:
37144743Smarkm	    tcpd_warn("%s: no such process name in %s", pat, inetcf);
37244743Smarkm	    inet_set(pat, WR_YES);		/* shut up next time */
37344743Smarkm	    break;
37444743Smarkm	case WR_NOT:
37544743Smarkm	    tcpd_warn("%s: service possibly not wrapped", pat);
37644743Smarkm	    inet_set(pat, WR_YES);
37744743Smarkm	    break;
37844743Smarkm	}
37944743Smarkm    }
38044743Smarkm}
38144743Smarkm
38244743Smarkm/* check_user - criticize user pattern */
38344743Smarkm
38444743Smarkmstatic void check_user(pat)
38544743Smarkmchar   *pat;
38644743Smarkm{
38744743Smarkm    if (pat[0] == '@') {			/* @netgroup */
38844743Smarkm	tcpd_warn("%s: user name begins with \"@\"", pat);
38951495Ssheldonh    } else if (pat[0] == '/') {
39051495Ssheldonh	tcpd_warn("%s: user name begins with \"/\"", pat);
39144743Smarkm    } else if (pat[0] == '.') {
39244743Smarkm	tcpd_warn("%s: user name begins with dot", pat);
39344743Smarkm    } else if (pat[strlen(pat) - 1] == '.') {
39444743Smarkm	tcpd_warn("%s: user name ends in dot", pat);
39544743Smarkm    } else if (STR_EQ(pat, "ALL") || STR_EQ(pat, unknown)
39644743Smarkm	       || STR_EQ(pat, "KNOWN")) {
39744743Smarkm	 /* void */ ;
39844743Smarkm    } else if (STR_EQ(pat, "FAIL")) {		/* obsolete */
39944743Smarkm	tcpd_warn("FAIL is no longer recognized");
40044743Smarkm	tcpd_warn("(use EXCEPT or DENY instead)");
40144743Smarkm    } else if (reserved_name(pat)) {
40244743Smarkm	tcpd_warn("%s: user name may be reserved word", pat);
40344743Smarkm    }
40444743Smarkm}
40544743Smarkm
40644743Smarkm/* check_host - criticize host pattern */
40744743Smarkm
40844743Smarkmstatic int check_host(pat)
40944743Smarkmchar   *pat;
41044743Smarkm{
41151495Ssheldonh    char    buf[BUFSIZ];
41244743Smarkm    char   *mask;
41344743Smarkm    int     addr_count = 1;
41451495Ssheldonh    FILE   *fp;
41551495Ssheldonh    struct tcpd_context saved_context;
41651495Ssheldonh    char   *cp;
41751495Ssheldonh    char   *wsp = " \t\r\n";
41844743Smarkm
41944743Smarkm    if (pat[0] == '@') {			/* @netgroup */
42044743Smarkm#ifdef NO_NETGRENT
42144743Smarkm	/* SCO has no *netgrent() support */
42244743Smarkm#else
42344743Smarkm#ifdef NETGROUP
42444743Smarkm	char   *machinep;
42544743Smarkm	char   *userp;
42644743Smarkm	char   *domainp;
42744743Smarkm
42844743Smarkm	setnetgrent(pat + 1);
42944743Smarkm	if (getnetgrent(&machinep, &userp, &domainp) == 0)
43044743Smarkm	    tcpd_warn("%s: unknown or empty netgroup", pat + 1);
43144743Smarkm	endnetgrent();
43244743Smarkm#else
43344743Smarkm	tcpd_warn("netgroup support disabled");
43444743Smarkm#endif
43544743Smarkm#endif
43651495Ssheldonh    } else if (pat[0] == '/') {			/* /path/name */
43751495Ssheldonh	if ((fp = fopen(pat, "r")) != 0) {
43851495Ssheldonh	    saved_context = tcpd_context;
43951495Ssheldonh	    tcpd_context.file = pat;
44051495Ssheldonh	    tcpd_context.line = 0;
44151495Ssheldonh	    while (fgets(buf, sizeof(buf), fp)) {
44251495Ssheldonh		tcpd_context.line++;
44351495Ssheldonh		for (cp = strtok(buf, wsp); cp; cp = strtok((char *) 0, wsp))
44451495Ssheldonh		    check_host(cp);
44551495Ssheldonh	    }
44651495Ssheldonh	    tcpd_context = saved_context;
44751495Ssheldonh	    fclose(fp);
44851495Ssheldonh	} else if (errno != ENOENT) {
44951495Ssheldonh	    tcpd_warn("open %s: %m", pat);
45051495Ssheldonh	}
45144743Smarkm    } else if (mask = split_at(pat, '/')) {	/* network/netmask */
45244743Smarkm	if (dot_quad_addr(pat) == INADDR_NONE
45344743Smarkm	    || dot_quad_addr(mask) == INADDR_NONE)
45444743Smarkm	    tcpd_warn("%s/%s: bad net/mask pattern", pat, mask);
45544743Smarkm    } else if (STR_EQ(pat, "FAIL")) {		/* obsolete */
45644743Smarkm	tcpd_warn("FAIL is no longer recognized");
45744743Smarkm	tcpd_warn("(use EXCEPT or DENY instead)");
45844743Smarkm    } else if (reserved_name(pat)) {		/* other reserved */
45944743Smarkm	 /* void */ ;
46044743Smarkm    } else if (NOT_INADDR(pat)) {		/* internet name */
46144743Smarkm	if (pat[strlen(pat) - 1] == '.') {
46244743Smarkm	    tcpd_warn("%s: domain or host name ends in dot", pat);
46344743Smarkm	} else if (pat[0] != '.') {
46444743Smarkm	    addr_count = check_dns(pat);
46544743Smarkm	}
46644743Smarkm    } else {					/* numeric form */
46744743Smarkm	if (STR_EQ(pat, "0.0.0.0") || STR_EQ(pat, "255.255.255.255")) {
46844743Smarkm	    /* void */ ;
46944743Smarkm	} else if (pat[0] == '.') {
47044743Smarkm	    tcpd_warn("%s: network number begins with dot", pat);
47144743Smarkm	} else if (pat[strlen(pat) - 1] != '.') {
47244743Smarkm	    check_dns(pat);
47344743Smarkm	}
47444743Smarkm    }
47544743Smarkm    return (addr_count);
47644743Smarkm}
47744743Smarkm
47844743Smarkm/* reserved_name - determine if name is reserved */
47944743Smarkm
48044743Smarkmstatic int reserved_name(pat)
48144743Smarkmchar   *pat;
48244743Smarkm{
48344743Smarkm    return (STR_EQ(pat, unknown)
48444743Smarkm	    || STR_EQ(pat, "KNOWN")
48544743Smarkm	    || STR_EQ(pat, paranoid)
48644743Smarkm	    || STR_EQ(pat, "ALL")
48744743Smarkm	    || STR_EQ(pat, "LOCAL"));
48844743Smarkm}
489