1 /* $NetBSD: resconf.c,v 1.8 2014/12/10 04:37:59 christos Exp $ */
4 * Copyright (C) 2009, 2011, 2012, 2014 Internet Systems Consortium, Inc. ("ISC")
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.
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.
21 /*! \file resconf.c */
24 * Module for parsing resolv.conf files (largely derived from lwconfig.c).
26 * irs_resconf_load() opens the file filename and parses it to initialize
27 * the configuration structure.
29 * \section lwconfig_return Return Values
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.
35 * \section lwconfig_see See Also
37 * stdio(3), \link resolver resolver \endlink
39 * \section files Files
47 #include <sys/types.h>
48 #include <sys/socket.h>
58 #include <isc/magic.h>
60 #include <isc/netaddr.h>
61 #include <isc/sockaddr.h>
64 #include <irs/netdb.h>
65 #include <irs/resconf.h>
67 #define IRS_RESCONF_MAGIC ISC_MAGIC('R', 'E', 'S', 'c')
68 #define IRS_RESCONF_VALID(c) ISC_MAGIC_VALID(c, IRS_RESCONF_MAGIC)
74 #if ! defined(NS_INADDRSZ)
78 #if ! defined(NS_IN6ADDRSZ)
79 #define NS_IN6ADDRSZ 16
83 * resolv.conf parameters
86 #define RESCONFMAXNAMESERVERS 3 /*%< max 3 "nameserver" entries */
87 #define RESCONFMAXSEARCH 8 /*%< max 8 domains in "search" entry */
88 #define RESCONFMAXLINELEN 256 /*%< max size of a line */
89 #define RESCONFMAXSORTLIST 10 /*%< max 10 */
92 * configuration data structure
97 * The configuration data is a thread-specific object, and does not
103 isc_sockaddrlist_t nameservers
;
104 unsigned int numns
; /*%< number of configured servers */
107 char *search
[RESCONFMAXSEARCH
];
108 isc_uint8_t searchnxt
; /*%< index for next free slot */
110 irs_resconf_searchlist_t searchlist
;
114 /*% mask has a non-zero 'family' if set */
116 } sortlist
[RESCONFMAXSORTLIST
];
117 isc_uint8_t sortlistnxt
;
119 /*%< non-zero if 'options debug' set */
120 isc_uint8_t resdebug
;
121 /*%< set to n in 'options ndots:n' */
126 resconf_parsenameserver(irs_resconf_t
*conf
, FILE *fp
);
128 resconf_parsedomain(irs_resconf_t
*conf
, FILE *fp
);
130 resconf_parsesearch(irs_resconf_t
*conf
, FILE *fp
);
132 resconf_parsesortlist(irs_resconf_t
*conf
, FILE *fp
);
134 resconf_parseoption(irs_resconf_t
*ctx
, FILE *fp
);
137 * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
144 while (ch
!= '\n' && ch
!= EOF
)
151 * Eats white space up to next newline or non-whitespace character (of
152 * EOF). Returns the last character read. Comments are considered white
160 while (ch
!= '\n' && ch
!= EOF
&& isspace((unsigned char)ch
))
163 if (ch
== ';' || ch
== '#')
170 * Skip over any leading whitespace and then read in the next sequence of
171 * non-whitespace characters. In this context newline is not considered
172 * whitespace. Returns EOF on end-of-file, or the character
173 * that caused the reading to stop.
176 getword(FILE *fp
, char *buffer
, size_t size
) {
180 REQUIRE(buffer
!= NULL
);
193 if (ch
== EOF
|| isspace((unsigned char)ch
))
195 else if ((size_t) (p
- buffer
) == size
- 1)
196 return (EOF
); /* Not enough space. */
206 add_server(isc_mem_t
*mctx
, const char *address_str
,
207 isc_sockaddrlist_t
*nameservers
)
210 isc_sockaddr_t
*address
= NULL
;
211 struct addrinfo hints
, *res
;
212 isc_result_t result
= ISC_R_SUCCESS
;
215 memset(&hints
, 0, sizeof(hints
));
216 hints
.ai_family
= AF_UNSPEC
;
217 hints
.ai_socktype
= SOCK_DGRAM
;
218 hints
.ai_protocol
= IPPROTO_UDP
;
219 hints
.ai_flags
= AI_NUMERICHOST
;
220 error
= getaddrinfo(address_str
, "53", &hints
, &res
);
222 return (ISC_R_BADADDRESSFORM
);
224 /* XXX: special case: treat all-0 IPv4 address as loopback */
225 if (res
->ai_family
== AF_INET
) {
227 unsigned char zeroaddress
[] = {0, 0, 0, 0};
228 unsigned char loopaddress
[] = {127, 0, 0, 1};
230 v4
= &((struct sockaddr_in
*)res
->ai_addr
)->sin_addr
;
231 if (memcmp(v4
, zeroaddress
, 4) == 0)
232 memmove(v4
, loopaddress
, 4);
235 address
= isc_mem_get(mctx
, sizeof(*address
));
236 if (address
== NULL
) {
237 result
= ISC_R_NOMEMORY
;
240 if (res
->ai_addrlen
> sizeof(address
->type
)) {
241 isc_mem_put(mctx
, address
, sizeof(*address
));
242 result
= ISC_R_RANGE
;
245 address
->length
= (unsigned int)res
->ai_addrlen
;
246 memmove(&address
->type
.ss
, res
->ai_addr
, res
->ai_addrlen
);
247 ISC_LINK_INIT(address
, link
);
248 ISC_LIST_APPEND(*nameservers
, address
, link
);
257 create_addr(const char *buffer
, isc_netaddr_t
*addr
, int convert_zero
) {
261 if (inet_aton(buffer
, &v4
) == 1) {
263 unsigned char zeroaddress
[] = {0, 0, 0, 0};
264 unsigned char loopaddress
[] = {127, 0, 0, 1};
265 if (memcmp(&v4
, zeroaddress
, 4) == 0)
266 memmove(&v4
, loopaddress
, 4);
268 addr
->family
= AF_INET
;
269 memmove(&addr
->type
.in
, &v4
, NS_INADDRSZ
);
271 } else if (inet_pton(AF_INET6
, buffer
, &v6
) == 1) {
272 addr
->family
= AF_INET6
;
273 memmove(&addr
->type
.in6
, &v6
, NS_IN6ADDRSZ
);
276 return (ISC_R_BADADDRESSFORM
); /* Unrecognised format. */
278 return (ISC_R_SUCCESS
);
282 resconf_parsenameserver(irs_resconf_t
*conf
, FILE *fp
) {
283 char word
[RESCONFMAXLINELEN
];
287 if (conf
->numns
== RESCONFMAXNAMESERVERS
)
288 return (ISC_R_SUCCESS
);
290 cp
= getword(fp
, word
, sizeof(word
));
291 if (strlen(word
) == 0U)
292 return (ISC_R_UNEXPECTEDEND
); /* Nothing on line. */
293 else if (cp
== ' ' || cp
== '\t')
296 if (cp
!= EOF
&& cp
!= '\n')
297 return (ISC_R_UNEXPECTEDTOKEN
); /* Extra junk on line. */
299 result
= add_server(conf
->mctx
, word
, &conf
->nameservers
);
300 if (result
!= ISC_R_SUCCESS
)
304 return (ISC_R_SUCCESS
);
308 resconf_parsedomain(irs_resconf_t
*conf
, FILE *fp
) {
309 char word
[RESCONFMAXLINELEN
];
312 res
= getword(fp
, word
, sizeof(word
));
313 if (strlen(word
) == 0U)
314 return (ISC_R_UNEXPECTEDEND
); /* Nothing else on line. */
315 else if (res
== ' ' || res
== '\t')
318 if (res
!= EOF
&& res
!= '\n')
319 return (ISC_R_UNEXPECTEDTOKEN
); /* Extra junk on line. */
321 if (conf
->domainname
!= NULL
)
322 isc_mem_free(conf
->mctx
, conf
->domainname
);
325 * Search and domain are mutually exclusive.
327 for (i
= 0; i
< RESCONFMAXSEARCH
; i
++) {
328 if (conf
->search
[i
] != NULL
) {
329 isc_mem_free(conf
->mctx
, conf
->search
[i
]);
330 conf
->search
[i
] = NULL
;
335 conf
->domainname
= isc_mem_strdup(conf
->mctx
, word
);
336 if (conf
->domainname
== NULL
)
337 return (ISC_R_NOMEMORY
);
339 return (ISC_R_SUCCESS
);
343 resconf_parsesearch(irs_resconf_t
*conf
, FILE *fp
) {
345 char word
[RESCONFMAXLINELEN
];
347 if (conf
->domainname
!= NULL
) {
349 * Search and domain are mutually exclusive.
351 isc_mem_free(conf
->mctx
, conf
->domainname
);
352 conf
->domainname
= NULL
;
356 * Remove any previous search definitions.
358 for (idx
= 0; idx
< RESCONFMAXSEARCH
; idx
++) {
359 if (conf
->search
[idx
] != NULL
) {
360 isc_mem_free(conf
->mctx
, conf
->search
[idx
]);
361 conf
->search
[idx
] = NULL
;
366 delim
= getword(fp
, word
, sizeof(word
));
367 if (strlen(word
) == 0U)
368 return (ISC_R_UNEXPECTEDEND
); /* Nothing else on line. */
371 while (strlen(word
) > 0U) {
372 if (conf
->searchnxt
== RESCONFMAXSEARCH
)
373 goto ignore
; /* Too many domains. */
375 conf
->search
[idx
] = isc_mem_strdup(conf
->mctx
, word
);
376 if (conf
->search
[idx
] == NULL
)
377 return (ISC_R_NOMEMORY
);
382 if (delim
== EOF
|| delim
== '\n')
385 delim
= getword(fp
, word
, sizeof(word
));
388 return (ISC_R_SUCCESS
);
392 resconf_parsesortlist(irs_resconf_t
*conf
, FILE *fp
) {
394 char word
[RESCONFMAXLINELEN
];
397 delim
= getword(fp
, word
, sizeof(word
));
398 if (strlen(word
) == 0U)
399 return (ISC_R_UNEXPECTEDEND
); /* Empty line after keyword. */
401 while (strlen(word
) > 0U) {
402 if (conf
->sortlistnxt
== RESCONFMAXSORTLIST
)
403 return (ISC_R_QUOTA
); /* Too many values. */
405 p
= strchr(word
, '/');
409 idx
= conf
->sortlistnxt
;
410 res
= create_addr(word
, &conf
->sortlist
[idx
].addr
, 1);
411 if (res
!= ISC_R_SUCCESS
)
415 res
= create_addr(p
, &conf
->sortlist
[idx
].mask
, 0);
416 if (res
!= ISC_R_SUCCESS
)
420 * Make up a mask. (XXX: is this correct?)
422 conf
->sortlist
[idx
].mask
= conf
->sortlist
[idx
].addr
;
423 memset(&conf
->sortlist
[idx
].mask
.type
, 0xff,
424 sizeof(conf
->sortlist
[idx
].mask
.type
));
429 if (delim
== EOF
|| delim
== '\n')
432 delim
= getword(fp
, word
, sizeof(word
));
435 return (ISC_R_SUCCESS
);
439 resconf_parseoption(irs_resconf_t
*conf
, FILE *fp
) {
443 char word
[RESCONFMAXLINELEN
];
445 delim
= getword(fp
, word
, sizeof(word
));
446 if (strlen(word
) == 0U)
447 return (ISC_R_UNEXPECTEDEND
); /* Empty line after keyword. */
449 while (strlen(word
) > 0U) {
450 if (strcmp("debug", word
) == 0) {
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
;
461 if (delim
== EOF
|| delim
== '\n')
464 delim
= getword(fp
, word
, sizeof(word
));
467 return (ISC_R_SUCCESS
);
471 add_search(irs_resconf_t
*conf
, char *domain
) {
472 irs_resconf_search_t
*entry
;
474 entry
= isc_mem_get(conf
->mctx
, sizeof(*entry
));
476 return (ISC_R_NOMEMORY
);
478 entry
->domain
= domain
;
479 ISC_LINK_INIT(entry
, link
);
480 ISC_LIST_APPEND(conf
->searchlist
, entry
, link
);
482 return (ISC_R_SUCCESS
);
485 /*% parses a file and fills in the data structure. */
487 irs_resconf_load(isc_mem_t
*mctx
, const char *filename
, irs_resconf_t
**confp
)
491 isc_result_t rval
, ret
= ISC_R_SUCCESS
;
495 REQUIRE(mctx
!= NULL
);
496 REQUIRE(filename
!= NULL
);
497 REQUIRE(strlen(filename
) > 0U);
498 REQUIRE(confp
!= NULL
&& *confp
== NULL
);
500 conf
= isc_mem_get(mctx
, sizeof(*conf
));
502 return (ISC_R_NOMEMORY
);
505 ISC_LIST_INIT(conf
->nameservers
);
507 conf
->domainname
= NULL
;
511 for (i
= 0; i
< RESCONFMAXSEARCH
; i
++)
512 conf
->search
[i
] = NULL
;
515 if ((fp
= fopen(filename
, "r")) != NULL
) {
517 stopchar
= getword(fp
, word
, sizeof(word
));
518 if (stopchar
== EOF
) {
519 rval
= ISC_R_SUCCESS
;
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
);
537 /* unrecognised word. Ignore entire line */
538 rval
= ISC_R_SUCCESS
;
539 stopchar
= eatline(fp
);
540 if (stopchar
== EOF
) {
544 if (ret
== ISC_R_SUCCESS
&& rval
!= ISC_R_SUCCESS
)
554 isc_mem_put(mctx
, conf
, sizeof(*conf
));
555 return (ISC_R_INVALIDFILE
);
559 /* If we don't find a nameserver fall back to localhost */
560 if (conf
->numns
== 0) {
561 INSIST(ISC_LIST_EMPTY(conf
->nameservers
));
563 /* XXX: should we catch errors? */
564 (void)add_server(conf
->mctx
, "127.0.0.1", &conf
->nameservers
);
565 (void)add_server(conf
->mctx
, "::1", &conf
->nameservers
);
569 * Construct unified search list from domain or configured
572 ISC_LIST_INIT(conf
->searchlist
);
573 if (conf
->domainname
!= NULL
) {
574 ret
= add_search(conf
, conf
->domainname
);
575 } else if (conf
->searchnxt
> 0) {
576 for (i
= 0; i
< conf
->searchnxt
; i
++) {
577 ret
= add_search(conf
, conf
->search
[i
]);
578 if (ret
!= ISC_R_SUCCESS
)
583 conf
->magic
= IRS_RESCONF_MAGIC
;
585 if (ret
!= ISC_R_SUCCESS
)
586 irs_resconf_destroy(&conf
);
589 ret
= ISC_R_FILENOTFOUND
;
597 irs_resconf_destroy(irs_resconf_t
**confp
) {
599 isc_sockaddr_t
*address
;
600 irs_resconf_search_t
*searchentry
;
603 REQUIRE(confp
!= NULL
);
605 REQUIRE(IRS_RESCONF_VALID(conf
));
607 while ((searchentry
= ISC_LIST_HEAD(conf
->searchlist
)) != NULL
) {
608 ISC_LIST_UNLINK(conf
->searchlist
, searchentry
, link
);
609 isc_mem_put(conf
->mctx
, searchentry
, sizeof(*searchentry
));
612 while ((address
= ISC_LIST_HEAD(conf
->nameservers
)) != NULL
) {
613 ISC_LIST_UNLINK(conf
->nameservers
, address
, link
);
614 isc_mem_put(conf
->mctx
, address
, sizeof(*address
));
617 if (conf
->domainname
!= NULL
)
618 isc_mem_free(conf
->mctx
, conf
->domainname
);
620 for (i
= 0; i
< RESCONFMAXSEARCH
; i
++) {
621 if (conf
->search
[i
] != NULL
)
622 isc_mem_free(conf
->mctx
, conf
->search
[i
]);
625 isc_mem_put(conf
->mctx
, conf
, sizeof(*conf
));
631 irs_resconf_getnameservers(irs_resconf_t
*conf
) {
632 REQUIRE(IRS_RESCONF_VALID(conf
));
634 return (&conf
->nameservers
);
637 irs_resconf_searchlist_t
*
638 irs_resconf_getsearchlist(irs_resconf_t
*conf
) {
639 REQUIRE(IRS_RESCONF_VALID(conf
));
641 return (&conf
->searchlist
);
645 irs_resconf_getndots(irs_resconf_t
*conf
) {
646 REQUIRE(IRS_RESCONF_VALID(conf
));
648 return ((unsigned int)conf
->ndots
);