Home | History | Annotate | Line # | Download | only in tcpdchk
      1 /*	$NetBSD: tcpdchk.c,v 1.13 2018/01/23 21:06:26 sevan Exp $	*/
      2 
      3  /*
      4   * tcpdchk - examine all tcpd access control rules and inetd.conf entries
      5   *
      6   * Usage: tcpdchk [-a] [-d] [-i inet_conf] [-v]
      7   *
      8   * -a: complain about implicit "allow" at end of rule.
      9   *
     10   * -d: rules in current directory.
     11   *
     12   * -i: location of inetd.conf file.
     13   *
     14   * -v: show all rules.
     15   *
     16   * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
     17   */
     18 
     19 #include <sys/cdefs.h>
     20 #ifndef lint
     21 #if 0
     22 static char sccsid[] = "@(#) tcpdchk.c 1.8 97/02/12 02:13:25";
     23 #else
     24 __RCSID("$NetBSD: tcpdchk.c,v 1.13 2018/01/23 21:06:26 sevan Exp $");
     25 #endif
     26 #endif
     27 
     28 /* System libraries. */
     29 
     30 #include <sys/types.h>
     31 #include <sys/stat.h>
     32 #include <netinet/in.h>
     33 #include <arpa/inet.h>
     34 #include <stdio.h>
     35 #include <syslog.h>
     36 #include <setjmp.h>
     37 #include <errno.h>
     38 #include <netdb.h>
     39 #include <string.h>
     40 #include <stdlib.h>
     41 #include <unistd.h>
     42 
     43 #ifndef INADDR_NONE
     44 #define INADDR_NONE     (-1)		/* XXX should be 0xffffffff */
     45 #endif
     46 
     47 #ifndef S_ISDIR
     48 #define S_ISDIR(m)	(((m) & S_IFMT) == S_IFDIR)
     49 #endif
     50 
     51 /* Application-specific. */
     52 
     53 #include "tcpd.h"
     54 #include "inetcf.h"
     55 #include "scaffold.h"
     56 
     57 #ifdef NO_NETGRENT
     58 	/* SCO has no *netgrent() support */
     59 #else
     60 # ifdef NETGROUP
     61 #  include <netgroup.h>
     62 # endif
     63 #endif
     64 
     65  /*
     66   * Stolen from hosts_access.c...
     67   */
     68 static const char sep[] = ", \t\n";
     69 
     70 #define	BUFLEN 2048
     71 
     72 int     resident = 0;
     73 int     hosts_access_verbose = 0;
     74 const char *hosts_allow_table = HOSTS_ALLOW;
     75 const char *hosts_deny_table = HOSTS_DENY;
     76 extern jmp_buf tcpd_buf;
     77 
     78  /*
     79   * Local stuff.
     80   */
     81 static void usage(void);
     82 static void parse_table(const char *, struct request_info *);
     83 static void print_list(char *, char *);
     84 static void check_daemon_list(char *);
     85 static void check_client_list(char *);
     86 static void check_daemon(char *);
     87 static void check_user(char *);
     88 #ifdef INET6
     89 static int check_inet_addr(char *);
     90 #endif
     91 static int check_host(char *);
     92 static int reserved_name(char *);
     93 
     94 #define PERMIT	1
     95 #define DENY	0
     96 
     97 #define YES	1
     98 #define	NO	0
     99 
    100 static int defl_verdict;
    101 static char *myname;
    102 static int allow_check;
    103 static char *inetcf;
    104 
    105 int
    106 main(int argc, char **argv)
    107 {
    108     struct request_info request;
    109     struct stat st;
    110     int     c;
    111 
    112     myname = argv[0];
    113 
    114     /*
    115      * Parse the JCL.
    116      */
    117     while ((c = getopt(argc, argv, "adi:v")) != -1) {
    118 	switch (c) {
    119 	case 'a':
    120 	    allow_check = 1;
    121 	    break;
    122 	case 'd':
    123 	    hosts_allow_table = "hosts.allow";
    124 	    hosts_deny_table = "hosts.deny";
    125 	    break;
    126 	case 'i':
    127 	    inetcf = optarg;
    128 	    break;
    129 	case 'v':
    130 	    hosts_access_verbose++;
    131 	    break;
    132 	default:
    133 	    usage();
    134 	    /* NOTREACHED */
    135 	}
    136     }
    137     if (argc != optind)
    138 	usage();
    139 
    140     /*
    141      * When confusion really strikes...
    142      */
    143     if (check_path(REAL_DAEMON_DIR, &st) < 0) {
    144 	tcpd_warn("REAL_DAEMON_DIR %s: %m", REAL_DAEMON_DIR);
    145     } else if (!S_ISDIR(st.st_mode)) {
    146 	tcpd_warn("REAL_DAEMON_DIR %s is not a directory", REAL_DAEMON_DIR);
    147     }
    148 
    149     /*
    150      * Process the inet configuration file (or its moral equivalent). This
    151      * information is used later to find references in hosts.allow/deny to
    152      * unwrapped services, and other possible problems.
    153      */
    154     inetcf = inet_cfg(inetcf);
    155     if (hosts_access_verbose)
    156 	printf("Using network configuration file: %s\n", inetcf);
    157 
    158     /*
    159      * These are not run from inetd but may have built-in access control.
    160      */
    161     inet_set("portmap", WR_NOT);
    162     inet_set("rpcbind", WR_NOT);
    163 
    164     /*
    165      * Check accessibility of access control files.
    166      */
    167     (void) check_path(hosts_allow_table, &st);
    168     (void) check_path(hosts_deny_table, &st);
    169 
    170     /*
    171      * Fake up an arbitrary service request.
    172      */
    173     request_init(&request,
    174 		 RQ_DAEMON, "daemon_name",
    175 		 RQ_SERVER_NAME, "server_hostname",
    176 		 RQ_SERVER_ADDR, "server_addr",
    177 		 RQ_USER, "user_name",
    178 		 RQ_CLIENT_NAME, "client_hostname",
    179 		 RQ_CLIENT_ADDR, "client_addr",
    180 		 RQ_FILE, 1,
    181 		 0);
    182 
    183     /*
    184      * Examine all access-control rules.
    185      */
    186     defl_verdict = PERMIT;
    187     parse_table(hosts_allow_table, &request);
    188     defl_verdict = DENY;
    189     parse_table(hosts_deny_table, &request);
    190     return (0);
    191 }
    192 
    193 /* usage - explain */
    194 
    195 static void
    196 usage(void)
    197 {
    198     fprintf(stderr, "usage: %s [-a] [-d] [-i inet_conf] [-v]\n", myname);
    199     fprintf(stderr, "	-a: report rules with implicit \"ALLOW\" at end\n");
    200     fprintf(stderr, "	-d: use allow/deny files in current directory\n");
    201     fprintf(stderr, "	-i: location of inetd.conf file\n");
    202     fprintf(stderr, "	-v: list all rules\n");
    203     exit(1);
    204 }
    205 
    206 /* parse_table - like table_match(), but examines _all_ entries */
    207 
    208 static void
    209 parse_table(const char *table, struct request_info *request)
    210 {
    211     FILE   *fp;
    212     volatile int     real_verdict;
    213     char    sv_list[BUFLEN];		/* becomes list of daemons */
    214     char   *cl_list;			/* becomes list of requests */
    215     char   *sh_cmd;			/* becomes optional shell command */
    216     int     verdict;
    217     volatile struct tcpd_context saved_context;
    218 
    219     saved_context = tcpd_context;		/* stupid compilers */
    220 
    221     if ((fp = fopen(table, "r")) != NULL) {
    222 	tcpd_context.file = table;
    223 	tcpd_context.line = 0;
    224 	while (xgets(sv_list, sizeof(sv_list), fp)) {
    225 	    if (sv_list[strlen(sv_list) - 1] != '\n') {
    226 		tcpd_warn("missing newline or line too long");
    227 		continue;
    228 	    }
    229 	    if (sv_list[0] == '#' || sv_list[strspn(sv_list, " \t\r\n")] == 0)
    230 		continue;
    231 	    if ((cl_list = split_at(sv_list, ':')) == 0) {
    232 		tcpd_warn("missing \":\" separator");
    233 		continue;
    234 	    }
    235 	    sh_cmd = split_at(cl_list, ':');
    236 
    237 	    if (hosts_access_verbose)
    238 		printf("\n>>> Rule %s line %d:\n",
    239 		       tcpd_context.file, tcpd_context.line);
    240 
    241 	    if (hosts_access_verbose)
    242 		print_list("daemons:  ", sv_list);
    243 	    check_daemon_list(sv_list);
    244 
    245 	    if (hosts_access_verbose)
    246 		print_list("clients:  ", cl_list);
    247 	    check_client_list(cl_list);
    248 
    249 #ifdef PROCESS_OPTIONS
    250 	    real_verdict = defl_verdict;
    251 	    if (sh_cmd) {
    252 		verdict = setjmp(tcpd_buf);
    253 		if (verdict != 0) {
    254 		    real_verdict = (verdict == AC_PERMIT);
    255 		} else {
    256 		    dry_run = 1;
    257 		    process_options(sh_cmd, request);
    258 		    if (dry_run == 1 && real_verdict && allow_check)
    259 			tcpd_warn("implicit \"allow\" at end of rule");
    260 		}
    261 	    } else if (defl_verdict && allow_check) {
    262 		tcpd_warn("implicit \"allow\" at end of rule");
    263 	    }
    264 	    if (hosts_access_verbose)
    265 		printf("access:   %s\n", real_verdict ? "granted" : "denied");
    266 #else
    267 	    if (sh_cmd)
    268 		shell_cmd(percent_x(buf, sizeof(buf), sh_cmd, request));
    269 	    if (hosts_access_verbose)
    270 		printf("access:   %s\n", defl_verdict ? "granted" : "denied");
    271 #endif
    272 	}
    273 	(void) fclose(fp);
    274     } else if (errno != ENOENT) {
    275 	tcpd_warn("cannot open %s: %m", table);
    276     }
    277     tcpd_context = saved_context;
    278 }
    279 
    280 /* print_list - pretty-print a list */
    281 
    282 static void print_list(char *title, char *list)
    283 {
    284     char    buf[BUFLEN];
    285     char   *cp;
    286     char   *next;
    287 
    288     fputs(title, stdout);
    289     strlcpy(buf, list, sizeof(buf));
    290 
    291     for (cp = strtok(buf, sep); cp != 0; cp = next) {
    292 	fputs(cp, stdout);
    293 	next = strtok((char *) 0, sep);
    294 	if (next != 0)
    295 	    fputs(" ", stdout);
    296     }
    297     fputs("\n", stdout);
    298 }
    299 
    300 /* check_daemon_list - criticize daemon list */
    301 
    302 static void check_daemon_list(char *list)
    303 {
    304     char    buf[BUFLEN];
    305     char   *cp;
    306     char   *host;
    307     int     daemons = 0;
    308 
    309     strlcpy(buf, list, sizeof(buf));
    310 
    311     for (cp = strtok(buf, sep); cp != 0; cp = strtok((char *) 0, sep)) {
    312 	if (STR_EQ(cp, "EXCEPT")) {
    313 	    daemons = 0;
    314 	} else {
    315 	    daemons++;
    316 	    if ((host = split_at(cp + 1, '@')) != 0 && check_host(host) > 1) {
    317 		tcpd_warn("host %s has more than one address", host);
    318 		tcpd_warn("(consider using an address instead)");
    319 	    }
    320 	    check_daemon(cp);
    321 	}
    322     }
    323     if (daemons == 0)
    324 	tcpd_warn("daemon list is empty or ends in EXCEPT");
    325 }
    326 
    327 /* check_client_list - criticize client list */
    328 
    329 static void check_client_list(char *list)
    330 {
    331     char    buf[BUFLEN];
    332     char   *cp;
    333     char   *host;
    334     int     clients = 0;
    335 #ifdef INET6
    336     int l;
    337 #endif
    338 
    339     strlcpy(buf, list, sizeof(buf));
    340 
    341     for (cp = strtok(buf, sep); cp != 0; cp = strtok((char *) 0, sep)) {
    342 #ifdef INET6
    343 	l = strlen(cp);
    344 	if (cp[0] == '[' && cp[l - 1] == ']') {
    345 	    cp[l - 1] = '\0';
    346 	    cp++;
    347 	}
    348 #endif
    349 	if (STR_EQ(cp, "EXCEPT")) {
    350 	    clients = 0;
    351 	} else {
    352 	    clients++;
    353 	    if ((host = split_at(cp + 1, '@')) != NULL) {	/* user@host */
    354 		check_user(cp);
    355 		check_host(host);
    356 	    } else {
    357 		check_host(cp);
    358 	    }
    359 	}
    360     }
    361     if (clients == 0)
    362 	tcpd_warn("client list is empty or ends in EXCEPT");
    363 }
    364 
    365 /* check_daemon - criticize daemon pattern */
    366 
    367 static void check_daemon(char *pat)
    368 {
    369     if (pat[0] == '@') {
    370 	tcpd_warn("%s: daemon name begins with \"@\"", pat);
    371     } else if (pat[0] == '.') {
    372 	tcpd_warn("%s: daemon name begins with dot", pat);
    373     } else if (pat[strlen(pat) - 1] == '.') {
    374 	tcpd_warn("%s: daemon name ends in dot", pat);
    375     } else if (STR_EQ(pat, "ALL") || STR_EQ(pat, unknown)) {
    376 	 /* void */ ;
    377     } else if (STR_EQ(pat, "FAIL")) {		/* obsolete */
    378 	tcpd_warn("FAIL is no longer recognized");
    379 	tcpd_warn("(use EXCEPT or DENY instead)");
    380     } else if (reserved_name(pat)) {
    381 	tcpd_warn("%s: daemon name may be reserved word", pat);
    382     } else {
    383 	switch (inet_get(pat)) {
    384 	case WR_UNKNOWN:
    385 	    tcpd_warn("%s: no such process name in %s", pat, inetcf);
    386 	    inet_set(pat, WR_YES);		/* shut up next time */
    387 	    break;
    388 	case WR_NOT:
    389 	    tcpd_warn("%s: service possibly not wrapped", pat);
    390 	    inet_set(pat, WR_YES);
    391 	    break;
    392 	}
    393     }
    394 }
    395 
    396 /* check_user - criticize user pattern */
    397 
    398 static void check_user(char *pat)
    399 {
    400     if (pat[0] == '@') {			/* @netgroup */
    401 	tcpd_warn("%s: user name begins with \"@\"", pat);
    402     } else if (pat[0] == '.') {
    403 	tcpd_warn("%s: user name begins with dot", pat);
    404     } else if (pat[strlen(pat) - 1] == '.') {
    405 	tcpd_warn("%s: user name ends in dot", pat);
    406     } else if (STR_EQ(pat, "ALL") || STR_EQ(pat, unknown)
    407 	       || STR_EQ(pat, "KNOWN")) {
    408 	 /* void */ ;
    409     } else if (STR_EQ(pat, "FAIL")) {		/* obsolete */
    410 	tcpd_warn("FAIL is no longer recognized");
    411 	tcpd_warn("(use EXCEPT or DENY instead)");
    412     } else if (reserved_name(pat)) {
    413 	tcpd_warn("%s: user name may be reserved word", pat);
    414     }
    415 }
    416 
    417 #ifdef INET6
    418 static int check_inet_addr(char *pat)
    419 {
    420 	struct addrinfo *res;
    421 
    422 	res = find_inet_addr(pat, AI_NUMERICHOST);
    423 	if (res) {
    424 		freeaddrinfo(res);
    425 		return 1;
    426 	} else
    427 		return 0;
    428 }
    429 #endif
    430 
    431 /* check_host - criticize host pattern */
    432 static int check_host(char *pat)
    433 {
    434     char   *mask;
    435     int     addr_count = 1;
    436 
    437     if (pat[0] == '@') {			/* @netgroup */
    438 #ifdef NO_NETGRENT
    439 	/* SCO has no *netgrent() support */
    440 #else
    441 #ifdef NETGROUP
    442 	const char   *machinep;
    443 	const char   *userp;
    444 	const char   *domainp;
    445 
    446 	setnetgrent(pat + 1);
    447 	if (getnetgrent(&machinep, &userp, &domainp) == 0)
    448 	    tcpd_warn("%s: unknown or empty netgroup", pat + 1);
    449 	endnetgrent();
    450 #else
    451 	tcpd_warn("netgroup support disabled");
    452 #endif
    453 #endif
    454     } else if ((mask = split_at(pat, '/')) != NULL) {	/* network/netmask */
    455 #ifdef INET6
    456 	char *ep;
    457 #endif
    458 	if (dot_quad_addr(pat, NULL) != INADDR_NONE
    459 	    || dot_quad_addr(mask, NULL) != INADDR_NONE)
    460 	    ; /*okay*/
    461 #ifdef INET6
    462 	else if (check_inet_addr(pat) && check_inet_addr(mask))
    463 	    ; /*okay*/
    464 	else if (check_inet_addr(pat) &&
    465 	    (ep = NULL, strtoul(mask, &ep, 10), ep && !*ep))
    466 	    ; /*okay*/
    467 #endif
    468 	else
    469 	    tcpd_warn("%s/%s: bad net/mask pattern", pat, mask);
    470     } else if (STR_EQ(pat, "FAIL")) {		/* obsolete */
    471 	tcpd_warn("FAIL is no longer recognized");
    472 	tcpd_warn("(use EXCEPT or DENY instead)");
    473     } else if (reserved_name(pat)) {		/* other reserved */
    474 	 /* void */ ;
    475     } else if (NOT_INADDR(pat)) {		/* internet name */
    476 	if (pat[strlen(pat) - 1] == '.') {
    477 	    tcpd_warn("%s: domain or host name ends in dot", pat);
    478 	} else if (pat[0] != '.') {
    479 	    addr_count = check_dns(pat);
    480 	}
    481     } else {					/* numeric form */
    482 	if (STR_EQ(pat, "0.0.0.0") || STR_EQ(pat, "255.255.255.255")) {
    483 	    /* void */ ;
    484 	} else if (pat[0] == '.') {
    485 	    tcpd_warn("%s: network number begins with dot", pat);
    486 	} else if (pat[strlen(pat) - 1] != '.') {
    487 	    check_dns(pat);
    488 	}
    489     }
    490     return (addr_count);
    491 }
    492 
    493 /* reserved_name - determine if name is reserved */
    494 
    495 static int reserved_name(char *pat)
    496 {
    497     return (STR_EQ(pat, unknown)
    498 	    || STR_EQ(pat, "KNOWN")
    499 	    || STR_EQ(pat, paranoid)
    500 	    || STR_EQ(pat, "ALL")
    501 	    || STR_EQ(pat, "LOCAL"));
    502 }
    503