mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-30 14:07:59 +00:00
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.
652 lines
15 KiB
C
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);
|
|
}
|