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