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