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