/*
* Copyright (C) 2000-2008, 2011, 2012, 2014, 2016 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/.
*/
/*! \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 <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 = (lwres_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 = (lwres_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 = (lwres_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);
}