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 22static 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 */ 68static const char sep[] = ", \t\n"; 69 70#define BUFLEN 2048 71 72int resident = 0; 73int hosts_access_verbose = 0; 74const char *hosts_allow_table = HOSTS_ALLOW; 75const char *hosts_deny_table = HOSTS_DENY; 76extern jmp_buf tcpd_buf; 77 78 /* 79 * Local stuff. 80 */ 81static void usage(void); 82static void parse_table(const char *, struct request_info *); 83static void print_list(char *, char *); 84static void check_daemon_list(char *); 85static void check_client_list(char *); 86static void check_daemon(char *); 87static void check_user(char *); 88#ifdef INET6 89static int check_inet_addr(char *); 90#endif 91static int check_host(char *); 92static int reserved_name(char *); 93 94#define PERMIT 1 95#define DENY 0 96 97#define YES 1 98#define NO 0 99 100static int defl_verdict; 101static char *myname; 102static int allow_check; 103static char *inetcf; 104 105int 106main(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 195static void 196usage(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 208static void 209parse_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 282static 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 302static 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 329static 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 367static 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 398static 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 418static 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 */ 432static 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 495static 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