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