4 * Copyright (C) 2009 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.
19 /* Id: resconf.c,v 1.3 2009/09/02 23:48:02 tbox Exp */
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
46 #include <sys/types.h>
47 #include <sys/socket.h>
56 #include <isc/magic.h>
58 #include <isc/netaddr.h>
59 #include <isc/sockaddr.h>
62 #include <irs/resconf.h>
64 #define IRS_RESCONF_MAGIC ISC_MAGIC('R', 'E', 'S', 'c')
65 #define IRS_RESCONF_VALID(c) ISC_MAGIC_VALID(c, IRS_RESCONF_MAGIC)
71 #if ! defined(NS_INADDRSZ)
75 #if ! defined(NS_IN6ADDRSZ)
76 #define NS_IN6ADDRSZ 16
80 * resolv.conf parameters
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 */
89 * configuration data structure
94 * The configuration data is a thread-specific object, and does not
100 isc_sockaddrlist_t nameservers
;
101 unsigned int numns
; /*%< number of configured servers */
104 char *search
[RESCONFMAXSEARCH
];
105 isc_uint8_t searchnxt
; /*%< index for next free slot */
107 irs_resconf_searchlist_t searchlist
;
111 /*% mask has a non-zero 'family' if set */
113 } sortlist
[RESCONFMAXSORTLIST
];
114 isc_uint8_t sortlistnxt
;
116 /*%< non-zero if 'options debug' set */
117 isc_uint8_t resdebug
;
118 /*%< set to n in 'options ndots:n' */
123 resconf_parsenameserver(irs_resconf_t
*conf
, FILE *fp
);
125 resconf_parsedomain(irs_resconf_t
*conf
, FILE *fp
);
127 resconf_parsesearch(irs_resconf_t
*conf
, FILE *fp
);
129 resconf_parsesortlist(irs_resconf_t
*conf
, FILE *fp
);
131 resconf_parseoption(irs_resconf_t
*ctx
, FILE *fp
);
134 * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
141 while (ch
!= '\n' && ch
!= EOF
)
148 * Eats white space up to next newline or non-whitespace character (of
149 * EOF). Returns the last character read. Comments are considered white
157 while (ch
!= '\n' && ch
!= EOF
&& isspace((unsigned char)ch
))
160 if (ch
== ';' || ch
== '#')
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.
173 getword(FILE *fp
, char *buffer
, size_t size
) {
177 REQUIRE(buffer
!= NULL
);
190 if (ch
== EOF
|| isspace((unsigned char)ch
))
192 else if ((size_t) (p
- buffer
) == size
- 1)
193 return (EOF
); /* Not enough space. */
203 add_server(isc_mem_t
*mctx
, const char *address_str
,
204 isc_sockaddrlist_t
*nameservers
)
207 isc_sockaddr_t
*address
= NULL
;
208 struct addrinfo hints
, *res
;
209 isc_result_t result
= ISC_R_SUCCESS
;
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
);
219 return (ISC_R_BADADDRESSFORM
);
221 /* XXX: special case: treat all-0 IPv4 address as loopback */
222 if (res
->ai_family
== AF_INET
) {
224 unsigned char zeroaddress
[] = {0, 0, 0, 0};
225 unsigned char loopaddress
[] = {127, 0, 0, 1};
227 v4
= &((struct sockaddr_in
*)res
->ai_addr
)->sin_addr
;
228 if (memcmp(v4
, zeroaddress
, 4) == 0)
229 memcpy(v4
, loopaddress
, 4);
232 address
= isc_mem_get(mctx
, sizeof(*address
));
233 if (address
== NULL
) {
234 result
= ISC_R_NOMEMORY
;
237 if (res
->ai_addrlen
> sizeof(address
->type
)) {
238 isc_mem_put(mctx
, address
, sizeof(*address
));
239 result
= ISC_R_RANGE
;
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
);
254 create_addr(const char *buffer
, isc_netaddr_t
*addr
, int convert_zero
) {
258 if (inet_aton(buffer
, &v4
) == 1) {
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);
265 addr
->family
= AF_INET
;
266 memcpy(&addr
->type
.in
, &v4
, NS_INADDRSZ
);
268 } else if (inet_pton(AF_INET6
, buffer
, &v6
) == 1) {
269 addr
->family
= AF_INET6
;
270 memcpy(&addr
->type
.in6
, &v6
, NS_IN6ADDRSZ
);
273 return (ISC_R_BADADDRESSFORM
); /* Unrecognised format. */
275 return (ISC_R_SUCCESS
);
279 resconf_parsenameserver(irs_resconf_t
*conf
, FILE *fp
) {
280 char word
[RESCONFMAXLINELEN
];
284 if (conf
->numns
== RESCONFMAXNAMESERVERS
)
285 return (ISC_R_SUCCESS
);
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')
293 if (cp
!= EOF
&& cp
!= '\n')
294 return (ISC_R_UNEXPECTEDTOKEN
); /* Extra junk on line. */
296 result
= add_server(conf
->mctx
, word
, &conf
->nameservers
);
297 if (result
!= ISC_R_SUCCESS
)
301 return (ISC_R_SUCCESS
);
305 resconf_parsedomain(irs_resconf_t
*conf
, FILE *fp
) {
306 char word
[RESCONFMAXLINELEN
];
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')
315 if (res
!= EOF
&& res
!= '\n')
316 return (ISC_R_UNEXPECTEDTOKEN
); /* Extra junk on line. */
318 if (conf
->domainname
!= NULL
)
319 isc_mem_free(conf
->mctx
, conf
->domainname
);
322 * Search and domain are mutually exclusive.
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
;
332 conf
->domainname
= isc_mem_strdup(conf
->mctx
, word
);
333 if (conf
->domainname
== NULL
)
334 return (ISC_R_NOMEMORY
);
336 return (ISC_R_SUCCESS
);
340 resconf_parsesearch(irs_resconf_t
*conf
, FILE *fp
) {
342 char word
[RESCONFMAXLINELEN
];
344 if (conf
->domainname
!= NULL
) {
346 * Search and domain are mutually exclusive.
348 isc_mem_free(conf
->mctx
, conf
->domainname
);
349 conf
->domainname
= NULL
;
353 * Remove any previous search definitions.
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
;
363 delim
= getword(fp
, word
, sizeof(word
));
364 if (strlen(word
) == 0U)
365 return (ISC_R_UNEXPECTEDEND
); /* Nothing else on line. */
368 while (strlen(word
) > 0U) {
369 if (conf
->searchnxt
== RESCONFMAXSEARCH
)
370 goto ignore
; /* Too many domains. */
372 conf
->search
[idx
] = isc_mem_strdup(conf
->mctx
, word
);
373 if (conf
->search
[idx
] == NULL
)
374 return (ISC_R_NOMEMORY
);
379 if (delim
== EOF
|| delim
== '\n')
382 delim
= getword(fp
, word
, sizeof(word
));
385 return (ISC_R_SUCCESS
);
389 resconf_parsesortlist(irs_resconf_t
*conf
, FILE *fp
) {
391 char word
[RESCONFMAXLINELEN
];
394 delim
= getword(fp
, word
, sizeof(word
));
395 if (strlen(word
) == 0U)
396 return (ISC_R_UNEXPECTEDEND
); /* Empty line after keyword. */
398 while (strlen(word
) > 0U) {
399 if (conf
->sortlistnxt
== RESCONFMAXSORTLIST
)
400 return (ISC_R_QUOTA
); /* Too many values. */
402 p
= strchr(word
, '/');
406 idx
= conf
->sortlistnxt
;
407 res
= create_addr(word
, &conf
->sortlist
[idx
].addr
, 1);
408 if (res
!= ISC_R_SUCCESS
)
412 res
= create_addr(p
, &conf
->sortlist
[idx
].mask
, 0);
413 if (res
!= ISC_R_SUCCESS
)
417 * Make up a mask. (XXX: is this correct?)
419 conf
->sortlist
[idx
].mask
= conf
->sortlist
[idx
].addr
;
420 memset(&conf
->sortlist
[idx
].mask
.type
, 0xff,
421 sizeof(conf
->sortlist
[idx
].mask
.type
));
426 if (delim
== EOF
|| delim
== '\n')
429 delim
= getword(fp
, word
, sizeof(word
));
432 return (ISC_R_SUCCESS
);
436 resconf_parseoption(irs_resconf_t
*conf
, FILE *fp
) {
440 char word
[RESCONFMAXLINELEN
];
442 delim
= getword(fp
, word
, sizeof(word
));
443 if (strlen(word
) == 0U)
444 return (ISC_R_UNEXPECTEDEND
); /* Empty line after keyword. */
446 while (strlen(word
) > 0U) {
447 if (strcmp("debug", word
) == 0) {
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
;
458 if (delim
== EOF
|| delim
== '\n')
461 delim
= getword(fp
, word
, sizeof(word
));
464 return (ISC_R_SUCCESS
);
468 add_search(irs_resconf_t
*conf
, char *domain
) {
469 irs_resconf_search_t
*entry
;
471 entry
= isc_mem_get(conf
->mctx
, sizeof(*entry
));
473 return (ISC_R_NOMEMORY
);
475 entry
->domain
= domain
;
476 ISC_LINK_INIT(entry
, link
);
477 ISC_LIST_APPEND(conf
->searchlist
, entry
, link
);
479 return (ISC_R_SUCCESS
);
482 /*% parses a file and fills in the data structure. */
484 irs_resconf_load(isc_mem_t
*mctx
, const char *filename
, irs_resconf_t
**confp
)
488 isc_result_t rval
, ret
;
492 REQUIRE(mctx
!= NULL
);
493 REQUIRE(filename
!= NULL
);
494 REQUIRE(strlen(filename
) > 0U);
495 REQUIRE(confp
!= NULL
&& *confp
== NULL
);
497 conf
= isc_mem_get(mctx
, sizeof(*conf
));
499 return (ISC_R_NOMEMORY
);
502 ISC_LIST_INIT(conf
->nameservers
);
504 conf
->domainname
= NULL
;
508 for (i
= 0; i
< RESCONFMAXSEARCH
; i
++)
509 conf
->search
[i
] = NULL
;
512 if ((fp
= fopen(filename
, "r")) == NULL
) {
513 isc_mem_put(mctx
, conf
, sizeof(*conf
));
514 return (ISC_R_INVALIDFILE
);
519 stopchar
= getword(fp
, word
, sizeof(word
));
520 if (stopchar
== EOF
) {
521 rval
= ISC_R_SUCCESS
;
525 if (strlen(word
) == 0U)
526 rval
= ISC_R_SUCCESS
;
527 else if (strcmp(word
, "nameserver") == 0)
528 rval
= resconf_parsenameserver(conf
, fp
);
529 else if (strcmp(word
, "domain") == 0)
530 rval
= resconf_parsedomain(conf
, fp
);
531 else if (strcmp(word
, "search") == 0)
532 rval
= resconf_parsesearch(conf
, fp
);
533 else if (strcmp(word
, "sortlist") == 0)
534 rval
= resconf_parsesortlist(conf
, fp
);
535 else if (strcmp(word
, "options") == 0)
536 rval
= resconf_parseoption(conf
, fp
);
538 /* unrecognised word. Ignore entire line */
539 rval
= ISC_R_SUCCESS
;
540 stopchar
= eatline(fp
);
541 if (stopchar
== EOF
) {
545 if (ret
== ISC_R_SUCCESS
&& rval
!= ISC_R_SUCCESS
)
551 /* If we don't find a nameserver fall back to localhost */
552 if (conf
->numns
== 0) {
553 INSIST(ISC_LIST_EMPTY(conf
->nameservers
));
555 /* XXX: should we catch errors? */
556 (void)add_server(conf
->mctx
, "127.0.0.1", &conf
->nameservers
);
557 (void)add_server(conf
->mctx
, "::1", &conf
->nameservers
);
561 * Construct unified search list from domain or configured
564 ISC_LIST_INIT(conf
->searchlist
);
565 if (conf
->domainname
!= NULL
) {
566 ret
= add_search(conf
, conf
->domainname
);
567 } else if (conf
->searchnxt
> 0) {
568 for (i
= 0; i
< conf
->searchnxt
; i
++) {
569 ret
= add_search(conf
, conf
->search
[i
]);
570 if (ret
!= ISC_R_SUCCESS
)
575 conf
->magic
= IRS_RESCONF_MAGIC
;
577 if (ret
!= ISC_R_SUCCESS
)
578 irs_resconf_destroy(&conf
);
586 irs_resconf_destroy(irs_resconf_t
**confp
) {
588 isc_sockaddr_t
*address
;
589 irs_resconf_search_t
*searchentry
;
592 REQUIRE(confp
!= NULL
);
594 REQUIRE(IRS_RESCONF_VALID(conf
));
596 while ((searchentry
= ISC_LIST_HEAD(conf
->searchlist
)) != NULL
) {
597 ISC_LIST_UNLINK(conf
->searchlist
, searchentry
, link
);
598 isc_mem_put(conf
->mctx
, searchentry
, sizeof(*searchentry
));
601 while ((address
= ISC_LIST_HEAD(conf
->nameservers
)) != NULL
) {
602 ISC_LIST_UNLINK(conf
->nameservers
, address
, link
);
603 isc_mem_put(conf
->mctx
, address
, sizeof(*address
));
606 if (conf
->domainname
!= NULL
)
607 isc_mem_free(conf
->mctx
, conf
->domainname
);
609 for (i
= 0; i
< RESCONFMAXSEARCH
; i
++) {
610 if (conf
->search
[i
] != NULL
)
611 isc_mem_free(conf
->mctx
, conf
->search
[i
]);
614 isc_mem_put(conf
->mctx
, conf
, sizeof(*conf
));
620 irs_resconf_getnameservers(irs_resconf_t
*conf
) {
621 REQUIRE(IRS_RESCONF_VALID(conf
));
623 return (&conf
->nameservers
);
626 irs_resconf_searchlist_t
*
627 irs_resconf_getsearchlist(irs_resconf_t
*conf
) {
628 REQUIRE(IRS_RESCONF_VALID(conf
));
630 return (&conf
->searchlist
);
634 irs_resconf_getndots(irs_resconf_t
*conf
) {
635 REQUIRE(IRS_RESCONF_VALID(conf
));
637 return ((unsigned int)conf
->ndots
);