xref: /openbsd-src/usr.bin/dig/lib/lwres/lwconfig.c (revision 03650efb1a649d1ea282e2b85a6314e369f55d3d)
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_parsedomain(lwres_conf_t *confdata, FILE *fp);
61 
62 static lwres_result_t
63 lwres_conf_parsesearch(lwres_conf_t *confdata,  FILE *fp);
64 
65 static lwres_result_t
66 lwres_conf_parseoption(lwres_conf_t *confdata,  FILE *fp);
67 
68 static void
69 lwres_resetaddr(lwres_addr_t *addr);
70 
71 static lwres_result_t
72 lwres_create_addr(const char *buff, lwres_addr_t *addr, int convert_zero);
73 
74 /*!
75  * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
76  */
77 static int
78 eatline(FILE *fp) {
79 	int ch;
80 
81 	ch = fgetc(fp);
82 	while (ch != '\n' && ch != EOF)
83 		ch = fgetc(fp);
84 
85 	return (ch);
86 }
87 
88 /*!
89  * Eats white space up to next newline or non-whitespace character (of
90  * EOF). Returns the last character read. Comments are considered white
91  * space.
92  */
93 static int
94 eatwhite(FILE *fp) {
95 	int ch;
96 
97 	ch = fgetc(fp);
98 	while (ch != '\n' && ch != EOF && isspace((unsigned char)ch))
99 		ch = fgetc(fp);
100 
101 	if (ch == ';' || ch == '#')
102 		ch = eatline(fp);
103 
104 	return (ch);
105 }
106 
107 /*!
108  * Skip over any leading whitespace and then read in the next sequence of
109  * non-whitespace characters. In this context newline is not considered
110  * whitespace. Returns EOF on end-of-file, or the character
111  * that caused the reading to stop.
112  */
113 static int
114 getword(FILE *fp, char *buffer, size_t size) {
115 	int ch;
116 	char *p = buffer;
117 
118 	assert(buffer != NULL);
119 	assert(size > 0U);
120 
121 	*p = '\0';
122 
123 	ch = eatwhite(fp);
124 
125 	if (ch == EOF)
126 		return (EOF);
127 
128 	do {
129 		*p = '\0';
130 
131 		if (ch == EOF || isspace((unsigned char)ch))
132 			break;
133 		else if ((size_t) (p - buffer) == size - 1)
134 			return (EOF);	/* Not enough space. */
135 
136 		*p++ = (char)ch;
137 		ch = fgetc(fp);
138 	} while (1);
139 
140 	return (ch);
141 }
142 
143 static void
144 lwres_resetaddr(lwres_addr_t *addr) {
145 	assert(addr != NULL);
146 
147 	memset(addr, 0, sizeof(*addr));
148 }
149 
150 /*% initializes data structure for subsequent config parsing. */
151 void
152 lwres_conf_init(lwres_conf_t *confdata, int lwresflags) {
153 	int i;
154 
155 	confdata->nsnext = 0;
156 	confdata->domainname = NULL;
157 	confdata->searchnxt = 0;
158 	confdata->ndots = 1;
159 	confdata->flags = lwresflags;
160 
161 	for (i = 0; i < LWRES_CONFMAXNAMESERVERS; i++)
162 		lwres_resetaddr(&confdata->nameservers[i]);
163 
164 	for (i = 0; i < LWRES_CONFMAXSEARCH; i++)
165 		confdata->search[i] = NULL;
166 
167 }
168 
169 /*% Frees up all the internal memory used by the config data structure, returning it to the lwres_context_t. */
170 void
171 lwres_conf_clear(lwres_conf_t *confdata) {
172 	int i;
173 
174 	for (i = 0; i < confdata->nsnext; i++)
175 		lwres_resetaddr(&confdata->nameservers[i]);
176 
177 	free(confdata->domainname);
178 	confdata->domainname = NULL;
179 
180 	for (i = 0; i < confdata->searchnxt; i++) {
181 		free(confdata->search[i]);
182 		confdata->search[i] = NULL;
183 	}
184 
185 	confdata->nsnext = 0;
186 	confdata->domainname = NULL;
187 	confdata->searchnxt = 0;
188 	confdata->ndots = 1;
189 }
190 
191 static lwres_result_t
192 lwres_conf_parsenameserver(lwres_conf_t *confdata,  FILE *fp) {
193 	char word[LWRES_CONFMAXLINELEN];
194 	int res, use_ipv4, use_ipv6;
195 	lwres_addr_t address;
196 
197 	if (confdata->nsnext == LWRES_CONFMAXNAMESERVERS)
198 		return (LWRES_R_SUCCESS);
199 
200 	res = getword(fp, word, sizeof(word));
201 	if (strlen(word) == 0U)
202 		return (LWRES_R_FAILURE); /* Nothing on line. */
203 	else if (res == ' ' || res == '\t')
204 		res = eatwhite(fp);
205 
206 	if (res != EOF && res != '\n')
207 		return (LWRES_R_FAILURE); /* Extra junk on line. */
208 
209 	res = lwres_create_addr(word, &address, 1);
210 	use_ipv4 = confdata->flags & LWRES_USEIPV4;
211 	use_ipv6 = confdata->flags & LWRES_USEIPV6;
212 	if (res == LWRES_R_SUCCESS &&
213 	    ((address.family == LWRES_ADDRTYPE_V4 && use_ipv4) ||
214 	    (address.family == LWRES_ADDRTYPE_V6 && use_ipv6))) {
215 		confdata->nameservers[confdata->nsnext++] = address;
216 	}
217 
218 	return (LWRES_R_SUCCESS);
219 }
220 
221 static lwres_result_t
222 lwres_conf_parsedomain(lwres_conf_t *confdata,  FILE *fp) {
223 	char word[LWRES_CONFMAXLINELEN];
224 	int res, i;
225 
226 	res = getword(fp, word, sizeof(word));
227 	if (strlen(word) == 0U)
228 		return (LWRES_R_FAILURE); /* Nothing else on line. */
229 	else if (res == ' ' || res == '\t')
230 		res = eatwhite(fp);
231 
232 	if (res != EOF && res != '\n')
233 		return (LWRES_R_FAILURE); /* Extra junk on line. */
234 
235 	free(confdata->domainname);
236 
237 	/*
238 	 * Search and domain are mutually exclusive.
239 	 */
240 	for (i = 0; i < LWRES_CONFMAXSEARCH; i++) {
241 		free(confdata->search[i]);
242 		confdata->search[i] = NULL;
243 	}
244 	confdata->searchnxt = 0;
245 
246 	confdata->domainname = strdup(word);
247 
248 	if (confdata->domainname == NULL)
249 		return (LWRES_R_FAILURE);
250 
251 	return (LWRES_R_SUCCESS);
252 }
253 
254 static lwres_result_t
255 lwres_conf_parsesearch(lwres_conf_t *confdata,  FILE *fp) {
256 	int idx, delim;
257 	char word[LWRES_CONFMAXLINELEN];
258 
259 	free(confdata->domainname);
260 	confdata->domainname = NULL;
261 
262 	/*
263 	 * Remove any previous search definitions.
264 	 */
265 	for (idx = 0; idx < LWRES_CONFMAXSEARCH; idx++) {
266 		free(confdata->search[idx]);
267 		confdata->search[idx] = NULL;
268 	}
269 	confdata->searchnxt = 0;
270 
271 	delim = getword(fp, word, sizeof(word));
272 	if (strlen(word) == 0U)
273 		return (LWRES_R_FAILURE); /* Nothing else on line. */
274 
275 	idx = 0;
276 	while (strlen(word) > 0U) {
277 		if (confdata->searchnxt == LWRES_CONFMAXSEARCH)
278 			goto ignore; /* Too many domains. */
279 
280 		confdata->search[idx] = strdup(word);
281 		if (confdata->search[idx] == NULL)
282 			return (LWRES_R_FAILURE);
283 		idx++;
284 		confdata->searchnxt++;
285 
286 	ignore:
287 		if (delim == EOF || delim == '\n')
288 			break;
289 		else
290 			delim = getword(fp, word, sizeof(word));
291 	}
292 
293 	return (LWRES_R_SUCCESS);
294 }
295 
296 static lwres_result_t
297 lwres_create_addr(const char *buffer, lwres_addr_t *addr, int convert_zero) {
298 	struct in_addr v4;
299 	struct in6_addr v6;
300 	char buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") +
301 		 sizeof("%4294967295")];
302 	char *percent;
303 	size_t n;
304 
305 	n = strlcpy(buf, buffer, sizeof(buf));
306 	if (n >= sizeof(buf))
307 		return (LWRES_R_FAILURE);
308 
309 	percent = strchr(buf, '%');
310 	if (percent != NULL)
311 		*percent = 0;
312 
313 	if (inet_aton(buffer, &v4) == 1) {
314 		if (convert_zero) {
315 			unsigned char zeroaddress[] = {0, 0, 0, 0};
316 			unsigned char loopaddress[] = {127, 0, 0, 1};
317 			if (memcmp(&v4, zeroaddress, 4) == 0)
318 				memmove(&v4, loopaddress, 4);
319 		}
320 		addr->family = LWRES_ADDRTYPE_V4;
321 		addr->length = sizeof(v4);
322 		addr->zone = 0;
323 		memcpy(addr->address, &v4, sizeof(v4));
324 
325 	} else if (inet_pton(AF_INET6, buf, &v6) == 1) {
326 		addr->family = LWRES_ADDRTYPE_V6;
327 		addr->length = sizeof(v6);
328 		memcpy(addr->address, &v6, sizeof(v6));
329 		if (percent != NULL) {
330 			unsigned long zone;
331 			char *ep;
332 
333 			percent++;
334 
335 			zone = if_nametoindex(percent);
336 			if (zone != 0U) {
337 				addr->zone = zone;
338 				return (LWRES_R_SUCCESS);
339 			}
340 			zone = strtoul(percent, &ep, 10);
341 			if (ep != percent && *ep == 0)
342 				addr->zone = zone;
343 			else
344 				return (LWRES_R_FAILURE);
345 		} else
346 			addr->zone = 0;
347 	} else
348 		return (LWRES_R_FAILURE); /* Unrecognised format. */
349 
350 	return (LWRES_R_SUCCESS);
351 }
352 
353 static lwres_result_t
354 lwres_conf_parseoption(lwres_conf_t *confdata,  FILE *fp) {
355 	int delim;
356 	long ndots;
357 	char *p;
358 	char word[LWRES_CONFMAXLINELEN];
359 
360 	delim = getword(fp, word, sizeof(word));
361 	if (strlen(word) == 0U)
362 		return (LWRES_R_FAILURE); /* Empty line after keyword. */
363 
364 	while (strlen(word) > 0U) {
365 		if (strncmp("ndots:", word, 6) == 0) {
366 			ndots = strtol(word + 6, &p, 10);
367 			if (*p != '\0') /* Bad string. */
368 				return (LWRES_R_FAILURE);
369 			if (ndots < 0 || ndots > 0xff) /* Out of range. */
370 				return (LWRES_R_FAILURE);
371 			confdata->ndots = (uint8_t)ndots;
372 		}
373 
374 		if (delim == EOF || delim == '\n')
375 			break;
376 		else
377 			delim = getword(fp, word, sizeof(word));
378 	}
379 
380 	return (LWRES_R_SUCCESS);
381 }
382 
383 /*% parses a file and fills in the data structure. */
384 lwres_result_t
385 lwres_conf_parse(lwres_conf_t *confdata, const char *filename) {
386 	FILE *fp = NULL;
387 	char word[256];
388 	lwres_result_t rval, ret;
389 	int stopchar;
390 
391 	assert(filename != NULL);
392 	assert(strlen(filename) > 0U);
393 	assert(confdata != NULL);
394 
395 	errno = 0;
396 	if ((fp = fopen(filename, "r")) == NULL)
397 		return (LWRES_R_NOTFOUND);
398 
399 	ret = LWRES_R_SUCCESS;
400 	do {
401 		stopchar = getword(fp, word, sizeof(word));
402 		if (stopchar == EOF)
403 			break;
404 
405 		if (strlen(word) == 0U)
406 			rval = LWRES_R_SUCCESS;
407 		else if (strcmp(word, "nameserver") == 0)
408 			rval = lwres_conf_parsenameserver(confdata, fp);
409 		else if (strcmp(word, "domain") == 0)
410 			rval = lwres_conf_parsedomain(confdata, fp);
411 		else if (strcmp(word, "search") == 0)
412 			rval = lwres_conf_parsesearch(confdata, fp);
413 		else if (strcmp(word, "options") == 0)
414 			rval = lwres_conf_parseoption(confdata, fp);
415 		else {
416 			/* unrecognised word. Ignore entire line */
417 			rval = LWRES_R_SUCCESS;
418 			stopchar = eatline(fp);
419 			if (stopchar == EOF) {
420 				break;
421 			}
422 		}
423 		if (ret == LWRES_R_SUCCESS && rval != LWRES_R_SUCCESS)
424 			ret = rval;
425 	} while (1);
426 
427 	fclose(fp);
428 
429 	return (ret);
430 }
431