blob: a6c2a8e62bb34f05468fe2eb4695f92083c3693e [file] [log] [blame]
/* MIT License
*
* Copyright (c) 1998 Massachusetts Institute of Technology
* Copyright (c) 2007 Daniel Stenberg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#include "ares_private.h"
#ifdef HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#ifdef HAVE_NETDB_H
# include <netdb.h>
#endif
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
#if defined(ANDROID) || defined(__ANDROID__)
# include <sys/system_properties.h>
# include "ares_android.h"
/* From the Bionic sources */
# define DNS_PROP_NAME_PREFIX "net.dns"
# define MAX_DNS_PROPERTIES 8
#endif
#if defined(CARES_USE_LIBRESOLV)
# include <resolv.h>
#endif
#if defined(USE_WINSOCK) && defined(HAVE_IPHLPAPI_H)
# include <iphlpapi.h>
#endif
#include "ares_inet_net_pton.h"
static unsigned char ip_natural_mask(const struct ares_addr *addr)
{
const unsigned char *ptr = NULL;
/* This is an odd one. If a raw ipv4 address is specified, then we take
* what is called a natural mask, which means we look at the first octet
* of the ip address and for values 0-127 we assume it is a class A (/8),
* for values 128-191 we assume it is a class B (/16), and for 192-223
* we assume it is a class C (/24). 223-239 is Class D which and 240-255 is
* Class E, however, there is no pre-defined mask for this, so we'll use
* /24 as well as that's what the old code did.
*
* For IPv6, we'll use /64.
*/
if (addr->family == AF_INET6) {
return 64;
}
ptr = (const unsigned char *)&addr->addr.addr4;
if (*ptr < 128) {
return 8;
}
if (*ptr < 192) {
return 16;
}
return 24;
}
static ares_bool_t sortlist_append(struct apattern **sortlist, size_t *nsort,
const struct apattern *pat)
{
struct apattern *newsort;
newsort = ares_realloc(*sortlist, (*nsort + 1) * sizeof(*newsort));
if (newsort == NULL) {
return ARES_FALSE; /* LCOV_EXCL_LINE: OutOfMemory */
}
*sortlist = newsort;
memcpy(&(*sortlist)[*nsort], pat, sizeof(**sortlist));
(*nsort)++;
return ARES_TRUE;
}
static ares_status_t parse_sort(ares_buf_t *buf, struct apattern *pat)
{
ares_status_t status;
const unsigned char ip_charset[] = "ABCDEFabcdef0123456789.:";
char ipaddr[INET6_ADDRSTRLEN] = "";
size_t addrlen;
memset(pat, 0, sizeof(*pat));
/* Consume any leading whitespace */
ares_buf_consume_whitespace(buf, ARES_TRUE);
/* If no length, just ignore, return ENOTFOUND as an indicator */
if (ares_buf_len(buf) == 0) {
return ARES_ENOTFOUND;
}
ares_buf_tag(buf);
/* Consume ip address */
if (ares_buf_consume_charset(buf, ip_charset, sizeof(ip_charset) - 1) == 0) {
return ARES_EBADSTR;
}
/* Fetch ip address */
status = ares_buf_tag_fetch_string(buf, ipaddr, sizeof(ipaddr));
if (status != ARES_SUCCESS) {
return status;
}
/* Parse it to make sure its valid */
pat->addr.family = AF_UNSPEC;
if (ares_dns_pton(ipaddr, &pat->addr, &addrlen) == NULL) {
return ARES_EBADSTR;
}
/* See if there is a subnet mask */
if (ares_buf_begins_with(buf, (const unsigned char *)"/", 1)) {
char maskstr[16];
const unsigned char ipv4_charset[] = "0123456789.";
/* Consume / */
ares_buf_consume(buf, 1);
ares_buf_tag(buf);
/* Consume mask */
if (ares_buf_consume_charset(buf, ipv4_charset, sizeof(ipv4_charset) - 1) ==
0) {
return ARES_EBADSTR;
}
/* Fetch mask */
status = ares_buf_tag_fetch_string(buf, maskstr, sizeof(maskstr));
if (status != ARES_SUCCESS) {
return status;
}
if (ares_str_isnum(maskstr)) {
/* Numeric mask */
int mask = atoi(maskstr);
if (mask < 0 || mask > 128) {
return ARES_EBADSTR;
}
if (pat->addr.family == AF_INET && mask > 32) {
return ARES_EBADSTR;
}
pat->mask = (unsigned char)mask;
} else {
/* Ipv4 subnet style mask */
struct ares_addr maskaddr;
const unsigned char *ptr;
memset(&maskaddr, 0, sizeof(maskaddr));
maskaddr.family = AF_INET;
if (ares_dns_pton(maskstr, &maskaddr, &addrlen) == NULL) {
return ARES_EBADSTR;
}
ptr = (const unsigned char *)&maskaddr.addr.addr4;
pat->mask = (unsigned char)(ares_count_bits_u8(ptr[0]) +
ares_count_bits_u8(ptr[1]) +
ares_count_bits_u8(ptr[2]) +
ares_count_bits_u8(ptr[3]));
}
} else {
pat->mask = ip_natural_mask(&pat->addr);
}
/* Consume any trailing whitespace */
ares_buf_consume_whitespace(buf, ARES_TRUE);
/* If we have any trailing bytes other than whitespace, its a parse failure */
if (ares_buf_len(buf) != 0) {
return ARES_EBADSTR;
}
return ARES_SUCCESS;
}
ares_status_t ares_parse_sortlist(struct apattern **sortlist, size_t *nsort,
const char *str)
{
ares_buf_t *buf = NULL;
ares_status_t status = ARES_SUCCESS;
ares_array_t *arr = NULL;
size_t num = 0;
size_t i;
if (sortlist == NULL || nsort == NULL || str == NULL) {
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
}
if (*sortlist != NULL) {
ares_free(*sortlist);
}
*sortlist = NULL;
*nsort = 0;
buf = ares_buf_create_const((const unsigned char *)str, ares_strlen(str));
if (buf == NULL) {
status = ARES_ENOMEM;
goto done;
}
/* Split on space or semicolon */
status = ares_buf_split(buf, (const unsigned char *)" ;", 2,
ARES_BUF_SPLIT_NONE, 0, &arr);
if (status != ARES_SUCCESS) {
goto done;
}
num = ares_array_len(arr);
for (i = 0; i < num; i++) {
ares_buf_t **bufptr = ares_array_at(arr, i);
ares_buf_t *entry = *bufptr;
struct apattern pat;
status = parse_sort(entry, &pat);
if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
goto done;
}
if (status != ARES_SUCCESS) {
continue;
}
if (!sortlist_append(sortlist, nsort, &pat)) {
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
status = ARES_SUCCESS;
done:
ares_buf_destroy(buf);
ares_array_destroy(arr);
if (status != ARES_SUCCESS) {
ares_free(*sortlist);
*sortlist = NULL;
*nsort = 0;
}
return status;
}
static ares_status_t config_search(ares_sysconfig_t *sysconfig, const char *str,
size_t max_domains)
{
if (sysconfig->domains && sysconfig->ndomains > 0) {
/* if we already have some domains present, free them first */
ares_strsplit_free(sysconfig->domains, sysconfig->ndomains);
sysconfig->domains = NULL;
sysconfig->ndomains = 0;
}
sysconfig->domains = ares_strsplit(str, ", ", &sysconfig->ndomains);
if (sysconfig->domains == NULL) {
return ARES_ENOMEM;
}
/* Truncate if necessary */
if (max_domains && sysconfig->ndomains > max_domains) {
size_t i;
for (i = max_domains; i < sysconfig->ndomains; i++) {
ares_free(sysconfig->domains[i]);
sysconfig->domains[i] = NULL;
}
sysconfig->ndomains = max_domains;
}
return ARES_SUCCESS;
}
static ares_status_t buf_fetch_string(ares_buf_t *buf, char *str,
size_t str_len)
{
ares_status_t status;
ares_buf_tag(buf);
ares_buf_consume(buf, ares_buf_len(buf));
status = ares_buf_tag_fetch_string(buf, str, str_len);
return status;
}
static ares_status_t config_lookup(ares_sysconfig_t *sysconfig, ares_buf_t *buf,
const char *separators)
{
ares_status_t status;
char lookupstr[32];
size_t lookupstr_cnt = 0;
char **lookups = NULL;
size_t num = 0;
size_t i;
size_t separators_len = ares_strlen(separators);
status =
ares_buf_split_str(buf, (const unsigned char *)separators, separators_len,
ARES_BUF_SPLIT_TRIM, 0, &lookups, &num);
if (status != ARES_SUCCESS) {
goto done;
}
for (i = 0; i < num; i++) {
const char *value = lookups[i];
char ch;
if (ares_strcaseeq(value, "dns") || ares_strcaseeq(value, "bind") ||
ares_strcaseeq(value, "resolv") || ares_strcaseeq(value, "resolve")) {
ch = 'b';
} else if (ares_strcaseeq(value, "files") ||
ares_strcaseeq(value, "file") ||
ares_strcaseeq(value, "local")) {
ch = 'f';
} else {
continue;
}
/* Look for a duplicate and ignore */
if (memchr(lookupstr, ch, lookupstr_cnt) == NULL) {
lookupstr[lookupstr_cnt++] = ch;
}
}
if (lookupstr_cnt) {
lookupstr[lookupstr_cnt] = 0;
ares_free(sysconfig->lookups);
sysconfig->lookups = ares_strdup(lookupstr);
if (sysconfig->lookups == NULL) {
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
status = ARES_SUCCESS;
done:
if (status != ARES_ENOMEM) {
status = ARES_SUCCESS;
}
ares_free_array(lookups, num, ares_free);
return status;
}
static ares_status_t process_option(ares_sysconfig_t *sysconfig,
ares_buf_t *option)
{
char **kv = NULL;
size_t num = 0;
const char *key;
const char *val;
unsigned int valint = 0;
ares_status_t status;
/* Split on : */
status = ares_buf_split_str(option, (const unsigned char *)":", 1,
ARES_BUF_SPLIT_TRIM, 2, &kv, &num);
if (status != ARES_SUCCESS) {
goto done;
}
if (num < 1) {
status = ARES_EBADSTR;
goto done;
}
key = kv[0];
if (num == 2) {
val = kv[1];
valint = (unsigned int)strtoul(val, NULL, 10);
}
if (ares_streq(key, "ndots")) {
sysconfig->ndots = valint;
} else if (ares_streq(key, "retrans") || ares_streq(key, "timeout")) {
if (valint == 0) {
return ARES_EFORMERR;
}
sysconfig->timeout_ms = valint * 1000;
} else if (ares_streq(key, "retry") || ares_streq(key, "attempts")) {
if (valint == 0) {
return ARES_EFORMERR;
}
sysconfig->tries = valint;
} else if (ares_streq(key, "rotate")) {
sysconfig->rotate = ARES_TRUE;
} else if (ares_streq(key, "use-vc") || ares_streq(key, "usevc")) {
sysconfig->usevc = ARES_TRUE;
}
done:
ares_free_array(kv, num, ares_free);
return status;
}
ares_status_t ares_sysconfig_set_options(ares_sysconfig_t *sysconfig,
const char *str)
{
ares_buf_t *buf = NULL;
ares_array_t *options = NULL;
size_t num;
size_t i;
ares_status_t status;
buf = ares_buf_create_const((const unsigned char *)str, ares_strlen(str));
if (buf == NULL) {
return ARES_ENOMEM;
}
status = ares_buf_split(buf, (const unsigned char *)" \t", 2,
ARES_BUF_SPLIT_TRIM, 0, &options);
if (status != ARES_SUCCESS) {
goto done;
}
num = ares_array_len(options);
for (i = 0; i < num; i++) {
ares_buf_t **bufptr = ares_array_at(options, i);
ares_buf_t *valbuf = *bufptr;
status = process_option(sysconfig, valbuf);
/* Out of memory is the only fatal condition */
if (status == ARES_ENOMEM) {
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
status = ARES_SUCCESS;
done:
ares_array_destroy(options);
ares_buf_destroy(buf);
return status;
}
ares_status_t ares_init_by_environment(ares_sysconfig_t *sysconfig)
{
const char *localdomain;
const char *res_options;
ares_status_t status;
localdomain = getenv("LOCALDOMAIN");
if (localdomain) {
char *temp = ares_strdup(localdomain);
if (temp == NULL) {
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
status = config_search(sysconfig, temp, 1);
ares_free(temp);
if (status != ARES_SUCCESS) {
return status;
}
}
res_options = getenv("RES_OPTIONS");
if (res_options) {
status = ares_sysconfig_set_options(sysconfig, res_options);
if (status != ARES_SUCCESS) {
return status;
}
}
return ARES_SUCCESS;
}
/* Configuration Files:
* /etc/resolv.conf
* - All Unix-like systems
* - Comments start with ; or #
* - Lines have a keyword followed by a value that is interpreted specific
* to the keyword:
* - Keywords:
* - nameserver - IP address of nameserver with optional port (using a :
* prefix). If using an ipv6 address and specifying a port, the ipv6
* address must be encapsulated in brackets. For link-local ipv6
* addresses, the interface can also be specified with a % prefix. e.g.:
* "nameserver [fe80::1]:1234%iface"
* This keyword may be specified multiple times.
* - search - whitespace separated list of domains
* - domain - obsolete, same as search except only a single domain
* - lookup / hostresorder - local, bind, file, files
* - sortlist - whitespace separated ip-address/netmask pairs
* - options - options controlling resolver variables
* - ndots:n - set ndots option
* - timeout:n (retrans:n) - timeout per query attempt in seconds
* - attempts:n (retry:n) - number of times resolver will send query
* - rotate - round-robin selection of name servers
* - use-vc / usevc - force tcp
* /etc/nsswitch.conf
* - Modern Linux, FreeBSD, HP-UX, Solaris
* - Search order set via:
* "hosts: files dns mdns4_minimal mdns4"
* - files is /etc/hosts
* - dns is dns
* - mdns4_minimal does mdns only if ending in .local
* - mdns4 does not limit to domains ending in .local
* /etc/netsvc.conf
* - AIX
* - Search order set via:
* "hosts = local , bind"
* - bind is dns
* - local is /etc/hosts
* /etc/svc.conf
* - Tru64
* - Same format as /etc/netsvc.conf
* /etc/host.conf
* - Early FreeBSD, Early Linux
* - Not worth supporting, format varied based on system, FreeBSD used
* just a line per search order, Linux used "order " and a comma
* delimited list of "bind" and "hosts"
*/
/* This function will only return ARES_SUCCESS or ARES_ENOMEM. Any other
* conditions are ignored. Users may mess up config files, but we want to
* process anything we can. */
ares_status_t ares_sysconfig_parse_resolv_line(const ares_channel_t *channel,
ares_sysconfig_t *sysconfig,
ares_buf_t *line)
{
char option[32];
char value[512];
ares_status_t status = ARES_SUCCESS;
/* Ignore lines beginning with a comment */
if (ares_buf_begins_with(line, (const unsigned char *)"#", 1) ||
ares_buf_begins_with(line, (const unsigned char *)";", 1)) {
return ARES_SUCCESS;
}
ares_buf_tag(line);
/* Shouldn't be possible, but if it happens, ignore the line. */
if (ares_buf_consume_nonwhitespace(line) == 0) {
return ARES_SUCCESS;
}
status = ares_buf_tag_fetch_string(line, option, sizeof(option));
if (status != ARES_SUCCESS) {
return ARES_SUCCESS;
}
ares_buf_consume_whitespace(line, ARES_TRUE);
status = buf_fetch_string(line, value, sizeof(value));
if (status != ARES_SUCCESS) {
return ARES_SUCCESS;
}
ares_str_trim(value);
if (*value == 0) {
return ARES_SUCCESS;
}
/* At this point we have a string option and a string value, both trimmed
* of leading and trailing whitespace. Lets try to evaluate them */
if (ares_streq(option, "domain")) {
/* Domain is legacy, don't overwrite an existing config set by search */
if (sysconfig->domains == NULL) {
status = config_search(sysconfig, value, 1);
}
} else if (ares_streq(option, "lookup") ||
ares_streq(option, "hostresorder")) {
ares_buf_tag_rollback(line);
status = config_lookup(sysconfig, line, " \t");
} else if (ares_streq(option, "search")) {
status = config_search(sysconfig, value, 0);
} else if (ares_streq(option, "nameserver")) {
status = ares_sconfig_append_fromstr(channel, &sysconfig->sconfig, value,
ARES_TRUE);
} else if (ares_streq(option, "sortlist")) {
/* Ignore all failures except ENOMEM. If the sysadmin set a bad
* sortlist, just ignore the sortlist, don't cause an inoperable
* channel */
status =
ares_parse_sortlist(&sysconfig->sortlist, &sysconfig->nsortlist, value);
if (status != ARES_ENOMEM) {
status = ARES_SUCCESS;
}
} else if (ares_streq(option, "options")) {
status = ares_sysconfig_set_options(sysconfig, value);
}
return status;
}
/* This function will only return ARES_SUCCESS or ARES_ENOMEM. Any other
* conditions are ignored. Users may mess up config files, but we want to
* process anything we can. */
static ares_status_t parse_nsswitch_line(const ares_channel_t *channel,
ares_sysconfig_t *sysconfig,
ares_buf_t *line)
{
char option[32];
ares_status_t status = ARES_SUCCESS;
ares_array_t *sects = NULL;
ares_buf_t **bufptr;
ares_buf_t *buf;
(void)channel;
/* Ignore lines beginning with a comment */
if (ares_buf_begins_with(line, (const unsigned char *)"#", 1)) {
return ARES_SUCCESS;
}
/* database : values (space delimited) */
status = ares_buf_split(line, (const unsigned char *)":", 1,
ARES_BUF_SPLIT_TRIM, 2, &sects);
if (status != ARES_SUCCESS || ares_array_len(sects) != 2) {
goto done;
}
bufptr = ares_array_at(sects, 0);
buf = *bufptr;
status = buf_fetch_string(buf, option, sizeof(option));
if (status != ARES_SUCCESS) {
goto done;
}
/* Only support "hosts:" */
if (!ares_streq(option, "hosts")) {
goto done;
}
/* Values are space separated */
bufptr = ares_array_at(sects, 1);
buf = *bufptr;
status = config_lookup(sysconfig, buf, " \t");
done:
ares_array_destroy(sects);
if (status != ARES_ENOMEM) {
status = ARES_SUCCESS;
}
return status;
}
/* This function will only return ARES_SUCCESS or ARES_ENOMEM. Any other
* conditions are ignored. Users may mess up config files, but we want to
* process anything we can. */
static ares_status_t parse_svcconf_line(const ares_channel_t *channel,
ares_sysconfig_t *sysconfig,
ares_buf_t *line)
{
char option[32];
ares_buf_t **bufptr;
ares_buf_t *buf;
ares_status_t status = ARES_SUCCESS;
ares_array_t *sects = NULL;
(void)channel;
/* Ignore lines beginning with a comment */
if (ares_buf_begins_with(line, (const unsigned char *)"#", 1)) {
return ARES_SUCCESS;
}
/* database = values (comma delimited)*/
status = ares_buf_split(line, (const unsigned char *)"=", 1,
ARES_BUF_SPLIT_TRIM, 2, &sects);
if (status != ARES_SUCCESS || ares_array_len(sects) != 2) {
goto done;
}
bufptr = ares_array_at(sects, 0);
buf = *bufptr;
status = buf_fetch_string(buf, option, sizeof(option));
if (status != ARES_SUCCESS) {
goto done;
}
/* Only support "hosts=" */
if (!ares_streq(option, "hosts")) {
goto done;
}
/* Values are comma separated */
bufptr = ares_array_at(sects, 1);
buf = *bufptr;
status = config_lookup(sysconfig, buf, ",");
done:
ares_array_destroy(sects);
if (status != ARES_ENOMEM) {
status = ARES_SUCCESS;
}
return status;
}
ares_status_t ares_sysconfig_process_buf(const ares_channel_t *channel,
ares_sysconfig_t *sysconfig,
ares_buf_t *buf,
ares_sysconfig_line_cb_t cb)
{
ares_array_t *lines = NULL;
size_t num;
size_t i;
ares_status_t status;
status = ares_buf_split(buf, (const unsigned char *)"\n", 1,
ARES_BUF_SPLIT_TRIM, 0, &lines);
if (status != ARES_SUCCESS) {
goto done;
}
num = ares_array_len(lines);
for (i = 0; i < num; i++) {
ares_buf_t **bufptr = ares_array_at(lines, i);
ares_buf_t *line = *bufptr;
status = cb(channel, sysconfig, line);
if (status != ARES_SUCCESS) {
goto done;
}
}
done:
ares_array_destroy(lines);
return status;
}
/* Should only return:
* ARES_ENOTFOUND - file not found
* ARES_EFILE - error reading file (perms)
* ARES_ENOMEM - out of memory
* ARES_SUCCESS - file processed, doesn't necessarily mean it was a good
* file, but we're not erroring out if we can't parse
* something (or anything at all) */
static ares_status_t process_config_lines(const ares_channel_t *channel,
const char *filename,
ares_sysconfig_t *sysconfig,
ares_sysconfig_line_cb_t cb)
{
ares_status_t status = ARES_SUCCESS;
ares_buf_t *buf = NULL;
buf = ares_buf_create();
if (buf == NULL) {
status = ARES_ENOMEM;
goto done;
}
status = ares_buf_load_file(filename, buf);
if (status != ARES_SUCCESS) {
goto done;
}
status = ares_sysconfig_process_buf(channel, sysconfig, buf, cb);
done:
ares_buf_destroy(buf);
return status;
}
ares_status_t ares_init_sysconfig_files(const ares_channel_t *channel,
ares_sysconfig_t *sysconfig,
ares_bool_t process_resolvconf)
{
ares_status_t status = ARES_SUCCESS;
/* Resolv.conf */
if (process_resolvconf) {
status = process_config_lines(channel,
(channel->resolvconf_path != NULL)
? channel->resolvconf_path
: PATH_RESOLV_CONF,
sysconfig, ares_sysconfig_parse_resolv_line);
if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
goto done;
}
}
/* Nsswitch.conf */
status = process_config_lines(channel, "/etc/nsswitch.conf", sysconfig,
parse_nsswitch_line);
if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
goto done;
}
/* netsvc.conf */
status = process_config_lines(channel, "/etc/netsvc.conf", sysconfig,
parse_svcconf_line);
if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
goto done;
}
/* svc.conf */
status = process_config_lines(channel, "/etc/svc.conf", sysconfig,
parse_svcconf_line);
if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
goto done;
}
status = ARES_SUCCESS;
done:
return status;
}