2 * Copyright (C) 2009, 2011, 2012, 2014, 2015 Internet Systems Consortium, Inc. ("ISC")
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.
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.
19 /*! \file resconf.c */
22 * Module for parsing resolv.conf files (largely derived from lwconfig.c).
24 * irs_resconf_load() opens the file filename and parses it to initialize
25 * the configuration structure.
27 * \section lwconfig_return Return Values
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.
33 * \section lwconfig_see See Also
35 * stdio(3), \link resolver resolver \endlink
37 * \section files Files
44 #include <sys/types.h>
45 #include <sys/socket.h>
54 #include <isc/magic.h>
56 #include <isc/netaddr.h>
57 #include <isc/sockaddr.h>
60 #include <irs/resconf.h>
62 #define IRS_RESCONF_MAGIC ISC_MAGIC('R', 'E', 'S', 'c')
63 #define IRS_RESCONF_VALID(c) ISC_MAGIC_VALID(c, IRS_RESCONF_MAGIC)
69 #if ! defined(NS_INADDRSZ)
73 #if ! defined(NS_IN6ADDRSZ)
74 #define NS_IN6ADDRSZ 16
78 * resolv.conf parameters
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 */
87 * configuration data structure
92 * The configuration data is a thread-specific object, and does not
98 isc_sockaddrlist_t nameservers;
99 unsigned int numns; /*%< number of configured servers */
102 char *search[RESCONFMAXSEARCH];
103 isc_uint8_t searchnxt; /*%< index for next free slot */
105 irs_resconf_searchlist_t searchlist;
109 /*% mask has a non-zero 'family' if set */
111 } sortlist[RESCONFMAXSORTLIST];
112 isc_uint8_t sortlistnxt;
114 /*%< non-zero if 'options debug' set */
115 isc_uint8_t resdebug;
116 /*%< set to n in 'options ndots:n' */
121 resconf_parsenameserver(irs_resconf_t *conf, FILE *fp);
123 resconf_parsedomain(irs_resconf_t *conf, FILE *fp);
125 resconf_parsesearch(irs_resconf_t *conf, FILE *fp);
127 resconf_parsesortlist(irs_resconf_t *conf, FILE *fp);
129 resconf_parseoption(irs_resconf_t *ctx, FILE *fp);
132 * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
139 while (ch != '\n' && ch != EOF)
146 * Eats white space up to next newline or non-whitespace character (of
147 * EOF). Returns the last character read. Comments are considered white
155 while (ch != '\n' && ch != EOF && isspace((unsigned char)ch))
158 if (ch == ';' || ch == '#')
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.
171 getword(FILE *fp, char *buffer, size_t size) {
175 REQUIRE(buffer != NULL);
189 if (ch == EOF || isspace((unsigned char)ch))
191 else if ((size_t) (p - buffer) == size - 1)
192 return (EOF); /* Not enough space. */
202 add_server(isc_mem_t *mctx, const char *address_str,
203 isc_sockaddrlist_t *nameservers)
206 isc_sockaddr_t *address = NULL;
207 struct addrinfo hints, *res;
208 isc_result_t result = ISC_R_SUCCESS;
211 memset(&hints, 0, sizeof(hints));
212 hints.ai_family = AF_UNSPEC;
213 hints.ai_socktype = SOCK_DGRAM;
214 hints.ai_protocol = IPPROTO_UDP;
215 hints.ai_flags = AI_NUMERICHOST;
216 error = getaddrinfo(address_str, "53", &hints, &res);
218 return (ISC_R_BADADDRESSFORM);
220 /* XXX: special case: treat all-0 IPv4 address as loopback */
221 if (res->ai_family == AF_INET) {
223 unsigned char zeroaddress[] = {0, 0, 0, 0};
224 unsigned char loopaddress[] = {127, 0, 0, 1};
226 v4 = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
227 if (memcmp(v4, zeroaddress, 4) == 0)
228 memmove(v4, loopaddress, 4);
231 address = isc_mem_get(mctx, sizeof(*address));
232 if (address == NULL) {
233 result = ISC_R_NOMEMORY;
236 if (res->ai_addrlen > sizeof(address->type)) {
237 isc_mem_put(mctx, address, sizeof(*address));
238 result = ISC_R_RANGE;
241 address->length = res->ai_addrlen;
242 memmove(&address->type.ss, res->ai_addr, res->ai_addrlen);
243 ISC_LINK_INIT(address, link);
244 ISC_LIST_APPEND(*nameservers, address, link);
253 create_addr(const char *buffer, isc_netaddr_t *addr, int convert_zero) {
257 if (inet_aton(buffer, &v4) == 1) {
259 unsigned char zeroaddress[] = {0, 0, 0, 0};
260 unsigned char loopaddress[] = {127, 0, 0, 1};
261 if (memcmp(&v4, zeroaddress, 4) == 0)
262 memmove(&v4, loopaddress, 4);
264 addr->family = AF_INET;
265 memmove(&addr->type.in, &v4, NS_INADDRSZ);
267 } else if (inet_pton(AF_INET6, buffer, &v6) == 1) {
268 addr->family = AF_INET6;
269 memmove(&addr->type.in6, &v6, NS_IN6ADDRSZ);
272 return (ISC_R_BADADDRESSFORM); /* Unrecognised format. */
274 return (ISC_R_SUCCESS);
278 resconf_parsenameserver(irs_resconf_t *conf, FILE *fp) {
279 char word[RESCONFMAXLINELEN];
283 if (conf->numns == RESCONFMAXNAMESERVERS)
284 return (ISC_R_SUCCESS);
286 cp = getword(fp, word, sizeof(word));
287 if (strlen(word) == 0U)
288 return (ISC_R_UNEXPECTEDEND); /* Nothing on line. */
289 else if (cp == ' ' || cp == '\t')
292 if (cp != EOF && cp != '\n')
293 return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
295 result = add_server(conf->mctx, word, &conf->nameservers);
296 if (result != ISC_R_SUCCESS)
300 return (ISC_R_SUCCESS);
304 resconf_parsedomain(irs_resconf_t *conf, FILE *fp) {
305 char word[RESCONFMAXLINELEN];
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')
314 if (res != EOF && res != '\n')
315 return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
317 if (conf->domainname != NULL)
318 isc_mem_free(conf->mctx, conf->domainname);
321 * Search and domain are mutually exclusive.
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;
331 conf->domainname = isc_mem_strdup(conf->mctx, word);
332 if (conf->domainname == NULL)
333 return (ISC_R_NOMEMORY);
335 return (ISC_R_SUCCESS);
339 resconf_parsesearch(irs_resconf_t *conf, FILE *fp) {
341 char word[RESCONFMAXLINELEN];
343 if (conf->domainname != NULL) {
345 * Search and domain are mutually exclusive.
347 isc_mem_free(conf->mctx, conf->domainname);
348 conf->domainname = NULL;
352 * Remove any previous search definitions.
354 for (idx = 0; idx < RESCONFMAXSEARCH; idx++) {
355 if (conf->search[idx] != NULL) {
356 isc_mem_free(conf->mctx, conf->search[idx]);
357 conf->search[idx] = NULL;
362 delim = getword(fp, word, sizeof(word));
363 if (strlen(word) == 0U)
364 return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */
367 while (strlen(word) > 0U) {
368 if (conf->searchnxt == RESCONFMAXSEARCH)
369 goto ignore; /* Too many domains. */
371 conf->search[idx] = isc_mem_strdup(conf->mctx, word);
372 if (conf->search[idx] == NULL)
373 return (ISC_R_NOMEMORY);
378 if (delim == EOF || delim == '\n')
381 delim = getword(fp, word, sizeof(word));
384 return (ISC_R_SUCCESS);
388 resconf_parsesortlist(irs_resconf_t *conf, FILE *fp) {
390 char word[RESCONFMAXLINELEN];
393 delim = getword(fp, word, sizeof(word));
394 if (strlen(word) == 0U)
395 return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
397 while (strlen(word) > 0U) {
398 if (conf->sortlistnxt == RESCONFMAXSORTLIST)
399 return (ISC_R_QUOTA); /* Too many values. */
401 p = strchr(word, '/');
405 idx = conf->sortlistnxt;
406 res = create_addr(word, &conf->sortlist[idx].addr, 1);
407 if (res != ISC_R_SUCCESS)
411 res = create_addr(p, &conf->sortlist[idx].mask, 0);
412 if (res != ISC_R_SUCCESS)
416 * Make up a mask. (XXX: is this correct?)
418 conf->sortlist[idx].mask = conf->sortlist[idx].addr;
419 memset(&conf->sortlist[idx].mask.type, 0xff,
420 sizeof(conf->sortlist[idx].mask.type));
425 if (delim == EOF || delim == '\n')
428 delim = getword(fp, word, sizeof(word));
431 return (ISC_R_SUCCESS);
435 resconf_parseoption(irs_resconf_t *conf, FILE *fp) {
439 char word[RESCONFMAXLINELEN];
441 delim = getword(fp, word, sizeof(word));
442 if (strlen(word) == 0U)
443 return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
445 while (strlen(word) > 0U) {
446 if (strcmp("debug", word) == 0) {
448 } else if (strncmp("ndots:", word, 6) == 0) {
449 ndots = strtol(word + 6, &p, 10);
450 if (*p != '\0') /* Bad string. */
451 return (ISC_R_UNEXPECTEDTOKEN);
452 if (ndots < 0 || ndots > 0xff) /* Out of range. */
453 return (ISC_R_RANGE);
454 conf->ndots = (isc_uint8_t)ndots;
457 if (delim == EOF || delim == '\n')
460 delim = getword(fp, word, sizeof(word));
463 return (ISC_R_SUCCESS);
467 add_search(irs_resconf_t *conf, char *domain) {
468 irs_resconf_search_t *entry;
470 entry = isc_mem_get(conf->mctx, sizeof(*entry));
472 return (ISC_R_NOMEMORY);
474 entry->domain = domain;
475 ISC_LINK_INIT(entry, link);
476 ISC_LIST_APPEND(conf->searchlist, entry, link);
478 return (ISC_R_SUCCESS);
481 /*% parses a file and fills in the data structure. */
483 irs_resconf_load(isc_mem_t *mctx, const char *filename, irs_resconf_t **confp)
487 isc_result_t rval, ret = ISC_R_SUCCESS;
491 REQUIRE(mctx != NULL);
492 REQUIRE(filename != NULL);
493 REQUIRE(strlen(filename) > 0U);
494 REQUIRE(confp != NULL && *confp == NULL);
496 conf = isc_mem_get(mctx, sizeof(*conf));
498 return (ISC_R_NOMEMORY);
501 ISC_LIST_INIT(conf->nameservers);
503 conf->domainname = NULL;
507 for (i = 0; i < RESCONFMAXSEARCH; i++)
508 conf->search[i] = NULL;
511 if ((fp = fopen(filename, "r")) != NULL) {
513 stopchar = getword(fp, word, sizeof(word));
514 if (stopchar == EOF) {
515 rval = ISC_R_SUCCESS;
520 if (strlen(word) == 0U)
521 rval = ISC_R_SUCCESS;
522 else if (strcmp(word, "nameserver") == 0)
523 rval = resconf_parsenameserver(conf, fp);
524 else if (strcmp(word, "domain") == 0)
525 rval = resconf_parsedomain(conf, fp);
526 else if (strcmp(word, "search") == 0)
527 rval = resconf_parsesearch(conf, fp);
528 else if (strcmp(word, "sortlist") == 0)
529 rval = resconf_parsesortlist(conf, fp);
530 else if (strcmp(word, "options") == 0)
531 rval = resconf_parseoption(conf, fp);
533 /* unrecognised word. Ignore entire line */
534 rval = ISC_R_SUCCESS;
535 stopchar = eatline(fp);
536 if (stopchar == EOF) {
540 if (ret == ISC_R_SUCCESS && rval != ISC_R_SUCCESS)
550 isc_mem_put(mctx, conf, sizeof(*conf));
551 return (ISC_R_INVALIDFILE);
555 /* If we don't find a nameserver fall back to localhost */
556 if (conf->numns == 0) {
557 INSIST(ISC_LIST_EMPTY(conf->nameservers));
559 /* XXX: should we catch errors? */
560 (void)add_server(conf->mctx, "127.0.0.1", &conf->nameservers);
561 (void)add_server(conf->mctx, "::1", &conf->nameservers);
565 * Construct unified search list from domain or configured
568 ISC_LIST_INIT(conf->searchlist);
569 if (conf->domainname != NULL) {
570 ret = add_search(conf, conf->domainname);
571 } else if (conf->searchnxt > 0) {
572 for (i = 0; i < conf->searchnxt; i++) {
573 ret = add_search(conf, conf->search[i]);
574 if (ret != ISC_R_SUCCESS)
579 conf->magic = IRS_RESCONF_MAGIC;
581 if (ret != ISC_R_SUCCESS)
582 irs_resconf_destroy(&conf);
585 ret = ISC_R_FILENOTFOUND;
593 irs_resconf_destroy(irs_resconf_t **confp) {
595 isc_sockaddr_t *address;
596 irs_resconf_search_t *searchentry;
599 REQUIRE(confp != NULL);
601 REQUIRE(IRS_RESCONF_VALID(conf));
603 while ((searchentry = ISC_LIST_HEAD(conf->searchlist)) != NULL) {
604 ISC_LIST_UNLINK(conf->searchlist, searchentry, link);
605 isc_mem_put(conf->mctx, searchentry, sizeof(*searchentry));
608 while ((address = ISC_LIST_HEAD(conf->nameservers)) != NULL) {
609 ISC_LIST_UNLINK(conf->nameservers, address, link);
610 isc_mem_put(conf->mctx, address, sizeof(*address));
613 if (conf->domainname != NULL)
614 isc_mem_free(conf->mctx, conf->domainname);
616 for (i = 0; i < RESCONFMAXSEARCH; i++) {
617 if (conf->search[i] != NULL)
618 isc_mem_free(conf->mctx, conf->search[i]);
621 isc_mem_put(conf->mctx, conf, sizeof(*conf));
627 irs_resconf_getnameservers(irs_resconf_t *conf) {
628 REQUIRE(IRS_RESCONF_VALID(conf));
630 return (&conf->nameservers);
633 irs_resconf_searchlist_t *
634 irs_resconf_getsearchlist(irs_resconf_t *conf) {
635 REQUIRE(IRS_RESCONF_VALID(conf));
637 return (&conf->searchlist);
641 irs_resconf_getndots(irs_resconf_t *conf) {
642 REQUIRE(IRS_RESCONF_VALID(conf));
644 return ((unsigned int)conf->ndots);