1 /* $NetBSD: resconf.c,v 1.1 2024/02/18 20:57:47 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * SPDX-License-Identifier: MPL-2.0 7 * 8 * This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 * 12 * See the COPYRIGHT file distributed with this work for additional 13 * information regarding copyright ownership. 14 */ 15 16 /*! \file resconf.c */ 17 18 /** 19 * Module for parsing resolv.conf files (largely derived from lwconfig.c). 20 * 21 * irs_resconf_load() opens the file filename and parses it to initialize 22 * the configuration structure. 23 * 24 * \section lwconfig_return Return Values 25 * 26 * irs_resconf_load() returns #IRS_R_SUCCESS if it successfully read and 27 * parsed filename. It returns a non-0 error code if filename could not be 28 * opened or contained incorrect resolver statements. 29 * 30 * \section lwconfig_see See Also 31 * 32 * stdio(3), \link resolver resolver \endlink 33 * 34 * \section files Files 35 * 36 * /etc/resolv.conf 37 */ 38 39 #ifndef WIN32 40 #include <netdb.h> 41 #include <sys/socket.h> 42 #include <sys/types.h> 43 #endif /* ifndef WIN32 */ 44 45 #include <ctype.h> 46 #include <errno.h> 47 #include <inttypes.h> 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <string.h> 51 52 #include <isc/magic.h> 53 #include <isc/mem.h> 54 #include <isc/netaddr.h> 55 #include <isc/sockaddr.h> 56 #include <isc/util.h> 57 58 #include <irs/netdb.h> 59 #include <irs/resconf.h> 60 61 #define IRS_RESCONF_MAGIC ISC_MAGIC('R', 'E', 'S', 'c') 62 #define IRS_RESCONF_VALID(c) ISC_MAGIC_VALID(c, IRS_RESCONF_MAGIC) 63 64 /*! 65 * protocol constants 66 */ 67 68 #if !defined(NS_INADDRSZ) 69 #define NS_INADDRSZ 4 70 #endif /* if !defined(NS_INADDRSZ) */ 71 72 #if !defined(NS_IN6ADDRSZ) 73 #define NS_IN6ADDRSZ 16 74 #endif /* if !defined(NS_IN6ADDRSZ) */ 75 76 /*! 77 * resolv.conf parameters 78 */ 79 80 #define RESCONFMAXNAMESERVERS 3U /*%< max 3 "nameserver" entries */ 81 #define RESCONFMAXSEARCH 8U /*%< max 8 domains in "search" entry */ 82 #define RESCONFMAXLINELEN 256U /*%< max size of a line */ 83 #define RESCONFMAXSORTLIST 10U /*%< max 10 */ 84 85 /*! 86 * configuration data structure 87 */ 88 89 struct irs_resconf { 90 /* 91 * The configuration data is a thread-specific object, and does not 92 * need to be locked. 93 */ 94 unsigned int magic; 95 isc_mem_t *mctx; 96 97 isc_sockaddrlist_t nameservers; 98 unsigned int numns; /*%< number of configured servers 99 * */ 100 101 char *domainname; 102 char *search[RESCONFMAXSEARCH]; 103 uint8_t searchnxt; /*%< index for next free slot 104 * */ 105 106 irs_resconf_searchlist_t searchlist; 107 108 struct { 109 isc_netaddr_t addr; 110 /*% mask has a non-zero 'family' if set */ 111 isc_netaddr_t mask; 112 } sortlist[RESCONFMAXSORTLIST]; 113 uint8_t sortlistnxt; 114 115 /*%< non-zero if 'options debug' set */ 116 uint8_t resdebug; 117 /*%< set to n in 'options ndots:n' */ 118 uint8_t ndots; 119 }; 120 121 static isc_result_t 122 resconf_parsenameserver(irs_resconf_t *conf, FILE *fp); 123 static isc_result_t 124 resconf_parsedomain(irs_resconf_t *conf, FILE *fp); 125 static isc_result_t 126 resconf_parsesearch(irs_resconf_t *conf, FILE *fp); 127 static isc_result_t 128 resconf_parsesortlist(irs_resconf_t *conf, FILE *fp); 129 static isc_result_t 130 resconf_parseoption(irs_resconf_t *ctx, FILE *fp); 131 132 #if HAVE_GET_WIN32_NAMESERVERS 133 static isc_result_t 134 get_win32_nameservers(irs_resconf_t *conf); 135 #endif /* if HAVE_GET_WIN32_NAMESERVERS */ 136 137 /*! 138 * Eat characters from FP until EOL or EOF. Returns EOF or '\n' 139 */ 140 static int 141 eatline(FILE *fp) { 142 int ch; 143 144 ch = fgetc(fp); 145 while (ch != '\n' && ch != EOF) { 146 ch = fgetc(fp); 147 } 148 149 return (ch); 150 } 151 152 /*! 153 * Eats white space up to next newline or non-whitespace character (of 154 * EOF). Returns the last character read. Comments are considered white 155 * space. 156 */ 157 static int 158 eatwhite(FILE *fp) { 159 int ch; 160 161 ch = fgetc(fp); 162 while (ch != '\n' && ch != EOF && isspace((unsigned char)ch)) { 163 ch = fgetc(fp); 164 } 165 166 if (ch == ';' || ch == '#') { 167 ch = eatline(fp); 168 } 169 170 return (ch); 171 } 172 173 /*! 174 * Skip over any leading whitespace and then read in the next sequence of 175 * non-whitespace characters. In this context newline is not considered 176 * whitespace. Returns EOF on end-of-file, or the character 177 * that caused the reading to stop. 178 */ 179 static int 180 getword(FILE *fp, char *buffer, size_t size) { 181 int ch; 182 char *p; 183 184 REQUIRE(buffer != NULL); 185 REQUIRE(size > 0U); 186 187 p = buffer; 188 *p = '\0'; 189 190 ch = eatwhite(fp); 191 192 if (ch == EOF) { 193 return (EOF); 194 } 195 196 do { 197 *p = '\0'; 198 199 if (ch == EOF || isspace((unsigned char)ch)) { 200 break; 201 } else if ((size_t)(p - buffer) == size - 1) { 202 return (EOF); /* Not enough space. */ 203 } 204 205 *p++ = (char)ch; 206 ch = fgetc(fp); 207 } while (1); 208 209 return (ch); 210 } 211 212 static isc_result_t 213 add_server(isc_mem_t *mctx, const char *address_str, 214 isc_sockaddrlist_t *nameservers) { 215 int error; 216 isc_sockaddr_t *address = NULL; 217 struct addrinfo hints, *res; 218 isc_result_t result = ISC_R_SUCCESS; 219 220 res = NULL; 221 memset(&hints, 0, sizeof(hints)); 222 hints.ai_family = AF_UNSPEC; 223 hints.ai_socktype = SOCK_DGRAM; 224 hints.ai_protocol = IPPROTO_UDP; 225 hints.ai_flags = AI_NUMERICHOST; 226 error = getaddrinfo(address_str, "53", &hints, &res); 227 if (error != 0) { 228 return (ISC_R_BADADDRESSFORM); 229 } 230 231 /* XXX: special case: treat all-0 IPv4 address as loopback */ 232 if (res->ai_family == AF_INET) { 233 struct in_addr *v4; 234 unsigned char zeroaddress[] = { 0, 0, 0, 0 }; 235 unsigned char loopaddress[] = { 127, 0, 0, 1 }; 236 237 v4 = &((struct sockaddr_in *)res->ai_addr)->sin_addr; 238 if (memcmp(v4, zeroaddress, 4) == 0) { 239 memmove(v4, loopaddress, 4); 240 } 241 } 242 243 address = isc_mem_get(mctx, sizeof(*address)); 244 if (res->ai_addrlen > sizeof(address->type)) { 245 isc_mem_put(mctx, address, sizeof(*address)); 246 result = ISC_R_RANGE; 247 goto cleanup; 248 } 249 address->length = (unsigned int)res->ai_addrlen; 250 memmove(&address->type.ss, res->ai_addr, res->ai_addrlen); 251 ISC_LINK_INIT(address, link); 252 ISC_LIST_APPEND(*nameservers, address, link); 253 254 cleanup: 255 freeaddrinfo(res); 256 257 return (result); 258 } 259 260 static isc_result_t 261 create_addr(const char *buffer, isc_netaddr_t *addr, int convert_zero) { 262 struct in_addr v4; 263 struct in6_addr v6; 264 265 if (inet_pton(AF_INET, buffer, &v4) == 1) { 266 if (convert_zero) { 267 unsigned char zeroaddress[] = { 0, 0, 0, 0 }; 268 unsigned char loopaddress[] = { 127, 0, 0, 1 }; 269 if (memcmp(&v4, zeroaddress, 4) == 0) { 270 memmove(&v4, loopaddress, 4); 271 } 272 } 273 addr->family = AF_INET; 274 memmove(&addr->type.in, &v4, NS_INADDRSZ); 275 addr->zone = 0; 276 } else if (inet_pton(AF_INET6, buffer, &v6) == 1) { 277 addr->family = AF_INET6; 278 memmove(&addr->type.in6, &v6, NS_IN6ADDRSZ); 279 addr->zone = 0; 280 } else { 281 return (ISC_R_BADADDRESSFORM); /* Unrecognised format. */ 282 } 283 284 return (ISC_R_SUCCESS); 285 } 286 287 static isc_result_t 288 resconf_parsenameserver(irs_resconf_t *conf, FILE *fp) { 289 char word[RESCONFMAXLINELEN]; 290 int cp; 291 isc_result_t result; 292 293 cp = getword(fp, word, sizeof(word)); 294 if (strlen(word) == 0U) { 295 return (ISC_R_UNEXPECTEDEND); /* Nothing on line. */ 296 } else if (cp == ' ' || cp == '\t') { 297 cp = eatwhite(fp); 298 } 299 300 if (cp != EOF && cp != '\n') { 301 return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */ 302 } 303 304 if (conf->numns == RESCONFMAXNAMESERVERS) { 305 return (ISC_R_SUCCESS); 306 } 307 308 result = add_server(conf->mctx, word, &conf->nameservers); 309 if (result != ISC_R_SUCCESS) { 310 return (result); 311 } 312 conf->numns++; 313 314 return (ISC_R_SUCCESS); 315 } 316 317 static isc_result_t 318 resconf_parsedomain(irs_resconf_t *conf, FILE *fp) { 319 char word[RESCONFMAXLINELEN]; 320 int res; 321 unsigned int i; 322 323 res = getword(fp, word, sizeof(word)); 324 if (strlen(word) == 0U) { 325 return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */ 326 } else if (res == ' ' || res == '\t') { 327 res = eatwhite(fp); 328 } 329 330 if (res != EOF && res != '\n') { 331 return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */ 332 } 333 334 if (conf->domainname != NULL) { 335 isc_mem_free(conf->mctx, conf->domainname); 336 } 337 338 /* 339 * Search and domain are mutually exclusive. 340 */ 341 for (i = 0; i < RESCONFMAXSEARCH; i++) { 342 if (conf->search[i] != NULL) { 343 isc_mem_free(conf->mctx, conf->search[i]); 344 conf->search[i] = NULL; 345 } 346 } 347 conf->searchnxt = 0; 348 349 conf->domainname = isc_mem_strdup(conf->mctx, word); 350 351 return (ISC_R_SUCCESS); 352 } 353 354 static isc_result_t 355 resconf_parsesearch(irs_resconf_t *conf, FILE *fp) { 356 int delim; 357 unsigned int idx; 358 char word[RESCONFMAXLINELEN]; 359 360 if (conf->domainname != NULL) { 361 /* 362 * Search and domain are mutually exclusive. 363 */ 364 isc_mem_free(conf->mctx, conf->domainname); 365 conf->domainname = NULL; 366 } 367 368 /* 369 * Remove any previous search definitions. 370 */ 371 for (idx = 0; idx < RESCONFMAXSEARCH; idx++) { 372 if (conf->search[idx] != NULL) { 373 isc_mem_free(conf->mctx, conf->search[idx]); 374 conf->search[idx] = NULL; 375 } 376 } 377 conf->searchnxt = 0; 378 379 delim = getword(fp, word, sizeof(word)); 380 if (strlen(word) == 0U) { 381 return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */ 382 } 383 384 idx = 0; 385 while (strlen(word) > 0U) { 386 if (conf->searchnxt == RESCONFMAXSEARCH) { 387 goto ignore; /* Too many domains. */ 388 } 389 390 INSIST(idx < sizeof(conf->search) / sizeof(conf->search[0])); 391 conf->search[idx] = isc_mem_strdup(conf->mctx, word); 392 idx++; 393 conf->searchnxt++; 394 395 ignore: 396 if (delim == EOF || delim == '\n') { 397 break; 398 } else { 399 delim = getword(fp, word, sizeof(word)); 400 } 401 } 402 403 return (ISC_R_SUCCESS); 404 } 405 406 static isc_result_t 407 resconf_parsesortlist(irs_resconf_t *conf, FILE *fp) { 408 int delim, res; 409 unsigned int idx; 410 char word[RESCONFMAXLINELEN]; 411 char *p; 412 413 delim = getword(fp, word, sizeof(word)); 414 if (strlen(word) == 0U) { 415 return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */ 416 } 417 418 while (strlen(word) > 0U) { 419 if (conf->sortlistnxt == RESCONFMAXSORTLIST) { 420 return (ISC_R_QUOTA); /* Too many values. */ 421 } 422 423 p = strchr(word, '/'); 424 if (p != NULL) { 425 *p++ = '\0'; 426 } 427 428 idx = conf->sortlistnxt; 429 INSIST(idx < 430 sizeof(conf->sortlist) / sizeof(conf->sortlist[0])); 431 res = create_addr(word, &conf->sortlist[idx].addr, 1); 432 if (res != ISC_R_SUCCESS) { 433 return (res); 434 } 435 436 if (p != NULL) { 437 res = create_addr(p, &conf->sortlist[idx].mask, 0); 438 if (res != ISC_R_SUCCESS) { 439 return (res); 440 } 441 } else { 442 /* 443 * Make up a mask. (XXX: is this correct?) 444 */ 445 conf->sortlist[idx].mask = conf->sortlist[idx].addr; 446 memset(&conf->sortlist[idx].mask.type, 0xff, 447 sizeof(conf->sortlist[idx].mask.type)); 448 } 449 450 conf->sortlistnxt++; 451 452 if (delim == EOF || delim == '\n') { 453 break; 454 } else { 455 delim = getword(fp, word, sizeof(word)); 456 } 457 } 458 459 return (ISC_R_SUCCESS); 460 } 461 462 static isc_result_t 463 resconf_parseoption(irs_resconf_t *conf, FILE *fp) { 464 int delim; 465 long ndots; 466 char *p; 467 char word[RESCONFMAXLINELEN]; 468 469 delim = getword(fp, word, sizeof(word)); 470 if (strlen(word) == 0U) { 471 return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */ 472 } 473 474 while (strlen(word) > 0U) { 475 if (strcmp("debug", word) == 0) { 476 conf->resdebug = 1; 477 } else if (strncmp("ndots:", word, 6) == 0) { 478 ndots = strtol(word + 6, &p, 10); 479 if (*p != '\0') { /* Bad string. */ 480 return (ISC_R_UNEXPECTEDTOKEN); 481 } 482 if (ndots < 0 || ndots > 0xff) { /* Out of range. */ 483 return (ISC_R_RANGE); 484 } 485 conf->ndots = (uint8_t)ndots; 486 } 487 488 if (delim == EOF || delim == '\n') { 489 break; 490 } else { 491 delim = getword(fp, word, sizeof(word)); 492 } 493 } 494 495 return (ISC_R_SUCCESS); 496 } 497 498 static isc_result_t 499 add_search(irs_resconf_t *conf, char *domain) { 500 irs_resconf_search_t *entry; 501 502 entry = isc_mem_get(conf->mctx, sizeof(*entry)); 503 504 entry->domain = domain; 505 ISC_LINK_INIT(entry, link); 506 ISC_LIST_APPEND(conf->searchlist, entry, link); 507 508 return (ISC_R_SUCCESS); 509 } 510 511 /*% parses a file and fills in the data structure. */ 512 isc_result_t 513 irs_resconf_load(isc_mem_t *mctx, const char *filename, irs_resconf_t **confp) { 514 FILE *fp = NULL; 515 char word[256]; 516 isc_result_t rval, ret = ISC_R_SUCCESS; 517 irs_resconf_t *conf; 518 unsigned int i; 519 int stopchar; 520 521 REQUIRE(mctx != NULL); 522 REQUIRE(filename != NULL); 523 REQUIRE(strlen(filename) > 0U); 524 REQUIRE(confp != NULL && *confp == NULL); 525 526 conf = isc_mem_get(mctx, sizeof(*conf)); 527 528 conf->mctx = mctx; 529 ISC_LIST_INIT(conf->nameservers); 530 ISC_LIST_INIT(conf->searchlist); 531 conf->numns = 0; 532 conf->domainname = NULL; 533 conf->searchnxt = 0; 534 conf->sortlistnxt = 0; 535 conf->resdebug = 0; 536 conf->ndots = 1; 537 for (i = 0; i < RESCONFMAXSEARCH; i++) { 538 conf->search[i] = NULL; 539 } 540 541 errno = 0; 542 if ((fp = fopen(filename, "r")) != NULL) { 543 do { 544 stopchar = getword(fp, word, sizeof(word)); 545 if (stopchar == EOF) { 546 rval = ISC_R_SUCCESS; 547 POST(rval); 548 break; 549 } 550 551 if (strlen(word) == 0U) { 552 rval = ISC_R_SUCCESS; 553 } else if (strcmp(word, "nameserver") == 0) { 554 rval = resconf_parsenameserver(conf, fp); 555 } else if (strcmp(word, "domain") == 0) { 556 rval = resconf_parsedomain(conf, fp); 557 } else if (strcmp(word, "search") == 0) { 558 rval = resconf_parsesearch(conf, fp); 559 } else if (strcmp(word, "sortlist") == 0) { 560 rval = resconf_parsesortlist(conf, fp); 561 } else if (strcmp(word, "options") == 0) { 562 rval = resconf_parseoption(conf, fp); 563 } else { 564 /* unrecognised word. Ignore entire line */ 565 rval = ISC_R_SUCCESS; 566 stopchar = eatline(fp); 567 if (stopchar == EOF) { 568 break; 569 } 570 } 571 if (ret == ISC_R_SUCCESS && rval != ISC_R_SUCCESS) { 572 ret = rval; 573 } 574 } while (1); 575 576 fclose(fp); 577 } else { 578 switch (errno) { 579 case ENOENT: 580 break; 581 default: 582 isc_mem_put(mctx, conf, sizeof(*conf)); 583 return (ISC_R_INVALIDFILE); 584 } 585 } 586 587 if (ret != ISC_R_SUCCESS) { 588 goto error; 589 } 590 591 /* 592 * Construct unified search list from domain or configured 593 * search list 594 */ 595 if (conf->domainname != NULL) { 596 ret = add_search(conf, conf->domainname); 597 } else if (conf->searchnxt > 0) { 598 for (i = 0; i < conf->searchnxt; i++) { 599 ret = add_search(conf, conf->search[i]); 600 if (ret != ISC_R_SUCCESS) { 601 break; 602 } 603 } 604 } 605 606 #if HAVE_GET_WIN32_NAMESERVERS 607 ret = get_win32_nameservers(conf); 608 if (ret != ISC_R_SUCCESS) { 609 goto error; 610 } 611 #endif /* if HAVE_GET_WIN32_NAMESERVERS */ 612 613 /* If we don't find a nameserver fall back to localhost */ 614 if (conf->numns == 0U) { 615 INSIST(ISC_LIST_EMPTY(conf->nameservers)); 616 617 /* XXX: should we catch errors? */ 618 (void)add_server(conf->mctx, "::1", &conf->nameservers); 619 (void)add_server(conf->mctx, "127.0.0.1", &conf->nameservers); 620 } 621 622 error: 623 conf->magic = IRS_RESCONF_MAGIC; 624 625 if (ret != ISC_R_SUCCESS) { 626 irs_resconf_destroy(&conf); 627 } else { 628 if (fp == NULL) { 629 ret = ISC_R_FILENOTFOUND; 630 } 631 *confp = conf; 632 } 633 634 return (ret); 635 } 636 637 void 638 irs_resconf_destroy(irs_resconf_t **confp) { 639 irs_resconf_t *conf; 640 isc_sockaddr_t *address; 641 irs_resconf_search_t *searchentry; 642 unsigned int i; 643 644 REQUIRE(confp != NULL); 645 conf = *confp; 646 *confp = NULL; 647 REQUIRE(IRS_RESCONF_VALID(conf)); 648 649 while ((searchentry = ISC_LIST_HEAD(conf->searchlist)) != NULL) { 650 ISC_LIST_UNLINK(conf->searchlist, searchentry, link); 651 isc_mem_put(conf->mctx, searchentry, sizeof(*searchentry)); 652 } 653 654 while ((address = ISC_LIST_HEAD(conf->nameservers)) != NULL) { 655 ISC_LIST_UNLINK(conf->nameservers, address, link); 656 isc_mem_put(conf->mctx, address, sizeof(*address)); 657 } 658 659 if (conf->domainname != NULL) { 660 isc_mem_free(conf->mctx, conf->domainname); 661 } 662 663 for (i = 0; i < RESCONFMAXSEARCH; i++) { 664 if (conf->search[i] != NULL) { 665 isc_mem_free(conf->mctx, conf->search[i]); 666 } 667 } 668 669 isc_mem_put(conf->mctx, conf, sizeof(*conf)); 670 } 671 672 isc_sockaddrlist_t * 673 irs_resconf_getnameservers(irs_resconf_t *conf) { 674 REQUIRE(IRS_RESCONF_VALID(conf)); 675 676 return (&conf->nameservers); 677 } 678 679 irs_resconf_searchlist_t * 680 irs_resconf_getsearchlist(irs_resconf_t *conf) { 681 REQUIRE(IRS_RESCONF_VALID(conf)); 682 683 return (&conf->searchlist); 684 } 685 686 unsigned int 687 irs_resconf_getndots(irs_resconf_t *conf) { 688 REQUIRE(IRS_RESCONF_VALID(conf)); 689 690 return ((unsigned int)conf->ndots); 691 } 692