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