resconf.c revision 1.2
1/*	$NetBSD: resconf.c,v 1.2 2018/08/12 13:02:37 christos Exp $	*/
2
3/*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * See the COPYRIGHT file distributed with this work for additional
11 * information regarding copyright ownership.
12 */
13
14
15/*! \file resconf.c */
16
17/**
18 * Module for parsing resolv.conf files (largely derived from lwconfig.c).
19 *
20 *    irs_resconf_load() opens the file filename and parses it to initialize
21 *    the configuration structure.
22 *
23 * \section lwconfig_return Return Values
24 *
25 *    irs_resconf_load() returns #IRS_R_SUCCESS if it successfully read and
26 *    parsed filename. It returns a non-0 error code if filename could not be
27 *    opened or contained incorrect resolver statements.
28 *
29 * \section lwconfig_see See Also
30 *
31 *    stdio(3), \link resolver resolver \endlink
32 *
33 * \section files Files
34 *
35 *    /etc/resolv.conf
36 */
37
38#include <config.h>
39
40#ifndef WIN32
41#include <sys/types.h>
42#include <sys/socket.h>
43#include <netdb.h>
44#endif
45
46#include <ctype.h>
47#include <errno.h>
48#include <stdlib.h>
49#include <stdio.h>
50#include <string.h>
51
52#include <isc/magic.h>
53#include <isc/mem.h>
54#include <isc/netaddr.h>
55#include <isc/sockaddr.h>
56#include <isc/util.h>
57
58#include <irs/netdb.h>
59#include <irs/resconf.h>
60
61#define IRS_RESCONF_MAGIC		ISC_MAGIC('R', 'E', 'S', 'c')
62#define IRS_RESCONF_VALID(c)		ISC_MAGIC_VALID(c, IRS_RESCONF_MAGIC)
63
64/*!
65 * protocol constants
66 */
67
68#if ! defined(NS_INADDRSZ)
69#define NS_INADDRSZ	 4
70#endif
71
72#if ! defined(NS_IN6ADDRSZ)
73#define NS_IN6ADDRSZ	16
74#endif
75
76/*!
77 * resolv.conf parameters
78 */
79
80#define RESCONFMAXNAMESERVERS 3U	/*%< max 3 "nameserver" entries */
81#define RESCONFMAXSEARCH 8U		/*%< max 8 domains in "search" entry */
82#define RESCONFMAXLINELEN 256U		/*%< max size of a line */
83#define RESCONFMAXSORTLIST 10U		/*%< max 10 */
84
85/*!
86 * configuration data structure
87 */
88
89struct irs_resconf {
90	/*
91	 * The configuration data is a thread-specific object, and does not
92	 * need to be locked.
93	 */
94	unsigned int		magic;
95	isc_mem_t		*mctx;
96
97	isc_sockaddrlist_t	nameservers;
98	unsigned int		numns; /*%< number of configured servers */
99
100	char	       		*domainname;
101	char 	       		*search[RESCONFMAXSEARCH];
102	isc_uint8_t		searchnxt; /*%< index for next free slot */
103
104	irs_resconf_searchlist_t searchlist;
105
106	struct {
107		isc_netaddr_t	addr;
108		/*% mask has a non-zero 'family' if set */
109		isc_netaddr_t	mask;
110	} sortlist[RESCONFMAXSORTLIST];
111	isc_uint8_t		sortlistnxt;
112
113	/*%< non-zero if 'options debug' set */
114	isc_uint8_t		resdebug;
115	/*%< set to n in 'options ndots:n' */
116	isc_uint8_t		ndots;
117};
118
119static isc_result_t
120resconf_parsenameserver(irs_resconf_t *conf,  FILE *fp);
121static isc_result_t
122resconf_parsedomain(irs_resconf_t *conf,  FILE *fp);
123static isc_result_t
124resconf_parsesearch(irs_resconf_t *conf,  FILE *fp);
125static isc_result_t
126resconf_parsesortlist(irs_resconf_t *conf,  FILE *fp);
127static isc_result_t
128resconf_parseoption(irs_resconf_t *ctx,  FILE *fp);
129
130/*!
131 * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
132 */
133static int
134eatline(FILE *fp) {
135	int ch;
136
137	ch = fgetc(fp);
138	while (ch != '\n' && ch != EOF)
139		ch = fgetc(fp);
140
141	return (ch);
142}
143
144/*!
145 * Eats white space up to next newline or non-whitespace character (of
146 * EOF). Returns the last character read. Comments are considered white
147 * space.
148 */
149static int
150eatwhite(FILE *fp) {
151	int ch;
152
153	ch = fgetc(fp);
154	while (ch != '\n' && ch != EOF && isspace((unsigned char)ch))
155		ch = fgetc(fp);
156
157	if (ch == ';' || ch == '#')
158		ch = eatline(fp);
159
160	return (ch);
161}
162
163/*!
164 * Skip over any leading whitespace and then read in the next sequence of
165 * non-whitespace characters. In this context newline is not considered
166 * whitespace. Returns EOF on end-of-file, or the character
167 * that caused the reading to stop.
168 */
169static int
170getword(FILE *fp, char *buffer, size_t size) {
171	int ch;
172	char *p;
173
174	REQUIRE(buffer != NULL);
175	REQUIRE(size > 0U);
176
177	p = buffer;
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			memmove(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 = (unsigned int)res->ai_addrlen;
241	memmove(&address->type.ss, 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				memmove(&v4, loopaddress, 4);
262		}
263		addr->family = AF_INET;
264		memmove(&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		memmove(&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;
306	unsigned int i;
307
308	res = getword(fp, word, sizeof(word));
309	if (strlen(word) == 0U)
310		return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */
311	else if (res == ' ' || res == '\t')
312		res = eatwhite(fp);
313
314	if (res != EOF && res != '\n')
315		return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
316
317	if (conf->domainname != NULL)
318		isc_mem_free(conf->mctx, conf->domainname);
319
320	/*
321	 * Search and domain are mutually exclusive.
322	 */
323	for (i = 0; i < RESCONFMAXSEARCH; i++) {
324		if (conf->search[i] != NULL) {
325			isc_mem_free(conf->mctx, conf->search[i]);
326			conf->search[i] = NULL;
327		}
328	}
329	conf->searchnxt = 0;
330
331	conf->domainname = isc_mem_strdup(conf->mctx, word);
332	if (conf->domainname == NULL)
333		return (ISC_R_NOMEMORY);
334
335	return (ISC_R_SUCCESS);
336}
337
338static isc_result_t
339resconf_parsesearch(irs_resconf_t *conf,  FILE *fp) {
340	int delim;
341	unsigned int idx;
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		INSIST(idx < sizeof(conf->search)/sizeof(conf->search[0]));
373		conf->search[idx] = isc_mem_strdup(conf->mctx, word);
374		if (conf->search[idx] == NULL)
375			return (ISC_R_NOMEMORY);
376		idx++;
377		conf->searchnxt++;
378
379	ignore:
380		if (delim == EOF || delim == '\n')
381			break;
382		else
383			delim = getword(fp, word, sizeof(word));
384	}
385
386	return (ISC_R_SUCCESS);
387}
388
389static isc_result_t
390resconf_parsesortlist(irs_resconf_t *conf,  FILE *fp) {
391	int delim, res;
392	unsigned int idx;
393	char word[RESCONFMAXLINELEN];
394	char *p;
395
396	delim = getword(fp, word, sizeof(word));
397	if (strlen(word) == 0U)
398		return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
399
400	while (strlen(word) > 0U) {
401		if (conf->sortlistnxt == RESCONFMAXSORTLIST)
402			return (ISC_R_QUOTA); /* Too many values. */
403
404		p = strchr(word, '/');
405		if (p != NULL)
406			*p++ = '\0';
407
408		idx = conf->sortlistnxt;
409		INSIST(idx < sizeof(conf->sortlist)/sizeof(conf->sortlist[0]));
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
438static isc_result_t
439resconf_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
470static isc_result_t
471add_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. */
486isc_result_t
487irs_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	unsigned int i;
494	int stopchar;
495
496	REQUIRE(mctx != NULL);
497	REQUIRE(filename != NULL);
498	REQUIRE(strlen(filename) > 0U);
499	REQUIRE(confp != NULL && *confp == NULL);
500
501	conf = isc_mem_get(mctx, sizeof(*conf));
502	if (conf == NULL)
503		return (ISC_R_NOMEMORY);
504
505	conf->mctx = mctx;
506	ISC_LIST_INIT(conf->nameservers);
507	ISC_LIST_INIT(conf->searchlist);
508	conf->numns = 0;
509	conf->domainname = NULL;
510	conf->searchnxt = 0;
511	conf->sortlistnxt = 0;
512	conf->resdebug = 0;
513	conf->ndots = 1;
514	for (i = 0; i < RESCONFMAXSEARCH; i++)
515		conf->search[i] = NULL;
516
517	errno = 0;
518	if ((fp = fopen(filename, "r")) != NULL) {
519		do {
520			stopchar = getword(fp, word, sizeof(word));
521			if (stopchar == EOF) {
522				rval = ISC_R_SUCCESS;
523				POST(rval);
524				break;
525			}
526
527			if (strlen(word) == 0U)
528				rval = ISC_R_SUCCESS;
529			else if (strcmp(word, "nameserver") == 0)
530				rval = resconf_parsenameserver(conf, fp);
531			else if (strcmp(word, "domain") == 0)
532				rval = resconf_parsedomain(conf, fp);
533			else if (strcmp(word, "search") == 0)
534				rval = resconf_parsesearch(conf, fp);
535			else if (strcmp(word, "sortlist") == 0)
536				rval = resconf_parsesortlist(conf, fp);
537			else if (strcmp(word, "options") == 0)
538				rval = resconf_parseoption(conf, fp);
539			else {
540				/* unrecognised word. Ignore entire line */
541				rval = ISC_R_SUCCESS;
542				stopchar = eatline(fp);
543				if (stopchar == EOF) {
544					break;
545				}
546			}
547			if (ret == ISC_R_SUCCESS && rval != ISC_R_SUCCESS)
548				ret = rval;
549		} while (1);
550
551		fclose(fp);
552	} else {
553		switch (errno) {
554		case ENOENT:
555			break;
556		default:
557			isc_mem_put(mctx, conf, sizeof(*conf));
558			return (ISC_R_INVALIDFILE);
559		}
560	}
561
562	if (ret != ISC_R_SUCCESS) {
563		goto error;
564	}
565
566	/* If we don't find a nameserver fall back to localhost */
567	if (conf->numns == 0U) {
568		INSIST(ISC_LIST_EMPTY(conf->nameservers));
569
570		/* XXX: should we catch errors? */
571		(void)add_server(conf->mctx, "::1", &conf->nameservers);
572		(void)add_server(conf->mctx, "127.0.0.1", &conf->nameservers);
573	}
574
575	/*
576	 * Construct unified search list from domain or configured
577	 * search list
578	 */
579	if (conf->domainname != NULL) {
580		ret = add_search(conf, conf->domainname);
581	} else if (conf->searchnxt > 0) {
582		for (i = 0; i < conf->searchnxt; i++) {
583			ret = add_search(conf, conf->search[i]);
584			if (ret != ISC_R_SUCCESS)
585				break;
586		}
587	}
588
589 error:
590	conf->magic = IRS_RESCONF_MAGIC;
591
592	if (ret != ISC_R_SUCCESS)
593		irs_resconf_destroy(&conf);
594	else {
595		if (fp == NULL)
596			ret = ISC_R_FILENOTFOUND;
597		*confp = conf;
598	}
599
600	return (ret);
601}
602
603void
604irs_resconf_destroy(irs_resconf_t **confp) {
605	irs_resconf_t *conf;
606	isc_sockaddr_t *address;
607	irs_resconf_search_t *searchentry;
608	unsigned int i;
609
610	REQUIRE(confp != NULL);
611	conf = *confp;
612	REQUIRE(IRS_RESCONF_VALID(conf));
613
614	while ((searchentry = ISC_LIST_HEAD(conf->searchlist)) != NULL) {
615		ISC_LIST_UNLINK(conf->searchlist, searchentry, link);
616		isc_mem_put(conf->mctx, searchentry, sizeof(*searchentry));
617	}
618
619	while ((address = ISC_LIST_HEAD(conf->nameservers)) != NULL) {
620		ISC_LIST_UNLINK(conf->nameservers, address, link);
621		isc_mem_put(conf->mctx, address, sizeof(*address));
622	}
623
624	if (conf->domainname != NULL)
625		isc_mem_free(conf->mctx, conf->domainname);
626
627	for (i = 0; i < RESCONFMAXSEARCH; i++) {
628		if (conf->search[i] != NULL)
629			isc_mem_free(conf->mctx, conf->search[i]);
630	}
631
632	isc_mem_put(conf->mctx, conf, sizeof(*conf));
633
634	*confp = NULL;
635}
636
637isc_sockaddrlist_t *
638irs_resconf_getnameservers(irs_resconf_t *conf) {
639	REQUIRE(IRS_RESCONF_VALID(conf));
640
641	return (&conf->nameservers);
642}
643
644irs_resconf_searchlist_t *
645irs_resconf_getsearchlist(irs_resconf_t *conf) {
646	REQUIRE(IRS_RESCONF_VALID(conf));
647
648	return (&conf->searchlist);
649}
650
651unsigned int
652irs_resconf_getndots(irs_resconf_t *conf) {
653	REQUIRE(IRS_RESCONF_VALID(conf));
654
655	return ((unsigned int)conf->ndots);
656}
657