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