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