1 /* $NetBSD: resconf.c,v 1.8 2014/12/10 04:37:59 christos Exp $ */
2
3 /*
4 * Copyright (C) 2009, 2011, 2012, 2014 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 #ifndef WIN32
47 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <netdb.h>
50 #endif
51
52 #include <ctype.h>
53 #include <errno.h>
54 #include <stdlib.h>
55 #include <stdio.h>
56 #include <string.h>
57
58 #include <isc/magic.h>
59 #include <isc/mem.h>
60 #include <isc/netaddr.h>
61 #include <isc/sockaddr.h>
62 #include <isc/util.h>
63
64 #include <irs/netdb.h>
65 #include <irs/resconf.h>
66
67 #define IRS_RESCONF_MAGIC ISC_MAGIC('R', 'E', 'S', 'c')
68 #define IRS_RESCONF_VALID(c) ISC_MAGIC_VALID(c, IRS_RESCONF_MAGIC)
69
70 /*!
71 * protocol constants
72 */
73
74 #if ! defined(NS_INADDRSZ)
75 #define NS_INADDRSZ 4
76 #endif
77
78 #if ! defined(NS_IN6ADDRSZ)
79 #define NS_IN6ADDRSZ 16
80 #endif
81
82 /*!
83 * resolv.conf parameters
84 */
85
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 */
90
91 /*!
92 * configuration data structure
93 */
94
95 struct irs_resconf {
96 /*
97 * The configuration data is a thread-specific object, and does not
98 * need to be locked.
99 */
100 unsigned int magic;
101 isc_mem_t *mctx;
102
103 isc_sockaddrlist_t nameservers;
104 unsigned int numns; /*%< number of configured servers */
105
106 char *domainname;
107 char *search[RESCONFMAXSEARCH];
108 isc_uint8_t searchnxt; /*%< index for next free slot */
109
110 irs_resconf_searchlist_t searchlist;
111
112 struct {
113 isc_netaddr_t addr;
114 /*% mask has a non-zero 'family' if set */
115 isc_netaddr_t mask;
116 } sortlist[RESCONFMAXSORTLIST];
117 isc_uint8_t sortlistnxt;
118
119 /*%< non-zero if 'options debug' set */
120 isc_uint8_t resdebug;
121 /*%< set to n in 'options ndots:n' */
122 isc_uint8_t ndots;
123 };
124
125 static isc_result_t
126 resconf_parsenameserver(irs_resconf_t *conf, FILE *fp);
127 static isc_result_t
128 resconf_parsedomain(irs_resconf_t *conf, FILE *fp);
129 static isc_result_t
130 resconf_parsesearch(irs_resconf_t *conf, FILE *fp);
131 static isc_result_t
132 resconf_parsesortlist(irs_resconf_t *conf, FILE *fp);
133 static isc_result_t
134 resconf_parseoption(irs_resconf_t *ctx, FILE *fp);
135
136 /*!
137 * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
138 */
139 static int
eatline(FILE * fp)140 eatline(FILE *fp) {
141 int ch;
142
143 ch = fgetc(fp);
144 while (ch != '\n' && ch != EOF)
145 ch = fgetc(fp);
146
147 return (ch);
148 }
149
150 /*!
151 * Eats white space up to next newline or non-whitespace character (of
152 * EOF). Returns the last character read. Comments are considered white
153 * space.
154 */
155 static int
eatwhite(FILE * fp)156 eatwhite(FILE *fp) {
157 int ch;
158
159 ch = fgetc(fp);
160 while (ch != '\n' && ch != EOF && isspace((unsigned char)ch))
161 ch = fgetc(fp);
162
163 if (ch == ';' || ch == '#')
164 ch = eatline(fp);
165
166 return (ch);
167 }
168
169 /*!
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.
174 */
175 static int
getword(FILE * fp,char * buffer,size_t size)176 getword(FILE *fp, char *buffer, size_t size) {
177 int ch;
178 char *p = buffer;
179
180 REQUIRE(buffer != NULL);
181 REQUIRE(size > 0U);
182
183 *p = '\0';
184
185 ch = eatwhite(fp);
186
187 if (ch == EOF)
188 return (EOF);
189
190 do {
191 *p = '\0';
192
193 if (ch == EOF || isspace((unsigned char)ch))
194 break;
195 else if ((size_t) (p - buffer) == size - 1)
196 return (EOF); /* Not enough space. */
197
198 *p++ = (char)ch;
199 ch = fgetc(fp);
200 } while (1);
201
202 return (ch);
203 }
204
205 static isc_result_t
add_server(isc_mem_t * mctx,const char * address_str,isc_sockaddrlist_t * nameservers)206 add_server(isc_mem_t *mctx, const char *address_str,
207 isc_sockaddrlist_t *nameservers)
208 {
209 int error;
210 isc_sockaddr_t *address = NULL;
211 struct addrinfo hints, *res;
212 isc_result_t result = ISC_R_SUCCESS;
213
214 res = NULL;
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);
221 if (error != 0)
222 return (ISC_R_BADADDRESSFORM);
223
224 /* XXX: special case: treat all-0 IPv4 address as loopback */
225 if (res->ai_family == AF_INET) {
226 struct in_addr *v4;
227 unsigned char zeroaddress[] = {0, 0, 0, 0};
228 unsigned char loopaddress[] = {127, 0, 0, 1};
229
230 v4 = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
231 if (memcmp(v4, zeroaddress, 4) == 0)
232 memmove(v4, loopaddress, 4);
233 }
234
235 address = isc_mem_get(mctx, sizeof(*address));
236 if (address == NULL) {
237 result = ISC_R_NOMEMORY;
238 goto cleanup;
239 }
240 if (res->ai_addrlen > sizeof(address->type)) {
241 isc_mem_put(mctx, address, sizeof(*address));
242 result = ISC_R_RANGE;
243 goto cleanup;
244 }
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);
249
250 cleanup:
251 freeaddrinfo(res);
252
253 return (result);
254 }
255
256 static isc_result_t
create_addr(const char * buffer,isc_netaddr_t * addr,int convert_zero)257 create_addr(const char *buffer, isc_netaddr_t *addr, int convert_zero) {
258 struct in_addr v4;
259 struct in6_addr v6;
260
261 if (inet_aton(buffer, &v4) == 1) {
262 if (convert_zero) {
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);
267 }
268 addr->family = AF_INET;
269 memmove(&addr->type.in, &v4, NS_INADDRSZ);
270 addr->zone = 0;
271 } else if (inet_pton(AF_INET6, buffer, &v6) == 1) {
272 addr->family = AF_INET6;
273 memmove(&addr->type.in6, &v6, NS_IN6ADDRSZ);
274 addr->zone = 0;
275 } else
276 return (ISC_R_BADADDRESSFORM); /* Unrecognised format. */
277
278 return (ISC_R_SUCCESS);
279 }
280
281 static isc_result_t
resconf_parsenameserver(irs_resconf_t * conf,FILE * fp)282 resconf_parsenameserver(irs_resconf_t *conf, FILE *fp) {
283 char word[RESCONFMAXLINELEN];
284 int cp;
285 isc_result_t result;
286
287 if (conf->numns == RESCONFMAXNAMESERVERS)
288 return (ISC_R_SUCCESS);
289
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')
294 cp = eatwhite(fp);
295
296 if (cp != EOF && cp != '\n')
297 return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
298
299 result = add_server(conf->mctx, word, &conf->nameservers);
300 if (result != ISC_R_SUCCESS)
301 return (result);
302 conf->numns++;
303
304 return (ISC_R_SUCCESS);
305 }
306
307 static isc_result_t
resconf_parsedomain(irs_resconf_t * conf,FILE * fp)308 resconf_parsedomain(irs_resconf_t *conf, FILE *fp) {
309 char word[RESCONFMAXLINELEN];
310 int res, i;
311
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')
316 res = eatwhite(fp);
317
318 if (res != EOF && res != '\n')
319 return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
320
321 if (conf->domainname != NULL)
322 isc_mem_free(conf->mctx, conf->domainname);
323
324 /*
325 * Search and domain are mutually exclusive.
326 */
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;
331 }
332 }
333 conf->searchnxt = 0;
334
335 conf->domainname = isc_mem_strdup(conf->mctx, word);
336 if (conf->domainname == NULL)
337 return (ISC_R_NOMEMORY);
338
339 return (ISC_R_SUCCESS);
340 }
341
342 static isc_result_t
resconf_parsesearch(irs_resconf_t * conf,FILE * fp)343 resconf_parsesearch(irs_resconf_t *conf, FILE *fp) {
344 int idx, delim;
345 char word[RESCONFMAXLINELEN];
346
347 if (conf->domainname != NULL) {
348 /*
349 * Search and domain are mutually exclusive.
350 */
351 isc_mem_free(conf->mctx, conf->domainname);
352 conf->domainname = NULL;
353 }
354
355 /*
356 * Remove any previous search definitions.
357 */
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;
362 }
363 }
364 conf->searchnxt = 0;
365
366 delim = getword(fp, word, sizeof(word));
367 if (strlen(word) == 0U)
368 return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */
369
370 idx = 0;
371 while (strlen(word) > 0U) {
372 if (conf->searchnxt == RESCONFMAXSEARCH)
373 goto ignore; /* Too many domains. */
374
375 conf->search[idx] = isc_mem_strdup(conf->mctx, word);
376 if (conf->search[idx] == NULL)
377 return (ISC_R_NOMEMORY);
378 idx++;
379 conf->searchnxt++;
380
381 ignore:
382 if (delim == EOF || delim == '\n')
383 break;
384 else
385 delim = getword(fp, word, sizeof(word));
386 }
387
388 return (ISC_R_SUCCESS);
389 }
390
391 static isc_result_t
resconf_parsesortlist(irs_resconf_t * conf,FILE * fp)392 resconf_parsesortlist(irs_resconf_t *conf, FILE *fp) {
393 int delim, res, idx;
394 char word[RESCONFMAXLINELEN];
395 char *p;
396
397 delim = getword(fp, word, sizeof(word));
398 if (strlen(word) == 0U)
399 return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
400
401 while (strlen(word) > 0U) {
402 if (conf->sortlistnxt == RESCONFMAXSORTLIST)
403 return (ISC_R_QUOTA); /* Too many values. */
404
405 p = strchr(word, '/');
406 if (p != NULL)
407 *p++ = '\0';
408
409 idx = conf->sortlistnxt;
410 res = create_addr(word, &conf->sortlist[idx].addr, 1);
411 if (res != ISC_R_SUCCESS)
412 return (res);
413
414 if (p != NULL) {
415 res = create_addr(p, &conf->sortlist[idx].mask, 0);
416 if (res != ISC_R_SUCCESS)
417 return (res);
418 } else {
419 /*
420 * Make up a mask. (XXX: is this correct?)
421 */
422 conf->sortlist[idx].mask = conf->sortlist[idx].addr;
423 memset(&conf->sortlist[idx].mask.type, 0xff,
424 sizeof(conf->sortlist[idx].mask.type));
425 }
426
427 conf->sortlistnxt++;
428
429 if (delim == EOF || delim == '\n')
430 break;
431 else
432 delim = getword(fp, word, sizeof(word));
433 }
434
435 return (ISC_R_SUCCESS);
436 }
437
438 static isc_result_t
resconf_parseoption(irs_resconf_t * conf,FILE * fp)439 resconf_parseoption(irs_resconf_t *conf, FILE *fp) {
440 int delim;
441 long ndots;
442 char *p;
443 char word[RESCONFMAXLINELEN];
444
445 delim = getword(fp, word, sizeof(word));
446 if (strlen(word) == 0U)
447 return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
448
449 while (strlen(word) > 0U) {
450 if (strcmp("debug", word) == 0) {
451 conf->resdebug = 1;
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;
459 }
460
461 if (delim == EOF || delim == '\n')
462 break;
463 else
464 delim = getword(fp, word, sizeof(word));
465 }
466
467 return (ISC_R_SUCCESS);
468 }
469
470 static isc_result_t
add_search(irs_resconf_t * conf,char * domain)471 add_search(irs_resconf_t *conf, char *domain) {
472 irs_resconf_search_t *entry;
473
474 entry = isc_mem_get(conf->mctx, sizeof(*entry));
475 if (entry == NULL)
476 return (ISC_R_NOMEMORY);
477
478 entry->domain = domain;
479 ISC_LINK_INIT(entry, link);
480 ISC_LIST_APPEND(conf->searchlist, entry, link);
481
482 return (ISC_R_SUCCESS);
483 }
484
485 /*% parses a file and fills in the data structure. */
486 isc_result_t
irs_resconf_load(isc_mem_t * mctx,const char * filename,irs_resconf_t ** confp)487 irs_resconf_load(isc_mem_t *mctx, const char *filename, irs_resconf_t **confp)
488 {
489 FILE *fp = NULL;
490 char word[256];
491 isc_result_t rval, ret = ISC_R_SUCCESS;
492 irs_resconf_t *conf;
493 int i, stopchar;
494
495 REQUIRE(mctx != NULL);
496 REQUIRE(filename != NULL);
497 REQUIRE(strlen(filename) > 0U);
498 REQUIRE(confp != NULL && *confp == NULL);
499
500 conf = isc_mem_get(mctx, sizeof(*conf));
501 if (conf == NULL)
502 return (ISC_R_NOMEMORY);
503
504 conf->mctx = mctx;
505 ISC_LIST_INIT(conf->nameservers);
506 conf->numns = 0;
507 conf->domainname = NULL;
508 conf->searchnxt = 0;
509 conf->resdebug = 0;
510 conf->ndots = 1;
511 for (i = 0; i < RESCONFMAXSEARCH; i++)
512 conf->search[i] = NULL;
513
514 errno = 0;
515 if ((fp = fopen(filename, "r")) != NULL) {
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 } else {
550 switch (errno) {
551 case ENOENT:
552 break;
553 default:
554 isc_mem_put(mctx, conf, sizeof(*conf));
555 return (ISC_R_INVALIDFILE);
556 }
557 }
558
559 /* If we don't find a nameserver fall back to localhost */
560 if (conf->numns == 0) {
561 INSIST(ISC_LIST_EMPTY(conf->nameservers));
562
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);
566 }
567
568 /*
569 * Construct unified search list from domain or configured
570 * search list
571 */
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)
579 break;
580 }
581 }
582
583 conf->magic = IRS_RESCONF_MAGIC;
584
585 if (ret != ISC_R_SUCCESS)
586 irs_resconf_destroy(&conf);
587 else {
588 if (fp == NULL)
589 ret = ISC_R_FILENOTFOUND;
590 *confp = conf;
591 }
592
593 return (ret);
594 }
595
596 void
irs_resconf_destroy(irs_resconf_t ** confp)597 irs_resconf_destroy(irs_resconf_t **confp) {
598 irs_resconf_t *conf;
599 isc_sockaddr_t *address;
600 irs_resconf_search_t *searchentry;
601 int i;
602
603 REQUIRE(confp != NULL);
604 conf = *confp;
605 REQUIRE(IRS_RESCONF_VALID(conf));
606
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));
610 }
611
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));
615 }
616
617 if (conf->domainname != NULL)
618 isc_mem_free(conf->mctx, conf->domainname);
619
620 for (i = 0; i < RESCONFMAXSEARCH; i++) {
621 if (conf->search[i] != NULL)
622 isc_mem_free(conf->mctx, conf->search[i]);
623 }
624
625 isc_mem_put(conf->mctx, conf, sizeof(*conf));
626
627 *confp = NULL;
628 }
629
630 isc_sockaddrlist_t *
irs_resconf_getnameservers(irs_resconf_t * conf)631 irs_resconf_getnameservers(irs_resconf_t *conf) {
632 REQUIRE(IRS_RESCONF_VALID(conf));
633
634 return (&conf->nameservers);
635 }
636
637 irs_resconf_searchlist_t *
irs_resconf_getsearchlist(irs_resconf_t * conf)638 irs_resconf_getsearchlist(irs_resconf_t *conf) {
639 REQUIRE(IRS_RESCONF_VALID(conf));
640
641 return (&conf->searchlist);
642 }
643
644 unsigned int
irs_resconf_getndots(irs_resconf_t * conf)645 irs_resconf_getndots(irs_resconf_t *conf) {
646 REQUIRE(IRS_RESCONF_VALID(conf));
647
648 return ((unsigned int)conf->ndots);
649 }
650