diff options
Diffstat (limited to '')
-rw-r--r-- | lib/lwres/lwconfig.c | 797 |
1 files changed, 797 insertions, 0 deletions
diff --git a/lib/lwres/lwconfig.c b/lib/lwres/lwconfig.c new file mode 100644 index 0000000..36367b2 --- /dev/null +++ b/lib/lwres/lwconfig.c @@ -0,0 +1,797 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +/** + * Module for parsing resolv.conf files. + * + * lwres_conf_init() creates an empty lwres_conf_t structure for + * lightweight resolver context ctx. + * + * lwres_conf_clear() frees up all the internal memory used by that + * lwres_conf_t structure in resolver context ctx. + * + * lwres_conf_parse() opens the file filename and parses it to initialise + * the resolver context ctx's lwres_conf_t structure. + * + * lwres_conf_print() prints the lwres_conf_t structure for resolver + * context ctx to the FILE fp. + * + * \section lwconfig_return Return Values + * + * lwres_conf_parse() returns #LWRES_R_SUCCESS if it successfully read and + * parsed filename. It returns #LWRES_R_FAILURE if filename could not be + * opened or contained incorrect resolver statements. + * + * lwres_conf_print() returns #LWRES_R_SUCCESS unless an error occurred + * when converting the network addresses to a numeric host address + * string. If this happens, the function returns #LWRES_R_FAILURE. + * + * \section lwconfig_see See Also + * + * stdio(3), \link resolver resolver \endlink + * + * \section files Files + * + * /etc/resolv.conf + */ + +#include <config.h> + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <lwres/lwbuffer.h> +#include <lwres/lwres.h> +#include <lwres/net.h> +#include <lwres/result.h> +#include <lwres/stdlib.h> +#include <lwres/string.h> + +#include "assert_p.h" +#include "context_p.h" +#include "print_p.h" + + +#if ! defined(NS_INADDRSZ) +#define NS_INADDRSZ 4 +#endif + +#if ! defined(NS_IN6ADDRSZ) +#define NS_IN6ADDRSZ 16 +#endif + +static lwres_result_t +lwres_conf_parsenameserver(lwres_context_t *ctx, FILE *fp); + +static lwres_result_t +lwres_conf_parselwserver(lwres_context_t *ctx, FILE *fp); + +static lwres_result_t +lwres_conf_parsedomain(lwres_context_t *ctx, FILE *fp); + +static lwres_result_t +lwres_conf_parsesearch(lwres_context_t *ctx, FILE *fp); + +static lwres_result_t +lwres_conf_parsesortlist(lwres_context_t *ctx, FILE *fp); + +static lwres_result_t +lwres_conf_parseoption(lwres_context_t *ctx, FILE *fp); + +static void +lwres_resetaddr(lwres_addr_t *addr); + +static lwres_result_t +lwres_create_addr(const char *buff, lwres_addr_t *addr, int convert_zero); + +static int lwresaddr2af(int lwresaddrtype); + + +static int +lwresaddr2af(int lwresaddrtype) +{ + int af = 0; + + switch (lwresaddrtype) { + case LWRES_ADDRTYPE_V4: + af = AF_INET; + break; + + case LWRES_ADDRTYPE_V6: + af = AF_INET6; + break; + } + + return (af); +} + + +/*! + * Eat characters from FP until EOL or EOF. Returns EOF or '\n' + */ +static int +eatline(FILE *fp) { + int ch; + + ch = fgetc(fp); + while (ch != '\n' && ch != EOF) + ch = fgetc(fp); + + return (ch); +} + + +/*! + * Eats white space up to next newline or non-whitespace character (of + * EOF). Returns the last character read. Comments are considered white + * space. + */ +static int +eatwhite(FILE *fp) { + int ch; + + ch = fgetc(fp); + while (ch != '\n' && ch != EOF && isspace((unsigned char)ch)) + ch = fgetc(fp); + + if (ch == ';' || ch == '#') + ch = eatline(fp); + + return (ch); +} + + +/*! + * Skip over any leading whitespace and then read in the next sequence of + * non-whitespace characters. In this context newline is not considered + * whitespace. Returns EOF on end-of-file, or the character + * that caused the reading to stop. + */ +static int +getword(FILE *fp, char *buffer, size_t size) { + int ch; + char *p = buffer; + + REQUIRE(buffer != NULL); + REQUIRE(size > 0U); + + *p = '\0'; + + ch = eatwhite(fp); + + if (ch == EOF) + return (EOF); + + do { + *p = '\0'; + + if (ch == EOF || isspace((unsigned char)ch)) + break; + else if ((size_t) (p - buffer) == size - 1) + return (EOF); /* Not enough space. */ + + *p++ = (char)ch; + ch = fgetc(fp); + } while (1); + + return (ch); +} + +static void +lwres_resetaddr(lwres_addr_t *addr) { + REQUIRE(addr != NULL); + + memset(addr->address, 0, LWRES_ADDR_MAXLEN); + addr->family = 0; + addr->length = 0; + addr->zone = 0; +} + +static char * +lwres_strdup(lwres_context_t *ctx, const char *str) { + char *p; + + REQUIRE(str != NULL); + REQUIRE(strlen(str) > 0U); + + p = CTXMALLOC(strlen(str) + 1); + if (p != NULL) + strcpy(p, str); + + return (p); +} + +/*% intializes data structure for subsequent config parsing. */ +void +lwres_conf_init(lwres_context_t *ctx) { + int i; + lwres_conf_t *confdata; + + REQUIRE(ctx != NULL); + confdata = &ctx->confdata; + + confdata->nsnext = 0; + confdata->lwnext = 0; + confdata->domainname = NULL; + confdata->searchnxt = 0; + confdata->sortlistnxt = 0; + confdata->resdebug = 0; + confdata->ndots = 1; + confdata->no_tld_query = 0; + confdata->attempts = 0; + confdata->timeout = 0; + + for (i = 0; i < LWRES_CONFMAXNAMESERVERS; i++) + lwres_resetaddr(&confdata->nameservers[i]); + + for (i = 0; i < LWRES_CONFMAXSEARCH; i++) + confdata->search[i] = NULL; + + for (i = 0; i < LWRES_CONFMAXSORTLIST; i++) { + lwres_resetaddr(&confdata->sortlist[i].addr); + lwres_resetaddr(&confdata->sortlist[i].mask); + } +} + +/*% Frees up all the internal memory used by the config data structure, returning it to the lwres_context_t. */ +void +lwres_conf_clear(lwres_context_t *ctx) { + int i; + lwres_conf_t *confdata; + + REQUIRE(ctx != NULL); + confdata = &ctx->confdata; + + for (i = 0; i < confdata->nsnext; i++) + lwres_resetaddr(&confdata->nameservers[i]); + + if (confdata->domainname != NULL) { + CTXFREE(confdata->domainname, + strlen(confdata->domainname) + 1); + confdata->domainname = NULL; + } + + for (i = 0; i < confdata->searchnxt; i++) { + if (confdata->search[i] != NULL) { + CTXFREE(confdata->search[i], + strlen(confdata->search[i]) + 1); + confdata->search[i] = NULL; + } + } + + for (i = 0; i < LWRES_CONFMAXSORTLIST; i++) { + lwres_resetaddr(&confdata->sortlist[i].addr); + lwres_resetaddr(&confdata->sortlist[i].mask); + } + + confdata->nsnext = 0; + confdata->lwnext = 0; + confdata->domainname = NULL; + confdata->searchnxt = 0; + confdata->sortlistnxt = 0; + confdata->resdebug = 0; + confdata->ndots = 1; + confdata->no_tld_query = 0; + confdata->attempts = 0; + confdata->timeout = 0; +} + +static lwres_result_t +lwres_conf_parsenameserver(lwres_context_t *ctx, FILE *fp) { + char word[LWRES_CONFMAXLINELEN]; + int res; + lwres_conf_t *confdata; + lwres_addr_t address; + + confdata = &ctx->confdata; + + if (confdata->nsnext == LWRES_CONFMAXNAMESERVERS) + return (LWRES_R_SUCCESS); + + res = getword(fp, word, sizeof(word)); + if (strlen(word) == 0U) + return (LWRES_R_FAILURE); /* Nothing on line. */ + else if (res == ' ' || res == '\t') + res = eatwhite(fp); + + if (res != EOF && res != '\n') + return (LWRES_R_FAILURE); /* Extra junk on line. */ + + res = lwres_create_addr(word, &address, 1); + if (res == LWRES_R_SUCCESS && + ((address.family == LWRES_ADDRTYPE_V4 && ctx->use_ipv4 == 1) || + (address.family == LWRES_ADDRTYPE_V6 && ctx->use_ipv6 == 1))) { + confdata->nameservers[confdata->nsnext++] = address; + } + + return (LWRES_R_SUCCESS); +} + +static lwres_result_t +lwres_conf_parselwserver(lwres_context_t *ctx, FILE *fp) { + char word[LWRES_CONFMAXLINELEN]; + int res; + lwres_conf_t *confdata; + + confdata = &ctx->confdata; + + if (confdata->lwnext == LWRES_CONFMAXLWSERVERS) + return (LWRES_R_SUCCESS); + + res = getword(fp, word, sizeof(word)); + if (strlen(word) == 0U) + return (LWRES_R_FAILURE); /* Nothing on line. */ + else if (res == ' ' || res == '\t') + res = eatwhite(fp); + + if (res != EOF && res != '\n') + return (LWRES_R_FAILURE); /* Extra junk on line. */ + + res = lwres_create_addr(word, + &confdata->lwservers[confdata->lwnext++], 1); + if (res != LWRES_R_SUCCESS) + return (res); + + return (LWRES_R_SUCCESS); +} + +static lwres_result_t +lwres_conf_parsedomain(lwres_context_t *ctx, FILE *fp) { + char word[LWRES_CONFMAXLINELEN]; + int res, i; + lwres_conf_t *confdata; + + confdata = &ctx->confdata; + + res = getword(fp, word, sizeof(word)); + if (strlen(word) == 0U) + return (LWRES_R_FAILURE); /* Nothing else on line. */ + else if (res == ' ' || res == '\t') + res = eatwhite(fp); + + if (res != EOF && res != '\n') + return (LWRES_R_FAILURE); /* Extra junk on line. */ + + if (confdata->domainname != NULL) + CTXFREE(confdata->domainname, + strlen(confdata->domainname) + 1); /* */ + + /* + * Search and domain are mutually exclusive. + */ + for (i = 0; i < LWRES_CONFMAXSEARCH; i++) { + if (confdata->search[i] != NULL) { + CTXFREE(confdata->search[i], + strlen(confdata->search[i])+1); + confdata->search[i] = NULL; + } + } + confdata->searchnxt = 0; + + confdata->domainname = lwres_strdup(ctx, word); + + if (confdata->domainname == NULL) + return (LWRES_R_FAILURE); + + return (LWRES_R_SUCCESS); +} + +static lwres_result_t +lwres_conf_parsesearch(lwres_context_t *ctx, FILE *fp) { + int idx, delim; + char word[LWRES_CONFMAXLINELEN]; + lwres_conf_t *confdata; + + confdata = &ctx->confdata; + + if (confdata->domainname != NULL) { + /* + * Search and domain are mutually exclusive. + */ + CTXFREE(confdata->domainname, + strlen(confdata->domainname) + 1); + confdata->domainname = NULL; + } + + /* + * Remove any previous search definitions. + */ + for (idx = 0; idx < LWRES_CONFMAXSEARCH; idx++) { + if (confdata->search[idx] != NULL) { + CTXFREE(confdata->search[idx], + strlen(confdata->search[idx])+1); + confdata->search[idx] = NULL; + } + } + confdata->searchnxt = 0; + + delim = getword(fp, word, sizeof(word)); + if (strlen(word) == 0U) + return (LWRES_R_FAILURE); /* Nothing else on line. */ + + idx = 0; + while (strlen(word) > 0U) { + if (confdata->searchnxt == LWRES_CONFMAXSEARCH) + goto ignore; /* Too many domains. */ + + confdata->search[idx] = lwres_strdup(ctx, word); + if (confdata->search[idx] == NULL) + return (LWRES_R_FAILURE); + idx++; + confdata->searchnxt++; + + ignore: + if (delim == EOF || delim == '\n') + break; + else + delim = getword(fp, word, sizeof(word)); + } + + return (LWRES_R_SUCCESS); +} + +static lwres_result_t +lwres_create_addr(const char *buffer, lwres_addr_t *addr, int convert_zero) { + struct in_addr v4; + struct in6_addr v6; + char buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") + + sizeof("%4294967295")]; + char *percent; + size_t n; + + n = strlcpy(buf, buffer, sizeof(buf)); + if (n >= sizeof(buf)) + return (LWRES_R_FAILURE); + + percent = strchr(buf, '%'); + if (percent != NULL) + *percent = 0; + + if (lwres_net_aton(buffer, &v4) == 1) { + if (convert_zero) { + unsigned char zeroaddress[] = {0, 0, 0, 0}; + unsigned char loopaddress[] = {127, 0, 0, 1}; + if (memcmp(&v4, zeroaddress, 4) == 0) + memmove(&v4, loopaddress, 4); + } + addr->family = LWRES_ADDRTYPE_V4; + addr->length = NS_INADDRSZ; + addr->zone = 0; + memmove((void *)addr->address, &v4, NS_INADDRSZ); + + } else if (lwres_net_pton(AF_INET6, buf, &v6) == 1) { + addr->family = LWRES_ADDRTYPE_V6; + addr->length = NS_IN6ADDRSZ; + memmove((void *)addr->address, &v6, NS_IN6ADDRSZ); + if (percent != NULL) { + unsigned long zone; + char *ep; + + percent++; + +#ifdef HAVE_IF_NAMETOINDEX + zone = if_nametoindex(percent); + if (zone != 0U) { + addr->zone = zone; + return (LWRES_R_SUCCESS); + } +#endif + zone = strtoul(percent, &ep, 10); + if (ep != percent && *ep == 0) + addr->zone = zone; + else + return (LWRES_R_FAILURE); + } else + addr->zone = 0; + } else + return (LWRES_R_FAILURE); /* Unrecognised format. */ + + return (LWRES_R_SUCCESS); +} + +static lwres_result_t +lwres_conf_parsesortlist(lwres_context_t *ctx, FILE *fp) { + int delim, res, idx; + char word[LWRES_CONFMAXLINELEN]; + char *p; + lwres_conf_t *confdata; + + confdata = &ctx->confdata; + + delim = getword(fp, word, sizeof(word)); + if (strlen(word) == 0U) + return (LWRES_R_FAILURE); /* Empty line after keyword. */ + + while (strlen(word) > 0U) { + if (confdata->sortlistnxt == LWRES_CONFMAXSORTLIST) + return (LWRES_R_FAILURE); /* Too many values. */ + + p = strchr(word, '/'); + if (p != NULL) + *p++ = '\0'; + + idx = confdata->sortlistnxt; + res = lwres_create_addr(word, &confdata->sortlist[idx].addr, 1); + if (res != LWRES_R_SUCCESS) + return (res); + + if (p != NULL) { + res = lwres_create_addr(p, + &confdata->sortlist[idx].mask, + 0); + if (res != LWRES_R_SUCCESS) + return (res); + } else { + /* + * Make up a mask. + */ + confdata->sortlist[idx].mask = + confdata->sortlist[idx].addr; + + memset(&confdata->sortlist[idx].mask.address, 0xff, + confdata->sortlist[idx].addr.length); + } + + confdata->sortlistnxt++; + + if (delim == EOF || delim == '\n') + break; + else + delim = getword(fp, word, sizeof(word)); + } + + return (LWRES_R_SUCCESS); +} + +static lwres_result_t +lwres_conf_parseoption(lwres_context_t *ctx, FILE *fp) { + int delim; + long ndots; + long attempts; + long timeout; + char *p; + char word[LWRES_CONFMAXLINELEN]; + lwres_conf_t *confdata; + + REQUIRE(ctx != NULL); + confdata = &ctx->confdata; + + delim = getword(fp, word, sizeof(word)); + if (strlen(word) == 0U) + return (LWRES_R_FAILURE); /* Empty line after keyword. */ + + while (strlen(word) > 0U) { + if (strcmp("debug", word) == 0) { + confdata->resdebug = 1; + } else if (strcmp("no_tld_query", word) == 0) { + confdata->no_tld_query = 1; + } else if (strcmp("debug", word) == 0) { + confdata->resdebug = 1; + } else if (strncmp("ndots:", word, 6) == 0) { + ndots = strtol(word + 6, &p, 10); + if (*p != '\0') /* Bad string. */ + return (LWRES_R_FAILURE); + if (ndots < 0 || ndots > 0xff) /* Out of range. */ + return (LWRES_R_FAILURE); + confdata->ndots = (uint8_t)ndots; + } else if (strncmp("timeout:", word, 8) == 0) { + timeout = strtol(word + 8, &p, 10); + if (*p != '\0') /* Bad string. */ + return (LWRES_R_FAILURE); + confdata->timeout = (int32_t)timeout; + } else if (strncmp("attempts:", word, 9) == 0) { + attempts = strtol(word + 9, &p, 10); + if (*p != '\0') /* Bad string. */ + return (LWRES_R_FAILURE); + if (attempts < 0) /* Out of range. */ + return (LWRES_R_FAILURE); + confdata->attempts = (int32_t)attempts; + } + + if (delim == EOF || delim == '\n') + break; + else + delim = getword(fp, word, sizeof(word)); + } + + return (LWRES_R_SUCCESS); +} + +/*% parses a file and fills in the data structure. */ +lwres_result_t +lwres_conf_parse(lwres_context_t *ctx, const char *filename) { + FILE *fp = NULL; + char word[256]; + lwres_result_t rval, ret; + lwres_conf_t *confdata; + int stopchar; + + REQUIRE(ctx != NULL); + confdata = &ctx->confdata; + + REQUIRE(filename != NULL); + REQUIRE(strlen(filename) > 0U); + REQUIRE(confdata != NULL); + + errno = 0; + if ((fp = fopen(filename, "r")) == NULL) + return (LWRES_R_NOTFOUND); + + ret = LWRES_R_SUCCESS; + do { + stopchar = getword(fp, word, sizeof(word)); + if (stopchar == EOF) { + rval = LWRES_R_SUCCESS; + POST(rval); + break; + } + + if (strlen(word) == 0U) + rval = LWRES_R_SUCCESS; + else if (strcmp(word, "nameserver") == 0) + rval = lwres_conf_parsenameserver(ctx, fp); + else if (strcmp(word, "lwserver") == 0) + rval = lwres_conf_parselwserver(ctx, fp); + else if (strcmp(word, "domain") == 0) + rval = lwres_conf_parsedomain(ctx, fp); + else if (strcmp(word, "search") == 0) + rval = lwres_conf_parsesearch(ctx, fp); + else if (strcmp(word, "sortlist") == 0) + rval = lwres_conf_parsesortlist(ctx, fp); + else if (strcmp(word, "options") == 0) + rval = lwres_conf_parseoption(ctx, fp); + else { + /* unrecognised word. Ignore entire line */ + rval = LWRES_R_SUCCESS; + stopchar = eatline(fp); + if (stopchar == EOF) { + break; + } + } + if (ret == LWRES_R_SUCCESS && rval != LWRES_R_SUCCESS) + ret = rval; + } while (1); + + fclose(fp); + + return (ret); +} + +/*% Prints the config data structure to the FILE. */ +lwres_result_t +lwres_conf_print(lwres_context_t *ctx, FILE *fp) { + int i; + int af; + char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + char buf[sizeof("%4000000000")]; + const char *p; + lwres_conf_t *confdata; + lwres_addr_t tmpaddr; + + REQUIRE(ctx != NULL); + confdata = &ctx->confdata; + + REQUIRE(confdata->nsnext <= LWRES_CONFMAXNAMESERVERS); + + for (i = 0; i < confdata->nsnext; i++) { + af = lwresaddr2af(confdata->nameservers[i].family); + + p = lwres_net_ntop(af, confdata->nameservers[i].address, + tmp, sizeof(tmp)); + if (p != tmp) + return (LWRES_R_FAILURE); + + if (af == AF_INET6 && confdata->lwservers[i].zone != 0) { + snprintf(buf, sizeof(buf), "%%%u", + confdata->nameservers[i].zone); + } else + buf[0] = 0; + + fprintf(fp, "nameserver %s%s\n", tmp, buf); + } + + for (i = 0; i < confdata->lwnext; i++) { + af = lwresaddr2af(confdata->lwservers[i].family); + + p = lwres_net_ntop(af, confdata->lwservers[i].address, + tmp, sizeof(tmp)); + if (p != tmp) + return (LWRES_R_FAILURE); + + if (af == AF_INET6 && confdata->lwservers[i].zone != 0) { + snprintf(buf, sizeof(buf), "%%%u", + confdata->nameservers[i].zone); + } else + buf[0] = 0; + + fprintf(fp, "lwserver %s%s\n", tmp, buf); + } + + if (confdata->domainname != NULL) { + fprintf(fp, "domain %s\n", confdata->domainname); + } else if (confdata->searchnxt > 0) { + REQUIRE(confdata->searchnxt <= LWRES_CONFMAXSEARCH); + + fprintf(fp, "search"); + for (i = 0; i < confdata->searchnxt; i++) + fprintf(fp, " %s", confdata->search[i]); + fputc('\n', fp); + } + + REQUIRE(confdata->sortlistnxt <= LWRES_CONFMAXSORTLIST); + + if (confdata->sortlistnxt > 0) { + fputs("sortlist", fp); + for (i = 0; i < confdata->sortlistnxt; i++) { + af = lwresaddr2af(confdata->sortlist[i].addr.family); + + p = lwres_net_ntop(af, + confdata->sortlist[i].addr.address, + tmp, sizeof(tmp)); + if (p != tmp) + return (LWRES_R_FAILURE); + + fprintf(fp, " %s", tmp); + + tmpaddr = confdata->sortlist[i].mask; + memset(&tmpaddr.address, 0xff, tmpaddr.length); + + if (memcmp(&tmpaddr.address, + confdata->sortlist[i].mask.address, + confdata->sortlist[i].mask.length) != 0) { + af = lwresaddr2af( + confdata->sortlist[i].mask.family); + p = lwres_net_ntop + (af, + confdata->sortlist[i].mask.address, + tmp, sizeof(tmp)); + if (p != tmp) + return (LWRES_R_FAILURE); + + fprintf(fp, "/%s", tmp); + } + } + fputc('\n', fp); + } + + if (confdata->resdebug) + fprintf(fp, "options debug\n"); + + if (confdata->ndots > 0) + fprintf(fp, "options ndots:%d\n", confdata->ndots); + + if (confdata->no_tld_query) + fprintf(fp, "options no_tld_query\n"); + + if (confdata->attempts) + fprintf(fp, "options attempts:%d\n", confdata->attempts); + + if (confdata->timeout) + fprintf(fp, "options timeout:%d\n", confdata->timeout); + + return (LWRES_R_SUCCESS); +} + +/*% Returns a pointer to the current config structure. */ +lwres_conf_t * +lwres_conf_get(lwres_context_t *ctx) { + REQUIRE(ctx != NULL); + + return (&ctx->confdata); +} |