resconf.c revision 1.4
1/*	$NetBSD: resconf.c,v 1.4 2020/05/24 19:46:25 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/*! \file resconf.c */
15
16/**
17 * Module for parsing resolv.conf files (largely derived from lwconfig.c).
18 *
19 *    irs_resconf_load() opens the file filename and parses it to initialize
20 *    the configuration structure.
21 *
22 * \section lwconfig_return Return Values
23 *
24 *    irs_resconf_load() returns #IRS_R_SUCCESS if it successfully read and
25 *    parsed filename. It returns a non-0 error code if filename could not be
26 *    opened or contained incorrect resolver statements.
27 *
28 * \section lwconfig_see See Also
29 *
30 *    stdio(3), \link resolver resolver \endlink
31 *
32 * \section files Files
33 *
34 *    /etc/resolv.conf
35 */
36
37#ifndef WIN32
38#include <netdb.h>
39#include <sys/socket.h>
40#include <sys/types.h>
41#endif /* ifndef WIN32 */
42
43#include <ctype.h>
44#include <errno.h>
45#include <inttypes.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49
50#include <isc/magic.h>
51#include <isc/mem.h>
52#include <isc/netaddr.h>
53#include <isc/sockaddr.h>
54#include <isc/util.h>
55
56#include <irs/netdb.h>
57#include <irs/resconf.h>
58
59#define IRS_RESCONF_MAGIC    ISC_MAGIC('R', 'E', 'S', 'c')
60#define IRS_RESCONF_VALID(c) ISC_MAGIC_VALID(c, IRS_RESCONF_MAGIC)
61
62/*!
63 * protocol constants
64 */
65
66#if !defined(NS_INADDRSZ)
67#define NS_INADDRSZ 4
68#endif /* if !defined(NS_INADDRSZ) */
69
70#if !defined(NS_IN6ADDRSZ)
71#define NS_IN6ADDRSZ 16
72#endif /* if !defined(NS_IN6ADDRSZ) */
73
74/*!
75 * resolv.conf parameters
76 */
77
78#define RESCONFMAXNAMESERVERS 3U   /*%< max 3 "nameserver" entries */
79#define RESCONFMAXSEARCH      8U   /*%< max 8 domains in "search" entry */
80#define RESCONFMAXLINELEN     256U /*%< max size of a line */
81#define RESCONFMAXSORTLIST    10U  /*%< max 10 */
82
83/*!
84 * configuration data structure
85 */
86
87struct irs_resconf {
88	/*
89	 * The configuration data is a thread-specific object, and does not
90	 * need to be locked.
91	 */
92	unsigned int magic;
93	isc_mem_t *mctx;
94
95	isc_sockaddrlist_t nameservers;
96	unsigned int numns; /*%< number of configured servers
97			     * */
98
99	char *domainname;
100	char *search[RESCONFMAXSEARCH];
101	uint8_t searchnxt; /*%< index for next free slot
102			    * */
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	uint8_t sortlistnxt;
112
113	/*%< non-zero if 'options debug' set */
114	uint8_t resdebug;
115	/*%< set to n in 'options ndots:n' */
116	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#if HAVE_GET_WIN32_NAMESERVERS
131static isc_result_t
132get_win32_nameservers(irs_resconf_t *conf);
133#endif /* if HAVE_GET_WIN32_NAMESERVERS */
134
135/*!
136 * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
137 */
138static int
139eatline(FILE *fp) {
140	int ch;
141
142	ch = fgetc(fp);
143	while (ch != '\n' && ch != EOF) {
144		ch = fgetc(fp);
145	}
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 */
155static int
156eatwhite(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
164	if (ch == ';' || ch == '#') {
165		ch = eatline(fp);
166	}
167
168	return (ch);
169}
170
171/*!
172 * Skip over any leading whitespace and then read in the next sequence of
173 * non-whitespace characters. In this context newline is not considered
174 * whitespace. Returns EOF on end-of-file, or the character
175 * that caused the reading to stop.
176 */
177static int
178getword(FILE *fp, char *buffer, size_t size) {
179	int ch;
180	char *p;
181
182	REQUIRE(buffer != NULL);
183	REQUIRE(size > 0U);
184
185	p = buffer;
186	*p = '\0';
187
188	ch = eatwhite(fp);
189
190	if (ch == EOF) {
191		return (EOF);
192	}
193
194	do {
195		*p = '\0';
196
197		if (ch == EOF || isspace((unsigned char)ch)) {
198			break;
199		} else if ((size_t)(p - buffer) == size - 1) {
200			return (EOF); /* Not enough space. */
201		}
202
203		*p++ = (char)ch;
204		ch = fgetc(fp);
205	} while (1);
206
207	return (ch);
208}
209
210static isc_result_t
211add_server(isc_mem_t *mctx, const char *address_str,
212	   isc_sockaddrlist_t *nameservers) {
213	int error;
214	isc_sockaddr_t *address = NULL;
215	struct addrinfo hints, *res;
216	isc_result_t result = ISC_R_SUCCESS;
217
218	res = NULL;
219	memset(&hints, 0, sizeof(hints));
220	hints.ai_family = AF_UNSPEC;
221	hints.ai_socktype = SOCK_DGRAM;
222	hints.ai_protocol = IPPROTO_UDP;
223	hints.ai_flags = AI_NUMERICHOST;
224	error = getaddrinfo(address_str, "53", &hints, &res);
225	if (error != 0) {
226		return (ISC_R_BADADDRESSFORM);
227	}
228
229	/* XXX: special case: treat all-0 IPv4 address as loopback */
230	if (res->ai_family == AF_INET) {
231		struct in_addr *v4;
232		unsigned char zeroaddress[] = { 0, 0, 0, 0 };
233		unsigned char loopaddress[] = { 127, 0, 0, 1 };
234
235		v4 = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
236		if (memcmp(v4, zeroaddress, 4) == 0) {
237			memmove(v4, loopaddress, 4);
238		}
239	}
240
241	address = isc_mem_get(mctx, sizeof(*address));
242	if (res->ai_addrlen > sizeof(address->type)) {
243		isc_mem_put(mctx, address, sizeof(*address));
244		result = ISC_R_RANGE;
245		goto cleanup;
246	}
247	address->length = (unsigned int)res->ai_addrlen;
248	memmove(&address->type.ss, res->ai_addr, res->ai_addrlen);
249	ISC_LINK_INIT(address, link);
250	ISC_LIST_APPEND(*nameservers, address, link);
251
252cleanup:
253	freeaddrinfo(res);
254
255	return (result);
256}
257
258static isc_result_t
259create_addr(const char *buffer, isc_netaddr_t *addr, int convert_zero) {
260	struct in_addr v4;
261	struct in6_addr v6;
262
263	if (inet_pton(AF_INET, buffer, &v4) == 1) {
264		if (convert_zero) {
265			unsigned char zeroaddress[] = { 0, 0, 0, 0 };
266			unsigned char loopaddress[] = { 127, 0, 0, 1 };
267			if (memcmp(&v4, zeroaddress, 4) == 0) {
268				memmove(&v4, loopaddress, 4);
269			}
270		}
271		addr->family = AF_INET;
272		memmove(&addr->type.in, &v4, NS_INADDRSZ);
273		addr->zone = 0;
274	} else if (inet_pton(AF_INET6, buffer, &v6) == 1) {
275		addr->family = AF_INET6;
276		memmove(&addr->type.in6, &v6, NS_IN6ADDRSZ);
277		addr->zone = 0;
278	} else {
279		return (ISC_R_BADADDRESSFORM); /* Unrecognised format. */
280	}
281
282	return (ISC_R_SUCCESS);
283}
284
285static isc_result_t
286resconf_parsenameserver(irs_resconf_t *conf, FILE *fp) {
287	char word[RESCONFMAXLINELEN];
288	int cp;
289	isc_result_t result;
290
291	if (conf->numns == RESCONFMAXNAMESERVERS) {
292		return (ISC_R_SUCCESS);
293	}
294
295	cp = getword(fp, word, sizeof(word));
296	if (strlen(word) == 0U) {
297		return (ISC_R_UNEXPECTEDEND); /* Nothing on line. */
298	} else if (cp == ' ' || cp == '\t') {
299		cp = eatwhite(fp);
300	}
301
302	if (cp != EOF && cp != '\n') {
303		return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
304	}
305
306	result = add_server(conf->mctx, word, &conf->nameservers);
307	if (result != ISC_R_SUCCESS) {
308		return (result);
309	}
310	conf->numns++;
311
312	return (ISC_R_SUCCESS);
313}
314
315static isc_result_t
316resconf_parsedomain(irs_resconf_t *conf, FILE *fp) {
317	char word[RESCONFMAXLINELEN];
318	int res;
319	unsigned int i;
320
321	res = getword(fp, word, sizeof(word));
322	if (strlen(word) == 0U) {
323		return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */
324	} else if (res == ' ' || res == '\t') {
325		res = eatwhite(fp);
326	}
327
328	if (res != EOF && res != '\n') {
329		return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
330	}
331
332	if (conf->domainname != NULL) {
333		isc_mem_free(conf->mctx, conf->domainname);
334	}
335
336	/*
337	 * Search and domain are mutually exclusive.
338	 */
339	for (i = 0; i < RESCONFMAXSEARCH; i++) {
340		if (conf->search[i] != NULL) {
341			isc_mem_free(conf->mctx, conf->search[i]);
342			conf->search[i] = NULL;
343		}
344	}
345	conf->searchnxt = 0;
346
347	conf->domainname = isc_mem_strdup(conf->mctx, word);
348
349	return (ISC_R_SUCCESS);
350}
351
352static isc_result_t
353resconf_parsesearch(irs_resconf_t *conf, FILE *fp) {
354	int delim;
355	unsigned int idx;
356	char word[RESCONFMAXLINELEN];
357
358	if (conf->domainname != NULL) {
359		/*
360		 * Search and domain are mutually exclusive.
361		 */
362		isc_mem_free(conf->mctx, conf->domainname);
363		conf->domainname = NULL;
364	}
365
366	/*
367	 * Remove any previous search definitions.
368	 */
369	for (idx = 0; idx < RESCONFMAXSEARCH; idx++) {
370		if (conf->search[idx] != NULL) {
371			isc_mem_free(conf->mctx, conf->search[idx]);
372			conf->search[idx] = NULL;
373		}
374	}
375	conf->searchnxt = 0;
376
377	delim = getword(fp, word, sizeof(word));
378	if (strlen(word) == 0U) {
379		return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */
380	}
381
382	idx = 0;
383	while (strlen(word) > 0U) {
384		if (conf->searchnxt == RESCONFMAXSEARCH) {
385			goto ignore; /* Too many domains. */
386		}
387
388		INSIST(idx < sizeof(conf->search) / sizeof(conf->search[0]));
389		conf->search[idx] = isc_mem_strdup(conf->mctx, word);
390		idx++;
391		conf->searchnxt++;
392
393	ignore:
394		if (delim == EOF || delim == '\n') {
395			break;
396		} else {
397			delim = getword(fp, word, sizeof(word));
398		}
399	}
400
401	return (ISC_R_SUCCESS);
402}
403
404static isc_result_t
405resconf_parsesortlist(irs_resconf_t *conf, FILE *fp) {
406	int delim, res;
407	unsigned int idx;
408	char word[RESCONFMAXLINELEN];
409	char *p;
410
411	delim = getword(fp, word, sizeof(word));
412	if (strlen(word) == 0U) {
413		return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
414	}
415
416	while (strlen(word) > 0U) {
417		if (conf->sortlistnxt == RESCONFMAXSORTLIST) {
418			return (ISC_R_QUOTA); /* Too many values. */
419		}
420
421		p = strchr(word, '/');
422		if (p != NULL) {
423			*p++ = '\0';
424		}
425
426		idx = conf->sortlistnxt;
427		INSIST(idx <
428		       sizeof(conf->sortlist) / sizeof(conf->sortlist[0]));
429		res = create_addr(word, &conf->sortlist[idx].addr, 1);
430		if (res != ISC_R_SUCCESS) {
431			return (res);
432		}
433
434		if (p != NULL) {
435			res = create_addr(p, &conf->sortlist[idx].mask, 0);
436			if (res != ISC_R_SUCCESS) {
437				return (res);
438			}
439		} else {
440			/*
441			 * Make up a mask. (XXX: is this correct?)
442			 */
443			conf->sortlist[idx].mask = conf->sortlist[idx].addr;
444			memset(&conf->sortlist[idx].mask.type, 0xff,
445			       sizeof(conf->sortlist[idx].mask.type));
446		}
447
448		conf->sortlistnxt++;
449
450		if (delim == EOF || delim == '\n') {
451			break;
452		} else {
453			delim = getword(fp, word, sizeof(word));
454		}
455	}
456
457	return (ISC_R_SUCCESS);
458}
459
460static isc_result_t
461resconf_parseoption(irs_resconf_t *conf, FILE *fp) {
462	int delim;
463	long ndots;
464	char *p;
465	char word[RESCONFMAXLINELEN];
466
467	delim = getword(fp, word, sizeof(word));
468	if (strlen(word) == 0U) {
469		return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
470	}
471
472	while (strlen(word) > 0U) {
473		if (strcmp("debug", word) == 0) {
474			conf->resdebug = 1;
475		} else if (strncmp("ndots:", word, 6) == 0) {
476			ndots = strtol(word + 6, &p, 10);
477			if (*p != '\0') { /* Bad string. */
478				return (ISC_R_UNEXPECTEDTOKEN);
479			}
480			if (ndots < 0 || ndots > 0xff) { /* Out of range. */
481				return (ISC_R_RANGE);
482			}
483			conf->ndots = (uint8_t)ndots;
484		}
485
486		if (delim == EOF || delim == '\n') {
487			break;
488		} else {
489			delim = getword(fp, word, sizeof(word));
490		}
491	}
492
493	return (ISC_R_SUCCESS);
494}
495
496static isc_result_t
497add_search(irs_resconf_t *conf, char *domain) {
498	irs_resconf_search_t *entry;
499
500	entry = isc_mem_get(conf->mctx, sizeof(*entry));
501
502	entry->domain = domain;
503	ISC_LINK_INIT(entry, link);
504	ISC_LIST_APPEND(conf->searchlist, entry, link);
505
506	return (ISC_R_SUCCESS);
507}
508
509/*% parses a file and fills in the data structure. */
510isc_result_t
511irs_resconf_load(isc_mem_t *mctx, const char *filename, irs_resconf_t **confp) {
512	FILE *fp = NULL;
513	char word[256];
514	isc_result_t rval, ret = ISC_R_SUCCESS;
515	irs_resconf_t *conf;
516	unsigned int i;
517	int stopchar;
518
519	REQUIRE(mctx != NULL);
520	REQUIRE(filename != NULL);
521	REQUIRE(strlen(filename) > 0U);
522	REQUIRE(confp != NULL && *confp == NULL);
523
524	conf = isc_mem_get(mctx, sizeof(*conf));
525
526	conf->mctx = mctx;
527	ISC_LIST_INIT(conf->nameservers);
528	ISC_LIST_INIT(conf->searchlist);
529	conf->numns = 0;
530	conf->domainname = NULL;
531	conf->searchnxt = 0;
532	conf->sortlistnxt = 0;
533	conf->resdebug = 0;
534	conf->ndots = 1;
535	for (i = 0; i < RESCONFMAXSEARCH; i++) {
536		conf->search[i] = NULL;
537	}
538
539	errno = 0;
540	if ((fp = fopen(filename, "r")) != NULL) {
541		do {
542			stopchar = getword(fp, word, sizeof(word));
543			if (stopchar == EOF) {
544				rval = ISC_R_SUCCESS;
545				POST(rval);
546				break;
547			}
548
549			if (strlen(word) == 0U) {
550				rval = ISC_R_SUCCESS;
551			} else if (strcmp(word, "nameserver") == 0) {
552				rval = resconf_parsenameserver(conf, fp);
553			} else if (strcmp(word, "domain") == 0) {
554				rval = resconf_parsedomain(conf, fp);
555			} else if (strcmp(word, "search") == 0) {
556				rval = resconf_parsesearch(conf, fp);
557			} else if (strcmp(word, "sortlist") == 0) {
558				rval = resconf_parsesortlist(conf, fp);
559			} else if (strcmp(word, "options") == 0) {
560				rval = resconf_parseoption(conf, fp);
561			} else {
562				/* unrecognised word. Ignore entire line */
563				rval = ISC_R_SUCCESS;
564				stopchar = eatline(fp);
565				if (stopchar == EOF) {
566					break;
567				}
568			}
569			if (ret == ISC_R_SUCCESS && rval != ISC_R_SUCCESS) {
570				ret = rval;
571			}
572		} while (1);
573
574		fclose(fp);
575	} else {
576		switch (errno) {
577		case ENOENT:
578			break;
579		default:
580			isc_mem_put(mctx, conf, sizeof(*conf));
581			return (ISC_R_INVALIDFILE);
582		}
583	}
584
585	if (ret != ISC_R_SUCCESS) {
586		goto error;
587	}
588
589	/*
590	 * Construct unified search list from domain or configured
591	 * search list
592	 */
593	if (conf->domainname != NULL) {
594		ret = add_search(conf, conf->domainname);
595	} else if (conf->searchnxt > 0) {
596		for (i = 0; i < conf->searchnxt; i++) {
597			ret = add_search(conf, conf->search[i]);
598			if (ret != ISC_R_SUCCESS) {
599				break;
600			}
601		}
602	}
603
604#if HAVE_GET_WIN32_NAMESERVERS
605	ret = get_win32_nameservers(conf);
606	if (ret != ISC_R_SUCCESS) {
607		goto error;
608	}
609#endif /* if HAVE_GET_WIN32_NAMESERVERS */
610
611	/* If we don't find a nameserver fall back to localhost */
612	if (conf->numns == 0U) {
613		INSIST(ISC_LIST_EMPTY(conf->nameservers));
614
615		/* XXX: should we catch errors? */
616		(void)add_server(conf->mctx, "::1", &conf->nameservers);
617		(void)add_server(conf->mctx, "127.0.0.1", &conf->nameservers);
618	}
619
620error:
621	conf->magic = IRS_RESCONF_MAGIC;
622
623	if (ret != ISC_R_SUCCESS) {
624		irs_resconf_destroy(&conf);
625	} else {
626		if (fp == NULL) {
627			ret = ISC_R_FILENOTFOUND;
628		}
629		*confp = conf;
630	}
631
632	return (ret);
633}
634
635void
636irs_resconf_destroy(irs_resconf_t **confp) {
637	irs_resconf_t *conf;
638	isc_sockaddr_t *address;
639	irs_resconf_search_t *searchentry;
640	unsigned int i;
641
642	REQUIRE(confp != NULL);
643	conf = *confp;
644	*confp = NULL;
645	REQUIRE(IRS_RESCONF_VALID(conf));
646
647	while ((searchentry = ISC_LIST_HEAD(conf->searchlist)) != NULL) {
648		ISC_LIST_UNLINK(conf->searchlist, searchentry, link);
649		isc_mem_put(conf->mctx, searchentry, sizeof(*searchentry));
650	}
651
652	while ((address = ISC_LIST_HEAD(conf->nameservers)) != NULL) {
653		ISC_LIST_UNLINK(conf->nameservers, address, link);
654		isc_mem_put(conf->mctx, address, sizeof(*address));
655	}
656
657	if (conf->domainname != NULL) {
658		isc_mem_free(conf->mctx, conf->domainname);
659	}
660
661	for (i = 0; i < RESCONFMAXSEARCH; i++) {
662		if (conf->search[i] != NULL) {
663			isc_mem_free(conf->mctx, conf->search[i]);
664		}
665	}
666
667	isc_mem_put(conf->mctx, conf, sizeof(*conf));
668}
669
670isc_sockaddrlist_t *
671irs_resconf_getnameservers(irs_resconf_t *conf) {
672	REQUIRE(IRS_RESCONF_VALID(conf));
673
674	return (&conf->nameservers);
675}
676
677irs_resconf_searchlist_t *
678irs_resconf_getsearchlist(irs_resconf_t *conf) {
679	REQUIRE(IRS_RESCONF_VALID(conf));
680
681	return (&conf->searchlist);
682}
683
684unsigned int
685irs_resconf_getndots(irs_resconf_t *conf) {
686	REQUIRE(IRS_RESCONF_VALID(conf));
687
688	return ((unsigned int)conf->ndots);
689}
690