1224090Sdougb/*
2262706Serwin * Copyright (C) 2009, 2011, 2012, 2014  Internet Systems Consortium, Inc. ("ISC")
3224090Sdougb *
4224090Sdougb * Permission to use, copy, modify, and/or distribute this software for any
5224090Sdougb * purpose with or without fee is hereby granted, provided that the above
6224090Sdougb * copyright notice and this permission notice appear in all copies.
7224090Sdougb *
8224090Sdougb * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
9224090Sdougb * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10224090Sdougb * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
11224090Sdougb * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12224090Sdougb * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
13224090Sdougb * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14224090Sdougb * PERFORMANCE OF THIS SOFTWARE.
15224090Sdougb */
16224090Sdougb
17234010Sdougb/* $Id$ */
18224090Sdougb
19224090Sdougb/*! \file resconf.c */
20224090Sdougb
21224090Sdougb/**
22224090Sdougb * Module for parsing resolv.conf files (largely derived from lwconfig.c).
23224090Sdougb *
24224090Sdougb *    irs_resconf_load() opens the file filename and parses it to initialize
25224090Sdougb *    the configuration structure.
26224090Sdougb *
27224090Sdougb * \section lwconfig_return Return Values
28224090Sdougb *
29224090Sdougb *    irs_resconf_load() returns #IRS_R_SUCCESS if it successfully read and
30224090Sdougb *    parsed filename. It returns a non-0 error code if filename could not be
31224090Sdougb *    opened or contained incorrect resolver statements.
32224090Sdougb *
33224090Sdougb * \section lwconfig_see See Also
34224090Sdougb *
35224090Sdougb *    stdio(3), \link resolver resolver \endlink
36224090Sdougb *
37224090Sdougb * \section files Files
38224090Sdougb *
39224090Sdougb *    /etc/resolv.conf
40224090Sdougb */
41224090Sdougb
42224090Sdougb#include <config.h>
43224090Sdougb
44224090Sdougb#include <sys/types.h>
45224090Sdougb#include <sys/socket.h>
46224090Sdougb
47224090Sdougb#include <ctype.h>
48224090Sdougb#include <errno.h>
49224090Sdougb#include <netdb.h>
50224090Sdougb#include <stdlib.h>
51224090Sdougb#include <stdio.h>
52224090Sdougb#include <string.h>
53224090Sdougb
54224090Sdougb#include <isc/magic.h>
55224090Sdougb#include <isc/mem.h>
56224090Sdougb#include <isc/netaddr.h>
57224090Sdougb#include <isc/sockaddr.h>
58224090Sdougb#include <isc/util.h>
59224090Sdougb
60224090Sdougb#include <irs/resconf.h>
61224090Sdougb
62224090Sdougb#define IRS_RESCONF_MAGIC		ISC_MAGIC('R', 'E', 'S', 'c')
63224090Sdougb#define IRS_RESCONF_VALID(c)		ISC_MAGIC_VALID(c, IRS_RESCONF_MAGIC)
64224090Sdougb
65224090Sdougb/*!
66224090Sdougb * protocol constants
67224090Sdougb */
68224090Sdougb
69224090Sdougb#if ! defined(NS_INADDRSZ)
70224090Sdougb#define NS_INADDRSZ	 4
71224090Sdougb#endif
72224090Sdougb
73224090Sdougb#if ! defined(NS_IN6ADDRSZ)
74224090Sdougb#define NS_IN6ADDRSZ	16
75224090Sdougb#endif
76224090Sdougb
77224090Sdougb/*!
78224090Sdougb * resolv.conf parameters
79224090Sdougb */
80224090Sdougb
81224090Sdougb#define RESCONFMAXNAMESERVERS 3		/*%< max 3 "nameserver" entries */
82224090Sdougb#define RESCONFMAXSEARCH 8		/*%< max 8 domains in "search" entry */
83224090Sdougb#define RESCONFMAXLINELEN 256		/*%< max size of a line */
84224090Sdougb#define RESCONFMAXSORTLIST 10		/*%< max 10 */
85224090Sdougb
86224090Sdougb/*!
87224090Sdougb * configuration data structure
88224090Sdougb */
89224090Sdougb
90224090Sdougbstruct irs_resconf {
91224090Sdougb	/*
92224090Sdougb	 * The configuration data is a thread-specific object, and does not
93224090Sdougb	 * need to be locked.
94224090Sdougb	 */
95224090Sdougb	unsigned int		magic;
96224090Sdougb	isc_mem_t		*mctx;
97224090Sdougb
98224090Sdougb	isc_sockaddrlist_t	nameservers;
99224090Sdougb	unsigned int		numns; /*%< number of configured servers */
100224090Sdougb
101224090Sdougb	char	       		*domainname;
102224090Sdougb	char 	       		*search[RESCONFMAXSEARCH];
103224090Sdougb	isc_uint8_t		searchnxt; /*%< index for next free slot */
104224090Sdougb
105224090Sdougb	irs_resconf_searchlist_t searchlist;
106224090Sdougb
107224090Sdougb	struct {
108224090Sdougb		isc_netaddr_t	addr;
109224090Sdougb		/*% mask has a non-zero 'family' if set */
110224090Sdougb		isc_netaddr_t	mask;
111224090Sdougb	} sortlist[RESCONFMAXSORTLIST];
112224090Sdougb	isc_uint8_t		sortlistnxt;
113224090Sdougb
114224090Sdougb	/*%< non-zero if 'options debug' set */
115224090Sdougb	isc_uint8_t		resdebug;
116224090Sdougb	/*%< set to n in 'options ndots:n' */
117224090Sdougb	isc_uint8_t		ndots;
118224090Sdougb};
119224090Sdougb
120224090Sdougbstatic isc_result_t
121224090Sdougbresconf_parsenameserver(irs_resconf_t *conf,  FILE *fp);
122224090Sdougbstatic isc_result_t
123224090Sdougbresconf_parsedomain(irs_resconf_t *conf,  FILE *fp);
124224090Sdougbstatic isc_result_t
125224090Sdougbresconf_parsesearch(irs_resconf_t *conf,  FILE *fp);
126224090Sdougbstatic isc_result_t
127224090Sdougbresconf_parsesortlist(irs_resconf_t *conf,  FILE *fp);
128224090Sdougbstatic isc_result_t
129224090Sdougbresconf_parseoption(irs_resconf_t *ctx,  FILE *fp);
130224090Sdougb
131224090Sdougb/*!
132224090Sdougb * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
133224090Sdougb */
134224090Sdougbstatic int
135224090Sdougbeatline(FILE *fp) {
136224090Sdougb	int ch;
137224090Sdougb
138224090Sdougb	ch = fgetc(fp);
139224090Sdougb	while (ch != '\n' && ch != EOF)
140224090Sdougb		ch = fgetc(fp);
141224090Sdougb
142224090Sdougb	return (ch);
143224090Sdougb}
144224090Sdougb
145224090Sdougb/*!
146224090Sdougb * Eats white space up to next newline or non-whitespace character (of
147224090Sdougb * EOF). Returns the last character read. Comments are considered white
148224090Sdougb * space.
149224090Sdougb */
150224090Sdougbstatic int
151224090Sdougbeatwhite(FILE *fp) {
152224090Sdougb	int ch;
153224090Sdougb
154224090Sdougb	ch = fgetc(fp);
155224090Sdougb	while (ch != '\n' && ch != EOF && isspace((unsigned char)ch))
156224090Sdougb		ch = fgetc(fp);
157224090Sdougb
158224090Sdougb	if (ch == ';' || ch == '#')
159224090Sdougb		ch = eatline(fp);
160224090Sdougb
161224090Sdougb	return (ch);
162224090Sdougb}
163224090Sdougb
164224090Sdougb/*!
165224090Sdougb * Skip over any leading whitespace and then read in the next sequence of
166224090Sdougb * non-whitespace characters. In this context newline is not considered
167224090Sdougb * whitespace. Returns EOF on end-of-file, or the character
168224090Sdougb * that caused the reading to stop.
169224090Sdougb */
170224090Sdougbstatic int
171224090Sdougbgetword(FILE *fp, char *buffer, size_t size) {
172224090Sdougb	int ch;
173224090Sdougb	char *p = buffer;
174224090Sdougb
175224090Sdougb	REQUIRE(buffer != NULL);
176224090Sdougb	REQUIRE(size > 0U);
177224090Sdougb
178224090Sdougb	*p = '\0';
179224090Sdougb
180224090Sdougb	ch = eatwhite(fp);
181224090Sdougb
182224090Sdougb	if (ch == EOF)
183224090Sdougb		return (EOF);
184224090Sdougb
185224090Sdougb	do {
186224090Sdougb		*p = '\0';
187224090Sdougb
188224090Sdougb		if (ch == EOF || isspace((unsigned char)ch))
189224090Sdougb			break;
190224090Sdougb		else if ((size_t) (p - buffer) == size - 1)
191224090Sdougb			return (EOF);	/* Not enough space. */
192224090Sdougb
193224090Sdougb		*p++ = (char)ch;
194224090Sdougb		ch = fgetc(fp);
195224090Sdougb	} while (1);
196224090Sdougb
197224090Sdougb	return (ch);
198224090Sdougb}
199224090Sdougb
200224090Sdougbstatic isc_result_t
201224090Sdougbadd_server(isc_mem_t *mctx, const char *address_str,
202224090Sdougb	   isc_sockaddrlist_t *nameservers)
203224090Sdougb{
204224090Sdougb	int error;
205224090Sdougb	isc_sockaddr_t *address = NULL;
206224090Sdougb	struct addrinfo hints, *res;
207224090Sdougb	isc_result_t result = ISC_R_SUCCESS;
208224090Sdougb
209224090Sdougb	res = NULL;
210224090Sdougb	memset(&hints, 0, sizeof(hints));
211224090Sdougb	hints.ai_family = AF_UNSPEC;
212224090Sdougb	hints.ai_socktype = SOCK_DGRAM;
213224090Sdougb	hints.ai_protocol = IPPROTO_UDP;
214224090Sdougb	hints.ai_flags = AI_NUMERICHOST;
215224090Sdougb	error = getaddrinfo(address_str, "53", &hints, &res);
216224090Sdougb	if (error != 0)
217224090Sdougb		return (ISC_R_BADADDRESSFORM);
218224090Sdougb
219224090Sdougb	/* XXX: special case: treat all-0 IPv4 address as loopback */
220224090Sdougb	if (res->ai_family == AF_INET) {
221224090Sdougb		struct in_addr *v4;
222224090Sdougb		unsigned char zeroaddress[] = {0, 0, 0, 0};
223224090Sdougb		unsigned char loopaddress[] = {127, 0, 0, 1};
224224090Sdougb
225224090Sdougb		v4 = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
226224090Sdougb		if (memcmp(v4, zeroaddress, 4) == 0)
227262706Serwin			memmove(v4, loopaddress, 4);
228224090Sdougb	}
229224090Sdougb
230224090Sdougb	address = isc_mem_get(mctx, sizeof(*address));
231224090Sdougb	if (address == NULL) {
232224090Sdougb		result = ISC_R_NOMEMORY;
233224090Sdougb		goto cleanup;
234224090Sdougb	}
235224090Sdougb	if (res->ai_addrlen > sizeof(address->type)) {
236224090Sdougb		isc_mem_put(mctx, address, sizeof(*address));
237224090Sdougb		result = ISC_R_RANGE;
238224090Sdougb		goto cleanup;
239224090Sdougb	}
240224090Sdougb	address->length = res->ai_addrlen;
241262706Serwin	memmove(&address->type.ss, res->ai_addr, res->ai_addrlen);
242224090Sdougb	ISC_LINK_INIT(address, link);
243224090Sdougb	ISC_LIST_APPEND(*nameservers, address, link);
244224090Sdougb
245224090Sdougb  cleanup:
246224090Sdougb	freeaddrinfo(res);
247224090Sdougb
248224090Sdougb	return (result);
249224090Sdougb}
250224090Sdougb
251224090Sdougbstatic isc_result_t
252224090Sdougbcreate_addr(const char *buffer, isc_netaddr_t *addr, int convert_zero) {
253224090Sdougb	struct in_addr v4;
254224090Sdougb	struct in6_addr v6;
255224090Sdougb
256224090Sdougb	if (inet_aton(buffer, &v4) == 1) {
257224090Sdougb		if (convert_zero) {
258224090Sdougb			unsigned char zeroaddress[] = {0, 0, 0, 0};
259224090Sdougb			unsigned char loopaddress[] = {127, 0, 0, 1};
260224090Sdougb			if (memcmp(&v4, zeroaddress, 4) == 0)
261262706Serwin				memmove(&v4, loopaddress, 4);
262224090Sdougb		}
263224090Sdougb		addr->family = AF_INET;
264262706Serwin		memmove(&addr->type.in, &v4, NS_INADDRSZ);
265224090Sdougb		addr->zone = 0;
266224090Sdougb	} else if (inet_pton(AF_INET6, buffer, &v6) == 1) {
267224090Sdougb		addr->family = AF_INET6;
268262706Serwin		memmove(&addr->type.in6, &v6, NS_IN6ADDRSZ);
269224090Sdougb		addr->zone = 0;
270224090Sdougb	} else
271224090Sdougb		return (ISC_R_BADADDRESSFORM); /* Unrecognised format. */
272224090Sdougb
273224090Sdougb	return (ISC_R_SUCCESS);
274224090Sdougb}
275224090Sdougb
276224090Sdougbstatic isc_result_t
277224090Sdougbresconf_parsenameserver(irs_resconf_t *conf,  FILE *fp) {
278224090Sdougb	char word[RESCONFMAXLINELEN];
279224090Sdougb	int cp;
280224090Sdougb	isc_result_t result;
281224090Sdougb
282224090Sdougb	if (conf->numns == RESCONFMAXNAMESERVERS)
283224090Sdougb		return (ISC_R_SUCCESS);
284224090Sdougb
285224090Sdougb	cp = getword(fp, word, sizeof(word));
286224090Sdougb	if (strlen(word) == 0U)
287224090Sdougb		return (ISC_R_UNEXPECTEDEND); /* Nothing on line. */
288224090Sdougb	else if (cp == ' ' || cp == '\t')
289224090Sdougb		cp = eatwhite(fp);
290224090Sdougb
291224090Sdougb	if (cp != EOF && cp != '\n')
292224090Sdougb		return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
293224090Sdougb
294224090Sdougb	result = add_server(conf->mctx, word, &conf->nameservers);
295224090Sdougb	if (result != ISC_R_SUCCESS)
296224090Sdougb		return (result);
297224090Sdougb	conf->numns++;
298224090Sdougb
299224090Sdougb	return (ISC_R_SUCCESS);
300224090Sdougb}
301224090Sdougb
302224090Sdougbstatic isc_result_t
303224090Sdougbresconf_parsedomain(irs_resconf_t *conf,  FILE *fp) {
304224090Sdougb	char word[RESCONFMAXLINELEN];
305224090Sdougb	int res, i;
306224090Sdougb
307224090Sdougb	res = getword(fp, word, sizeof(word));
308224090Sdougb	if (strlen(word) == 0U)
309224090Sdougb		return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */
310224090Sdougb	else if (res == ' ' || res == '\t')
311224090Sdougb		res = eatwhite(fp);
312224090Sdougb
313224090Sdougb	if (res != EOF && res != '\n')
314224090Sdougb		return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
315224090Sdougb
316224090Sdougb	if (conf->domainname != NULL)
317224090Sdougb		isc_mem_free(conf->mctx, conf->domainname);
318224090Sdougb
319224090Sdougb	/*
320224090Sdougb	 * Search and domain are mutually exclusive.
321224090Sdougb	 */
322224090Sdougb	for (i = 0; i < RESCONFMAXSEARCH; i++) {
323224090Sdougb		if (conf->search[i] != NULL) {
324224090Sdougb			isc_mem_free(conf->mctx, conf->search[i]);
325224090Sdougb			conf->search[i] = NULL;
326224090Sdougb		}
327224090Sdougb	}
328224090Sdougb	conf->searchnxt = 0;
329224090Sdougb
330224090Sdougb	conf->domainname = isc_mem_strdup(conf->mctx, word);
331224090Sdougb	if (conf->domainname == NULL)
332224090Sdougb		return (ISC_R_NOMEMORY);
333224090Sdougb
334224090Sdougb	return (ISC_R_SUCCESS);
335224090Sdougb}
336224090Sdougb
337224090Sdougbstatic isc_result_t
338224090Sdougbresconf_parsesearch(irs_resconf_t *conf,  FILE *fp) {
339224090Sdougb	int idx, delim;
340224090Sdougb	char word[RESCONFMAXLINELEN];
341224090Sdougb
342224090Sdougb	if (conf->domainname != NULL) {
343224090Sdougb		/*
344224090Sdougb		 * Search and domain are mutually exclusive.
345224090Sdougb		 */
346224090Sdougb		isc_mem_free(conf->mctx, conf->domainname);
347224090Sdougb		conf->domainname = NULL;
348224090Sdougb	}
349224090Sdougb
350224090Sdougb	/*
351224090Sdougb	 * Remove any previous search definitions.
352224090Sdougb	 */
353224090Sdougb	for (idx = 0; idx < RESCONFMAXSEARCH; idx++) {
354224090Sdougb		if (conf->search[idx] != NULL) {
355224090Sdougb			isc_mem_free(conf->mctx, conf->search[idx]);
356224090Sdougb			conf->search[idx] = NULL;
357224090Sdougb		}
358224090Sdougb	}
359224090Sdougb	conf->searchnxt = 0;
360224090Sdougb
361224090Sdougb	delim = getword(fp, word, sizeof(word));
362224090Sdougb	if (strlen(word) == 0U)
363224090Sdougb		return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */
364224090Sdougb
365224090Sdougb	idx = 0;
366224090Sdougb	while (strlen(word) > 0U) {
367224090Sdougb		if (conf->searchnxt == RESCONFMAXSEARCH)
368224090Sdougb			goto ignore; /* Too many domains. */
369224090Sdougb
370224090Sdougb		conf->search[idx] = isc_mem_strdup(conf->mctx, word);
371224090Sdougb		if (conf->search[idx] == NULL)
372224090Sdougb			return (ISC_R_NOMEMORY);
373224090Sdougb		idx++;
374224090Sdougb		conf->searchnxt++;
375224090Sdougb
376224090Sdougb	ignore:
377224090Sdougb		if (delim == EOF || delim == '\n')
378224090Sdougb			break;
379224090Sdougb		else
380224090Sdougb			delim = getword(fp, word, sizeof(word));
381224090Sdougb	}
382224090Sdougb
383224090Sdougb	return (ISC_R_SUCCESS);
384224090Sdougb}
385224090Sdougb
386224090Sdougbstatic isc_result_t
387224090Sdougbresconf_parsesortlist(irs_resconf_t *conf,  FILE *fp) {
388224090Sdougb	int delim, res, idx;
389224090Sdougb	char word[RESCONFMAXLINELEN];
390224090Sdougb	char *p;
391224090Sdougb
392224090Sdougb	delim = getword(fp, word, sizeof(word));
393224090Sdougb	if (strlen(word) == 0U)
394224090Sdougb		return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
395224090Sdougb
396224090Sdougb	while (strlen(word) > 0U) {
397224090Sdougb		if (conf->sortlistnxt == RESCONFMAXSORTLIST)
398224090Sdougb			return (ISC_R_QUOTA); /* Too many values. */
399224090Sdougb
400224090Sdougb		p = strchr(word, '/');
401224090Sdougb		if (p != NULL)
402224090Sdougb			*p++ = '\0';
403224090Sdougb
404224090Sdougb		idx = conf->sortlistnxt;
405224090Sdougb		res = create_addr(word, &conf->sortlist[idx].addr, 1);
406224090Sdougb		if (res != ISC_R_SUCCESS)
407224090Sdougb			return (res);
408224090Sdougb
409224090Sdougb		if (p != NULL) {
410224090Sdougb			res = create_addr(p, &conf->sortlist[idx].mask, 0);
411224090Sdougb			if (res != ISC_R_SUCCESS)
412224090Sdougb				return (res);
413224090Sdougb		} else {
414224090Sdougb			/*
415224090Sdougb			 * Make up a mask. (XXX: is this correct?)
416224090Sdougb			 */
417224090Sdougb			conf->sortlist[idx].mask = conf->sortlist[idx].addr;
418224090Sdougb			memset(&conf->sortlist[idx].mask.type, 0xff,
419224090Sdougb			       sizeof(conf->sortlist[idx].mask.type));
420224090Sdougb		}
421224090Sdougb
422224090Sdougb		conf->sortlistnxt++;
423224090Sdougb
424224090Sdougb		if (delim == EOF || delim == '\n')
425224090Sdougb			break;
426224090Sdougb		else
427224090Sdougb			delim = getword(fp, word, sizeof(word));
428224090Sdougb	}
429224090Sdougb
430224090Sdougb	return (ISC_R_SUCCESS);
431224090Sdougb}
432224090Sdougb
433224090Sdougbstatic isc_result_t
434224090Sdougbresconf_parseoption(irs_resconf_t *conf,  FILE *fp) {
435224090Sdougb	int delim;
436224090Sdougb	long ndots;
437224090Sdougb	char *p;
438224090Sdougb	char word[RESCONFMAXLINELEN];
439224090Sdougb
440224090Sdougb	delim = getword(fp, word, sizeof(word));
441224090Sdougb	if (strlen(word) == 0U)
442224090Sdougb		return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
443224090Sdougb
444224090Sdougb	while (strlen(word) > 0U) {
445224090Sdougb		if (strcmp("debug", word) == 0) {
446224090Sdougb			conf->resdebug = 1;
447224090Sdougb		} else if (strncmp("ndots:", word, 6) == 0) {
448224090Sdougb			ndots = strtol(word + 6, &p, 10);
449224090Sdougb			if (*p != '\0') /* Bad string. */
450224090Sdougb				return (ISC_R_UNEXPECTEDTOKEN);
451224090Sdougb			if (ndots < 0 || ndots > 0xff) /* Out of range. */
452224090Sdougb				return (ISC_R_RANGE);
453224090Sdougb			conf->ndots = (isc_uint8_t)ndots;
454224090Sdougb		}
455224090Sdougb
456224090Sdougb		if (delim == EOF || delim == '\n')
457224090Sdougb			break;
458224090Sdougb		else
459224090Sdougb			delim = getword(fp, word, sizeof(word));
460224090Sdougb	}
461224090Sdougb
462224090Sdougb	return (ISC_R_SUCCESS);
463224090Sdougb}
464224090Sdougb
465224090Sdougbstatic isc_result_t
466224090Sdougbadd_search(irs_resconf_t *conf, char *domain) {
467224090Sdougb	irs_resconf_search_t *entry;
468224090Sdougb
469224090Sdougb	entry = isc_mem_get(conf->mctx, sizeof(*entry));
470224090Sdougb	if (entry == NULL)
471224090Sdougb		return (ISC_R_NOMEMORY);
472224090Sdougb
473224090Sdougb	entry->domain = domain;
474224090Sdougb	ISC_LINK_INIT(entry, link);
475224090Sdougb	ISC_LIST_APPEND(conf->searchlist, entry, link);
476224090Sdougb
477224090Sdougb	return (ISC_R_SUCCESS);
478224090Sdougb}
479224090Sdougb
480224090Sdougb/*% parses a file and fills in the data structure. */
481224090Sdougbisc_result_t
482224090Sdougbirs_resconf_load(isc_mem_t *mctx, const char *filename, irs_resconf_t **confp)
483224090Sdougb{
484224090Sdougb	FILE *fp = NULL;
485224090Sdougb	char word[256];
486262706Serwin	isc_result_t rval, ret = ISC_R_SUCCESS;
487224090Sdougb	irs_resconf_t *conf;
488224090Sdougb	int i, stopchar;
489224090Sdougb
490224090Sdougb	REQUIRE(mctx != NULL);
491224090Sdougb	REQUIRE(filename != NULL);
492224090Sdougb	REQUIRE(strlen(filename) > 0U);
493224090Sdougb	REQUIRE(confp != NULL && *confp == NULL);
494224090Sdougb
495224090Sdougb	conf = isc_mem_get(mctx, sizeof(*conf));
496224090Sdougb	if (conf == NULL)
497224090Sdougb		return (ISC_R_NOMEMORY);
498224090Sdougb
499224090Sdougb	conf->mctx = mctx;
500224090Sdougb	ISC_LIST_INIT(conf->nameservers);
501224090Sdougb	conf->numns = 0;
502224090Sdougb	conf->domainname = NULL;
503224090Sdougb	conf->searchnxt = 0;
504224090Sdougb	conf->resdebug = 0;
505224090Sdougb	conf->ndots = 1;
506224090Sdougb	for (i = 0; i < RESCONFMAXSEARCH; i++)
507224090Sdougb		conf->search[i] = NULL;
508224090Sdougb
509224090Sdougb	errno = 0;
510262706Serwin	if ((fp = fopen(filename, "r")) != NULL) {
511262706Serwin		do {
512262706Serwin			stopchar = getword(fp, word, sizeof(word));
513224090Sdougb			if (stopchar == EOF) {
514262706Serwin				rval = ISC_R_SUCCESS;
515262706Serwin				POST(rval);
516224090Sdougb				break;
517224090Sdougb			}
518262706Serwin
519262706Serwin			if (strlen(word) == 0U)
520262706Serwin				rval = ISC_R_SUCCESS;
521262706Serwin			else if (strcmp(word, "nameserver") == 0)
522262706Serwin				rval = resconf_parsenameserver(conf, fp);
523262706Serwin			else if (strcmp(word, "domain") == 0)
524262706Serwin				rval = resconf_parsedomain(conf, fp);
525262706Serwin			else if (strcmp(word, "search") == 0)
526262706Serwin				rval = resconf_parsesearch(conf, fp);
527262706Serwin			else if (strcmp(word, "sortlist") == 0)
528262706Serwin				rval = resconf_parsesortlist(conf, fp);
529262706Serwin			else if (strcmp(word, "options") == 0)
530262706Serwin				rval = resconf_parseoption(conf, fp);
531262706Serwin			else {
532262706Serwin				/* unrecognised word. Ignore entire line */
533262706Serwin				rval = ISC_R_SUCCESS;
534262706Serwin				stopchar = eatline(fp);
535262706Serwin				if (stopchar == EOF) {
536262706Serwin					break;
537262706Serwin				}
538262706Serwin			}
539262706Serwin			if (ret == ISC_R_SUCCESS && rval != ISC_R_SUCCESS)
540262706Serwin				ret = rval;
541262706Serwin		} while (1);
542262706Serwin
543262706Serwin		fclose(fp);
544262706Serwin	} else {
545262706Serwin		switch (errno) {
546262706Serwin		case ENOENT:
547262706Serwin			break;
548262706Serwin		default:
549262706Serwin			isc_mem_put(mctx, conf, sizeof(*conf));
550262706Serwin			return (ISC_R_INVALIDFILE);
551224090Sdougb		}
552262706Serwin	}
553224090Sdougb
554224090Sdougb	/* If we don't find a nameserver fall back to localhost */
555224090Sdougb	if (conf->numns == 0) {
556224090Sdougb		INSIST(ISC_LIST_EMPTY(conf->nameservers));
557224090Sdougb
558224090Sdougb		/* XXX: should we catch errors? */
559224090Sdougb		(void)add_server(conf->mctx, "127.0.0.1", &conf->nameservers);
560224090Sdougb		(void)add_server(conf->mctx, "::1", &conf->nameservers);
561224090Sdougb	}
562224090Sdougb
563224090Sdougb	/*
564224090Sdougb	 * Construct unified search list from domain or configured
565224090Sdougb	 * search list
566224090Sdougb	 */
567224090Sdougb	ISC_LIST_INIT(conf->searchlist);
568224090Sdougb	if (conf->domainname != NULL) {
569224090Sdougb		ret = add_search(conf, conf->domainname);
570224090Sdougb	} else if (conf->searchnxt > 0) {
571224090Sdougb		for (i = 0; i < conf->searchnxt; i++) {
572224090Sdougb			ret = add_search(conf, conf->search[i]);
573224090Sdougb			if (ret != ISC_R_SUCCESS)
574224090Sdougb				break;
575224090Sdougb		}
576224090Sdougb	}
577224090Sdougb
578224090Sdougb	conf->magic = IRS_RESCONF_MAGIC;
579224090Sdougb
580224090Sdougb	if (ret != ISC_R_SUCCESS)
581224090Sdougb		irs_resconf_destroy(&conf);
582262706Serwin	else {
583262706Serwin		if (fp == NULL)
584262706Serwin			ret = ISC_R_FILENOTFOUND;
585224090Sdougb		*confp = conf;
586262706Serwin	}
587224090Sdougb
588224090Sdougb	return (ret);
589224090Sdougb}
590224090Sdougb
591224090Sdougbvoid
592224090Sdougbirs_resconf_destroy(irs_resconf_t **confp) {
593224090Sdougb	irs_resconf_t *conf;
594224090Sdougb	isc_sockaddr_t *address;
595224090Sdougb	irs_resconf_search_t *searchentry;
596224090Sdougb	int i;
597224090Sdougb
598224090Sdougb	REQUIRE(confp != NULL);
599224090Sdougb	conf = *confp;
600224090Sdougb	REQUIRE(IRS_RESCONF_VALID(conf));
601224090Sdougb
602224090Sdougb	while ((searchentry = ISC_LIST_HEAD(conf->searchlist)) != NULL) {
603224090Sdougb		ISC_LIST_UNLINK(conf->searchlist, searchentry, link);
604224090Sdougb		isc_mem_put(conf->mctx, searchentry, sizeof(*searchentry));
605224090Sdougb	}
606224090Sdougb
607224090Sdougb	while ((address = ISC_LIST_HEAD(conf->nameservers)) != NULL) {
608224090Sdougb		ISC_LIST_UNLINK(conf->nameservers, address, link);
609224090Sdougb		isc_mem_put(conf->mctx, address, sizeof(*address));
610224090Sdougb	}
611224090Sdougb
612224090Sdougb	if (conf->domainname != NULL)
613224090Sdougb		isc_mem_free(conf->mctx, conf->domainname);
614224090Sdougb
615224090Sdougb	for (i = 0; i < RESCONFMAXSEARCH; i++) {
616224090Sdougb		if (conf->search[i] != NULL)
617224090Sdougb			isc_mem_free(conf->mctx, conf->search[i]);
618224090Sdougb	}
619224090Sdougb
620224090Sdougb	isc_mem_put(conf->mctx, conf, sizeof(*conf));
621224090Sdougb
622224090Sdougb	*confp = NULL;
623224090Sdougb}
624224090Sdougb
625224090Sdougbisc_sockaddrlist_t *
626224090Sdougbirs_resconf_getnameservers(irs_resconf_t *conf) {
627224090Sdougb	REQUIRE(IRS_RESCONF_VALID(conf));
628224090Sdougb
629224090Sdougb	return (&conf->nameservers);
630224090Sdougb}
631224090Sdougb
632224090Sdougbirs_resconf_searchlist_t *
633224090Sdougbirs_resconf_getsearchlist(irs_resconf_t *conf) {
634224090Sdougb	REQUIRE(IRS_RESCONF_VALID(conf));
635224090Sdougb
636224090Sdougb	return (&conf->searchlist);
637224090Sdougb}
638224090Sdougb
639224090Sdougbunsigned int
640224090Sdougbirs_resconf_getndots(irs_resconf_t *conf) {
641224090Sdougb	REQUIRE(IRS_RESCONF_VALID(conf));
642224090Sdougb
643224090Sdougb	return ((unsigned int)conf->ndots);
644224090Sdougb}
645