]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - contrib/bind9/lib/irs/resconf.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / contrib / bind9 / lib / irs / resconf.c
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
90 struct 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
120 static isc_result_t
121 resconf_parsenameserver(irs_resconf_t *conf,  FILE *fp);
122 static isc_result_t
123 resconf_parsedomain(irs_resconf_t *conf,  FILE *fp);
124 static isc_result_t
125 resconf_parsesearch(irs_resconf_t *conf,  FILE *fp);
126 static isc_result_t
127 resconf_parsesortlist(irs_resconf_t *conf,  FILE *fp);
128 static isc_result_t
129 resconf_parseoption(irs_resconf_t *ctx,  FILE *fp);
130
131 /*!
132  * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
133  */
134 static int
135 eatline(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  */
150 static int
151 eatwhite(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  */
170 static int
171 getword(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
200 static isc_result_t
201 add_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
251 static isc_result_t
252 create_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
276 static isc_result_t
277 resconf_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
302 static isc_result_t
303 resconf_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
337 static isc_result_t
338 resconf_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
386 static isc_result_t
387 resconf_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
433 static isc_result_t
434 resconf_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
465 static isc_result_t
466 add_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. */
481 isc_result_t
482 irs_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
584 void
585 irs_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
618 isc_sockaddrlist_t *
619 irs_resconf_getnameservers(irs_resconf_t *conf) {
620         REQUIRE(IRS_RESCONF_VALID(conf));
621
622         return (&conf->nameservers);
623 }
624
625 irs_resconf_searchlist_t *
626 irs_resconf_getsearchlist(irs_resconf_t *conf) {
627         REQUIRE(IRS_RESCONF_VALID(conf));
628
629         return (&conf->searchlist);
630 }
631
632 unsigned int
633 irs_resconf_getndots(irs_resconf_t *conf) {
634         REQUIRE(IRS_RESCONF_VALID(conf));
635
636         return ((unsigned int)conf->ndots);
637 }