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
eatline(FILE * fp)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
eatwhite(FILE * fp)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
getword(FILE * fp,char * buffer,size_t size)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
lwres_resetaddr(lwres_addr_t * addr)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
lwres_conf_init(lwres_conf_t * confdata,int lwresflags)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
lwres_conf_clear(lwres_conf_t * confdata)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
lwres_conf_parsenameserver(lwres_conf_t * confdata,FILE * fp)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
lwres_conf_parsedomain(lwres_conf_t * confdata,FILE * fp)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
lwres_conf_parsesearch(lwres_conf_t * confdata,FILE * fp)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
lwres_create_addr(const char * buffer,lwres_addr_t * addr,int convert_zero)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
lwres_conf_parseoption(lwres_conf_t * confdata,FILE * fp)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
lwres_conf_parse(lwres_conf_t * confdata,const char * filename)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