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