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