1 /* $NetBSD: parse.c,v 1.2 2021/10/12 22:51:28 rillig Exp $ */ 2 3 /*- 4 * Copyright (c) 1998, 2003 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility, 9 * NASA Ames Research Center and by Matthias Scheler. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 /* 34 * Copyright (c) 1983, 1991, 1993, 1994 35 * The Regents of the University of California. All rights reserved. 36 * 37 * Redistribution and use in source and binary forms, with or without 38 * modification, are permitted provided that the following conditions 39 * are met: 40 * 1. Redistributions of source code must retain the above copyright 41 * notice, this list of conditions and the following disclaimer. 42 * 2. Redistributions in binary form must reproduce the above copyright 43 * notice, this list of conditions and the following disclaimer in the 44 * documentation and/or other materials provided with the distribution. 45 * 3. Neither the name of the University nor the names of its contributors 46 * may be used to endorse or promote products derived from this software 47 * without specific prior written permission. 48 * 49 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 50 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 51 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 52 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 53 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 54 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 55 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 56 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 57 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 58 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 59 * SUCH DAMAGE. 60 */ 61 62 #include <sys/cdefs.h> 63 #ifndef lint 64 #if 0 65 static char sccsid[] = "@(#)inetd.c 8.4 (Berkeley) 4/13/94"; 66 #else 67 __RCSID("$NetBSD: parse.c,v 1.2 2021/10/12 22:51:28 rillig Exp $"); 68 #endif 69 #endif /* not lint */ 70 71 /* 72 * This file contains code and state for loading and managing servtabs. 73 * The "positional" syntax parsing is performed in this file. See parse_v2.c 74 * for "key-values" syntax parsing. 75 */ 76 77 #include <sys/param.h> 78 #include <sys/stat.h> 79 #include <sys/socket.h> 80 #include <sys/queue.h> 81 82 #include <ctype.h> 83 #include <err.h> 84 #include <errno.h> 85 #include <fcntl.h> 86 #include <glob.h> 87 #include <libgen.h> 88 #include <stdio.h> 89 #include <stdlib.h> 90 #include <string.h> 91 #include <syslog.h> 92 #include <unistd.h> 93 94 #include "inetd.h" 95 96 static void config(void); 97 static void endconfig(void); 98 static struct servtab *enter(struct servtab *); 99 static struct servtab *getconfigent(char **); 100 #ifdef DEBUG_ENABLE 101 static void print_service(const char *, struct servtab *); 102 #endif 103 static struct servtab init_servtab(void); 104 static void include_configs(char *); 105 static int glob_error(const char *, int); 106 static void read_glob_configs(char *); 107 static void prepare_next_config(const char*); 108 static bool is_same_service(const struct servtab *, const struct servtab *); 109 static char *gen_file_pattern(const char *, const char *); 110 static bool check_no_reinclude(const char *); 111 static void include_matched_path(char *); 112 static void purge_unchecked(void); 113 static void freeconfig(struct servtab *); 114 static char *skip(char **); 115 116 size_t line_number; 117 FILE *fconfig; 118 /* Temporary storage for new servtab */ 119 static struct servtab serv; 120 /* Current line from current config file */ 121 static char line[LINE_MAX]; 122 char *defhost; 123 #ifdef IPSEC 124 char *policy; 125 #endif 126 127 /* 128 * Recursively merge loaded service definitions with any defined 129 * in the current or included config files. 130 */ 131 static void 132 config(void) 133 { 134 struct servtab *sep, *cp; 135 /* 136 * Current position in line, used with key-values notation, 137 * saves cp across getconfigent calls. 138 */ 139 char *current_pos; 140 size_t n; 141 142 /* open config file from beginning */ 143 fconfig = fopen(CONFIG, "r"); 144 if (fconfig == NULL) { 145 DPRINTF("Could not open file \"%s\": %s", 146 CONFIG, strerror(errno)); 147 syslog(LOG_ERR, "%s: %m", CONFIG); 148 return; 149 } 150 151 /* First call to nextline will advance line_number to 1 */ 152 line_number = 0; 153 154 /* Start parsing at the beginning of the first line */ 155 current_pos = nextline(fconfig); 156 157 while ((cp = getconfigent(¤t_pos)) != NULL) { 158 /* Find an already existing service definition */ 159 for (sep = servtab; sep != NULL; sep = sep->se_next) 160 if (is_same_service(sep, cp)) 161 break; 162 if (sep != NULL) { 163 int i; 164 165 #define SWAP(type, a, b) {type c = a; a = b; b = c;} 166 167 /* 168 * sep->se_wait may be holding the pid of a daemon 169 * that we're waiting for. If so, don't overwrite 170 * it unless the config file explicitly says don't 171 * wait. 172 */ 173 if (cp->se_bi == 0 && 174 (sep->se_wait == 1 || cp->se_wait == 0)) 175 sep->se_wait = cp->se_wait; 176 SWAP(char *, sep->se_user, cp->se_user); 177 SWAP(char *, sep->se_group, cp->se_group); 178 SWAP(char *, sep->se_server, cp->se_server); 179 for (i = 0; i < MAXARGV; i++) 180 SWAP(char *, sep->se_argv[i], cp->se_argv[i]); 181 #ifdef IPSEC 182 SWAP(char *, sep->se_policy, cp->se_policy); 183 #endif 184 SWAP(service_type, cp->se_type, sep->se_type); 185 SWAP(size_t, cp->se_service_max, sep->se_service_max); 186 SWAP(size_t, cp->se_ip_max, sep->se_ip_max); 187 #undef SWAP 188 if (isrpcservice(sep)) 189 unregister_rpc(sep); 190 sep->se_rpcversl = cp->se_rpcversl; 191 sep->se_rpcversh = cp->se_rpcversh; 192 freeconfig(cp); 193 #ifdef DEBUG_ENABLE 194 if (debug) 195 print_service("REDO", sep); 196 #endif 197 } else { 198 sep = enter(cp); 199 #ifdef DEBUG_ENABLE 200 if (debug) 201 print_service("ADD ", sep); 202 #endif 203 } 204 sep->se_checked = 1; 205 206 /* 207 * Remainder of config(void) checks validity of servtab options 208 * and sets up the service by setting up sockets 209 * (in setup(servtab)). 210 */ 211 switch (sep->se_family) { 212 case AF_LOCAL: 213 if (sep->se_fd != -1) 214 break; 215 n = strlen(sep->se_service); 216 if (n >= sizeof(sep->se_ctrladdr_un.sun_path)) { 217 syslog(LOG_ERR, "%s/%s: address too long", 218 sep->se_service, sep->se_proto); 219 sep->se_checked = 0; 220 continue; 221 } 222 (void)unlink(sep->se_service); 223 strlcpy(sep->se_ctrladdr_un.sun_path, 224 sep->se_service, n + 1); 225 sep->se_ctrladdr_un.sun_family = AF_LOCAL; 226 sep->se_ctrladdr_size = (socklen_t)(n + 227 sizeof(sep->se_ctrladdr_un) - 228 sizeof(sep->se_ctrladdr_un.sun_path)); 229 if (!ISMUX(sep)) 230 setup(sep); 231 break; 232 case AF_INET: 233 #ifdef INET6 234 case AF_INET6: 235 #endif 236 { 237 struct addrinfo hints, *res; 238 char *host; 239 const char *port; 240 int error; 241 int s; 242 243 /* check if the family is supported */ 244 s = socket(sep->se_family, SOCK_DGRAM, 0); 245 if (s < 0) { 246 syslog(LOG_WARNING, 247 "%s/%s: %s: the address family is not " 248 "supported by the kernel", 249 sep->se_service, sep->se_proto, 250 sep->se_hostaddr); 251 sep->se_checked = false; 252 continue; 253 } 254 close(s); 255 256 memset(&hints, 0, sizeof(hints)); 257 hints.ai_family = sep->se_family; 258 hints.ai_socktype = sep->se_socktype; 259 hints.ai_flags = AI_PASSIVE; 260 if (strcmp(sep->se_hostaddr, "*") == 0) 261 host = NULL; 262 else 263 host = sep->se_hostaddr; 264 if (isrpcservice(sep) || ISMUX(sep)) 265 port = "0"; 266 else 267 port = sep->se_service; 268 error = getaddrinfo(host, port, &hints, &res); 269 if (error != 0) { 270 if (error == EAI_SERVICE) { 271 /* gai_strerror not friendly enough */ 272 syslog(LOG_WARNING, SERV_FMT ": " 273 "unknown service", 274 SERV_PARAMS(sep)); 275 } else { 276 syslog(LOG_ERR, SERV_FMT ": %s: %s", 277 SERV_PARAMS(sep), 278 sep->se_hostaddr, 279 gai_strerror(error)); 280 } 281 sep->se_checked = false; 282 continue; 283 } 284 if (res->ai_next != NULL) { 285 syslog(LOG_ERR, SERV_FMT 286 ": %s: resolved to multiple addr", 287 SERV_PARAMS(sep), 288 sep->se_hostaddr); 289 sep->se_checked = false; 290 freeaddrinfo(res); 291 continue; 292 } 293 memcpy(&sep->se_ctrladdr, res->ai_addr, 294 res->ai_addrlen); 295 if (ISMUX(sep)) { 296 sep->se_fd = -1; 297 freeaddrinfo(res); 298 continue; 299 } 300 sep->se_ctrladdr_size = res->ai_addrlen; 301 freeaddrinfo(res); 302 #ifdef RPC 303 if (isrpcservice(sep)) { 304 struct rpcent *rp; 305 306 sep->se_rpcprog = atoi(sep->se_service); 307 if (sep->se_rpcprog == 0) { 308 rp = getrpcbyname(sep->se_service); 309 if (rp == 0) { 310 syslog(LOG_ERR, 311 SERV_FMT 312 ": unknown service", 313 SERV_PARAMS(sep)); 314 sep->se_checked = false; 315 continue; 316 } 317 sep->se_rpcprog = rp->r_number; 318 } 319 if (sep->se_fd == -1 && !ISMUX(sep)) 320 setup(sep); 321 if (sep->se_fd != -1) 322 register_rpc(sep); 323 } else 324 #endif 325 { 326 if (sep->se_fd >= 0) 327 close_sep(sep); 328 if (sep->se_fd == -1 && !ISMUX(sep)) 329 setup(sep); 330 } 331 } 332 } 333 } 334 endconfig(); 335 } 336 337 static struct servtab * 338 enter(struct servtab *cp) 339 { 340 struct servtab *sep; 341 342 sep = malloc(sizeof (*sep)); 343 if (sep == NULL) { 344 syslog(LOG_ERR, "Out of memory."); 345 exit(EXIT_FAILURE); 346 } 347 *sep = *cp; 348 sep->se_fd = -1; 349 sep->se_rpcprog = -1; 350 sep->se_next = servtab; 351 servtab = sep; 352 return (sep); 353 } 354 355 static void 356 endconfig(void) 357 { 358 if (fconfig != NULL) { 359 (void) fclose(fconfig); 360 fconfig = NULL; 361 } 362 if (defhost != NULL) { 363 free(defhost); 364 defhost = NULL; 365 } 366 367 #ifdef IPSEC 368 if (policy != NULL) { 369 free(policy); 370 policy = NULL; 371 } 372 #endif 373 374 } 375 376 #define LOG_EARLY_ENDCONF() \ 377 ERR("Exiting %s early. Some services will be unavailable", CONFIG) 378 379 #define LOG_TOO_FEW_ARGS() \ 380 ERR("Expected more arguments") 381 382 /* Parse the next service and apply any directives, and returns it as servtab */ 383 static struct servtab * 384 getconfigent(char **current_pos) 385 { 386 struct servtab *sep = &serv; 387 int argc, val; 388 char *cp, *cp0, *arg, *buf0, *buf1, *sz0, *sz1; 389 static char TCPMUX_TOKEN[] = "tcpmux/"; 390 #define MUX_LEN (sizeof(TCPMUX_TOKEN)-1) 391 char *hostdelim; 392 393 /* 394 * Pre-condition: current_pos points into line, 395 * line contains config line. Continue where the last getconfigent 396 * left off. Allows for multiple service definitions per line. 397 */ 398 cp = *current_pos; 399 400 if (/*CONSTCOND*/false) { 401 /* 402 * Go to the next line, but only after attemting to read the 403 * current one! Keep reading until we find a valid definition 404 * or EOF. 405 */ 406 more: 407 cp = nextline(fconfig); 408 } 409 410 if (cp == NULL) { 411 /* EOF or I/O error, let config() know to exit the file */ 412 return NULL; 413 } 414 415 /* Comments and IPsec policies */ 416 if (cp[0] == '#') { 417 #ifdef IPSEC 418 /* lines starting with #@ is not a comment, but the policy */ 419 if (cp[1] == '@') { 420 char *p; 421 for (p = cp + 2; isspace((unsigned char)*p); p++) 422 ; 423 if (*p == '\0') { 424 if (policy) 425 free(policy); 426 policy = NULL; 427 } else { 428 if (ipsecsetup_test(p) < 0) { 429 ERR("Invalid IPsec policy \"%s\"", p); 430 LOG_EARLY_ENDCONF(); 431 /* 432 * Stop reading the current config to 433 * prevent services from being run 434 * without IPsec. 435 */ 436 return NULL; 437 } else { 438 if (policy) 439 free(policy); 440 policy = newstr(p); 441 } 442 } 443 } 444 #endif 445 446 goto more; 447 } 448 449 /* Parse next token: listen-addr/hostname, service-spec, .include */ 450 arg = skip(&cp); 451 452 if (cp == NULL) { 453 goto more; 454 } 455 456 if (arg[0] == '.') { 457 if (strcmp(&arg[1], "include") == 0) { 458 /* include directive */ 459 arg = skip(&cp); 460 if (arg == NULL) { 461 LOG_TOO_FEW_ARGS(); 462 return NULL; 463 } 464 include_configs(arg); 465 goto more; 466 } else { 467 ERR("Unknown directive '%s'", &arg[1]); 468 goto more; 469 } 470 } 471 472 /* After this point, we might need to store data in a servtab */ 473 *sep = init_servtab(); 474 475 /* Check for a host name. */ 476 hostdelim = strrchr(arg, ':'); 477 if (hostdelim != NULL) { 478 *hostdelim = '\0'; 479 if (arg[0] == '[' && hostdelim > arg && hostdelim[-1] == ']') { 480 hostdelim[-1] = '\0'; 481 sep->se_hostaddr = newstr(arg + 1); 482 } else 483 sep->se_hostaddr = newstr(arg); 484 arg = hostdelim + 1; 485 /* 486 * If the line is of the form `host:', then just change the 487 * default host for the following lines. 488 */ 489 if (*arg == '\0') { 490 arg = skip(&cp); 491 if (cp == NULL) { 492 free(defhost); 493 defhost = sep->se_hostaddr; 494 goto more; 495 } 496 } 497 } else { 498 /* No host address found, set it to NULL to indicate absence */ 499 sep->se_hostaddr = NULL; 500 } 501 if (strncmp(arg, TCPMUX_TOKEN, MUX_LEN) == 0) { 502 char *c = arg + MUX_LEN; 503 if (*c == '+') { 504 sep->se_type = MUXPLUS_TYPE; 505 c++; 506 } else 507 sep->se_type = MUX_TYPE; 508 sep->se_service = newstr(c); 509 } else { 510 sep->se_service = newstr(arg); 511 sep->se_type = NORM_TYPE; 512 } 513 514 DPRINTCONF("Found service definition '%s'", sep->se_service); 515 516 /* on/off/socktype */ 517 arg = skip(&cp); 518 if (arg == NULL) { 519 LOG_TOO_FEW_ARGS(); 520 freeconfig(sep); 521 goto more; 522 } 523 524 /* Check for new v2 syntax */ 525 if (strcmp(arg, "on") == 0 || strncmp(arg, "on#", 3) == 0) { 526 527 if (arg[2] == '#') { 528 cp = nextline(fconfig); 529 } 530 531 switch(parse_syntax_v2(sep, &cp)) { 532 case V2_SUCCESS: 533 *current_pos = cp; 534 return sep; 535 case V2_SKIP: 536 /* 537 * Skip invalid definitions, freeconfig is called in 538 * parse_v2.c 539 */ 540 *current_pos = cp; 541 freeconfig(sep); 542 goto more; 543 case V2_ERROR: 544 /* 545 * Unrecoverable error, stop reading. freeconfig 546 * is called in parse_v2.c 547 */ 548 LOG_EARLY_ENDCONF(); 549 freeconfig(sep); 550 return NULL; 551 } 552 } else if (strcmp(arg, "off") == 0 || strncmp(arg, "off#", 4) == 0) { 553 554 if (arg[3] == '#') { 555 cp = nextline(fconfig); 556 } 557 558 /* Parse syntax the same as with 'on', but ignore the result */ 559 switch(parse_syntax_v2(sep, &cp)) { 560 case V2_SUCCESS: 561 case V2_SKIP: 562 *current_pos = cp; 563 freeconfig(sep); 564 goto more; 565 case V2_ERROR: 566 /* Unrecoverable error, stop reading */ 567 LOG_EARLY_ENDCONF(); 568 freeconfig(sep); 569 return NULL; 570 } 571 } else { 572 /* continue parsing v1 */ 573 parse_socktype(arg, sep); 574 if (sep->se_socktype == SOCK_STREAM) { 575 parse_accept_filter(arg, sep); 576 } 577 if (sep->se_hostaddr == NULL) { 578 /* Set host to current default */ 579 sep->se_hostaddr = newstr(defhost); 580 } 581 } 582 583 /* protocol */ 584 arg = skip(&cp); 585 if (arg == NULL) { 586 LOG_TOO_FEW_ARGS(); 587 freeconfig(sep); 588 goto more; 589 } 590 if (sep->se_type == NORM_TYPE && 591 strncmp(arg, "faith/", strlen("faith/")) == 0) { 592 arg += strlen("faith/"); 593 sep->se_type = FAITH_TYPE; 594 } 595 sep->se_proto = newstr(arg); 596 597 #define MALFORMED(arg) \ 598 do { \ 599 ERR("%s: malformed buffer size option `%s'", \ 600 sep->se_service, (arg)); \ 601 freeconfig(sep); \ 602 goto more; \ 603 } while (false) 604 605 #define GETVAL(arg) \ 606 do { \ 607 if (!isdigit((unsigned char)*(arg))) \ 608 MALFORMED(arg); \ 609 val = (int)strtol((arg), &cp0, 10); \ 610 if (cp0 != NULL) { \ 611 if (cp0[1] != '\0') \ 612 MALFORMED((arg)); \ 613 if (cp0[0] == 'k') \ 614 val *= 1024; \ 615 if (cp0[0] == 'm') \ 616 val *= 1024 * 1024; \ 617 } \ 618 if (val < 1) { \ 619 ERR("%s: invalid buffer size `%s'", \ 620 sep->se_service, (arg)); \ 621 freeconfig(sep); \ 622 goto more; \ 623 } \ 624 } while (false) 625 626 #define ASSIGN(arg) \ 627 do { \ 628 if (strcmp((arg), "sndbuf") == 0) \ 629 sep->se_sndbuf = val; \ 630 else if (strcmp((arg), "rcvbuf") == 0) \ 631 sep->se_rcvbuf = val; \ 632 else \ 633 MALFORMED((arg)); \ 634 } while (false) 635 636 /* 637 * Extract the send and receive buffer sizes before parsing 638 * the protocol. 639 */ 640 sep->se_sndbuf = sep->se_rcvbuf = 0; 641 buf0 = buf1 = sz0 = sz1 = NULL; 642 if ((buf0 = strchr(sep->se_proto, ',')) != NULL) { 643 /* Not meaningful for Tcpmux services. */ 644 if (ISMUX(sep)) { 645 ERR("%s: can't specify buffer sizes for " 646 "tcpmux services", sep->se_service); 647 goto more; 648 } 649 650 /* Skip the , */ 651 *buf0++ = '\0'; 652 653 /* Check to see if another socket buffer size was specified. */ 654 if ((buf1 = strchr(buf0, ',')) != NULL) { 655 /* Skip the , */ 656 *buf1++ = '\0'; 657 658 /* Make sure a 3rd one wasn't specified. */ 659 if (strchr(buf1, ',') != NULL) { 660 ERR("%s: too many buffer sizes", 661 sep->se_service); 662 goto more; 663 } 664 665 /* Locate the size. */ 666 if ((sz1 = strchr(buf1, '=')) == NULL) 667 MALFORMED(buf1); 668 669 /* Skip the = */ 670 *sz1++ = '\0'; 671 } 672 673 /* Locate the size. */ 674 if ((sz0 = strchr(buf0, '=')) == NULL) 675 MALFORMED(buf0); 676 677 /* Skip the = */ 678 *sz0++ = '\0'; 679 680 GETVAL(sz0); 681 ASSIGN(buf0); 682 683 if (buf1 != NULL) { 684 GETVAL(sz1); 685 ASSIGN(buf1); 686 } 687 } 688 689 #undef ASSIGN 690 #undef GETVAL 691 #undef MALFORMED 692 693 if (parse_protocol(sep)) { 694 freeconfig(sep); 695 goto more; 696 } 697 698 /* wait/nowait:max */ 699 arg = skip(&cp); 700 if (arg == NULL) { 701 LOG_TOO_FEW_ARGS(); 702 freeconfig(sep); 703 goto more; 704 } 705 706 /* Rate limiting parsing */ { 707 char *cp1; 708 if ((cp1 = strchr(arg, ':')) == NULL) 709 cp1 = strchr(arg, '.'); 710 if (cp1 != NULL) { 711 int rstatus; 712 *cp1++ = '\0'; 713 sep->se_service_max = (size_t)strtou(cp1, NULL, 10, 0, 714 SERVTAB_COUNT_MAX, &rstatus); 715 716 if (rstatus != 0) { 717 if (rstatus != ERANGE) { 718 /* For compatibility w/ atoi parsing */ 719 sep->se_service_max = 0; 720 } 721 722 WRN("Improper \"max\" value '%s', " 723 "using '%zu' instead: %s", 724 cp1, 725 sep->se_service_max, 726 strerror(rstatus)); 727 } 728 729 } else 730 sep->se_service_max = TOOMANY; 731 } 732 if (parse_wait(sep, strcmp(arg, "wait") == 0)) { 733 freeconfig(sep); 734 goto more; 735 } 736 737 /* Parse user:group token */ 738 arg = skip(&cp); 739 if (arg == NULL) { 740 LOG_TOO_FEW_ARGS(); 741 freeconfig(sep); 742 goto more; 743 } 744 char* separator = strchr(arg, ':'); 745 if (separator == NULL) { 746 /* Backwards compatibility, allow dot instead of colon */ 747 separator = strchr(arg, '.'); 748 } 749 750 if (separator == NULL) { 751 /* Only user was specified */ 752 sep->se_group = NULL; 753 } else { 754 *separator = '\0'; 755 sep->se_group = newstr(separator + 1); 756 } 757 758 sep->se_user = newstr(arg); 759 760 /* Parser server-program (path to binary or "internal") */ 761 arg = skip(&cp); 762 if (arg == NULL) { 763 LOG_TOO_FEW_ARGS(); 764 freeconfig(sep); 765 goto more; 766 } 767 if (parse_server(sep, arg)) { 768 freeconfig(sep); 769 goto more; 770 } 771 772 argc = 0; 773 for (arg = skip(&cp); cp != NULL; arg = skip(&cp)) { 774 if (argc < MAXARGV) 775 sep->se_argv[argc++] = newstr(arg); 776 } 777 while (argc <= MAXARGV) 778 sep->se_argv[argc++] = NULL; 779 #ifdef IPSEC 780 sep->se_policy = policy != NULL ? newstr(policy) : NULL; 781 #endif 782 /* getconfigent read a positional service def, move to next line */ 783 *current_pos = nextline(fconfig); 784 return (sep); 785 } 786 787 void 788 freeconfig(struct servtab *cp) 789 { 790 int i; 791 792 free(cp->se_hostaddr); 793 free(cp->se_service); 794 free(cp->se_proto); 795 free(cp->se_user); 796 free(cp->se_group); 797 free(cp->se_server); 798 for (i = 0; i < MAXARGV; i++) 799 free(cp->se_argv[i]); 800 #ifdef IPSEC 801 free(cp->se_policy); 802 #endif 803 } 804 805 /* 806 * Get next token *in the current service definition* from config file. 807 * Allows multi-line parse if single space or single tab-indented. 808 * Things in quotes are considered single token. 809 * Advances cp to next token. 810 */ 811 static char * 812 skip(char **cpp) 813 { 814 char *cp = *cpp; 815 char *start; 816 char quote; 817 818 if (*cpp == NULL) 819 return (NULL); 820 821 again: 822 while (*cp == ' ' || *cp == '\t') 823 cp++; 824 if (*cp == '\0') { 825 int c; 826 827 c = getc(fconfig); 828 (void) ungetc(c, fconfig); 829 if (c == ' ' || c == '\t') 830 if ((cp = nextline(fconfig)) != NULL) 831 goto again; 832 *cpp = NULL; 833 return (NULL); 834 } 835 start = cp; 836 /* Parse shell-style quotes */ 837 quote = '\0'; 838 while (*cp != '\0' && (quote != '\0' || (*cp != ' ' && *cp != '\t'))) { 839 if (*cp == '\'' || *cp == '"') { 840 if (quote != '\0' && *cp != quote) 841 cp++; 842 else { 843 if (quote != '\0') 844 quote = '\0'; 845 else 846 quote = *cp; 847 memmove(cp, cp+1, strlen(cp)); 848 } 849 } else 850 cp++; 851 } 852 if (*cp != '\0') 853 *cp++ = '\0'; 854 *cpp = cp; 855 return (start); 856 } 857 858 char * 859 nextline(FILE *fd) 860 { 861 char *cp; 862 863 if (fgets(line, (int)sizeof(line), fd) == NULL) { 864 if (ferror(fd) != 0) { 865 ERR("Error when reading next line: %s", 866 strerror(errno)); 867 } 868 return NULL; 869 } 870 cp = strchr(line, '\n'); 871 if (cp != NULL) 872 *cp = '\0'; 873 line_number++; 874 return line; 875 } 876 877 char * 878 newstr(const char *cp) 879 { 880 char *dp; 881 if ((dp = strdup((cp != NULL) ? cp : "")) != NULL) 882 return (dp); 883 syslog(LOG_ERR, "strdup: %m"); 884 exit(EXIT_FAILURE); 885 /*NOTREACHED*/ 886 } 887 888 #ifdef DEBUG_ENABLE 889 /* 890 * print_service: 891 * Dump relevant information to stderr 892 */ 893 static void 894 print_service(const char *action, struct servtab *sep) 895 { 896 897 if (isrpcservice(sep)) 898 fprintf(stderr, 899 "%s: %s rpcprog=%d, rpcvers = %d/%d, proto=%s, " 900 "wait.max=%d.%zu, " 901 "user:group=%s:%s builtin=%lx server=%s" 902 #ifdef IPSEC 903 " policy=\"%s\"" 904 #endif 905 "\n", 906 action, sep->se_service, 907 sep->se_rpcprog, sep->se_rpcversh, sep->se_rpcversl, 908 sep->se_proto, sep->se_wait, sep->se_service_max, 909 sep->se_user, sep->se_group, 910 (long)sep->se_bi, sep->se_server 911 #ifdef IPSEC 912 , (sep->se_policy != NULL ? sep->se_policy : "") 913 #endif 914 ); 915 else 916 fprintf(stderr, 917 "%s: %s:%s proto=%s%s, wait.max=%d.%zu, user:group=%s:%s " 918 "builtin=%lx " 919 "server=%s" 920 #ifdef IPSEC 921 " policy=%s" 922 #endif 923 "\n", 924 action, sep->se_hostaddr, sep->se_service, 925 sep->se_type == FAITH_TYPE ? "faith/" : "", 926 sep->se_proto, 927 sep->se_wait, sep->se_service_max, sep->se_user, 928 sep->se_group, (long)sep->se_bi, sep->se_server 929 #ifdef IPSEC 930 , (sep->se_policy != NULL ? sep->se_policy : "") 931 #endif 932 ); 933 } 934 #endif 935 936 void 937 config_root(void) 938 { 939 struct servtab *sep; 940 /* Uncheck services */ 941 for (sep = servtab; sep != NULL; sep = sep->se_next) { 942 sep->se_checked = false; 943 } 944 defhost = newstr("*"); 945 #ifdef IPSEC 946 policy = NULL; 947 #endif 948 fconfig = NULL; 949 config(); 950 purge_unchecked(); 951 } 952 953 static void 954 purge_unchecked(void) 955 { 956 struct servtab *sep, **sepp = &servtab; 957 int servtab_count = 0; 958 while ((sep = *sepp) != NULL) { 959 if (sep->se_checked) { 960 sepp = &sep->se_next; 961 servtab_count++; 962 continue; 963 } 964 *sepp = sep->se_next; 965 if (sep->se_fd >= 0) 966 close_sep(sep); 967 if (isrpcservice(sep)) 968 unregister_rpc(sep); 969 if (sep->se_family == AF_LOCAL) 970 (void)unlink(sep->se_service); 971 #ifdef DEBUG_ENABLE 972 if (debug) 973 print_service("FREE", sep); 974 #endif 975 freeconfig(sep); 976 free(sep); 977 } 978 DPRINTF("%d service(s) loaded.", servtab_count); 979 } 980 981 static bool 982 is_same_service(const struct servtab *sep, const struct servtab *cp) 983 { 984 return 985 strcmp(sep->se_service, cp->se_service) == 0 && 986 strcmp(sep->se_hostaddr, cp->se_hostaddr) == 0 && 987 strcmp(sep->se_proto, cp->se_proto) == 0 && 988 sep->se_family == cp->se_family && 989 ISMUX(sep) == ISMUX(cp); 990 } 991 992 int 993 parse_protocol(struct servtab *sep) 994 { 995 int val; 996 997 if (strcmp(sep->se_proto, "unix") == 0) { 998 sep->se_family = AF_LOCAL; 999 } else { 1000 val = (int)strlen(sep->se_proto); 1001 if (val == 0) { 1002 ERR("%s: invalid protocol specified", 1003 sep->se_service); 1004 return -1; 1005 } 1006 val = sep->se_proto[val - 1]; 1007 switch (val) { 1008 case '4': /*tcp4 or udp4*/ 1009 sep->se_family = AF_INET; 1010 break; 1011 #ifdef INET6 1012 case '6': /*tcp6 or udp6*/ 1013 sep->se_family = AF_INET6; 1014 break; 1015 #endif 1016 default: 1017 /* 1018 * Use 'default' IP version which is IPv4, may 1019 * eventually be changed to AF_INET6 1020 */ 1021 sep->se_family = AF_INET; 1022 break; 1023 } 1024 if (strncmp(sep->se_proto, "rpc/", 4) == 0) { 1025 #ifdef RPC 1026 char *cp1, *ccp; 1027 cp1 = strchr(sep->se_service, '/'); 1028 if (cp1 == 0) { 1029 ERR("%s: no rpc version", 1030 sep->se_service); 1031 return -1; 1032 } 1033 *cp1++ = '\0'; 1034 sep->se_rpcversl = sep->se_rpcversh = 1035 (int)strtol(cp1, &ccp, 0); 1036 if (ccp == cp1) { 1037 badafterall: 1038 ERR("%s/%s: bad rpc version", 1039 sep->se_service, cp1); 1040 return -1; 1041 } 1042 if (*ccp == '-') { 1043 cp1 = ccp + 1; 1044 sep->se_rpcversh = (int)strtol(cp1, &ccp, 0); 1045 if (ccp == cp1) 1046 goto badafterall; 1047 } 1048 #else 1049 ERR("%s: rpc services not supported", 1050 sep->se_service); 1051 return -1; 1052 #endif /* RPC */ 1053 } 1054 } 1055 return 0; 1056 } 1057 1058 int 1059 parse_wait(struct servtab *sep, int wait) 1060 { 1061 if (!ISMUX(sep)) { 1062 sep->se_wait = wait; 1063 return 0; 1064 } 1065 /* 1066 * Silently enforce "nowait" for TCPMUX services since 1067 * they don't have an assigned port to listen on. 1068 */ 1069 sep->se_wait = 0; 1070 1071 if (strncmp(sep->se_proto, "tcp", 3)) { 1072 ERR("bad protocol for tcpmux service %s", 1073 sep->se_service); 1074 return -1; 1075 } 1076 if (sep->se_socktype != SOCK_STREAM) { 1077 ERR("bad socket type for tcpmux service %s", 1078 sep->se_service); 1079 return -1; 1080 } 1081 return 0; 1082 } 1083 1084 int 1085 parse_server(struct servtab *sep, const char *arg) 1086 { 1087 sep->se_server = newstr(arg); 1088 if (strcmp(sep->se_server, "internal") != 0) { 1089 sep->se_bi = NULL; 1090 return 0; 1091 } 1092 1093 if (!try_biltin(sep)) { 1094 ERR("Internal service %s unknown", sep->se_service); 1095 return -1; 1096 } 1097 return 0; 1098 } 1099 1100 /* TODO test to make sure accept filter still works */ 1101 void 1102 parse_accept_filter(char *arg, struct servtab *sep) 1103 { 1104 char *accf, *accf_arg; 1105 /* one and only one accept filter */ 1106 accf = strchr(arg, ':'); 1107 if (accf == NULL) 1108 return; 1109 if (accf != strrchr(arg, ':') || *(accf + 1) == '\0') { 1110 /* more than one || nothing beyond */ 1111 sep->se_socktype = -1; 1112 return; 1113 } 1114 1115 accf++; /* skip delimiter */ 1116 strlcpy(sep->se_accf.af_name, accf, sizeof(sep->se_accf.af_name)); 1117 accf_arg = strchr(accf, ','); 1118 if (accf_arg == NULL) /* zero or one arg, no more */ 1119 return; 1120 1121 if (strrchr(accf, ',') != accf_arg) { 1122 sep->se_socktype = -1; 1123 } else { 1124 accf_arg++; 1125 strlcpy(sep->se_accf.af_arg, accf_arg, 1126 sizeof(sep->se_accf.af_arg)); 1127 } 1128 } 1129 1130 void 1131 parse_socktype(char* arg, struct servtab* sep) 1132 { 1133 /* stream socket may have an accept filter, only check first chars */ 1134 if (strncmp(arg, "stream", sizeof("stream") - 1) == 0) 1135 sep->se_socktype = SOCK_STREAM; 1136 else if (strcmp(arg, "dgram") == 0) 1137 sep->se_socktype = SOCK_DGRAM; 1138 else if (strcmp(arg, "rdm") == 0) 1139 sep->se_socktype = SOCK_RDM; 1140 else if (strcmp(arg, "seqpacket") == 0) 1141 sep->se_socktype = SOCK_SEQPACKET; 1142 else if (strcmp(arg, "raw") == 0) 1143 sep->se_socktype = SOCK_RAW; 1144 else 1145 sep->se_socktype = -1; 1146 } 1147 1148 static struct servtab 1149 init_servtab(void) 1150 { 1151 /* This does not set every field to default. See enter() as well */ 1152 return (struct servtab) { 1153 /* 1154 * Set se_max to non-zero so uninitialized value is not 1155 * a valid value. Useful in v2 syntax parsing. 1156 */ 1157 .se_service_max = SERVTAB_UNSPEC_SIZE_T, 1158 .se_ip_max = SERVTAB_UNSPEC_SIZE_T, 1159 .se_wait = SERVTAB_UNSPEC_VAL, 1160 .se_socktype = SERVTAB_UNSPEC_VAL, 1161 .se_rl_ip_list = SLIST_HEAD_INITIALIZER(se_ip_list_head) 1162 /* All other fields initialized to 0 or null */ 1163 }; 1164 } 1165 1166 /* Include directives bookkeeping structure */ 1167 struct file_list { 1168 /* Absolute path used for checking for circular references */ 1169 char *abs; 1170 /* Pointer to the absolute path of the parent config file, 1171 * on the stack */ 1172 struct file_list *next; 1173 } *file_list_head; 1174 1175 static void 1176 include_configs(char *pattern) 1177 { 1178 /* Allocate global per-config state on the thread stack */ 1179 const char* save_CONFIG; 1180 FILE *save_fconfig; 1181 size_t save_line_number; 1182 char *save_defhost; 1183 struct file_list new_file; 1184 #ifdef IPSEC 1185 char *save_policy; 1186 #endif 1187 1188 /* Store current globals on the stack */ 1189 save_CONFIG = CONFIG; 1190 save_fconfig = fconfig; 1191 save_line_number = line_number; 1192 save_defhost = defhost; 1193 new_file.abs = realpath(CONFIG, NULL); 1194 new_file.next = file_list_head; 1195 #ifdef IPSEC 1196 save_policy = policy; 1197 #endif 1198 /* Put new_file at the top of the config stack */ 1199 file_list_head = &new_file; 1200 read_glob_configs(pattern); 1201 free(new_file.abs); 1202 /* Pop new_file off the stack */ 1203 file_list_head = new_file.next; 1204 1205 /* Restore global per-config state */ 1206 CONFIG = save_CONFIG; 1207 fconfig = save_fconfig; 1208 line_number = save_line_number; 1209 defhost = save_defhost; 1210 #ifdef IPSEC 1211 policy = save_policy; 1212 #endif 1213 } 1214 1215 static void 1216 prepare_next_config(const char *file_name) 1217 { 1218 /* Setup new state that is normally only done in main */ 1219 CONFIG = file_name; 1220 1221 /* Inherit default host and IPsec policy */ 1222 defhost = newstr(defhost); 1223 1224 #ifdef IPSEC 1225 policy = (policy == NULL) ? NULL : newstr(policy); 1226 #endif 1227 } 1228 1229 static void 1230 read_glob_configs(char *pattern) 1231 { 1232 glob_t results; 1233 char *full_pattern; 1234 int glob_result; 1235 full_pattern = gen_file_pattern(CONFIG, pattern); 1236 1237 DPRINTCONF("Found include directive '%s'", full_pattern); 1238 1239 glob_result = glob(full_pattern, GLOB_NOSORT, glob_error, &results); 1240 switch(glob_result) { 1241 case 0: 1242 /* No glob errors */ 1243 break; 1244 case GLOB_ABORTED: 1245 ERR("Error while searching for include files"); 1246 break; 1247 case GLOB_NOMATCH: 1248 /* It's fine if no files were matched. */ 1249 DPRINTCONF("No files matched pattern '%s'", full_pattern); 1250 break; 1251 case GLOB_NOSPACE: 1252 ERR("Error when searching for include files: %s", 1253 strerror(errno)); 1254 break; 1255 default: 1256 ERR("Unknown glob(3) error %d", errno); 1257 break; 1258 } 1259 free(full_pattern); 1260 1261 for (size_t i = 0; i < results.gl_pathc; i++) { 1262 include_matched_path(results.gl_pathv[i]); 1263 } 1264 1265 globfree(&results); 1266 } 1267 1268 static void 1269 include_matched_path(char *glob_path) 1270 { 1271 struct stat sb; 1272 char *tmp; 1273 1274 if (lstat(glob_path, &sb) != 0) { 1275 ERR("Error calling stat on path '%s': %s", glob_path, 1276 strerror(errno)); 1277 return; 1278 } 1279 1280 if (!S_ISREG(sb.st_mode) && !S_ISLNK(sb.st_mode)) { 1281 DPRINTCONF("'%s' is not a file.", glob_path); 1282 ERR("The matched path '%s' is not a regular file", glob_path); 1283 return; 1284 } 1285 1286 DPRINTCONF("Include '%s'", glob_path); 1287 1288 if (S_ISLNK(sb.st_mode)) { 1289 tmp = glob_path; 1290 glob_path = realpath(tmp, NULL); 1291 } 1292 1293 /* Ensure the file is not being reincluded .*/ 1294 if (check_no_reinclude(glob_path)) { 1295 prepare_next_config(glob_path); 1296 config(); 1297 } else { 1298 DPRINTCONF("File '%s' already included in current include " 1299 "chain", glob_path); 1300 WRN("Including file '%s' would cause a circular " 1301 "dependency", glob_path); 1302 } 1303 1304 if (S_ISLNK(sb.st_mode)) { 1305 free(glob_path); 1306 glob_path = tmp; 1307 } 1308 } 1309 1310 static bool 1311 check_no_reinclude(const char *glob_path) 1312 { 1313 struct file_list *cur = file_list_head; 1314 char *abs_path = realpath(glob_path, NULL); 1315 1316 if (abs_path == NULL) { 1317 ERR("Error checking real path for '%s': %s", 1318 glob_path, strerror(errno)); 1319 return false; 1320 } 1321 1322 DPRINTCONF("Absolute path '%s'", abs_path); 1323 1324 for (cur = file_list_head; cur != NULL; cur = cur->next) { 1325 if (strcmp(cur->abs, abs_path) == 0) { 1326 /* file included more than once */ 1327 /* TODO relative or abs path for logging error? */ 1328 free(abs_path); 1329 return false; 1330 } 1331 } 1332 free(abs_path); 1333 return true; 1334 } 1335 1336 /* Resolve the pattern relative to the config file the pattern is from */ 1337 static char * 1338 gen_file_pattern(const char *cur_config, const char *pattern) 1339 { 1340 if (pattern[0] == '/') { 1341 /* Absolute paths don't need any normalization */ 1342 return newstr(pattern); 1343 } 1344 1345 /* pattern is relative */ 1346 /* Find the end of the file's directory */ 1347 size_t i, last = 0; 1348 for (i = 0; cur_config[i] != '\0'; i++) { 1349 if (cur_config[i] == '/') { 1350 last = i; 1351 } 1352 } 1353 1354 if (last == 0) { 1355 /* cur_config is just a filename, pattern already correct */ 1356 return newstr(pattern); 1357 } 1358 1359 /* Relativize pattern to cur_config file's directory */ 1360 char *full_pattern = malloc(last + 1 + strlen(pattern) + 1); 1361 if (full_pattern == NULL) { 1362 syslog(LOG_ERR, "Out of memory."); 1363 exit(EXIT_FAILURE); 1364 } 1365 memcpy(full_pattern, cur_config, last); 1366 full_pattern[last] = '/'; 1367 strcpy(&full_pattern[last + 1], pattern); 1368 return full_pattern; 1369 } 1370 1371 static int 1372 glob_error(const char *path, int error) 1373 { 1374 WRN("Error while resolving path '%s': %s", path, strerror(error)); 1375 return 0; 1376 } 1377