1/*
2 * Copyright (C) 2004-2008, 2011, 2012  Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (C) 2000-2003  Internet Software Consortium.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 * PERFORMANCE OF THIS SOFTWARE.
16 */
17
18/* $Id$ */
19
20/*! \file */
21
22/**
23 * Module for parsing resolv.conf files.
24 *
25 *    lwres_conf_init() creates an empty lwres_conf_t structure for
26 *    lightweight resolver context ctx.
27 *
28 *    lwres_conf_clear() frees up all the internal memory used by that
29 *    lwres_conf_t structure in resolver context ctx.
30 *
31 *    lwres_conf_parse() opens the file filename and parses it to initialise
32 *    the resolver context ctx's lwres_conf_t structure.
33 *
34 *    lwres_conf_print() prints the lwres_conf_t structure for resolver
35 *    context ctx to the FILE fp.
36 *
37 * \section lwconfig_return Return Values
38 *
39 *    lwres_conf_parse() returns #LWRES_R_SUCCESS if it successfully read and
40 *    parsed filename. It returns #LWRES_R_FAILURE if filename could not be
41 *    opened or contained incorrect resolver statements.
42 *
43 *    lwres_conf_print() returns #LWRES_R_SUCCESS unless an error occurred
44 *    when converting the network addresses to a numeric host address
45 *    string. If this happens, the function returns #LWRES_R_FAILURE.
46 *
47 * \section lwconfig_see See Also
48 *
49 *    stdio(3), \link resolver resolver \endlink
50 *
51 * \section files Files
52 *
53 *    /etc/resolv.conf
54 */
55
56#include <config.h>
57
58#include <assert.h>
59#include <ctype.h>
60#include <errno.h>
61#include <stdlib.h>
62#include <stdio.h>
63#include <string.h>
64#include <unistd.h>
65
66#include <lwres/lwbuffer.h>
67#include <lwres/lwres.h>
68#include <lwres/net.h>
69#include <lwres/result.h>
70
71#include "assert_p.h"
72#include "context_p.h"
73
74
75#if ! defined(NS_INADDRSZ)
76#define NS_INADDRSZ	 4
77#endif
78
79#if ! defined(NS_IN6ADDRSZ)
80#define NS_IN6ADDRSZ	16
81#endif
82
83static lwres_result_t
84lwres_conf_parsenameserver(lwres_context_t *ctx,  FILE *fp);
85
86static lwres_result_t
87lwres_conf_parselwserver(lwres_context_t *ctx,  FILE *fp);
88
89static lwres_result_t
90lwres_conf_parsedomain(lwres_context_t *ctx, FILE *fp);
91
92static lwres_result_t
93lwres_conf_parsesearch(lwres_context_t *ctx,  FILE *fp);
94
95static lwres_result_t
96lwres_conf_parsesortlist(lwres_context_t *ctx,  FILE *fp);
97
98static lwres_result_t
99lwres_conf_parseoption(lwres_context_t *ctx,  FILE *fp);
100
101static void
102lwres_resetaddr(lwres_addr_t *addr);
103
104static lwres_result_t
105lwres_create_addr(const char *buff, lwres_addr_t *addr, int convert_zero);
106
107static int lwresaddr2af(int lwresaddrtype);
108
109
110static int
111lwresaddr2af(int lwresaddrtype)
112{
113	int af = 0;
114
115	switch (lwresaddrtype) {
116	case LWRES_ADDRTYPE_V4:
117		af = AF_INET;
118		break;
119
120	case LWRES_ADDRTYPE_V6:
121		af = AF_INET6;
122		break;
123	}
124
125	return (af);
126}
127
128
129/*!
130 * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
131 */
132static int
133eatline(FILE *fp) {
134	int ch;
135
136	ch = fgetc(fp);
137	while (ch != '\n' && ch != EOF)
138		ch = fgetc(fp);
139
140	return (ch);
141}
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/*!
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 void
201lwres_resetaddr(lwres_addr_t *addr) {
202	REQUIRE(addr != NULL);
203
204	memset(addr->address, 0, LWRES_ADDR_MAXLEN);
205	addr->family = 0;
206	addr->length = 0;
207}
208
209static char *
210lwres_strdup(lwres_context_t *ctx, const char *str) {
211	char *p;
212
213	REQUIRE(str != NULL);
214	REQUIRE(strlen(str) > 0U);
215
216	p = CTXMALLOC(strlen(str) + 1);
217	if (p != NULL)
218		strcpy(p, str);
219
220	return (p);
221}
222
223/*% intializes data structure for subsequent config parsing. */
224void
225lwres_conf_init(lwres_context_t *ctx) {
226	int i;
227	lwres_conf_t *confdata;
228
229	REQUIRE(ctx != NULL);
230	confdata = &ctx->confdata;
231
232	confdata->nsnext = 0;
233	confdata->lwnext = 0;
234	confdata->domainname = NULL;
235	confdata->searchnxt = 0;
236	confdata->sortlistnxt = 0;
237	confdata->resdebug = 0;
238	confdata->ndots = 1;
239	confdata->no_tld_query = 0;
240
241	for (i = 0; i < LWRES_CONFMAXNAMESERVERS; i++)
242		lwres_resetaddr(&confdata->nameservers[i]);
243
244	for (i = 0; i < LWRES_CONFMAXSEARCH; i++)
245		confdata->search[i] = NULL;
246
247	for (i = 0; i < LWRES_CONFMAXSORTLIST; i++) {
248		lwres_resetaddr(&confdata->sortlist[i].addr);
249		lwres_resetaddr(&confdata->sortlist[i].mask);
250	}
251}
252
253/*% Frees up all the internal memory used by the config data structure, returning it to the lwres_context_t. */
254void
255lwres_conf_clear(lwres_context_t *ctx) {
256	int i;
257	lwres_conf_t *confdata;
258
259	REQUIRE(ctx != NULL);
260	confdata = &ctx->confdata;
261
262	for (i = 0; i < confdata->nsnext; i++)
263		lwres_resetaddr(&confdata->nameservers[i]);
264
265	if (confdata->domainname != NULL) {
266		CTXFREE(confdata->domainname,
267			strlen(confdata->domainname) + 1);
268		confdata->domainname = NULL;
269	}
270
271	for (i = 0; i < confdata->searchnxt; i++) {
272		if (confdata->search[i] != NULL) {
273			CTXFREE(confdata->search[i],
274				strlen(confdata->search[i]) + 1);
275			confdata->search[i] = NULL;
276		}
277	}
278
279	for (i = 0; i < LWRES_CONFMAXSORTLIST; i++) {
280		lwres_resetaddr(&confdata->sortlist[i].addr);
281		lwres_resetaddr(&confdata->sortlist[i].mask);
282	}
283
284	confdata->nsnext = 0;
285	confdata->lwnext = 0;
286	confdata->domainname = NULL;
287	confdata->searchnxt = 0;
288	confdata->sortlistnxt = 0;
289	confdata->resdebug = 0;
290	confdata->ndots = 1;
291	confdata->no_tld_query = 0;
292}
293
294static lwres_result_t
295lwres_conf_parsenameserver(lwres_context_t *ctx,  FILE *fp) {
296	char word[LWRES_CONFMAXLINELEN];
297	int res;
298	lwres_conf_t *confdata;
299	lwres_addr_t address;
300
301	confdata = &ctx->confdata;
302
303	if (confdata->nsnext == LWRES_CONFMAXNAMESERVERS)
304		return (LWRES_R_SUCCESS);
305
306	res = getword(fp, word, sizeof(word));
307	if (strlen(word) == 0U)
308		return (LWRES_R_FAILURE); /* Nothing on line. */
309	else if (res == ' ' || res == '\t')
310		res = eatwhite(fp);
311
312	if (res != EOF && res != '\n')
313		return (LWRES_R_FAILURE); /* Extra junk on line. */
314
315	res = lwres_create_addr(word, &address, 1);
316	if (res == LWRES_R_SUCCESS &&
317	    ((address.family == LWRES_ADDRTYPE_V4 && ctx->use_ipv4 == 1) ||
318	     (address.family == LWRES_ADDRTYPE_V6 && ctx->use_ipv6 == 1))) {
319		confdata->nameservers[confdata->nsnext++] = address;
320	}
321
322	return (LWRES_R_SUCCESS);
323}
324
325static lwres_result_t
326lwres_conf_parselwserver(lwres_context_t *ctx,  FILE *fp) {
327	char word[LWRES_CONFMAXLINELEN];
328	int res;
329	lwres_conf_t *confdata;
330
331	confdata = &ctx->confdata;
332
333	if (confdata->lwnext == LWRES_CONFMAXLWSERVERS)
334		return (LWRES_R_SUCCESS);
335
336	res = getword(fp, word, sizeof(word));
337	if (strlen(word) == 0U)
338		return (LWRES_R_FAILURE); /* Nothing on line. */
339	else if (res == ' ' || res == '\t')
340		res = eatwhite(fp);
341
342	if (res != EOF && res != '\n')
343		return (LWRES_R_FAILURE); /* Extra junk on line. */
344
345	res = lwres_create_addr(word,
346				&confdata->lwservers[confdata->lwnext++], 1);
347	if (res != LWRES_R_SUCCESS)
348		return (res);
349
350	return (LWRES_R_SUCCESS);
351}
352
353static lwres_result_t
354lwres_conf_parsedomain(lwres_context_t *ctx,  FILE *fp) {
355	char word[LWRES_CONFMAXLINELEN];
356	int res, i;
357	lwres_conf_t *confdata;
358
359	confdata = &ctx->confdata;
360
361	res = getword(fp, word, sizeof(word));
362	if (strlen(word) == 0U)
363		return (LWRES_R_FAILURE); /* Nothing else on line. */
364	else if (res == ' ' || res == '\t')
365		res = eatwhite(fp);
366
367	if (res != EOF && res != '\n')
368		return (LWRES_R_FAILURE); /* Extra junk on line. */
369
370	if (confdata->domainname != NULL)
371		CTXFREE(confdata->domainname,
372			strlen(confdata->domainname) + 1); /*  */
373
374	/*
375	 * Search and domain are mutually exclusive.
376	 */
377	for (i = 0; i < LWRES_CONFMAXSEARCH; i++) {
378		if (confdata->search[i] != NULL) {
379			CTXFREE(confdata->search[i],
380				strlen(confdata->search[i])+1);
381			confdata->search[i] = NULL;
382		}
383	}
384	confdata->searchnxt = 0;
385
386	confdata->domainname = lwres_strdup(ctx, word);
387
388	if (confdata->domainname == NULL)
389		return (LWRES_R_FAILURE);
390
391	return (LWRES_R_SUCCESS);
392}
393
394static lwres_result_t
395lwres_conf_parsesearch(lwres_context_t *ctx,  FILE *fp) {
396	int idx, delim;
397	char word[LWRES_CONFMAXLINELEN];
398	lwres_conf_t *confdata;
399
400	confdata = &ctx->confdata;
401
402	if (confdata->domainname != NULL) {
403		/*
404		 * Search and domain are mutually exclusive.
405		 */
406		CTXFREE(confdata->domainname,
407			strlen(confdata->domainname) + 1);
408		confdata->domainname = NULL;
409	}
410
411	/*
412	 * Remove any previous search definitions.
413	 */
414	for (idx = 0; idx < LWRES_CONFMAXSEARCH; idx++) {
415		if (confdata->search[idx] != NULL) {
416			CTXFREE(confdata->search[idx],
417				strlen(confdata->search[idx])+1);
418			confdata->search[idx] = NULL;
419		}
420	}
421	confdata->searchnxt = 0;
422
423	delim = getword(fp, word, sizeof(word));
424	if (strlen(word) == 0U)
425		return (LWRES_R_FAILURE); /* Nothing else on line. */
426
427	idx = 0;
428	while (strlen(word) > 0U) {
429		if (confdata->searchnxt == LWRES_CONFMAXSEARCH)
430			goto ignore; /* Too many domains. */
431
432		confdata->search[idx] = lwres_strdup(ctx, word);
433		if (confdata->search[idx] == NULL)
434			return (LWRES_R_FAILURE);
435		idx++;
436		confdata->searchnxt++;
437
438	ignore:
439		if (delim == EOF || delim == '\n')
440			break;
441		else
442			delim = getword(fp, word, sizeof(word));
443	}
444
445	return (LWRES_R_SUCCESS);
446}
447
448static lwres_result_t
449lwres_create_addr(const char *buffer, lwres_addr_t *addr, int convert_zero) {
450	struct in_addr v4;
451	struct in6_addr v6;
452
453	if (lwres_net_aton(buffer, &v4) == 1) {
454		if (convert_zero) {
455			unsigned char zeroaddress[] = {0, 0, 0, 0};
456			unsigned char loopaddress[] = {127, 0, 0, 1};
457			if (memcmp(&v4, zeroaddress, 4) == 0)
458				memcpy(&v4, loopaddress, 4);
459		}
460		addr->family = LWRES_ADDRTYPE_V4;
461		addr->length = NS_INADDRSZ;
462		memcpy((void *)addr->address, &v4, NS_INADDRSZ);
463
464	} else if (lwres_net_pton(AF_INET6, buffer, &v6) == 1) {
465		addr->family = LWRES_ADDRTYPE_V6;
466		addr->length = NS_IN6ADDRSZ;
467		memcpy((void *)addr->address, &v6, NS_IN6ADDRSZ);
468	} else {
469		return (LWRES_R_FAILURE); /* Unrecognised format. */
470	}
471
472	return (LWRES_R_SUCCESS);
473}
474
475static lwres_result_t
476lwres_conf_parsesortlist(lwres_context_t *ctx,  FILE *fp) {
477	int delim, res, idx;
478	char word[LWRES_CONFMAXLINELEN];
479	char *p;
480	lwres_conf_t *confdata;
481
482	confdata = &ctx->confdata;
483
484	delim = getword(fp, word, sizeof(word));
485	if (strlen(word) == 0U)
486		return (LWRES_R_FAILURE); /* Empty line after keyword. */
487
488	while (strlen(word) > 0U) {
489		if (confdata->sortlistnxt == LWRES_CONFMAXSORTLIST)
490			return (LWRES_R_FAILURE); /* Too many values. */
491
492		p = strchr(word, '/');
493		if (p != NULL)
494			*p++ = '\0';
495
496		idx = confdata->sortlistnxt;
497		res = lwres_create_addr(word, &confdata->sortlist[idx].addr, 1);
498		if (res != LWRES_R_SUCCESS)
499			return (res);
500
501		if (p != NULL) {
502			res = lwres_create_addr(p,
503						&confdata->sortlist[idx].mask,
504						0);
505			if (res != LWRES_R_SUCCESS)
506				return (res);
507		} else {
508			/*
509			 * Make up a mask.
510			 */
511			confdata->sortlist[idx].mask =
512				confdata->sortlist[idx].addr;
513
514			memset(&confdata->sortlist[idx].mask.address, 0xff,
515			       confdata->sortlist[idx].addr.length);
516		}
517
518		confdata->sortlistnxt++;
519
520		if (delim == EOF || delim == '\n')
521			break;
522		else
523			delim = getword(fp, word, sizeof(word));
524	}
525
526	return (LWRES_R_SUCCESS);
527}
528
529static lwres_result_t
530lwres_conf_parseoption(lwres_context_t *ctx,  FILE *fp) {
531	int delim;
532	long ndots;
533	char *p;
534	char word[LWRES_CONFMAXLINELEN];
535	lwres_conf_t *confdata;
536
537	REQUIRE(ctx != NULL);
538	confdata = &ctx->confdata;
539
540	delim = getword(fp, word, sizeof(word));
541	if (strlen(word) == 0U)
542		return (LWRES_R_FAILURE); /* Empty line after keyword. */
543
544	while (strlen(word) > 0U) {
545		if (strcmp("debug", word) == 0) {
546			confdata->resdebug = 1;
547		} else if (strcmp("no_tld_query", word) == 0) {
548			confdata->no_tld_query = 1;
549		} else if (strncmp("ndots:", word, 6) == 0) {
550			ndots = strtol(word + 6, &p, 10);
551			if (*p != '\0') /* Bad string. */
552				return (LWRES_R_FAILURE);
553			if (ndots < 0 || ndots > 0xff) /* Out of range. */
554				return (LWRES_R_FAILURE);
555			confdata->ndots = (lwres_uint8_t)ndots;
556		}
557
558		if (delim == EOF || delim == '\n')
559			break;
560		else
561			delim = getword(fp, word, sizeof(word));
562	}
563
564	return (LWRES_R_SUCCESS);
565}
566
567/*% parses a file and fills in the data structure. */
568lwres_result_t
569lwres_conf_parse(lwres_context_t *ctx, const char *filename) {
570	FILE *fp = NULL;
571	char word[256];
572	lwres_result_t rval, ret;
573	lwres_conf_t *confdata;
574	int stopchar;
575
576	REQUIRE(ctx != NULL);
577	confdata = &ctx->confdata;
578
579	REQUIRE(filename != NULL);
580	REQUIRE(strlen(filename) > 0U);
581	REQUIRE(confdata != NULL);
582
583	errno = 0;
584	if ((fp = fopen(filename, "r")) == NULL)
585		return (LWRES_R_NOTFOUND);
586
587	ret = LWRES_R_SUCCESS;
588	do {
589		stopchar = getword(fp, word, sizeof(word));
590		if (stopchar == EOF) {
591			rval = LWRES_R_SUCCESS;
592			POST(rval);
593			break;
594		}
595
596		if (strlen(word) == 0U)
597			rval = LWRES_R_SUCCESS;
598		else if (strcmp(word, "nameserver") == 0)
599			rval = lwres_conf_parsenameserver(ctx, fp);
600		else if (strcmp(word, "lwserver") == 0)
601			rval = lwres_conf_parselwserver(ctx, fp);
602		else if (strcmp(word, "domain") == 0)
603			rval = lwres_conf_parsedomain(ctx, fp);
604		else if (strcmp(word, "search") == 0)
605			rval = lwres_conf_parsesearch(ctx, fp);
606		else if (strcmp(word, "sortlist") == 0)
607			rval = lwres_conf_parsesortlist(ctx, fp);
608		else if (strcmp(word, "options") == 0)
609			rval = lwres_conf_parseoption(ctx, fp);
610		else {
611			/* unrecognised word. Ignore entire line */
612			rval = LWRES_R_SUCCESS;
613			stopchar = eatline(fp);
614			if (stopchar == EOF) {
615				break;
616			}
617		}
618		if (ret == LWRES_R_SUCCESS && rval != LWRES_R_SUCCESS)
619			ret = rval;
620	} while (1);
621
622	fclose(fp);
623
624	return (ret);
625}
626
627/*% Prints the config data structure to the FILE. */
628lwres_result_t
629lwres_conf_print(lwres_context_t *ctx, FILE *fp) {
630	int i;
631	int af;
632	char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
633	const char *p;
634	lwres_conf_t *confdata;
635	lwres_addr_t tmpaddr;
636
637	REQUIRE(ctx != NULL);
638	confdata = &ctx->confdata;
639
640	REQUIRE(confdata->nsnext <= LWRES_CONFMAXNAMESERVERS);
641
642	for (i = 0; i < confdata->nsnext; i++) {
643		af = lwresaddr2af(confdata->nameservers[i].family);
644
645		p = lwres_net_ntop(af, confdata->nameservers[i].address,
646				   tmp, sizeof(tmp));
647		if (p != tmp)
648			return (LWRES_R_FAILURE);
649
650		fprintf(fp, "nameserver %s\n", tmp);
651	}
652
653	for (i = 0; i < confdata->lwnext; i++) {
654		af = lwresaddr2af(confdata->lwservers[i].family);
655
656		p = lwres_net_ntop(af, confdata->lwservers[i].address,
657				   tmp, sizeof(tmp));
658		if (p != tmp)
659			return (LWRES_R_FAILURE);
660
661		fprintf(fp, "lwserver %s\n", tmp);
662	}
663
664	if (confdata->domainname != NULL) {
665		fprintf(fp, "domain %s\n", confdata->domainname);
666	} else if (confdata->searchnxt > 0) {
667		REQUIRE(confdata->searchnxt <= LWRES_CONFMAXSEARCH);
668
669		fprintf(fp, "search");
670		for (i = 0; i < confdata->searchnxt; i++)
671			fprintf(fp, " %s", confdata->search[i]);
672		fputc('\n', fp);
673	}
674
675	REQUIRE(confdata->sortlistnxt <= LWRES_CONFMAXSORTLIST);
676
677	if (confdata->sortlistnxt > 0) {
678		fputs("sortlist", fp);
679		for (i = 0; i < confdata->sortlistnxt; i++) {
680			af = lwresaddr2af(confdata->sortlist[i].addr.family);
681
682			p = lwres_net_ntop(af,
683					   confdata->sortlist[i].addr.address,
684					   tmp, sizeof(tmp));
685			if (p != tmp)
686				return (LWRES_R_FAILURE);
687
688			fprintf(fp, " %s", tmp);
689
690			tmpaddr = confdata->sortlist[i].mask;
691			memset(&tmpaddr.address, 0xff, tmpaddr.length);
692
693			if (memcmp(&tmpaddr.address,
694				   confdata->sortlist[i].mask.address,
695				   confdata->sortlist[i].mask.length) != 0) {
696				af = lwresaddr2af(
697					    confdata->sortlist[i].mask.family);
698				p = lwres_net_ntop
699					(af,
700					 confdata->sortlist[i].mask.address,
701					 tmp, sizeof(tmp));
702				if (p != tmp)
703					return (LWRES_R_FAILURE);
704
705				fprintf(fp, "/%s", tmp);
706			}
707		}
708		fputc('\n', fp);
709	}
710
711	if (confdata->resdebug)
712		fprintf(fp, "options debug\n");
713
714	if (confdata->ndots > 0)
715		fprintf(fp, "options ndots:%d\n", confdata->ndots);
716
717	if (confdata->no_tld_query)
718		fprintf(fp, "options no_tld_query\n");
719
720	return (LWRES_R_SUCCESS);
721}
722
723/*% Returns a pointer to the current config structure. */
724lwres_conf_t *
725lwres_conf_get(lwres_context_t *ctx) {
726	REQUIRE(ctx != NULL);
727
728	return (&ctx->confdata);
729}
730