resconf.c revision 1.6
1/* $NetBSD: resconf.c,v 1.6 2022/09/23 12:15:32 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 89struct 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 121static isc_result_t 122resconf_parsenameserver(irs_resconf_t *conf, FILE *fp); 123static isc_result_t 124resconf_parsedomain(irs_resconf_t *conf, FILE *fp); 125static isc_result_t 126resconf_parsesearch(irs_resconf_t *conf, FILE *fp); 127static isc_result_t 128resconf_parsesortlist(irs_resconf_t *conf, FILE *fp); 129static isc_result_t 130resconf_parseoption(irs_resconf_t *ctx, FILE *fp); 131 132#if HAVE_GET_WIN32_NAMESERVERS 133static isc_result_t 134get_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 */ 140static int 141eatline(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 */ 157static int 158eatwhite(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 */ 179static int 180getword(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 212static isc_result_t 213add_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 254cleanup: 255 freeaddrinfo(res); 256 257 return (result); 258} 259 260static isc_result_t 261create_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 287static isc_result_t 288resconf_parsenameserver(irs_resconf_t *conf, FILE *fp) { 289 char word[RESCONFMAXLINELEN]; 290 int cp; 291 isc_result_t result; 292 293 if (conf->numns == RESCONFMAXNAMESERVERS) { 294 return (ISC_R_SUCCESS); 295 } 296 297 cp = getword(fp, word, sizeof(word)); 298 if (strlen(word) == 0U) { 299 return (ISC_R_UNEXPECTEDEND); /* Nothing on line. */ 300 } else if (cp == ' ' || cp == '\t') { 301 cp = eatwhite(fp); 302 } 303 304 if (cp != EOF && cp != '\n') { 305 return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */ 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 317static isc_result_t 318resconf_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 354static isc_result_t 355resconf_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 406static isc_result_t 407resconf_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 462static isc_result_t 463resconf_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 498static isc_result_t 499add_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. */ 512isc_result_t 513irs_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 622error: 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 637void 638irs_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 672isc_sockaddrlist_t * 673irs_resconf_getnameservers(irs_resconf_t *conf) { 674 REQUIRE(IRS_RESCONF_VALID(conf)); 675 676 return (&conf->nameservers); 677} 678 679irs_resconf_searchlist_t * 680irs_resconf_getsearchlist(irs_resconf_t *conf) { 681 REQUIRE(IRS_RESCONF_VALID(conf)); 682 683 return (&conf->searchlist); 684} 685 686unsigned int 687irs_resconf_getndots(irs_resconf_t *conf) { 688 REQUIRE(IRS_RESCONF_VALID(conf)); 689 690 return ((unsigned int)conf->ndots); 691} 692