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