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