Home | History | Annotate | Line # | Download | only in dns
      1 /*	$NetBSD: resconf.c,v 1.4 2026/01/29 18:37:49 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      5  *
      6  * SPDX-License-Identifier: MPL-2.0
      7  *
      8  * This Source Code Form is subject to the terms of the Mozilla Public
      9  * License, v. 2.0. If a copy of the MPL was not distributed with this
     10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
     11  *
     12  * See the COPYRIGHT file distributed with this work for additional
     13  * information regarding copyright ownership.
     14  */
     15 
     16 /*! \file resconf.c */
     17 
     18 /**
     19  * Module for parsing resolv.conf files (largely derived from lwconfig.c).
     20  *
     21  *    irs_resconf_load() opens the file filename and parses it to initialize
     22  *    the configuration structure.
     23  *
     24  * \section lwconfig_return Return Values
     25  *
     26  *    irs_resconf_load() returns #IRS_R_SUCCESS if it successfully read and
     27  *    parsed filename. It returns a non-0 error code if filename could not be
     28  *    opened or contained incorrect resolver statements.
     29  *
     30  * \section lwconfig_see See Also
     31  *
     32  *    stdio(3), \link resolver resolver \endlink
     33  *
     34  * \section files Files
     35  *
     36  *    /etc/resolv.conf
     37  */
     38 
     39 #include <ctype.h>
     40 #include <errno.h>
     41 #include <inttypes.h>
     42 #include <netdb.h>
     43 #include <stdio.h>
     44 #include <stdlib.h>
     45 #include <string.h>
     46 #include <sys/socket.h>
     47 #include <sys/types.h>
     48 
     49 #include <isc/magic.h>
     50 #include <isc/mem.h>
     51 #include <isc/netaddr.h>
     52 #include <isc/sockaddr.h>
     53 #include <isc/util.h>
     54 
     55 #include <irs/resconf.h>
     56 
     57 #define IRS_RESCONF_MAGIC    ISC_MAGIC('R', 'E', 'S', 'c')
     58 #define IRS_RESCONF_VALID(c) ISC_MAGIC_VALID(c, IRS_RESCONF_MAGIC)
     59 
     60 /*!
     61  * protocol constants
     62  */
     63 
     64 #if !defined(NS_INADDRSZ)
     65 #define NS_INADDRSZ 4
     66 #endif /* if !defined(NS_INADDRSZ) */
     67 
     68 #if !defined(NS_IN6ADDRSZ)
     69 #define NS_IN6ADDRSZ 16
     70 #endif /* if !defined(NS_IN6ADDRSZ) */
     71 
     72 /*!
     73  * resolv.conf parameters
     74  */
     75 
     76 #define RESCONFMAXNAMESERVERS 3U   /*%< max 3 "nameserver" entries */
     77 #define RESCONFMAXSEARCH      8U   /*%< max 8 domains in "search" entry */
     78 #define RESCONFMAXLINELEN     256U /*%< max size of a line */
     79 #define RESCONFMAXSORTLIST    10U  /*%< max 10 */
     80 
     81 /*!
     82  * configuration data structure
     83  */
     84 
     85 struct irs_resconf {
     86 	/*
     87 	 * The configuration data is a thread-specific object, and does not
     88 	 * need to be locked.
     89 	 */
     90 	unsigned int magic;
     91 	isc_mem_t *mctx;
     92 
     93 	isc_sockaddrlist_t nameservers;
     94 	unsigned int numns; /*%< number of configured servers */
     95 
     96 	char *domainname;
     97 	char *search[RESCONFMAXSEARCH];
     98 	uint8_t searchnxt; /*%< index for next free slot */
     99 
    100 	irs_resconf_searchlist_t searchlist;
    101 
    102 	struct {
    103 		isc_netaddr_t addr;
    104 		/*% mask has a non-zero 'family' if set */
    105 		isc_netaddr_t mask;
    106 	} sortlist[RESCONFMAXSORTLIST];
    107 	uint8_t sortlistnxt;
    108 
    109 	/*%< non-zero if 'options debug' set */
    110 	uint8_t resdebug;
    111 	/*%< set to n in 'options ndots:n' */
    112 	uint8_t ndots;
    113 	/*%< set to n in 'options attempts:n' */
    114 	uint8_t attempts;
    115 	/*%< set to n in 'options timeout:n' */
    116 	uint8_t timeout;
    117 };
    118 
    119 static isc_result_t
    120 resconf_parsenameserver(irs_resconf_t *conf, FILE *fp);
    121 static isc_result_t
    122 resconf_parsedomain(irs_resconf_t *conf, FILE *fp);
    123 static isc_result_t
    124 resconf_parsesearch(irs_resconf_t *conf, FILE *fp);
    125 static isc_result_t
    126 resconf_parsesortlist(irs_resconf_t *conf, FILE *fp);
    127 static isc_result_t
    128 resconf_parseoption(irs_resconf_t *ctx, FILE *fp);
    129 
    130 /*!
    131  * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
    132  */
    133 static int
    134 eatline(FILE *fp) {
    135 	int ch;
    136 
    137 	ch = fgetc(fp);
    138 	while (ch != '\n' && ch != EOF) {
    139 		ch = fgetc(fp);
    140 	}
    141 
    142 	return ch;
    143 }
    144 
    145 /*!
    146  * Eats white space up to next newline or non-whitespace character (of
    147  * EOF). Returns the last character read. Comments are considered white
    148  * space.
    149  */
    150 static int
    151 eatwhite(FILE *fp) {
    152 	int ch;
    153 
    154 	ch = fgetc(fp);
    155 	while (ch != '\n' && ch != EOF && isspace((unsigned char)ch)) {
    156 		ch = fgetc(fp);
    157 	}
    158 
    159 	if (ch == ';' || ch == '#') {
    160 		ch = eatline(fp);
    161 	}
    162 
    163 	return ch;
    164 }
    165 
    166 /*!
    167  * Skip over any leading whitespace and then read in the next sequence of
    168  * non-whitespace characters. In this context newline is not considered
    169  * whitespace. Returns EOF on end-of-file, or the character
    170  * that caused the reading to stop.
    171  */
    172 static int
    173 getword(FILE *fp, char *buffer, size_t size) {
    174 	char *p = NULL;
    175 	int ch;
    176 
    177 	REQUIRE(buffer != NULL);
    178 	REQUIRE(size > 0U);
    179 
    180 	p = buffer;
    181 	*p = '\0';
    182 
    183 	ch = eatwhite(fp);
    184 
    185 	if (ch == EOF) {
    186 		return EOF;
    187 	}
    188 
    189 	do {
    190 		*p = '\0';
    191 
    192 		if (ch == EOF || isspace((unsigned char)ch)) {
    193 			break;
    194 		} else if ((size_t)(p - buffer) == size - 1) {
    195 			return EOF; /* Not enough space. */
    196 		}
    197 
    198 		*p++ = (char)ch;
    199 		ch = fgetc(fp);
    200 	} while (1);
    201 
    202 	return ch;
    203 }
    204 
    205 static isc_result_t
    206 add_server(isc_mem_t *mctx, const char *address_str,
    207 	   isc_sockaddrlist_t *nameservers) {
    208 	int error;
    209 	isc_sockaddr_t *address = NULL;
    210 	struct addrinfo hints, *res;
    211 	isc_result_t result = ISC_R_SUCCESS;
    212 
    213 	res = NULL;
    214 	memset(&hints, 0, sizeof(hints));
    215 	hints.ai_family = AF_UNSPEC;
    216 	hints.ai_socktype = SOCK_DGRAM;
    217 	hints.ai_protocol = IPPROTO_UDP;
    218 	hints.ai_flags = AI_NUMERICHOST;
    219 	error = getaddrinfo(address_str, "53", &hints, &res);
    220 	if (error != 0) {
    221 		return ISC_R_BADADDRESSFORM;
    222 	}
    223 
    224 	address = isc_mem_get(mctx, sizeof(*address));
    225 	if (res->ai_addrlen > sizeof(address->type)) {
    226 		isc_mem_put(mctx, address, sizeof(*address));
    227 		result = ISC_R_RANGE;
    228 		goto cleanup;
    229 	}
    230 
    231 	if (res->ai_family == AF_INET) {
    232 		struct in_addr *v4;
    233 		unsigned char zeroaddress[] = { 0, 0, 0, 0 };
    234 		unsigned char loopaddress[] = { 127, 0, 0, 1 };
    235 
    236 		/* XXX: special case: treat all-0 IPv4 address as loopback */
    237 		v4 = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
    238 		if (memcmp(v4, zeroaddress, 4) == 0) {
    239 			memmove(v4, loopaddress, 4);
    240 		}
    241 		memmove(&address->type.sin, res->ai_addr, res->ai_addrlen);
    242 	} else if (res->ai_family == AF_INET6) {
    243 		memmove(&address->type.sin6, res->ai_addr, res->ai_addrlen);
    244 	} else {
    245 		isc_mem_put(mctx, address, sizeof(*address));
    246 		UNEXPECTED_ERROR("ai_family (%d) not INET nor INET6",
    247 				 res->ai_family);
    248 		result = ISC_R_UNEXPECTED;
    249 		goto cleanup;
    250 	}
    251 	address->length = (unsigned int)res->ai_addrlen;
    252 
    253 	ISC_LINK_INIT(address, link);
    254 	ISC_LIST_APPEND(*nameservers, address, link);
    255 
    256 cleanup:
    257 	freeaddrinfo(res);
    258 
    259 	return result;
    260 }
    261 
    262 static isc_result_t
    263 create_addr(const char *buffer, isc_netaddr_t *addr, int convert_zero) {
    264 	struct in_addr v4;
    265 	struct in6_addr v6;
    266 
    267 	if (inet_pton(AF_INET, buffer, &v4) == 1) {
    268 		if (convert_zero) {
    269 			unsigned char zeroaddress[] = { 0, 0, 0, 0 };
    270 			unsigned char loopaddress[] = { 127, 0, 0, 1 };
    271 			if (memcmp(&v4, zeroaddress, 4) == 0) {
    272 				memmove(&v4, loopaddress, 4);
    273 			}
    274 		}
    275 		addr->family = AF_INET;
    276 		memmove(&addr->type.in, &v4, NS_INADDRSZ);
    277 		addr->zone = 0;
    278 	} else if (inet_pton(AF_INET6, buffer, &v6) == 1) {
    279 		addr->family = AF_INET6;
    280 		memmove(&addr->type.in6, &v6, NS_IN6ADDRSZ);
    281 		addr->zone = 0;
    282 	} else {
    283 		return ISC_R_BADADDRESSFORM; /* Unrecognised format. */
    284 	}
    285 
    286 	return ISC_R_SUCCESS;
    287 }
    288 
    289 static isc_result_t
    290 resconf_parsenameserver(irs_resconf_t *conf, FILE *fp) {
    291 	char word[RESCONFMAXLINELEN];
    292 	int cp;
    293 	isc_result_t result;
    294 
    295 	cp = getword(fp, word, sizeof(word));
    296 	if (strlen(word) == 0U) {
    297 		return ISC_R_UNEXPECTEDEND; /* Nothing on line. */
    298 	} else if (cp == ' ' || cp == '\t') {
    299 		cp = eatwhite(fp);
    300 	}
    301 
    302 	if (cp != EOF && cp != '\n') {
    303 		return ISC_R_UNEXPECTEDTOKEN; /* Extra junk on line. */
    304 	}
    305 
    306 	if (conf->numns == RESCONFMAXNAMESERVERS) {
    307 		return ISC_R_SUCCESS;
    308 	}
    309 
    310 	result = add_server(conf->mctx, word, &conf->nameservers);
    311 	if (result != ISC_R_SUCCESS) {
    312 		return result;
    313 	}
    314 	conf->numns++;
    315 
    316 	return ISC_R_SUCCESS;
    317 }
    318 
    319 static isc_result_t
    320 resconf_parsedomain(irs_resconf_t *conf, FILE *fp) {
    321 	char word[RESCONFMAXLINELEN];
    322 	int res;
    323 	unsigned int i;
    324 
    325 	res = getword(fp, word, sizeof(word));
    326 	if (strlen(word) == 0U) {
    327 		return ISC_R_UNEXPECTEDEND; /* Nothing else on line. */
    328 	} else if (res == ' ' || res == '\t') {
    329 		res = eatwhite(fp);
    330 	}
    331 
    332 	if (res != EOF && res != '\n') {
    333 		return ISC_R_UNEXPECTEDTOKEN; /* Extra junk on line. */
    334 	}
    335 
    336 	if (conf->domainname != NULL) {
    337 		isc_mem_free(conf->mctx, conf->domainname);
    338 	}
    339 
    340 	/*
    341 	 * Search and domain are mutually exclusive.
    342 	 */
    343 	for (i = 0; i < RESCONFMAXSEARCH; i++) {
    344 		if (conf->search[i] != NULL) {
    345 			isc_mem_free(conf->mctx, conf->search[i]);
    346 			conf->search[i] = NULL;
    347 		}
    348 	}
    349 	conf->searchnxt = 0;
    350 
    351 	conf->domainname = isc_mem_strdup(conf->mctx, word);
    352 
    353 	return ISC_R_SUCCESS;
    354 }
    355 
    356 static isc_result_t
    357 resconf_parsesearch(irs_resconf_t *conf, FILE *fp) {
    358 	int delim;
    359 	unsigned int idx;
    360 	char word[RESCONFMAXLINELEN];
    361 
    362 	if (conf->domainname != NULL) {
    363 		/*
    364 		 * Search and domain are mutually exclusive.
    365 		 */
    366 		isc_mem_free(conf->mctx, conf->domainname);
    367 		conf->domainname = NULL;
    368 	}
    369 
    370 	/*
    371 	 * Remove any previous search definitions.
    372 	 */
    373 	for (idx = 0; idx < RESCONFMAXSEARCH; idx++) {
    374 		if (conf->search[idx] != NULL) {
    375 			isc_mem_free(conf->mctx, conf->search[idx]);
    376 			conf->search[idx] = NULL;
    377 		}
    378 	}
    379 	conf->searchnxt = 0;
    380 
    381 	delim = getword(fp, word, sizeof(word));
    382 	if (strlen(word) == 0U) {
    383 		return ISC_R_UNEXPECTEDEND; /* Nothing else on line. */
    384 	}
    385 
    386 	idx = 0;
    387 	while (strlen(word) > 0U) {
    388 		if (conf->searchnxt == RESCONFMAXSEARCH) {
    389 			goto ignore; /* Too many domains. */
    390 		}
    391 
    392 		INSIST(idx < sizeof(conf->search) / sizeof(conf->search[0]));
    393 		conf->search[idx] = isc_mem_strdup(conf->mctx, word);
    394 		idx++;
    395 		conf->searchnxt++;
    396 
    397 	ignore:
    398 		if (delim == EOF || delim == '\n') {
    399 			break;
    400 		} else {
    401 			delim = getword(fp, word, sizeof(word));
    402 		}
    403 	}
    404 
    405 	return ISC_R_SUCCESS;
    406 }
    407 
    408 static isc_result_t
    409 resconf_parsesortlist(irs_resconf_t *conf, FILE *fp) {
    410 	int delim, res;
    411 	unsigned int idx;
    412 	char word[RESCONFMAXLINELEN];
    413 	char *p;
    414 
    415 	delim = getword(fp, word, sizeof(word));
    416 	if (strlen(word) == 0U) {
    417 		return ISC_R_UNEXPECTEDEND; /* Empty line after keyword. */
    418 	}
    419 
    420 	while (strlen(word) > 0U) {
    421 		if (conf->sortlistnxt == RESCONFMAXSORTLIST) {
    422 			return ISC_R_QUOTA; /* Too many values. */
    423 		}
    424 
    425 		p = strchr(word, '/');
    426 		if (p != NULL) {
    427 			*p++ = '\0';
    428 		}
    429 
    430 		idx = conf->sortlistnxt;
    431 		INSIST(idx <
    432 		       sizeof(conf->sortlist) / sizeof(conf->sortlist[0]));
    433 		res = create_addr(word, &conf->sortlist[idx].addr, 1);
    434 		if (res != ISC_R_SUCCESS) {
    435 			return res;
    436 		}
    437 
    438 		if (p != NULL) {
    439 			res = create_addr(p, &conf->sortlist[idx].mask, 0);
    440 			if (res != ISC_R_SUCCESS) {
    441 				return res;
    442 			}
    443 		} else {
    444 			/*
    445 			 * Make up a mask. (XXX: is this correct?)
    446 			 */
    447 			conf->sortlist[idx].mask = conf->sortlist[idx].addr;
    448 			memset(&conf->sortlist[idx].mask.type, 0xff,
    449 			       sizeof(conf->sortlist[idx].mask.type));
    450 		}
    451 
    452 		conf->sortlistnxt++;
    453 
    454 		if (delim == EOF || delim == '\n') {
    455 			break;
    456 		} else {
    457 			delim = getword(fp, word, sizeof(word));
    458 		}
    459 	}
    460 
    461 	return ISC_R_SUCCESS;
    462 }
    463 
    464 static isc_result_t
    465 resconf_optionnumber(const char *word, uint8_t *number) {
    466 	char *p;
    467 	long n;
    468 
    469 	n = strtol(word, &p, 10);
    470 	if (*p != '\0') { /* Bad string. */
    471 		return ISC_R_UNEXPECTEDTOKEN;
    472 	}
    473 	if (n < 0 || n > 0xff) { /* Out of range. */
    474 		return ISC_R_RANGE;
    475 	}
    476 	*number = n;
    477 	return ISC_R_SUCCESS;
    478 }
    479 
    480 static isc_result_t
    481 resconf_parseoption(irs_resconf_t *conf, FILE *fp) {
    482 	int delim;
    483 	isc_result_t result = ISC_R_SUCCESS;
    484 	char word[RESCONFMAXLINELEN];
    485 
    486 	delim = getword(fp, word, sizeof(word));
    487 	if (strlen(word) == 0U) {
    488 		return ISC_R_UNEXPECTEDEND; /* Empty line after keyword. */
    489 	}
    490 
    491 	while (strlen(word) > 0U) {
    492 		if (strcmp("debug", word) == 0) {
    493 			conf->resdebug = 1;
    494 		} else if (strncmp("ndots:", word, 6) == 0) {
    495 			CHECK(resconf_optionnumber(word + 6, &conf->ndots));
    496 		} else if (strncmp("attempts:", word, 9) == 0) {
    497 			CHECK(resconf_optionnumber(word + 9, &conf->attempts));
    498 		} else if (strncmp("timeout:", word, 8) == 0) {
    499 			CHECK(resconf_optionnumber(word + 8, &conf->timeout));
    500 		}
    501 
    502 		if (delim == EOF || delim == '\n') {
    503 			break;
    504 		} else {
    505 			delim = getword(fp, word, sizeof(word));
    506 		}
    507 	}
    508 
    509 cleanup:
    510 	return result;
    511 }
    512 
    513 static isc_result_t
    514 add_search(irs_resconf_t *conf, char *domain) {
    515 	irs_resconf_search_t *entry;
    516 
    517 	entry = isc_mem_get(conf->mctx, sizeof(*entry));
    518 
    519 	entry->domain = domain;
    520 	ISC_LINK_INIT(entry, link);
    521 	ISC_LIST_APPEND(conf->searchlist, entry, link);
    522 
    523 	return ISC_R_SUCCESS;
    524 }
    525 
    526 /*% parses a file and fills in the data structure. */
    527 isc_result_t
    528 irs_resconf_load(isc_mem_t *mctx, const char *filename, irs_resconf_t **confp) {
    529 	FILE *fp = NULL;
    530 	char word[256];
    531 	isc_result_t rval, ret = ISC_R_SUCCESS;
    532 	irs_resconf_t *conf;
    533 	unsigned int i;
    534 	int stopchar;
    535 
    536 	REQUIRE(mctx != NULL);
    537 	REQUIRE(filename != NULL);
    538 	REQUIRE(strlen(filename) > 0U);
    539 	REQUIRE(confp != NULL && *confp == NULL);
    540 
    541 	conf = isc_mem_get(mctx, sizeof(*conf));
    542 
    543 	conf->mctx = mctx;
    544 	ISC_LIST_INIT(conf->nameservers);
    545 	ISC_LIST_INIT(conf->searchlist);
    546 	conf->numns = 0;
    547 	conf->domainname = NULL;
    548 	conf->searchnxt = 0;
    549 	conf->sortlistnxt = 0;
    550 	conf->resdebug = 0;
    551 	conf->ndots = 1;
    552 	conf->attempts = 3;
    553 	conf->timeout = 0;
    554 	for (i = 0; i < RESCONFMAXSEARCH; i++) {
    555 		conf->search[i] = NULL;
    556 	}
    557 
    558 	errno = 0;
    559 	if ((fp = fopen(filename, "r")) != NULL) {
    560 		do {
    561 			stopchar = getword(fp, word, sizeof(word));
    562 			if (stopchar == EOF) {
    563 				rval = ISC_R_SUCCESS;
    564 				POST(rval);
    565 				break;
    566 			}
    567 
    568 			if (strlen(word) == 0U) {
    569 				rval = ISC_R_SUCCESS;
    570 			} else if (strcmp(word, "nameserver") == 0) {
    571 				rval = resconf_parsenameserver(conf, fp);
    572 			} else if (strcmp(word, "domain") == 0) {
    573 				rval = resconf_parsedomain(conf, fp);
    574 			} else if (strcmp(word, "search") == 0) {
    575 				rval = resconf_parsesearch(conf, fp);
    576 			} else if (strcmp(word, "sortlist") == 0) {
    577 				rval = resconf_parsesortlist(conf, fp);
    578 			} else if (strcmp(word, "options") == 0) {
    579 				rval = resconf_parseoption(conf, fp);
    580 			} else {
    581 				/* unrecognised word. Ignore entire line */
    582 				rval = ISC_R_SUCCESS;
    583 				if (stopchar != '\n') {
    584 					stopchar = eatline(fp);
    585 					if (stopchar == EOF) {
    586 						break;
    587 					}
    588 				}
    589 			}
    590 			if (ret == ISC_R_SUCCESS && rval != ISC_R_SUCCESS) {
    591 				ret = rval;
    592 			}
    593 		} while (1);
    594 
    595 		fclose(fp);
    596 	} else {
    597 		switch (errno) {
    598 		case ENOENT:
    599 			break;
    600 		default:
    601 			isc_mem_put(mctx, conf, sizeof(*conf));
    602 			return ISC_R_INVALIDFILE;
    603 		}
    604 	}
    605 
    606 	if (ret != ISC_R_SUCCESS) {
    607 		goto error;
    608 	}
    609 
    610 	/*
    611 	 * Construct unified search list from domain or configured
    612 	 * search list
    613 	 */
    614 	if (conf->domainname != NULL) {
    615 		ret = add_search(conf, conf->domainname);
    616 	} else if (conf->searchnxt > 0) {
    617 		for (i = 0; i < conf->searchnxt; i++) {
    618 			ret = add_search(conf, conf->search[i]);
    619 			if (ret != ISC_R_SUCCESS) {
    620 				break;
    621 			}
    622 		}
    623 	}
    624 
    625 	/* If we don't find a nameserver fall back to localhost */
    626 	if (conf->numns == 0U) {
    627 		INSIST(ISC_LIST_EMPTY(conf->nameservers));
    628 
    629 		/* XXX: should we catch errors? */
    630 		(void)add_server(conf->mctx, "::1", &conf->nameservers);
    631 		(void)add_server(conf->mctx, "127.0.0.1", &conf->nameservers);
    632 	}
    633 
    634 error:
    635 	conf->magic = IRS_RESCONF_MAGIC;
    636 
    637 	if (ret != ISC_R_SUCCESS) {
    638 		irs_resconf_destroy(&conf);
    639 	} else {
    640 		if (fp == NULL) {
    641 			ret = ISC_R_FILENOTFOUND;
    642 		}
    643 		*confp = conf;
    644 	}
    645 
    646 	return ret;
    647 }
    648 
    649 void
    650 irs_resconf_destroy(irs_resconf_t **confp) {
    651 	irs_resconf_t *conf;
    652 	isc_sockaddr_t *address;
    653 	irs_resconf_search_t *searchentry;
    654 	unsigned int i;
    655 
    656 	REQUIRE(confp != NULL);
    657 	conf = *confp;
    658 	*confp = NULL;
    659 	REQUIRE(IRS_RESCONF_VALID(conf));
    660 
    661 	while ((searchentry = ISC_LIST_HEAD(conf->searchlist)) != NULL) {
    662 		ISC_LIST_UNLINK(conf->searchlist, searchentry, link);
    663 		isc_mem_put(conf->mctx, searchentry, sizeof(*searchentry));
    664 	}
    665 
    666 	while ((address = ISC_LIST_HEAD(conf->nameservers)) != NULL) {
    667 		ISC_LIST_UNLINK(conf->nameservers, address, link);
    668 		isc_mem_put(conf->mctx, address, sizeof(*address));
    669 	}
    670 
    671 	if (conf->domainname != NULL) {
    672 		isc_mem_free(conf->mctx, conf->domainname);
    673 	}
    674 
    675 	for (i = 0; i < RESCONFMAXSEARCH; i++) {
    676 		if (conf->search[i] != NULL) {
    677 			isc_mem_free(conf->mctx, conf->search[i]);
    678 		}
    679 	}
    680 
    681 	isc_mem_put(conf->mctx, conf, sizeof(*conf));
    682 }
    683 
    684 isc_sockaddrlist_t *
    685 irs_resconf_getnameservers(irs_resconf_t *conf) {
    686 	REQUIRE(IRS_RESCONF_VALID(conf));
    687 
    688 	return &conf->nameservers;
    689 }
    690 
    691 irs_resconf_searchlist_t *
    692 irs_resconf_getsearchlist(irs_resconf_t *conf) {
    693 	REQUIRE(IRS_RESCONF_VALID(conf));
    694 
    695 	return &conf->searchlist;
    696 }
    697 
    698 unsigned int
    699 irs_resconf_getndots(irs_resconf_t *conf) {
    700 	REQUIRE(IRS_RESCONF_VALID(conf));
    701 
    702 	return (unsigned int)conf->ndots;
    703 }
    704 
    705 unsigned int
    706 irs_resconf_getattempts(irs_resconf_t *conf) {
    707 	REQUIRE(IRS_RESCONF_VALID(conf));
    708 
    709 	return (unsigned int)conf->attempts;
    710 }
    711 
    712 unsigned int
    713 irs_resconf_gettimeout(irs_resconf_t *conf) {
    714 	REQUIRE(IRS_RESCONF_VALID(conf));
    715 
    716 	return (unsigned int)conf->timeout;
    717 }
    718