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