2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-30 14:07:59 +00:00
bind/lib/irs/resconf.c
Ondřej Surý bc1d4c9cb4 Clear the pointer to destroyed object early using the semantic patch
Also disable the semantic patch as the code needs tweaks here and there because
some destroy functions might not destroy the object and return early if the
object is still in use.
2020-02-09 18:00:17 -08:00

652 lines
15 KiB
C

/*
* 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 resconf.c */
/**
* Module for parsing resolv.conf files (largely derived from lwconfig.c).
*
* irs_resconf_load() opens the file filename and parses it to initialize
* the configuration structure.
*
* \section lwconfig_return Return Values
*
* irs_resconf_load() returns #IRS_R_SUCCESS if it successfully read and
* parsed filename. It returns a non-0 error code if filename could not be
* opened or contained incorrect resolver statements.
*
* \section lwconfig_see See Also
*
* stdio(3), \link resolver resolver \endlink
*
* \section files Files
*
* /etc/resolv.conf
*/
#ifndef WIN32
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#endif
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <isc/magic.h>
#include <isc/mem.h>
#include <isc/netaddr.h>
#include <isc/sockaddr.h>
#include <isc/util.h>
#include <irs/netdb.h>
#include <irs/resconf.h>
#define IRS_RESCONF_MAGIC ISC_MAGIC('R', 'E', 'S', 'c')
#define IRS_RESCONF_VALID(c) ISC_MAGIC_VALID(c, IRS_RESCONF_MAGIC)
/*!
* protocol constants
*/
#if ! defined(NS_INADDRSZ)
#define NS_INADDRSZ 4
#endif
#if ! defined(NS_IN6ADDRSZ)
#define NS_IN6ADDRSZ 16
#endif
/*!
* resolv.conf parameters
*/
#define RESCONFMAXNAMESERVERS 3U /*%< max 3 "nameserver" entries */
#define RESCONFMAXSEARCH 8U /*%< max 8 domains in "search" entry */
#define RESCONFMAXLINELEN 256U /*%< max size of a line */
#define RESCONFMAXSORTLIST 10U /*%< max 10 */
/*!
* configuration data structure
*/
struct irs_resconf {
/*
* The configuration data is a thread-specific object, and does not
* need to be locked.
*/
unsigned int magic;
isc_mem_t *mctx;
isc_sockaddrlist_t nameservers;
unsigned int numns; /*%< number of configured servers */
char *domainname;
char *search[RESCONFMAXSEARCH];
uint8_t searchnxt; /*%< index for next free slot */
irs_resconf_searchlist_t searchlist;
struct {
isc_netaddr_t addr;
/*% mask has a non-zero 'family' if set */
isc_netaddr_t mask;
} sortlist[RESCONFMAXSORTLIST];
uint8_t sortlistnxt;
/*%< non-zero if 'options debug' set */
uint8_t resdebug;
/*%< set to n in 'options ndots:n' */
uint8_t ndots;
};
static isc_result_t
resconf_parsenameserver(irs_resconf_t *conf, FILE *fp);
static isc_result_t
resconf_parsedomain(irs_resconf_t *conf, FILE *fp);
static isc_result_t
resconf_parsesearch(irs_resconf_t *conf, FILE *fp);
static isc_result_t
resconf_parsesortlist(irs_resconf_t *conf, FILE *fp);
static isc_result_t
resconf_parseoption(irs_resconf_t *ctx, FILE *fp);
#if HAVE_GET_WIN32_NAMESERVERS
static isc_result_t get_win32_nameservers(irs_resconf_t *conf);
#endif
/*!
* 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;
REQUIRE(buffer != NULL);
REQUIRE(size > 0U);
p = buffer;
*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 isc_result_t
add_server(isc_mem_t *mctx, const char *address_str,
isc_sockaddrlist_t *nameservers)
{
int error;
isc_sockaddr_t *address = NULL;
struct addrinfo hints, *res;
isc_result_t result = ISC_R_SUCCESS;
res = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_NUMERICHOST;
error = getaddrinfo(address_str, "53", &hints, &res);
if (error != 0)
return (ISC_R_BADADDRESSFORM);
/* XXX: special case: treat all-0 IPv4 address as loopback */
if (res->ai_family == AF_INET) {
struct in_addr *v4;
unsigned char zeroaddress[] = {0, 0, 0, 0};
unsigned char loopaddress[] = {127, 0, 0, 1};
v4 = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
if (memcmp(v4, zeroaddress, 4) == 0)
memmove(v4, loopaddress, 4);
}
address = isc_mem_get(mctx, sizeof(*address));
if (res->ai_addrlen > sizeof(address->type)) {
isc_mem_put(mctx, address, sizeof(*address));
result = ISC_R_RANGE;
goto cleanup;
}
address->length = (unsigned int)res->ai_addrlen;
memmove(&address->type.ss, res->ai_addr, res->ai_addrlen);
ISC_LINK_INIT(address, link);
ISC_LIST_APPEND(*nameservers, address, link);
cleanup:
freeaddrinfo(res);
return (result);
}
static isc_result_t
create_addr(const char *buffer, isc_netaddr_t *addr, int convert_zero) {
struct in_addr v4;
struct in6_addr v6;
if (inet_pton(AF_INET, 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 = AF_INET;
memmove(&addr->type.in, &v4, NS_INADDRSZ);
addr->zone = 0;
} else if (inet_pton(AF_INET6, buffer, &v6) == 1) {
addr->family = AF_INET6;
memmove(&addr->type.in6, &v6, NS_IN6ADDRSZ);
addr->zone = 0;
} else
return (ISC_R_BADADDRESSFORM); /* Unrecognised format. */
return (ISC_R_SUCCESS);
}
static isc_result_t
resconf_parsenameserver(irs_resconf_t *conf, FILE *fp) {
char word[RESCONFMAXLINELEN];
int cp;
isc_result_t result;
if (conf->numns == RESCONFMAXNAMESERVERS)
return (ISC_R_SUCCESS);
cp = getword(fp, word, sizeof(word));
if (strlen(word) == 0U)
return (ISC_R_UNEXPECTEDEND); /* Nothing on line. */
else if (cp == ' ' || cp == '\t')
cp = eatwhite(fp);
if (cp != EOF && cp != '\n')
return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
result = add_server(conf->mctx, word, &conf->nameservers);
if (result != ISC_R_SUCCESS)
return (result);
conf->numns++;
return (ISC_R_SUCCESS);
}
static isc_result_t
resconf_parsedomain(irs_resconf_t *conf, FILE *fp) {
char word[RESCONFMAXLINELEN];
int res;
unsigned int i;
res = getword(fp, word, sizeof(word));
if (strlen(word) == 0U)
return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */
else if (res == ' ' || res == '\t')
res = eatwhite(fp);
if (res != EOF && res != '\n')
return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
if (conf->domainname != NULL)
isc_mem_free(conf->mctx, conf->domainname);
/*
* Search and domain are mutually exclusive.
*/
for (i = 0; i < RESCONFMAXSEARCH; i++) {
if (conf->search[i] != NULL) {
isc_mem_free(conf->mctx, conf->search[i]);
conf->search[i] = NULL;
}
}
conf->searchnxt = 0;
conf->domainname = isc_mem_strdup(conf->mctx, word);
return (ISC_R_SUCCESS);
}
static isc_result_t
resconf_parsesearch(irs_resconf_t *conf, FILE *fp) {
int delim;
unsigned int idx;
char word[RESCONFMAXLINELEN];
if (conf->domainname != NULL) {
/*
* Search and domain are mutually exclusive.
*/
isc_mem_free(conf->mctx, conf->domainname);
conf->domainname = NULL;
}
/*
* Remove any previous search definitions.
*/
for (idx = 0; idx < RESCONFMAXSEARCH; idx++) {
if (conf->search[idx] != NULL) {
isc_mem_free(conf->mctx, conf->search[idx]);
conf->search[idx] = NULL;
}
}
conf->searchnxt = 0;
delim = getword(fp, word, sizeof(word));
if (strlen(word) == 0U)
return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */
idx = 0;
while (strlen(word) > 0U) {
if (conf->searchnxt == RESCONFMAXSEARCH)
goto ignore; /* Too many domains. */
INSIST(idx < sizeof(conf->search)/sizeof(conf->search[0]));
conf->search[idx] = isc_mem_strdup(conf->mctx, word);
idx++;
conf->searchnxt++;
ignore:
if (delim == EOF || delim == '\n')
break;
else
delim = getword(fp, word, sizeof(word));
}
return (ISC_R_SUCCESS);
}
static isc_result_t
resconf_parsesortlist(irs_resconf_t *conf, FILE *fp) {
int delim, res;
unsigned int idx;
char word[RESCONFMAXLINELEN];
char *p;
delim = getword(fp, word, sizeof(word));
if (strlen(word) == 0U)
return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
while (strlen(word) > 0U) {
if (conf->sortlistnxt == RESCONFMAXSORTLIST)
return (ISC_R_QUOTA); /* Too many values. */
p = strchr(word, '/');
if (p != NULL)
*p++ = '\0';
idx = conf->sortlistnxt;
INSIST(idx < sizeof(conf->sortlist)/sizeof(conf->sortlist[0]));
res = create_addr(word, &conf->sortlist[idx].addr, 1);
if (res != ISC_R_SUCCESS)
return (res);
if (p != NULL) {
res = create_addr(p, &conf->sortlist[idx].mask, 0);
if (res != ISC_R_SUCCESS)
return (res);
} else {
/*
* Make up a mask. (XXX: is this correct?)
*/
conf->sortlist[idx].mask = conf->sortlist[idx].addr;
memset(&conf->sortlist[idx].mask.type, 0xff,
sizeof(conf->sortlist[idx].mask.type));
}
conf->sortlistnxt++;
if (delim == EOF || delim == '\n')
break;
else
delim = getword(fp, word, sizeof(word));
}
return (ISC_R_SUCCESS);
}
static isc_result_t
resconf_parseoption(irs_resconf_t *conf, FILE *fp) {
int delim;
long ndots;
char *p;
char word[RESCONFMAXLINELEN];
delim = getword(fp, word, sizeof(word));
if (strlen(word) == 0U)
return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
while (strlen(word) > 0U) {
if (strcmp("debug", word) == 0) {
conf->resdebug = 1;
} else if (strncmp("ndots:", word, 6) == 0) {
ndots = strtol(word + 6, &p, 10);
if (*p != '\0') /* Bad string. */
return (ISC_R_UNEXPECTEDTOKEN);
if (ndots < 0 || ndots > 0xff) /* Out of range. */
return (ISC_R_RANGE);
conf->ndots = (uint8_t)ndots;
}
if (delim == EOF || delim == '\n')
break;
else
delim = getword(fp, word, sizeof(word));
}
return (ISC_R_SUCCESS);
}
static isc_result_t
add_search(irs_resconf_t *conf, char *domain) {
irs_resconf_search_t *entry;
entry = isc_mem_get(conf->mctx, sizeof(*entry));
entry->domain = domain;
ISC_LINK_INIT(entry, link);
ISC_LIST_APPEND(conf->searchlist, entry, link);
return (ISC_R_SUCCESS);
}
/*% parses a file and fills in the data structure. */
isc_result_t
irs_resconf_load(isc_mem_t *mctx, const char *filename, irs_resconf_t **confp)
{
FILE *fp = NULL;
char word[256];
isc_result_t rval, ret = ISC_R_SUCCESS;
irs_resconf_t *conf;
unsigned int i;
int stopchar;
REQUIRE(mctx != NULL);
REQUIRE(filename != NULL);
REQUIRE(strlen(filename) > 0U);
REQUIRE(confp != NULL && *confp == NULL);
conf = isc_mem_get(mctx, sizeof(*conf));
conf->mctx = mctx;
ISC_LIST_INIT(conf->nameservers);
ISC_LIST_INIT(conf->searchlist);
conf->numns = 0;
conf->domainname = NULL;
conf->searchnxt = 0;
conf->sortlistnxt = 0;
conf->resdebug = 0;
conf->ndots = 1;
for (i = 0; i < RESCONFMAXSEARCH; i++)
conf->search[i] = NULL;
errno = 0;
if ((fp = fopen(filename, "r")) != NULL) {
do {
stopchar = getword(fp, word, sizeof(word));
if (stopchar == EOF) {
rval = ISC_R_SUCCESS;
POST(rval);
break;
}
if (strlen(word) == 0U)
rval = ISC_R_SUCCESS;
else if (strcmp(word, "nameserver") == 0)
rval = resconf_parsenameserver(conf, fp);
else if (strcmp(word, "domain") == 0)
rval = resconf_parsedomain(conf, fp);
else if (strcmp(word, "search") == 0)
rval = resconf_parsesearch(conf, fp);
else if (strcmp(word, "sortlist") == 0)
rval = resconf_parsesortlist(conf, fp);
else if (strcmp(word, "options") == 0)
rval = resconf_parseoption(conf, fp);
else {
/* unrecognised word. Ignore entire line */
rval = ISC_R_SUCCESS;
stopchar = eatline(fp);
if (stopchar == EOF) {
break;
}
}
if (ret == ISC_R_SUCCESS && rval != ISC_R_SUCCESS)
ret = rval;
} while (1);
fclose(fp);
} else {
switch (errno) {
case ENOENT:
break;
default:
isc_mem_put(mctx, conf, sizeof(*conf));
return (ISC_R_INVALIDFILE);
}
}
if (ret != ISC_R_SUCCESS) {
goto error;
}
/*
* Construct unified search list from domain or configured
* search list
*/
if (conf->domainname != NULL) {
ret = add_search(conf, conf->domainname);
} else if (conf->searchnxt > 0) {
for (i = 0; i < conf->searchnxt; i++) {
ret = add_search(conf, conf->search[i]);
if (ret != ISC_R_SUCCESS)
break;
}
}
#if HAVE_GET_WIN32_NAMESERVERS
ret = get_win32_nameservers(conf);
if (ret != ISC_R_SUCCESS) {
goto error;
}
#endif
/* If we don't find a nameserver fall back to localhost */
if (conf->numns == 0U) {
INSIST(ISC_LIST_EMPTY(conf->nameservers));
/* XXX: should we catch errors? */
(void)add_server(conf->mctx, "::1", &conf->nameservers);
(void)add_server(conf->mctx, "127.0.0.1", &conf->nameservers);
}
error:
conf->magic = IRS_RESCONF_MAGIC;
if (ret != ISC_R_SUCCESS)
irs_resconf_destroy(&conf);
else {
if (fp == NULL)
ret = ISC_R_FILENOTFOUND;
*confp = conf;
}
return (ret);
}
void
irs_resconf_destroy(irs_resconf_t **confp) {
irs_resconf_t *conf;
isc_sockaddr_t *address;
irs_resconf_search_t *searchentry;
unsigned int i;
REQUIRE(confp != NULL);
conf = *confp;
*confp = NULL;
REQUIRE(IRS_RESCONF_VALID(conf));
while ((searchentry = ISC_LIST_HEAD(conf->searchlist)) != NULL) {
ISC_LIST_UNLINK(conf->searchlist, searchentry, link);
isc_mem_put(conf->mctx, searchentry, sizeof(*searchentry));
}
while ((address = ISC_LIST_HEAD(conf->nameservers)) != NULL) {
ISC_LIST_UNLINK(conf->nameservers, address, link);
isc_mem_put(conf->mctx, address, sizeof(*address));
}
if (conf->domainname != NULL)
isc_mem_free(conf->mctx, conf->domainname);
for (i = 0; i < RESCONFMAXSEARCH; i++) {
if (conf->search[i] != NULL)
isc_mem_free(conf->mctx, conf->search[i]);
}
isc_mem_put(conf->mctx, conf, sizeof(*conf));
}
isc_sockaddrlist_t *
irs_resconf_getnameservers(irs_resconf_t *conf) {
REQUIRE(IRS_RESCONF_VALID(conf));
return (&conf->nameservers);
}
irs_resconf_searchlist_t *
irs_resconf_getsearchlist(irs_resconf_t *conf) {
REQUIRE(IRS_RESCONF_VALID(conf));
return (&conf->searchlist);
}
unsigned int
irs_resconf_getndots(irs_resconf_t *conf) {
REQUIRE(IRS_RESCONF_VALID(conf));
return ((unsigned int)conf->ndots);
}