xref: /openbsd/usr.bin/dig/lib/lwres/lwconfig.c (revision 771fbea0)
1 /*
2  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3  *
4  * Permission to use, copy, modify, and/or distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
9  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
11  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
13  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14  * PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 /*! \file */
18 
19 /**
20  * Module for parsing resolv.conf files.
21  *
22  *    lwres_conf_init() creates an empty lwres_conf_t structure for
23  *    lightweight resolver context ctx.
24  *
25  *    lwres_conf_clear() frees up all the internal memory used by that
26  *    lwres_conf_t structure in resolver context ctx.
27  *
28  *    lwres_conf_parse() opens the file filename and parses it to initialise
29  *    the resolver context ctx's lwres_conf_t structure.
30  *
31  * \section lwconfig_return Return Values
32  *
33  *    lwres_conf_parse() returns #LWRES_R_SUCCESS if it successfully read and
34  *    parsed filename. It returns #LWRES_R_FAILURE if filename could not be
35  *    opened or contained incorrect resolver statements.
36  *
37  * \section lwconfig_see See Also
38  *
39  *    stdio(3), \link resolver resolver \endlink
40  *
41  * \section files Files
42  *
43  *    /etc/resolv.conf
44  */
45 
46 #include <assert.h>
47 #include <ctype.h>
48 #include <errno.h>
49 #include <stdlib.h>
50 #include <stdio.h>
51 #include <string.h>
52 
53 #include <lwres/lwres.h>
54 #include <lwres/result.h>
55 
56 static lwres_result_t
57 lwres_conf_parsenameserver(lwres_conf_t *confdata,  FILE *fp);
58 
59 static lwres_result_t
60 lwres_conf_parselwserver(lwres_conf_t *confdata,  FILE *fp);
61 
62 static lwres_result_t
63 lwres_conf_parsedomain(lwres_conf_t *confdata, FILE *fp);
64 
65 static lwres_result_t
66 lwres_conf_parsesearch(lwres_conf_t *confdata,  FILE *fp);
67 
68 static lwres_result_t
69 lwres_conf_parsesortlist(lwres_conf_t *confdata,  FILE *fp);
70 
71 static lwres_result_t
72 lwres_conf_parseoption(lwres_conf_t *confdata,  FILE *fp);
73 
74 static void
75 lwres_resetaddr(lwres_addr_t *addr);
76 
77 static lwres_result_t
78 lwres_create_addr(const char *buff, lwres_addr_t *addr, int convert_zero);
79 
80 /*!
81  * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
82  */
83 static int
84 eatline(FILE *fp) {
85 	int ch;
86 
87 	ch = fgetc(fp);
88 	while (ch != '\n' && ch != EOF)
89 		ch = fgetc(fp);
90 
91 	return (ch);
92 }
93 
94 /*!
95  * Eats white space up to next newline or non-whitespace character (of
96  * EOF). Returns the last character read. Comments are considered white
97  * space.
98  */
99 static int
100 eatwhite(FILE *fp) {
101 	int ch;
102 
103 	ch = fgetc(fp);
104 	while (ch != '\n' && ch != EOF && isspace((unsigned char)ch))
105 		ch = fgetc(fp);
106 
107 	if (ch == ';' || ch == '#')
108 		ch = eatline(fp);
109 
110 	return (ch);
111 }
112 
113 /*!
114  * Skip over any leading whitespace and then read in the next sequence of
115  * non-whitespace characters. In this context newline is not considered
116  * whitespace. Returns EOF on end-of-file, or the character
117  * that caused the reading to stop.
118  */
119 static int
120 getword(FILE *fp, char *buffer, size_t size) {
121 	int ch;
122 	char *p = buffer;
123 
124 	assert(buffer != NULL);
125 	assert(size > 0U);
126 
127 	*p = '\0';
128 
129 	ch = eatwhite(fp);
130 
131 	if (ch == EOF)
132 		return (EOF);
133 
134 	do {
135 		*p = '\0';
136 
137 		if (ch == EOF || isspace((unsigned char)ch))
138 			break;
139 		else if ((size_t) (p - buffer) == size - 1)
140 			return (EOF);	/* Not enough space. */
141 
142 		*p++ = (char)ch;
143 		ch = fgetc(fp);
144 	} while (1);
145 
146 	return (ch);
147 }
148 
149 static void
150 lwres_resetaddr(lwres_addr_t *addr) {
151 	assert(addr != NULL);
152 
153 	memset(addr, 0, sizeof(*addr));
154 }
155 
156 /*% intializes data structure for subsequent config parsing. */
157 void
158 lwres_conf_init(lwres_conf_t *confdata, int lwresflags) {
159 	int i;
160 
161 	confdata->nsnext = 0;
162 	confdata->lwnext = 0;
163 	confdata->domainname = NULL;
164 	confdata->searchnxt = 0;
165 	confdata->sortlistnxt = 0;
166 	confdata->resdebug = 0;
167 	confdata->ndots = 1;
168 	confdata->no_tld_query = 0;
169 	confdata->flags = lwresflags;
170 
171 	for (i = 0; i < LWRES_CONFMAXNAMESERVERS; i++)
172 		lwres_resetaddr(&confdata->nameservers[i]);
173 
174 	for (i = 0; i < LWRES_CONFMAXSEARCH; i++)
175 		confdata->search[i] = NULL;
176 
177 	for (i = 0; i < LWRES_CONFMAXSORTLIST; i++) {
178 		lwres_resetaddr(&confdata->sortlist[i].addr);
179 		lwres_resetaddr(&confdata->sortlist[i].mask);
180 	}
181 }
182 
183 /*% Frees up all the internal memory used by the config data structure, returning it to the lwres_context_t. */
184 void
185 lwres_conf_clear(lwres_conf_t *confdata) {
186 	int i;
187 
188 	for (i = 0; i < confdata->nsnext; i++)
189 		lwres_resetaddr(&confdata->nameservers[i]);
190 
191 	free(confdata->domainname);
192 	confdata->domainname = NULL;
193 
194 	for (i = 0; i < confdata->searchnxt; i++) {
195 		free(confdata->search[i]);
196 		confdata->search[i] = NULL;
197 	}
198 
199 	for (i = 0; i < LWRES_CONFMAXSORTLIST; i++) {
200 		lwres_resetaddr(&confdata->sortlist[i].addr);
201 		lwres_resetaddr(&confdata->sortlist[i].mask);
202 	}
203 
204 	confdata->nsnext = 0;
205 	confdata->lwnext = 0;
206 	confdata->domainname = NULL;
207 	confdata->searchnxt = 0;
208 	confdata->sortlistnxt = 0;
209 	confdata->resdebug = 0;
210 	confdata->ndots = 1;
211 	confdata->no_tld_query = 0;
212 }
213 
214 static lwres_result_t
215 lwres_conf_parsenameserver(lwres_conf_t *confdata,  FILE *fp) {
216 	char word[LWRES_CONFMAXLINELEN];
217 	int res, use_ipv4, use_ipv6;
218 	lwres_addr_t address;
219 
220 	if (confdata->nsnext == LWRES_CONFMAXNAMESERVERS)
221 		return (LWRES_R_SUCCESS);
222 
223 	res = getword(fp, word, sizeof(word));
224 	if (strlen(word) == 0U)
225 		return (LWRES_R_FAILURE); /* Nothing on line. */
226 	else if (res == ' ' || res == '\t')
227 		res = eatwhite(fp);
228 
229 	if (res != EOF && res != '\n')
230 		return (LWRES_R_FAILURE); /* Extra junk on line. */
231 
232 	res = lwres_create_addr(word, &address, 1);
233 	use_ipv4 = confdata->flags & LWRES_USEIPV4;
234 	use_ipv6 = confdata->flags & LWRES_USEIPV6;
235 	if (res == LWRES_R_SUCCESS &&
236 	    ((address.family == LWRES_ADDRTYPE_V4 && use_ipv4) ||
237 	    (address.family == LWRES_ADDRTYPE_V6 && use_ipv6))) {
238 		confdata->nameservers[confdata->nsnext++] = address;
239 	}
240 
241 	return (LWRES_R_SUCCESS);
242 }
243 
244 static lwres_result_t
245 lwres_conf_parselwserver(lwres_conf_t *confdata,  FILE *fp) {
246 	char word[LWRES_CONFMAXLINELEN];
247 	int res;
248 
249 	if (confdata->lwnext == LWRES_CONFMAXLWSERVERS)
250 		return (LWRES_R_SUCCESS);
251 
252 	res = getword(fp, word, sizeof(word));
253 	if (strlen(word) == 0U)
254 		return (LWRES_R_FAILURE); /* Nothing on line. */
255 	else if (res == ' ' || res == '\t')
256 		res = eatwhite(fp);
257 
258 	if (res != EOF && res != '\n')
259 		return (LWRES_R_FAILURE); /* Extra junk on line. */
260 
261 	res = lwres_create_addr(word,
262 				&confdata->lwservers[confdata->lwnext++], 1);
263 	if (res != LWRES_R_SUCCESS)
264 		return (res);
265 
266 	return (LWRES_R_SUCCESS);
267 }
268 
269 static lwres_result_t
270 lwres_conf_parsedomain(lwres_conf_t *confdata,  FILE *fp) {
271 	char word[LWRES_CONFMAXLINELEN];
272 	int res, i;
273 
274 	res = getword(fp, word, sizeof(word));
275 	if (strlen(word) == 0U)
276 		return (LWRES_R_FAILURE); /* Nothing else on line. */
277 	else if (res == ' ' || res == '\t')
278 		res = eatwhite(fp);
279 
280 	if (res != EOF && res != '\n')
281 		return (LWRES_R_FAILURE); /* Extra junk on line. */
282 
283 	free(confdata->domainname);
284 
285 	/*
286 	 * Search and domain are mutually exclusive.
287 	 */
288 	for (i = 0; i < LWRES_CONFMAXSEARCH; i++) {
289 		free(confdata->search[i]);
290 		confdata->search[i] = NULL;
291 	}
292 	confdata->searchnxt = 0;
293 
294 	confdata->domainname = strdup(word);
295 
296 	if (confdata->domainname == NULL)
297 		return (LWRES_R_FAILURE);
298 
299 	return (LWRES_R_SUCCESS);
300 }
301 
302 static lwres_result_t
303 lwres_conf_parsesearch(lwres_conf_t *confdata,  FILE *fp) {
304 	int idx, delim;
305 	char word[LWRES_CONFMAXLINELEN];
306 
307 	free(confdata->domainname);
308 	confdata->domainname = NULL;
309 
310 	/*
311 	 * Remove any previous search definitions.
312 	 */
313 	for (idx = 0; idx < LWRES_CONFMAXSEARCH; idx++) {
314 		free(confdata->search[idx]);
315 		confdata->search[idx] = NULL;
316 	}
317 	confdata->searchnxt = 0;
318 
319 	delim = getword(fp, word, sizeof(word));
320 	if (strlen(word) == 0U)
321 		return (LWRES_R_FAILURE); /* Nothing else on line. */
322 
323 	idx = 0;
324 	while (strlen(word) > 0U) {
325 		if (confdata->searchnxt == LWRES_CONFMAXSEARCH)
326 			goto ignore; /* Too many domains. */
327 
328 		confdata->search[idx] = strdup(word);
329 		if (confdata->search[idx] == NULL)
330 			return (LWRES_R_FAILURE);
331 		idx++;
332 		confdata->searchnxt++;
333 
334 	ignore:
335 		if (delim == EOF || delim == '\n')
336 			break;
337 		else
338 			delim = getword(fp, word, sizeof(word));
339 	}
340 
341 	return (LWRES_R_SUCCESS);
342 }
343 
344 static lwres_result_t
345 lwres_create_addr(const char *buffer, lwres_addr_t *addr, int convert_zero) {
346 	struct in_addr v4;
347 	struct in6_addr v6;
348 	char buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") +
349 		 sizeof("%4294967295")];
350 	char *percent;
351 	size_t n;
352 
353 	n = strlcpy(buf, buffer, sizeof(buf));
354 	if (n >= sizeof(buf))
355 		return (LWRES_R_FAILURE);
356 
357 	percent = strchr(buf, '%');
358 	if (percent != NULL)
359 		*percent = 0;
360 
361 	if (inet_aton(buffer, &v4) == 1) {
362 		if (convert_zero) {
363 			unsigned char zeroaddress[] = {0, 0, 0, 0};
364 			unsigned char loopaddress[] = {127, 0, 0, 1};
365 			if (memcmp(&v4, zeroaddress, 4) == 0)
366 				memmove(&v4, loopaddress, 4);
367 		}
368 		addr->family = LWRES_ADDRTYPE_V4;
369 		addr->length = sizeof(v4);
370 		addr->zone = 0;
371 		memcpy(addr->address, &v4, sizeof(v4));
372 
373 	} else if (inet_pton(AF_INET6, buf, &v6) == 1) {
374 		addr->family = LWRES_ADDRTYPE_V6;
375 		addr->length = sizeof(v6);
376 		memcpy(addr->address, &v6, sizeof(v6));
377 		if (percent != NULL) {
378 			unsigned long zone;
379 			char *ep;
380 
381 			percent++;
382 
383 			zone = if_nametoindex(percent);
384 			if (zone != 0U) {
385 				addr->zone = zone;
386 				return (LWRES_R_SUCCESS);
387 			}
388 			zone = strtoul(percent, &ep, 10);
389 			if (ep != percent && *ep == 0)
390 				addr->zone = zone;
391 			else
392 				return (LWRES_R_FAILURE);
393 		} else
394 			addr->zone = 0;
395 	} else
396 		return (LWRES_R_FAILURE); /* Unrecognised format. */
397 
398 	return (LWRES_R_SUCCESS);
399 }
400 
401 static lwres_result_t
402 lwres_conf_parsesortlist(lwres_conf_t *confdata,  FILE *fp) {
403 	int delim, res, idx;
404 	char word[LWRES_CONFMAXLINELEN];
405 	char *p;
406 
407 	delim = getword(fp, word, sizeof(word));
408 	if (strlen(word) == 0U)
409 		return (LWRES_R_FAILURE); /* Empty line after keyword. */
410 
411 	while (strlen(word) > 0U) {
412 		if (confdata->sortlistnxt == LWRES_CONFMAXSORTLIST)
413 			return (LWRES_R_FAILURE); /* Too many values. */
414 
415 		p = strchr(word, '/');
416 		if (p != NULL)
417 			*p++ = '\0';
418 
419 		idx = confdata->sortlistnxt;
420 		res = lwres_create_addr(word, &confdata->sortlist[idx].addr, 1);
421 		if (res != LWRES_R_SUCCESS)
422 			return (res);
423 
424 		if (p != NULL) {
425 			res = lwres_create_addr(p,
426 						&confdata->sortlist[idx].mask,
427 						0);
428 			if (res != LWRES_R_SUCCESS)
429 				return (res);
430 		} else {
431 			/*
432 			 * Make up a mask.
433 			 */
434 			confdata->sortlist[idx].mask =
435 				confdata->sortlist[idx].addr;
436 
437 			memset(&confdata->sortlist[idx].mask.address, 0xff,
438 			       confdata->sortlist[idx].addr.length);
439 		}
440 
441 		confdata->sortlistnxt++;
442 
443 		if (delim == EOF || delim == '\n')
444 			break;
445 		else
446 			delim = getword(fp, word, sizeof(word));
447 	}
448 
449 	return (LWRES_R_SUCCESS);
450 }
451 
452 static lwres_result_t
453 lwres_conf_parseoption(lwres_conf_t *confdata,  FILE *fp) {
454 	int delim;
455 	long ndots;
456 	char *p;
457 	char word[LWRES_CONFMAXLINELEN];
458 
459 	delim = getword(fp, word, sizeof(word));
460 	if (strlen(word) == 0U)
461 		return (LWRES_R_FAILURE); /* Empty line after keyword. */
462 
463 	while (strlen(word) > 0U) {
464 		if (strcmp("debug", word) == 0) {
465 			confdata->resdebug = 1;
466 		} else if (strcmp("no_tld_query", word) == 0) {
467 			confdata->no_tld_query = 1;
468 		} else if (strncmp("ndots:", word, 6) == 0) {
469 			ndots = strtol(word + 6, &p, 10);
470 			if (*p != '\0') /* Bad string. */
471 				return (LWRES_R_FAILURE);
472 			if (ndots < 0 || ndots > 0xff) /* Out of range. */
473 				return (LWRES_R_FAILURE);
474 			confdata->ndots = (uint8_t)ndots;
475 		}
476 
477 		if (delim == EOF || delim == '\n')
478 			break;
479 		else
480 			delim = getword(fp, word, sizeof(word));
481 	}
482 
483 	return (LWRES_R_SUCCESS);
484 }
485 
486 /*% parses a file and fills in the data structure. */
487 lwres_result_t
488 lwres_conf_parse(lwres_conf_t *confdata, const char *filename) {
489 	FILE *fp = NULL;
490 	char word[256];
491 	lwres_result_t rval, ret;
492 	int stopchar;
493 
494 	assert(filename != NULL);
495 	assert(strlen(filename) > 0U);
496 	assert(confdata != NULL);
497 
498 	errno = 0;
499 	if ((fp = fopen(filename, "r")) == NULL)
500 		return (LWRES_R_NOTFOUND);
501 
502 	ret = LWRES_R_SUCCESS;
503 	do {
504 		stopchar = getword(fp, word, sizeof(word));
505 		if (stopchar == EOF)
506 			break;
507 
508 		if (strlen(word) == 0U)
509 			rval = LWRES_R_SUCCESS;
510 		else if (strcmp(word, "nameserver") == 0)
511 			rval = lwres_conf_parsenameserver(confdata, fp);
512 		else if (strcmp(word, "lwserver") == 0)
513 			rval = lwres_conf_parselwserver(confdata, fp);
514 		else if (strcmp(word, "domain") == 0)
515 			rval = lwres_conf_parsedomain(confdata, fp);
516 		else if (strcmp(word, "search") == 0)
517 			rval = lwres_conf_parsesearch(confdata, fp);
518 		else if (strcmp(word, "sortlist") == 0)
519 			rval = lwres_conf_parsesortlist(confdata, fp);
520 		else if (strcmp(word, "options") == 0)
521 			rval = lwres_conf_parseoption(confdata, fp);
522 		else {
523 			/* unrecognised word. Ignore entire line */
524 			rval = LWRES_R_SUCCESS;
525 			stopchar = eatline(fp);
526 			if (stopchar == EOF) {
527 				break;
528 			}
529 		}
530 		if (ret == LWRES_R_SUCCESS && rval != LWRES_R_SUCCESS)
531 			ret = rval;
532 	} while (1);
533 
534 	fclose(fp);
535 
536 	return (ret);
537 }
538