xref: /netbsd-src/external/mpl/bind/dist/lib/dns/resconf.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1*bcda20f6Schristos /*	$NetBSD: resconf.c,v 1.2 2025/01/26 16:25:24 christos Exp $	*/
29689912eSchristos 
39689912eSchristos /*
49689912eSchristos  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
59689912eSchristos  *
69689912eSchristos  * SPDX-License-Identifier: MPL-2.0
79689912eSchristos  *
89689912eSchristos  * This Source Code Form is subject to the terms of the Mozilla Public
99689912eSchristos  * License, v. 2.0. If a copy of the MPL was not distributed with this
109689912eSchristos  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
119689912eSchristos  *
129689912eSchristos  * See the COPYRIGHT file distributed with this work for additional
139689912eSchristos  * information regarding copyright ownership.
149689912eSchristos  */
159689912eSchristos 
169689912eSchristos /*! \file resconf.c */
179689912eSchristos 
189689912eSchristos /**
199689912eSchristos  * Module for parsing resolv.conf files (largely derived from lwconfig.c).
209689912eSchristos  *
219689912eSchristos  *    irs_resconf_load() opens the file filename and parses it to initialize
229689912eSchristos  *    the configuration structure.
239689912eSchristos  *
249689912eSchristos  * \section lwconfig_return Return Values
259689912eSchristos  *
269689912eSchristos  *    irs_resconf_load() returns #IRS_R_SUCCESS if it successfully read and
279689912eSchristos  *    parsed filename. It returns a non-0 error code if filename could not be
289689912eSchristos  *    opened or contained incorrect resolver statements.
299689912eSchristos  *
309689912eSchristos  * \section lwconfig_see See Also
319689912eSchristos  *
329689912eSchristos  *    stdio(3), \link resolver resolver \endlink
339689912eSchristos  *
349689912eSchristos  * \section files Files
359689912eSchristos  *
369689912eSchristos  *    /etc/resolv.conf
379689912eSchristos  */
389689912eSchristos 
399689912eSchristos #include <ctype.h>
409689912eSchristos #include <errno.h>
419689912eSchristos #include <inttypes.h>
429689912eSchristos #include <netdb.h>
439689912eSchristos #include <stdio.h>
449689912eSchristos #include <stdlib.h>
459689912eSchristos #include <string.h>
469689912eSchristos #include <sys/socket.h>
479689912eSchristos #include <sys/types.h>
489689912eSchristos 
499689912eSchristos #include <isc/magic.h>
509689912eSchristos #include <isc/mem.h>
519689912eSchristos #include <isc/netaddr.h>
529689912eSchristos #include <isc/sockaddr.h>
539689912eSchristos #include <isc/util.h>
549689912eSchristos 
559689912eSchristos #include <irs/resconf.h>
569689912eSchristos 
579689912eSchristos #define IRS_RESCONF_MAGIC    ISC_MAGIC('R', 'E', 'S', 'c')
589689912eSchristos #define IRS_RESCONF_VALID(c) ISC_MAGIC_VALID(c, IRS_RESCONF_MAGIC)
599689912eSchristos 
609689912eSchristos /*!
619689912eSchristos  * protocol constants
629689912eSchristos  */
639689912eSchristos 
649689912eSchristos #if !defined(NS_INADDRSZ)
659689912eSchristos #define NS_INADDRSZ 4
669689912eSchristos #endif /* if !defined(NS_INADDRSZ) */
679689912eSchristos 
689689912eSchristos #if !defined(NS_IN6ADDRSZ)
699689912eSchristos #define NS_IN6ADDRSZ 16
709689912eSchristos #endif /* if !defined(NS_IN6ADDRSZ) */
719689912eSchristos 
729689912eSchristos /*!
739689912eSchristos  * resolv.conf parameters
749689912eSchristos  */
759689912eSchristos 
769689912eSchristos #define RESCONFMAXNAMESERVERS 3U   /*%< max 3 "nameserver" entries */
779689912eSchristos #define RESCONFMAXSEARCH      8U   /*%< max 8 domains in "search" entry */
789689912eSchristos #define RESCONFMAXLINELEN     256U /*%< max size of a line */
799689912eSchristos #define RESCONFMAXSORTLIST    10U  /*%< max 10 */
809689912eSchristos 
819689912eSchristos #define CHECK(op)                            \
829689912eSchristos 	do {                                 \
839689912eSchristos 		result = (op);               \
849689912eSchristos 		if (result != ISC_R_SUCCESS) \
859689912eSchristos 			goto cleanup;        \
869689912eSchristos 	} while (0)
879689912eSchristos 
889689912eSchristos /*!
899689912eSchristos  * configuration data structure
909689912eSchristos  */
919689912eSchristos 
929689912eSchristos struct irs_resconf {
939689912eSchristos 	/*
949689912eSchristos 	 * The configuration data is a thread-specific object, and does not
959689912eSchristos 	 * need to be locked.
969689912eSchristos 	 */
979689912eSchristos 	unsigned int magic;
989689912eSchristos 	isc_mem_t *mctx;
999689912eSchristos 
1009689912eSchristos 	isc_sockaddrlist_t nameservers;
1019689912eSchristos 	unsigned int numns; /*%< number of configured servers */
1029689912eSchristos 
1039689912eSchristos 	char *domainname;
1049689912eSchristos 	char *search[RESCONFMAXSEARCH];
1059689912eSchristos 	uint8_t searchnxt; /*%< index for next free slot */
1069689912eSchristos 
1079689912eSchristos 	irs_resconf_searchlist_t searchlist;
1089689912eSchristos 
1099689912eSchristos 	struct {
1109689912eSchristos 		isc_netaddr_t addr;
1119689912eSchristos 		/*% mask has a non-zero 'family' if set */
1129689912eSchristos 		isc_netaddr_t mask;
1139689912eSchristos 	} sortlist[RESCONFMAXSORTLIST];
1149689912eSchristos 	uint8_t sortlistnxt;
1159689912eSchristos 
1169689912eSchristos 	/*%< non-zero if 'options debug' set */
1179689912eSchristos 	uint8_t resdebug;
1189689912eSchristos 	/*%< set to n in 'options ndots:n' */
1199689912eSchristos 	uint8_t ndots;
1209689912eSchristos 	/*%< set to n in 'options attempts:n' */
1219689912eSchristos 	uint8_t attempts;
1229689912eSchristos 	/*%< set to n in 'options timeout:n' */
1239689912eSchristos 	uint8_t timeout;
1249689912eSchristos };
1259689912eSchristos 
1269689912eSchristos static isc_result_t
1279689912eSchristos resconf_parsenameserver(irs_resconf_t *conf, FILE *fp);
1289689912eSchristos static isc_result_t
1299689912eSchristos resconf_parsedomain(irs_resconf_t *conf, FILE *fp);
1309689912eSchristos static isc_result_t
1319689912eSchristos resconf_parsesearch(irs_resconf_t *conf, FILE *fp);
1329689912eSchristos static isc_result_t
1339689912eSchristos resconf_parsesortlist(irs_resconf_t *conf, FILE *fp);
1349689912eSchristos static isc_result_t
1359689912eSchristos resconf_parseoption(irs_resconf_t *ctx, FILE *fp);
1369689912eSchristos 
1379689912eSchristos /*!
1389689912eSchristos  * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
1399689912eSchristos  */
1409689912eSchristos static int
1419689912eSchristos eatline(FILE *fp) {
1429689912eSchristos 	int ch;
1439689912eSchristos 
1449689912eSchristos 	ch = fgetc(fp);
1459689912eSchristos 	while (ch != '\n' && ch != EOF) {
1469689912eSchristos 		ch = fgetc(fp);
1479689912eSchristos 	}
1489689912eSchristos 
1499689912eSchristos 	return ch;
1509689912eSchristos }
1519689912eSchristos 
1529689912eSchristos /*!
1539689912eSchristos  * Eats white space up to next newline or non-whitespace character (of
1549689912eSchristos  * EOF). Returns the last character read. Comments are considered white
1559689912eSchristos  * space.
1569689912eSchristos  */
1579689912eSchristos static int
1589689912eSchristos eatwhite(FILE *fp) {
1599689912eSchristos 	int ch;
1609689912eSchristos 
1619689912eSchristos 	ch = fgetc(fp);
1629689912eSchristos 	while (ch != '\n' && ch != EOF && isspace((unsigned char)ch)) {
1639689912eSchristos 		ch = fgetc(fp);
1649689912eSchristos 	}
1659689912eSchristos 
1669689912eSchristos 	if (ch == ';' || ch == '#') {
1679689912eSchristos 		ch = eatline(fp);
1689689912eSchristos 	}
1699689912eSchristos 
1709689912eSchristos 	return ch;
1719689912eSchristos }
1729689912eSchristos 
1739689912eSchristos /*!
1749689912eSchristos  * Skip over any leading whitespace and then read in the next sequence of
1759689912eSchristos  * non-whitespace characters. In this context newline is not considered
1769689912eSchristos  * whitespace. Returns EOF on end-of-file, or the character
1779689912eSchristos  * that caused the reading to stop.
1789689912eSchristos  */
1799689912eSchristos static int
1809689912eSchristos getword(FILE *fp, char *buffer, size_t size) {
1819689912eSchristos 	char *p = NULL;
1829689912eSchristos 	int ch;
1839689912eSchristos 
1849689912eSchristos 	REQUIRE(buffer != NULL);
1859689912eSchristos 	REQUIRE(size > 0U);
1869689912eSchristos 
1879689912eSchristos 	p = buffer;
1889689912eSchristos 	*p = '\0';
1899689912eSchristos 
1909689912eSchristos 	ch = eatwhite(fp);
1919689912eSchristos 
1929689912eSchristos 	if (ch == EOF) {
1939689912eSchristos 		return EOF;
1949689912eSchristos 	}
1959689912eSchristos 
1969689912eSchristos 	do {
1979689912eSchristos 		*p = '\0';
1989689912eSchristos 
1999689912eSchristos 		if (ch == EOF || isspace((unsigned char)ch)) {
2009689912eSchristos 			break;
2019689912eSchristos 		} else if ((size_t)(p - buffer) == size - 1) {
2029689912eSchristos 			return EOF; /* Not enough space. */
2039689912eSchristos 		}
2049689912eSchristos 
2059689912eSchristos 		*p++ = (char)ch;
2069689912eSchristos 		ch = fgetc(fp);
2079689912eSchristos 	} while (1);
2089689912eSchristos 
2099689912eSchristos 	return ch;
2109689912eSchristos }
2119689912eSchristos 
2129689912eSchristos static isc_result_t
2139689912eSchristos add_server(isc_mem_t *mctx, const char *address_str,
2149689912eSchristos 	   isc_sockaddrlist_t *nameservers) {
2159689912eSchristos 	int error;
2169689912eSchristos 	isc_sockaddr_t *address = NULL;
2179689912eSchristos 	struct addrinfo hints, *res;
2189689912eSchristos 	isc_result_t result = ISC_R_SUCCESS;
2199689912eSchristos 
2209689912eSchristos 	res = NULL;
2219689912eSchristos 	memset(&hints, 0, sizeof(hints));
2229689912eSchristos 	hints.ai_family = AF_UNSPEC;
2239689912eSchristos 	hints.ai_socktype = SOCK_DGRAM;
2249689912eSchristos 	hints.ai_protocol = IPPROTO_UDP;
2259689912eSchristos 	hints.ai_flags = AI_NUMERICHOST;
2269689912eSchristos 	error = getaddrinfo(address_str, "53", &hints, &res);
2279689912eSchristos 	if (error != 0) {
2289689912eSchristos 		return ISC_R_BADADDRESSFORM;
2299689912eSchristos 	}
2309689912eSchristos 
2319689912eSchristos 	address = isc_mem_get(mctx, sizeof(*address));
2329689912eSchristos 	if (res->ai_addrlen > sizeof(address->type)) {
2339689912eSchristos 		isc_mem_put(mctx, address, sizeof(*address));
2349689912eSchristos 		result = ISC_R_RANGE;
2359689912eSchristos 		goto cleanup;
2369689912eSchristos 	}
2379689912eSchristos 
2389689912eSchristos 	if (res->ai_family == AF_INET) {
2399689912eSchristos 		struct in_addr *v4;
2409689912eSchristos 		unsigned char zeroaddress[] = { 0, 0, 0, 0 };
2419689912eSchristos 		unsigned char loopaddress[] = { 127, 0, 0, 1 };
2429689912eSchristos 
2439689912eSchristos 		/* XXX: special case: treat all-0 IPv4 address as loopback */
2449689912eSchristos 		v4 = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
2459689912eSchristos 		if (memcmp(v4, zeroaddress, 4) == 0) {
2469689912eSchristos 			memmove(v4, loopaddress, 4);
2479689912eSchristos 		}
2489689912eSchristos 		memmove(&address->type.sin, res->ai_addr, res->ai_addrlen);
2499689912eSchristos 	} else if (res->ai_family == AF_INET6) {
2509689912eSchristos 		memmove(&address->type.sin6, res->ai_addr, res->ai_addrlen);
2519689912eSchristos 	} else {
2529689912eSchristos 		isc_mem_put(mctx, address, sizeof(*address));
2539689912eSchristos 		UNEXPECTED_ERROR("ai_family (%d) not INET nor INET6",
2549689912eSchristos 				 res->ai_family);
2559689912eSchristos 		result = ISC_R_UNEXPECTED;
2569689912eSchristos 		goto cleanup;
2579689912eSchristos 	}
2589689912eSchristos 	address->length = (unsigned int)res->ai_addrlen;
2599689912eSchristos 
2609689912eSchristos 	ISC_LINK_INIT(address, link);
2619689912eSchristos 	ISC_LIST_APPEND(*nameservers, address, link);
2629689912eSchristos 
2639689912eSchristos cleanup:
2649689912eSchristos 	freeaddrinfo(res);
2659689912eSchristos 
2669689912eSchristos 	return result;
2679689912eSchristos }
2689689912eSchristos 
2699689912eSchristos static isc_result_t
2709689912eSchristos create_addr(const char *buffer, isc_netaddr_t *addr, int convert_zero) {
2719689912eSchristos 	struct in_addr v4;
2729689912eSchristos 	struct in6_addr v6;
2739689912eSchristos 
2749689912eSchristos 	if (inet_pton(AF_INET, buffer, &v4) == 1) {
2759689912eSchristos 		if (convert_zero) {
2769689912eSchristos 			unsigned char zeroaddress[] = { 0, 0, 0, 0 };
2779689912eSchristos 			unsigned char loopaddress[] = { 127, 0, 0, 1 };
2789689912eSchristos 			if (memcmp(&v4, zeroaddress, 4) == 0) {
2799689912eSchristos 				memmove(&v4, loopaddress, 4);
2809689912eSchristos 			}
2819689912eSchristos 		}
2829689912eSchristos 		addr->family = AF_INET;
2839689912eSchristos 		memmove(&addr->type.in, &v4, NS_INADDRSZ);
2849689912eSchristos 		addr->zone = 0;
2859689912eSchristos 	} else if (inet_pton(AF_INET6, buffer, &v6) == 1) {
2869689912eSchristos 		addr->family = AF_INET6;
2879689912eSchristos 		memmove(&addr->type.in6, &v6, NS_IN6ADDRSZ);
2889689912eSchristos 		addr->zone = 0;
2899689912eSchristos 	} else {
2909689912eSchristos 		return ISC_R_BADADDRESSFORM; /* Unrecognised format. */
2919689912eSchristos 	}
2929689912eSchristos 
2939689912eSchristos 	return ISC_R_SUCCESS;
2949689912eSchristos }
2959689912eSchristos 
2969689912eSchristos static isc_result_t
2979689912eSchristos resconf_parsenameserver(irs_resconf_t *conf, FILE *fp) {
2989689912eSchristos 	char word[RESCONFMAXLINELEN];
2999689912eSchristos 	int cp;
3009689912eSchristos 	isc_result_t result;
3019689912eSchristos 
3029689912eSchristos 	cp = getword(fp, word, sizeof(word));
3039689912eSchristos 	if (strlen(word) == 0U) {
3049689912eSchristos 		return ISC_R_UNEXPECTEDEND; /* Nothing on line. */
3059689912eSchristos 	} else if (cp == ' ' || cp == '\t') {
3069689912eSchristos 		cp = eatwhite(fp);
3079689912eSchristos 	}
3089689912eSchristos 
3099689912eSchristos 	if (cp != EOF && cp != '\n') {
3109689912eSchristos 		return ISC_R_UNEXPECTEDTOKEN; /* Extra junk on line. */
3119689912eSchristos 	}
3129689912eSchristos 
3139689912eSchristos 	if (conf->numns == RESCONFMAXNAMESERVERS) {
3149689912eSchristos 		return ISC_R_SUCCESS;
3159689912eSchristos 	}
3169689912eSchristos 
3179689912eSchristos 	result = add_server(conf->mctx, word, &conf->nameservers);
3189689912eSchristos 	if (result != ISC_R_SUCCESS) {
3199689912eSchristos 		return result;
3209689912eSchristos 	}
3219689912eSchristos 	conf->numns++;
3229689912eSchristos 
3239689912eSchristos 	return ISC_R_SUCCESS;
3249689912eSchristos }
3259689912eSchristos 
3269689912eSchristos static isc_result_t
3279689912eSchristos resconf_parsedomain(irs_resconf_t *conf, FILE *fp) {
3289689912eSchristos 	char word[RESCONFMAXLINELEN];
3299689912eSchristos 	int res;
3309689912eSchristos 	unsigned int i;
3319689912eSchristos 
3329689912eSchristos 	res = getword(fp, word, sizeof(word));
3339689912eSchristos 	if (strlen(word) == 0U) {
3349689912eSchristos 		return ISC_R_UNEXPECTEDEND; /* Nothing else on line. */
3359689912eSchristos 	} else if (res == ' ' || res == '\t') {
3369689912eSchristos 		res = eatwhite(fp);
3379689912eSchristos 	}
3389689912eSchristos 
3399689912eSchristos 	if (res != EOF && res != '\n') {
3409689912eSchristos 		return ISC_R_UNEXPECTEDTOKEN; /* Extra junk on line. */
3419689912eSchristos 	}
3429689912eSchristos 
3439689912eSchristos 	if (conf->domainname != NULL) {
3449689912eSchristos 		isc_mem_free(conf->mctx, conf->domainname);
3459689912eSchristos 	}
3469689912eSchristos 
3479689912eSchristos 	/*
3489689912eSchristos 	 * Search and domain are mutually exclusive.
3499689912eSchristos 	 */
3509689912eSchristos 	for (i = 0; i < RESCONFMAXSEARCH; i++) {
3519689912eSchristos 		if (conf->search[i] != NULL) {
3529689912eSchristos 			isc_mem_free(conf->mctx, conf->search[i]);
3539689912eSchristos 			conf->search[i] = NULL;
3549689912eSchristos 		}
3559689912eSchristos 	}
3569689912eSchristos 	conf->searchnxt = 0;
3579689912eSchristos 
3589689912eSchristos 	conf->domainname = isc_mem_strdup(conf->mctx, word);
3599689912eSchristos 
3609689912eSchristos 	return ISC_R_SUCCESS;
3619689912eSchristos }
3629689912eSchristos 
3639689912eSchristos static isc_result_t
3649689912eSchristos resconf_parsesearch(irs_resconf_t *conf, FILE *fp) {
3659689912eSchristos 	int delim;
3669689912eSchristos 	unsigned int idx;
3679689912eSchristos 	char word[RESCONFMAXLINELEN];
3689689912eSchristos 
3699689912eSchristos 	if (conf->domainname != NULL) {
3709689912eSchristos 		/*
3719689912eSchristos 		 * Search and domain are mutually exclusive.
3729689912eSchristos 		 */
3739689912eSchristos 		isc_mem_free(conf->mctx, conf->domainname);
3749689912eSchristos 		conf->domainname = NULL;
3759689912eSchristos 	}
3769689912eSchristos 
3779689912eSchristos 	/*
3789689912eSchristos 	 * Remove any previous search definitions.
3799689912eSchristos 	 */
3809689912eSchristos 	for (idx = 0; idx < RESCONFMAXSEARCH; idx++) {
3819689912eSchristos 		if (conf->search[idx] != NULL) {
3829689912eSchristos 			isc_mem_free(conf->mctx, conf->search[idx]);
3839689912eSchristos 			conf->search[idx] = NULL;
3849689912eSchristos 		}
3859689912eSchristos 	}
3869689912eSchristos 	conf->searchnxt = 0;
3879689912eSchristos 
3889689912eSchristos 	delim = getword(fp, word, sizeof(word));
3899689912eSchristos 	if (strlen(word) == 0U) {
3909689912eSchristos 		return ISC_R_UNEXPECTEDEND; /* Nothing else on line. */
3919689912eSchristos 	}
3929689912eSchristos 
3939689912eSchristos 	idx = 0;
3949689912eSchristos 	while (strlen(word) > 0U) {
3959689912eSchristos 		if (conf->searchnxt == RESCONFMAXSEARCH) {
3969689912eSchristos 			goto ignore; /* Too many domains. */
3979689912eSchristos 		}
3989689912eSchristos 
3999689912eSchristos 		INSIST(idx < sizeof(conf->search) / sizeof(conf->search[0]));
4009689912eSchristos 		conf->search[idx] = isc_mem_strdup(conf->mctx, word);
4019689912eSchristos 		idx++;
4029689912eSchristos 		conf->searchnxt++;
4039689912eSchristos 
4049689912eSchristos 	ignore:
4059689912eSchristos 		if (delim == EOF || delim == '\n') {
4069689912eSchristos 			break;
4079689912eSchristos 		} else {
4089689912eSchristos 			delim = getword(fp, word, sizeof(word));
4099689912eSchristos 		}
4109689912eSchristos 	}
4119689912eSchristos 
4129689912eSchristos 	return ISC_R_SUCCESS;
4139689912eSchristos }
4149689912eSchristos 
4159689912eSchristos static isc_result_t
4169689912eSchristos resconf_parsesortlist(irs_resconf_t *conf, FILE *fp) {
4179689912eSchristos 	int delim, res;
4189689912eSchristos 	unsigned int idx;
4199689912eSchristos 	char word[RESCONFMAXLINELEN];
4209689912eSchristos 	char *p;
4219689912eSchristos 
4229689912eSchristos 	delim = getword(fp, word, sizeof(word));
4239689912eSchristos 	if (strlen(word) == 0U) {
4249689912eSchristos 		return ISC_R_UNEXPECTEDEND; /* Empty line after keyword. */
4259689912eSchristos 	}
4269689912eSchristos 
4279689912eSchristos 	while (strlen(word) > 0U) {
4289689912eSchristos 		if (conf->sortlistnxt == RESCONFMAXSORTLIST) {
4299689912eSchristos 			return ISC_R_QUOTA; /* Too many values. */
4309689912eSchristos 		}
4319689912eSchristos 
4329689912eSchristos 		p = strchr(word, '/');
4339689912eSchristos 		if (p != NULL) {
4349689912eSchristos 			*p++ = '\0';
4359689912eSchristos 		}
4369689912eSchristos 
4379689912eSchristos 		idx = conf->sortlistnxt;
4389689912eSchristos 		INSIST(idx <
4399689912eSchristos 		       sizeof(conf->sortlist) / sizeof(conf->sortlist[0]));
4409689912eSchristos 		res = create_addr(word, &conf->sortlist[idx].addr, 1);
4419689912eSchristos 		if (res != ISC_R_SUCCESS) {
4429689912eSchristos 			return res;
4439689912eSchristos 		}
4449689912eSchristos 
4459689912eSchristos 		if (p != NULL) {
4469689912eSchristos 			res = create_addr(p, &conf->sortlist[idx].mask, 0);
4479689912eSchristos 			if (res != ISC_R_SUCCESS) {
4489689912eSchristos 				return res;
4499689912eSchristos 			}
4509689912eSchristos 		} else {
4519689912eSchristos 			/*
4529689912eSchristos 			 * Make up a mask. (XXX: is this correct?)
4539689912eSchristos 			 */
4549689912eSchristos 			conf->sortlist[idx].mask = conf->sortlist[idx].addr;
4559689912eSchristos 			memset(&conf->sortlist[idx].mask.type, 0xff,
4569689912eSchristos 			       sizeof(conf->sortlist[idx].mask.type));
4579689912eSchristos 		}
4589689912eSchristos 
4599689912eSchristos 		conf->sortlistnxt++;
4609689912eSchristos 
4619689912eSchristos 		if (delim == EOF || delim == '\n') {
4629689912eSchristos 			break;
4639689912eSchristos 		} else {
4649689912eSchristos 			delim = getword(fp, word, sizeof(word));
4659689912eSchristos 		}
4669689912eSchristos 	}
4679689912eSchristos 
4689689912eSchristos 	return ISC_R_SUCCESS;
4699689912eSchristos }
4709689912eSchristos 
4719689912eSchristos static isc_result_t
4729689912eSchristos resconf_optionnumber(const char *word, uint8_t *number) {
4739689912eSchristos 	char *p;
4749689912eSchristos 	long n;
4759689912eSchristos 
4769689912eSchristos 	n = strtol(word, &p, 10);
4779689912eSchristos 	if (*p != '\0') { /* Bad string. */
4789689912eSchristos 		return ISC_R_UNEXPECTEDTOKEN;
4799689912eSchristos 	}
4809689912eSchristos 	if (n < 0 || n > 0xff) { /* Out of range. */
4819689912eSchristos 		return ISC_R_RANGE;
4829689912eSchristos 	}
4839689912eSchristos 	*number = n;
4849689912eSchristos 	return ISC_R_SUCCESS;
4859689912eSchristos }
4869689912eSchristos 
4879689912eSchristos static isc_result_t
4889689912eSchristos resconf_parseoption(irs_resconf_t *conf, FILE *fp) {
4899689912eSchristos 	int delim;
4909689912eSchristos 	isc_result_t result = ISC_R_SUCCESS;
4919689912eSchristos 	char word[RESCONFMAXLINELEN];
4929689912eSchristos 
4939689912eSchristos 	delim = getword(fp, word, sizeof(word));
4949689912eSchristos 	if (strlen(word) == 0U) {
4959689912eSchristos 		return ISC_R_UNEXPECTEDEND; /* Empty line after keyword. */
4969689912eSchristos 	}
4979689912eSchristos 
4989689912eSchristos 	while (strlen(word) > 0U) {
4999689912eSchristos 		if (strcmp("debug", word) == 0) {
5009689912eSchristos 			conf->resdebug = 1;
5019689912eSchristos 		} else if (strncmp("ndots:", word, 6) == 0) {
5029689912eSchristos 			CHECK(resconf_optionnumber(word + 6, &conf->ndots));
5039689912eSchristos 		} else if (strncmp("attempts:", word, 9) == 0) {
5049689912eSchristos 			CHECK(resconf_optionnumber(word + 9, &conf->attempts));
5059689912eSchristos 		} else if (strncmp("timeout:", word, 8) == 0) {
5069689912eSchristos 			CHECK(resconf_optionnumber(word + 8, &conf->timeout));
5079689912eSchristos 		}
5089689912eSchristos 
5099689912eSchristos 		if (delim == EOF || delim == '\n') {
5109689912eSchristos 			break;
5119689912eSchristos 		} else {
5129689912eSchristos 			delim = getword(fp, word, sizeof(word));
5139689912eSchristos 		}
5149689912eSchristos 	}
5159689912eSchristos 
5169689912eSchristos cleanup:
5179689912eSchristos 	return result;
5189689912eSchristos }
5199689912eSchristos 
5209689912eSchristos static isc_result_t
5219689912eSchristos add_search(irs_resconf_t *conf, char *domain) {
5229689912eSchristos 	irs_resconf_search_t *entry;
5239689912eSchristos 
5249689912eSchristos 	entry = isc_mem_get(conf->mctx, sizeof(*entry));
5259689912eSchristos 
5269689912eSchristos 	entry->domain = domain;
5279689912eSchristos 	ISC_LINK_INIT(entry, link);
5289689912eSchristos 	ISC_LIST_APPEND(conf->searchlist, entry, link);
5299689912eSchristos 
5309689912eSchristos 	return ISC_R_SUCCESS;
5319689912eSchristos }
5329689912eSchristos 
5339689912eSchristos /*% parses a file and fills in the data structure. */
5349689912eSchristos isc_result_t
5359689912eSchristos irs_resconf_load(isc_mem_t *mctx, const char *filename, irs_resconf_t **confp) {
5369689912eSchristos 	FILE *fp = NULL;
5379689912eSchristos 	char word[256];
5389689912eSchristos 	isc_result_t rval, ret = ISC_R_SUCCESS;
5399689912eSchristos 	irs_resconf_t *conf;
5409689912eSchristos 	unsigned int i;
5419689912eSchristos 	int stopchar;
5429689912eSchristos 
5439689912eSchristos 	REQUIRE(mctx != NULL);
5449689912eSchristos 	REQUIRE(filename != NULL);
5459689912eSchristos 	REQUIRE(strlen(filename) > 0U);
5469689912eSchristos 	REQUIRE(confp != NULL && *confp == NULL);
5479689912eSchristos 
5489689912eSchristos 	conf = isc_mem_get(mctx, sizeof(*conf));
5499689912eSchristos 
5509689912eSchristos 	conf->mctx = mctx;
5519689912eSchristos 	ISC_LIST_INIT(conf->nameservers);
5529689912eSchristos 	ISC_LIST_INIT(conf->searchlist);
5539689912eSchristos 	conf->numns = 0;
5549689912eSchristos 	conf->domainname = NULL;
5559689912eSchristos 	conf->searchnxt = 0;
5569689912eSchristos 	conf->sortlistnxt = 0;
5579689912eSchristos 	conf->resdebug = 0;
5589689912eSchristos 	conf->ndots = 1;
5599689912eSchristos 	conf->attempts = 3;
5609689912eSchristos 	conf->timeout = 0;
5619689912eSchristos 	for (i = 0; i < RESCONFMAXSEARCH; i++) {
5629689912eSchristos 		conf->search[i] = NULL;
5639689912eSchristos 	}
5649689912eSchristos 
5659689912eSchristos 	errno = 0;
5669689912eSchristos 	if ((fp = fopen(filename, "r")) != NULL) {
5679689912eSchristos 		do {
5689689912eSchristos 			stopchar = getword(fp, word, sizeof(word));
5699689912eSchristos 			if (stopchar == EOF) {
5709689912eSchristos 				rval = ISC_R_SUCCESS;
5719689912eSchristos 				POST(rval);
5729689912eSchristos 				break;
5739689912eSchristos 			}
5749689912eSchristos 
5759689912eSchristos 			if (strlen(word) == 0U) {
5769689912eSchristos 				rval = ISC_R_SUCCESS;
5779689912eSchristos 			} else if (strcmp(word, "nameserver") == 0) {
5789689912eSchristos 				rval = resconf_parsenameserver(conf, fp);
5799689912eSchristos 			} else if (strcmp(word, "domain") == 0) {
5809689912eSchristos 				rval = resconf_parsedomain(conf, fp);
5819689912eSchristos 			} else if (strcmp(word, "search") == 0) {
5829689912eSchristos 				rval = resconf_parsesearch(conf, fp);
5839689912eSchristos 			} else if (strcmp(word, "sortlist") == 0) {
5849689912eSchristos 				rval = resconf_parsesortlist(conf, fp);
5859689912eSchristos 			} else if (strcmp(word, "options") == 0) {
5869689912eSchristos 				rval = resconf_parseoption(conf, fp);
5879689912eSchristos 			} else {
5889689912eSchristos 				/* unrecognised word. Ignore entire line */
5899689912eSchristos 				rval = ISC_R_SUCCESS;
5909689912eSchristos 				stopchar = eatline(fp);
5919689912eSchristos 				if (stopchar == EOF) {
5929689912eSchristos 					break;
5939689912eSchristos 				}
5949689912eSchristos 			}
5959689912eSchristos 			if (ret == ISC_R_SUCCESS && rval != ISC_R_SUCCESS) {
5969689912eSchristos 				ret = rval;
5979689912eSchristos 			}
5989689912eSchristos 		} while (1);
5999689912eSchristos 
6009689912eSchristos 		fclose(fp);
6019689912eSchristos 	} else {
6029689912eSchristos 		switch (errno) {
6039689912eSchristos 		case ENOENT:
6049689912eSchristos 			break;
6059689912eSchristos 		default:
6069689912eSchristos 			isc_mem_put(mctx, conf, sizeof(*conf));
6079689912eSchristos 			return ISC_R_INVALIDFILE;
6089689912eSchristos 		}
6099689912eSchristos 	}
6109689912eSchristos 
6119689912eSchristos 	if (ret != ISC_R_SUCCESS) {
6129689912eSchristos 		goto error;
6139689912eSchristos 	}
6149689912eSchristos 
6159689912eSchristos 	/*
6169689912eSchristos 	 * Construct unified search list from domain or configured
6179689912eSchristos 	 * search list
6189689912eSchristos 	 */
6199689912eSchristos 	if (conf->domainname != NULL) {
6209689912eSchristos 		ret = add_search(conf, conf->domainname);
6219689912eSchristos 	} else if (conf->searchnxt > 0) {
6229689912eSchristos 		for (i = 0; i < conf->searchnxt; i++) {
6239689912eSchristos 			ret = add_search(conf, conf->search[i]);
6249689912eSchristos 			if (ret != ISC_R_SUCCESS) {
6259689912eSchristos 				break;
6269689912eSchristos 			}
6279689912eSchristos 		}
6289689912eSchristos 	}
6299689912eSchristos 
6309689912eSchristos 	/* If we don't find a nameserver fall back to localhost */
6319689912eSchristos 	if (conf->numns == 0U) {
6329689912eSchristos 		INSIST(ISC_LIST_EMPTY(conf->nameservers));
6339689912eSchristos 
6349689912eSchristos 		/* XXX: should we catch errors? */
6359689912eSchristos 		(void)add_server(conf->mctx, "::1", &conf->nameservers);
6369689912eSchristos 		(void)add_server(conf->mctx, "127.0.0.1", &conf->nameservers);
6379689912eSchristos 	}
6389689912eSchristos 
6399689912eSchristos error:
6409689912eSchristos 	conf->magic = IRS_RESCONF_MAGIC;
6419689912eSchristos 
6429689912eSchristos 	if (ret != ISC_R_SUCCESS) {
6439689912eSchristos 		irs_resconf_destroy(&conf);
6449689912eSchristos 	} else {
6459689912eSchristos 		if (fp == NULL) {
6469689912eSchristos 			ret = ISC_R_FILENOTFOUND;
6479689912eSchristos 		}
6489689912eSchristos 		*confp = conf;
6499689912eSchristos 	}
6509689912eSchristos 
6519689912eSchristos 	return ret;
6529689912eSchristos }
6539689912eSchristos 
6549689912eSchristos void
6559689912eSchristos irs_resconf_destroy(irs_resconf_t **confp) {
6569689912eSchristos 	irs_resconf_t *conf;
6579689912eSchristos 	isc_sockaddr_t *address;
6589689912eSchristos 	irs_resconf_search_t *searchentry;
6599689912eSchristos 	unsigned int i;
6609689912eSchristos 
6619689912eSchristos 	REQUIRE(confp != NULL);
6629689912eSchristos 	conf = *confp;
6639689912eSchristos 	*confp = NULL;
6649689912eSchristos 	REQUIRE(IRS_RESCONF_VALID(conf));
6659689912eSchristos 
6669689912eSchristos 	while ((searchentry = ISC_LIST_HEAD(conf->searchlist)) != NULL) {
6679689912eSchristos 		ISC_LIST_UNLINK(conf->searchlist, searchentry, link);
6689689912eSchristos 		isc_mem_put(conf->mctx, searchentry, sizeof(*searchentry));
6699689912eSchristos 	}
6709689912eSchristos 
6719689912eSchristos 	while ((address = ISC_LIST_HEAD(conf->nameservers)) != NULL) {
6729689912eSchristos 		ISC_LIST_UNLINK(conf->nameservers, address, link);
6739689912eSchristos 		isc_mem_put(conf->mctx, address, sizeof(*address));
6749689912eSchristos 	}
6759689912eSchristos 
6769689912eSchristos 	if (conf->domainname != NULL) {
6779689912eSchristos 		isc_mem_free(conf->mctx, conf->domainname);
6789689912eSchristos 	}
6799689912eSchristos 
6809689912eSchristos 	for (i = 0; i < RESCONFMAXSEARCH; i++) {
6819689912eSchristos 		if (conf->search[i] != NULL) {
6829689912eSchristos 			isc_mem_free(conf->mctx, conf->search[i]);
6839689912eSchristos 		}
6849689912eSchristos 	}
6859689912eSchristos 
6869689912eSchristos 	isc_mem_put(conf->mctx, conf, sizeof(*conf));
6879689912eSchristos }
6889689912eSchristos 
6899689912eSchristos isc_sockaddrlist_t *
6909689912eSchristos irs_resconf_getnameservers(irs_resconf_t *conf) {
6919689912eSchristos 	REQUIRE(IRS_RESCONF_VALID(conf));
6929689912eSchristos 
6939689912eSchristos 	return &conf->nameservers;
6949689912eSchristos }
6959689912eSchristos 
6969689912eSchristos irs_resconf_searchlist_t *
6979689912eSchristos irs_resconf_getsearchlist(irs_resconf_t *conf) {
6989689912eSchristos 	REQUIRE(IRS_RESCONF_VALID(conf));
6999689912eSchristos 
7009689912eSchristos 	return &conf->searchlist;
7019689912eSchristos }
7029689912eSchristos 
7039689912eSchristos unsigned int
7049689912eSchristos irs_resconf_getndots(irs_resconf_t *conf) {
7059689912eSchristos 	REQUIRE(IRS_RESCONF_VALID(conf));
7069689912eSchristos 
7079689912eSchristos 	return (unsigned int)conf->ndots;
7089689912eSchristos }
7099689912eSchristos 
7109689912eSchristos unsigned int
7119689912eSchristos irs_resconf_getattempts(irs_resconf_t *conf) {
7129689912eSchristos 	REQUIRE(IRS_RESCONF_VALID(conf));
7139689912eSchristos 
7149689912eSchristos 	return (unsigned int)conf->attempts;
7159689912eSchristos }
7169689912eSchristos 
7179689912eSchristos unsigned int
7189689912eSchristos irs_resconf_gettimeout(irs_resconf_t *conf) {
7199689912eSchristos 	REQUIRE(IRS_RESCONF_VALID(conf));
7209689912eSchristos 
7219689912eSchristos 	return (unsigned int)conf->timeout;
7229689912eSchristos }
723