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