Replace configuration file parsers with memory-safe parser (#725)

Rewrite configuration parsers using new memory safe parsing functions.
After CVE-2024-25629 its obvious that we need to prioritize again on
getting all the hand written parsers with direct pointer manipulation
replaced. They're just not safe and hard to audit. It was yet another
example of 20+yr old code having a memory safety issue just now coming
to light.

Though these parsers are definitely less efficient, they're written with
memory safety in mind, and any performance difference is going to be
meaningless for something that only happens once a while.

Fix By: Brad House (@bradh352)
pull/727/head
Brad House 11 months ago committed by GitHub
parent e4a4346596
commit a2a8578ee0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      src/lib/Makefile.inc
  2. 227
      src/lib/ares__buf.c
  3. 35
      src/lib/ares__buf.h
  4. 90
      src/lib/ares__hosts_file.c
  5. 90
      src/lib/ares__read_line.c
  6. 4
      src/lib/ares_private.h
  7. 155
      src/lib/ares_search.c
  8. 116
      src/lib/ares_str.c
  9. 17
      src/lib/ares_str.h
  10. 2
      src/lib/ares_strsplit.c
  11. 4
      src/lib/ares_sysconfig.c
  12. 737
      src/lib/ares_sysconfig_files.c
  13. 10
      src/lib/ares_update_servers.c
  14. 26
      test/ares-test-init.cc
  15. 43
      test/ares-test-internal.cc

@ -13,7 +13,6 @@ CSOURCES = ares__addrinfo2hostent.c \
ares__iface_ips.c \
ares__llist.c \
ares__parse_into_addrinfo.c \
ares__read_line.c \
ares__slist.c \
ares__socket.c \
ares__sortaddrinfo.c \

@ -45,47 +45,6 @@ struct ares__buf {
* SIZE_MAX if not set. */
};
ares_bool_t ares__isprint(int ch)
{
if (ch >= 0x20 && ch <= 0x7E) {
return ARES_TRUE;
}
return ARES_FALSE;
}
/* Character set allowed by hostnames. This is to include the normal
* domain name character set plus:
* - underscores which are used in SRV records.
* - Forward slashes such as are used for classless in-addr.arpa
* delegation (CNAMEs)
* - Asterisks may be used for wildcard domains in CNAMEs as seen in the
* real world.
* While RFC 2181 section 11 does state not to do validation,
* that applies to servers, not clients. Vulnerabilities have been
* reported when this validation is not performed. Security is more
* important than edge-case compatibility (which is probably invalid
* anyhow). */
ares_bool_t ares__is_hostnamech(int ch)
{
/* [A-Za-z0-9-*._/]
* Don't use isalnum() as it is locale-specific
*/
if (ch >= 'A' && ch <= 'Z') {
return ARES_TRUE;
}
if (ch >= 'a' && ch <= 'z') {
return ARES_TRUE;
}
if (ch >= '0' && ch <= '9') {
return ARES_TRUE;
}
if (ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '*') {
return ARES_TRUE;
}
return ARES_FALSE;
}
ares__buf_t *ares__buf_create(void)
{
ares__buf_t *buf = ares_malloc_zero(sizeof(*buf));
@ -630,6 +589,24 @@ ares_status_t ares__buf_fetch_bytes_into_buf(ares__buf_t *buf,
return ares__buf_consume(buf, len);
}
static ares_bool_t ares__is_whitespace(unsigned char c,
ares_bool_t include_linefeed)
{
switch (c) {
case '\r':
case '\t':
case ' ':
case '\v':
case '\f':
return ARES_TRUE;
case '\n':
return include_linefeed;
default:
break;
}
return ARES_FALSE;
}
size_t ares__buf_consume_whitespace(ares__buf_t *buf,
ares_bool_t include_linefeed)
{
@ -642,24 +619,11 @@ size_t ares__buf_consume_whitespace(ares__buf_t *buf,
}
for (i = 0; i < remaining_len; i++) {
switch (ptr[i]) {
case '\r':
case '\t':
case ' ':
case '\v':
case '\f':
break;
case '\n':
if (!include_linefeed) {
goto done;
}
break;
default:
goto done;
if (!ares__is_whitespace(ptr[i], include_linefeed)) {
break;
}
}
done:
if (i > 0) {
ares__buf_consume(buf, i);
}
@ -677,20 +641,11 @@ size_t ares__buf_consume_nonwhitespace(ares__buf_t *buf)
}
for (i = 0; i < remaining_len; i++) {
switch (ptr[i]) {
case '\r':
case '\t':
case ' ':
case '\v':
case '\f':
case '\n':
goto done;
default:
break;
if (ares__is_whitespace(ptr[i], ARES_TRUE)) {
break;
}
}
done:
if (i > 0) {
ares__buf_consume(buf, i);
}
@ -826,7 +781,7 @@ static ares_bool_t ares__buf_split_isduplicate(ares__llist_t *list,
ares_status_t ares__buf_split(ares__buf_t *buf, const unsigned char *delims,
size_t delims_len, ares__buf_split_t flags,
ares__llist_t **list)
size_t max_sections, ares__llist_t **list)
{
ares_status_t status = ARES_SUCCESS;
ares_bool_t first = ARES_TRUE;
@ -842,20 +797,57 @@ ares_status_t ares__buf_split(ares__buf_t *buf, const unsigned char *delims,
}
while (ares__buf_len(buf)) {
size_t len;
size_t len = 0;
const unsigned char *ptr;
if (first) {
/* No delimiter yet, just tag the start */
ares__buf_tag(buf);
} else {
if (flags & ARES_BUF_SPLIT_DONT_CONSUME_DELIMS) {
/* tag then eat delimiter so its first byte in buffer */
ares__buf_tag(buf);
ares__buf_consume(buf, 1);
} else {
/* throw away delimiter */
ares__buf_consume(buf, 1);
ares__buf_tag(buf);
}
}
if (max_sections && ares__llist_len(*list) >= max_sections - 1) {
ares__buf_consume(buf, ares__buf_len(buf));
} else {
ares__buf_consume_until_charset(buf, delims, delims_len, ARES_FALSE);
}
ares__buf_tag(buf);
ptr = ares__buf_tag_fetch(buf, &len);
len = ares__buf_consume_until_charset(buf, delims, delims_len, ARES_FALSE);
/* Shouldn't be possible */
if (ptr == NULL) {
status = ARES_EFORMERR;
goto done;
}
if (flags & ARES_BUF_SPLIT_LTRIM) {
size_t i;
for (i = 0; i < len; i++) {
if (!ares__is_whitespace(ptr[i], ARES_TRUE)) {
break;
}
}
ptr += i;
len -= i;
}
/* Don't treat a delimiter as part of the length */
if (!first && len && flags & ARES_BUF_SPLIT_DONT_CONSUME_DELIMS) {
len--;
if (flags & ARES_BUF_SPLIT_RTRIM) {
while (len && ares__is_whitespace(ptr[len - 1], ARES_TRUE)) {
len--;
}
}
if (len != 0 || flags & ARES_BUF_SPLIT_ALLOW_BLANK) {
const unsigned char *ptr = ares__buf_tag_fetch(buf, &len);
ares__buf_t *data;
ares__buf_t *data;
if (!(flags & ARES_BUF_SPLIT_NO_DUPLICATES) ||
!ares__buf_split_isduplicate(*list, ptr, len, flags)) {
@ -880,12 +872,6 @@ ares_status_t ares__buf_split(ares__buf_t *buf, const unsigned char *delims,
}
}
if (!(flags & ARES_BUF_SPLIT_DONT_CONSUME_DELIMS) &&
ares__buf_len(buf) != 0) {
/* Consume delimiter */
ares__buf_consume(buf, 1);
}
first = ARES_FALSE;
}
@ -1150,3 +1136,80 @@ ares_status_t ares__buf_hexdump(ares__buf_t *buf, const unsigned char *data,
return ARES_SUCCESS;
}
ares_status_t ares__buf_load_file(const char *filename, ares__buf_t *buf)
{
FILE *fp = NULL;
unsigned char *ptr = NULL;
size_t len = 0;
size_t ptr_len = 0;
long ftell_len = 0;
ares_status_t status;
if (filename == NULL || buf == NULL) {
return ARES_EFORMERR;
}
fp = fopen(filename, "rb");
if (fp == NULL) {
int error = ERRNO;
switch (error) {
case ENOENT:
case ESRCH:
status = ARES_ENOTFOUND;
goto done;
default:
DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
strerror(error)));
DEBUGF(fprintf(stderr, "Error opening file: %s\n", filename));
status = ARES_EFILE;
goto done;
}
}
/* Get length portably, fstat() is POSIX, not C */
if (fseek(fp, 0, SEEK_END) != 0) {
status = ARES_EFILE;
goto done;
}
ftell_len = ftell(fp);
if (ftell_len < 0) {
status = ARES_EFILE;
goto done;
}
len = (size_t)ftell_len;
if (fseek(fp, 0, SEEK_SET) != 0) {
status = ARES_EFILE;
goto done;
}
if (len == 0) {
status = ARES_SUCCESS;
goto done;
}
/* Read entire data into buffer */
ptr_len = len;
ptr = ares__buf_append_start(buf, &ptr_len);
if (ptr == NULL) {
status = ARES_ENOMEM;
goto done;
}
ptr_len = fread(ptr, 1, len, fp);
if (ptr_len != len) {
status = ARES_EFILE;
goto done;
}
ares__buf_append_finish(buf, len);
status = ARES_SUCCESS;
done:
if (fp != NULL) {
fclose(fp);
}
return status;
}

@ -373,7 +373,8 @@ size_t ares__buf_consume_whitespace(ares__buf_t *buf,
size_t ares__buf_consume_nonwhitespace(ares__buf_t *buf);
/*! Consume until a character in the character set provided is reached
/*! Consume until a character in the character set provided is reached. Does
* not include the character from the charset at the end.
*
* \param[in] buf Initialized buffer object
* \param[in] charset character set
@ -414,7 +415,9 @@ typedef enum {
/*! No flags */
ARES_BUF_SPLIT_NONE = 0,
/*! The delimiter will be the first character in the buffer, except the
* first buffer since the start doesn't have a delimiter
* first buffer since the start doesn't have a delimiter. This option is
* incompatible with ARES_BUF_SPLIT_LTRIM since the delimiter is always
* the first character.
*/
ARES_BUF_SPLIT_DONT_CONSUME_DELIMS = 1 << 0,
/*! Allow blank sections, by default blank sections are not emitted. If using
@ -424,7 +427,13 @@ typedef enum {
/*! Remove duplicate entries */
ARES_BUF_SPLIT_NO_DUPLICATES = 1 << 2,
/*! Perform case-insensitive matching when comparing values */
ARES_BUF_SPLIT_CASE_INSENSITIVE = 1 << 3
ARES_BUF_SPLIT_CASE_INSENSITIVE = 1 << 3,
/*! Trim leading whitespace from buffer */
ARES_BUF_SPLIT_LTRIM = 1 << 4,
/*! Trim trailing whitespace from buffer */
ARES_BUF_SPLIT_RTRIM = 1 << 5,
/*! Trim leading and trailing whitespace from buffer */
ARES_BUF_SPLIT_TRIM = (ARES_BUF_SPLIT_LTRIM | ARES_BUF_SPLIT_RTRIM)
} ares__buf_split_t;
/*! Split the provided buffer into multiple sub-buffers stored in the variable
@ -435,6 +444,12 @@ typedef enum {
* \param[in] delims Possible delimiters
* \param[in] delims_len Length of possible delimiters
* \param[in] flags One more more flags
* \param[in] max_sections Maximum number of sections. Use 0 for
* unlimited. Useful for splitting key/value
* pairs where the delimiter may be a valid
* character in the value. A value of 1 would
* have little usefulness and would effectively
* ignore the delimiter itself.
* \param[out] list Result. Depending on flags, this may be a
* valid list with no elements. Use
* ares__llist_destroy() to free the memory which
@ -444,7 +459,7 @@ typedef enum {
*/
ares_status_t ares__buf_split(ares__buf_t *buf, const unsigned char *delims,
size_t delims_len, ares__buf_split_t flags,
ares__llist_t **list);
size_t max_sections, ares__llist_t **list);
/*! Check the unprocessed buffer to see if it begins with the sequence of
@ -567,6 +582,18 @@ ares_status_t ares__buf_parse_dns_str(ares__buf_t *buf, size_t remaining_len,
ares_status_t ares__buf_parse_dns_binstr(ares__buf_t *buf, size_t remaining_len,
unsigned char **bin, size_t *bin_len,
ares_bool_t allow_multiple);
/*! Load data from specified file path into provided buffer. The entire file
* is loaded into memory.
*
* \param[in] filename complete path to file
* \param[in,out] buf Initialized (non-const) buffer object to load data
* into
* \return ARES_ENOTFOUND if file not found, ARES_EFILE if issues reading
* file, ARES_ENOMEM if out of memory, ARES_SUCCESS on success.
*/
ares_status_t ares__buf_load_file(const char *filename, ares__buf_t *buf);
/*! @} */
#endif /* __ARES__BUF_H */

@ -98,94 +98,6 @@ struct ares_hosts_entry {
ares__llist_t *hosts;
};
static ares_status_t ares__read_file_into_buf(const char *filename,
ares__buf_t *buf)
{
FILE *fp = NULL;
unsigned char *ptr = NULL;
size_t len = 0;
size_t ptr_len = 0;
long ftell_len = 0;
ares_status_t status;
if (filename == NULL || buf == NULL) {
return ARES_EFORMERR;
}
fp = fopen(filename, "rb");
if (fp == NULL) {
int error = ERRNO;
switch (error) {
case ENOENT:
case ESRCH:
status = ARES_ENOTFOUND;
goto done;
default:
DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
strerror(error)));
DEBUGF(fprintf(stderr, "Error opening file: %s\n", filename));
status = ARES_EFILE;
goto done;
}
}
/* Get length portably, fstat() is POSIX, not C */
if (fseek(fp, 0, SEEK_END) != 0) {
status = ARES_EFILE;
goto done;
}
ftell_len = ftell(fp);
if (ftell_len < 0) {
status = ARES_EFILE;
goto done;
}
len = (size_t)ftell_len;
if (fseek(fp, 0, SEEK_SET) != 0) {
status = ARES_EFILE;
goto done;
}
if (len == 0) {
status = ARES_SUCCESS;
goto done;
}
/* Read entire data into buffer */
ptr_len = len;
ptr = ares__buf_append_start(buf, &ptr_len);
if (ptr == NULL) {
status = ARES_ENOMEM;
goto done;
}
ptr_len = fread(ptr, 1, len, fp);
if (ptr_len != len) {
status = ARES_EFILE;
goto done;
}
ares__buf_append_finish(buf, len);
status = ARES_SUCCESS;
done:
if (fp != NULL) {
fclose(fp);
}
return status;
}
static ares_bool_t ares__is_hostname(const char *str)
{
size_t i;
for (i = 0; str[i] != 0; i++) {
if (!ares__is_hostnamech(str[i])) {
return ARES_FALSE;
}
}
return ARES_TRUE;
}
const void *ares_dns_pton(const char *ipaddr, struct ares_addr *addr,
size_t *out_len)
@ -605,7 +517,7 @@ static ares_status_t ares__parse_hosts(const char *filename,
goto done;
}
status = ares__read_file_into_buf(filename, buf);
status = ares__buf_load_file(filename, buf);
if (status != ARES_SUCCESS) {
goto done;
}

@ -1,90 +0,0 @@
/* MIT License
*
* Copyright (c) 1998 Massachusetts Institute of Technology
* Copyright (c) The c-ares project and its contributors
*
* 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_setup.h"
#include "ares.h"
#include "ares_private.h"
/* This is an internal function. Its contract is to read a line from
* a file into a dynamically allocated buffer, zeroing the trailing
* newline if there is one. The calling routine may call
* ares__read_line multiple times with the same buf and bufsize
* pointers; *buf will be reallocated and *bufsize adjusted as
* appropriate. The initial value of *buf should be NULL. After the
* calling routine is done reading lines, it should free *buf.
*/
ares_status_t ares__read_line(FILE *fp, char **buf, size_t *bufsize)
{
char *newbuf;
size_t offset = 0;
size_t len;
if (*buf == NULL) {
*buf = ares_malloc(128);
if (!*buf) {
return ARES_ENOMEM;
}
*bufsize = 128;
}
for (;;) {
int bytestoread = (int)(*bufsize - offset);
if (!fgets(*buf + offset, bytestoread, fp)) {
return (offset != 0) ? 0 : (ferror(fp)) ? ARES_EFILE : ARES_EOF;
}
len = offset + ares_strlen(*buf + offset);
/* Probably means there was an embedded NULL as the first character in
* the line, throw away line */
if (len == 0) {
offset = 0;
continue;
}
if ((*buf)[len - 1] == '\n') {
(*buf)[len - 1] = 0;
break;
}
offset = len;
if (len < *bufsize - 1) {
continue;
}
/* Allocate more space. */
newbuf = ares_realloc(*buf, *bufsize * 2);
if (!newbuf) {
ares_free(*buf);
*buf = NULL;
return ARES_ENOMEM;
}
*buf = newbuf;
*bufsize *= 2;
}
return ARES_SUCCESS;
}

@ -350,7 +350,6 @@ void ares__close_connection(struct server_connection *conn);
void ares__close_sockets(struct server_state *server);
void ares__check_cleanup_conn(const ares_channel_t *channel,
struct server_connection *conn);
ares_status_t ares__read_line(FILE *fp, char **buf, size_t *bufsize);
void ares__free_query(struct query *query);
ares_rand_state *ares__init_rand_state(void);
@ -391,6 +390,7 @@ typedef struct {
size_t tries;
ares_bool_t rotate;
size_t timeout_ms;
ares_bool_t usevc;
} ares_sysconfig_t;
ares_status_t ares__init_by_environment(ares_sysconfig_t *sysconfig);
@ -458,7 +458,6 @@ ares_ssize_t ares__socket_recv(ares_channel_t *channel, ares_socket_t s,
void ares__close_socket(ares_channel, ares_socket_t);
int ares__connect_socket(ares_channel_t *channel, ares_socket_t sockfd,
const struct sockaddr *addr, ares_socklen_t addrlen);
ares_bool_t ares__is_hostnamech(int ch);
void ares__destroy_server(struct server_state *server);
ares_status_t ares__servers_update(ares_channel_t *channel,
@ -494,7 +493,6 @@ ares_status_t ares__hosts_entry_to_addrinfo(const ares_hosts_entry_t *entry,
unsigned short port,
ares_bool_t want_cnames,
struct ares_addrinfo *ai);
ares_bool_t ares__isprint(int ch);
/*! Parse a compressed DNS name as defined in RFC1035 starting at the current

@ -260,6 +260,101 @@ ares_status_t ares__cat_domain(const char *name, const char *domain, char **s)
return ARES_SUCCESS;
}
static ares_status_t ares__lookup_hostaliases(const char *name, char **alias)
{
ares_status_t status = ARES_SUCCESS;
const char *hostaliases = getenv("HOSTALIASES");
ares__buf_t *buf = NULL;
ares__llist_t *lines = NULL;
ares__llist_node_t *node;
*alias = NULL;
if (hostaliases == NULL) {
status = ARES_ENOTFOUND;
goto done;
}
buf = ares__buf_create();
if (buf == NULL) {
status = ARES_ENOMEM;
goto done;
}
status = ares__buf_load_file(hostaliases, buf);
if (status != ARES_SUCCESS) {
goto done;
}
/* The HOSTALIASES file is structured as one alias per line. The first
* field in the line is the simple hostname with no periods, followed by
* whitespace, then the full domain name, e.g.:
*
* c-ares www.c-ares.org
* curl www.curl.se
*/
status = ares__buf_split(buf, (const unsigned char *)"\n", 1,
ARES_BUF_SPLIT_TRIM, 0, &lines);
if (status != ARES_SUCCESS) {
goto done;
}
for (node = ares__llist_node_first(lines); node != NULL;
node = ares__llist_node_next(node)) {
ares__buf_t *line = ares__llist_node_val(node);
char hostname[64] = "";
char fqdn[256] = "";
/* Pull off hostname */
ares__buf_tag(line);
ares__buf_consume_nonwhitespace(line);
if (ares__buf_tag_fetch_string(line, hostname, sizeof(hostname)) !=
ARES_SUCCESS) {
continue;
}
/* Match hostname */
if (strcasecmp(hostname, name) != 0) {
continue;
}
/* consume whitespace */
ares__buf_consume_whitespace(line, ARES_TRUE);
/* pull off fqdn */
ares__buf_tag(line);
ares__buf_consume_nonwhitespace(line);
if (ares__buf_tag_fetch_string(line, fqdn, sizeof(fqdn)) != ARES_SUCCESS ||
ares_strlen(fqdn) == 0) {
continue;
}
/* Validate characterset */
if (!ares__is_hostname(fqdn)) {
continue;
}
*alias = ares_strdup(fqdn);
if (*alias == NULL) {
status = ARES_ENOMEM;
goto done;
}
/* Good! */
status = ARES_SUCCESS;
goto done;
}
status = ARES_ENOTFOUND;
done:
ares__buf_destroy(buf);
ares__llist_destroy(lines);
return status;
}
/* Determine if this name only yields one query. If it does, set *s to
* the string we should query, in an allocated buffer. If not, set *s
* to NULL.
@ -268,14 +363,9 @@ ares_status_t ares__single_domain(const ares_channel_t *channel,
const char *name, char **s)
{
size_t len = ares_strlen(name);
const char *hostaliases;
FILE *fp;
char *line = NULL;
ares_status_t status;
size_t linesize;
const char *p;
const char *q;
int error;
*s = NULL;
/* If the name contains a trailing dot, then the single query is the name
* sans the trailing dot.
@ -286,54 +376,9 @@ ares_status_t ares__single_domain(const ares_channel_t *channel,
}
if (!(channel->flags & ARES_FLAG_NOALIASES) && !strchr(name, '.')) {
/* The name might be a host alias. */
hostaliases = getenv("HOSTALIASES");
if (hostaliases) {
fp = fopen(hostaliases, "r");
if (fp) {
while ((status = ares__read_line(fp, &line, &linesize)) ==
ARES_SUCCESS) {
if (strncasecmp(line, name, len) != 0 || !ISSPACE(line[len])) {
continue;
}
p = line + len;
while (ISSPACE(*p)) {
p++;
}
if (*p) {
q = p + 1;
while (*q && !ISSPACE(*q)) {
q++;
}
*s = ares_malloc((size_t)(q - p + 1));
if (*s) {
memcpy(*s, p, (size_t)(q - p));
(*s)[q - p] = 0;
}
ares_free(line);
fclose(fp);
return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
}
}
ares_free(line);
fclose(fp);
if (status != ARES_SUCCESS && status != ARES_EOF) {
return status;
}
} else {
error = ERRNO;
switch (error) {
case ENOENT:
case ESRCH:
break;
default:
DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
strerror(error)));
DEBUGF(fprintf(stderr, "Error opening file: %s\n", hostaliases));
*s = NULL;
return ARES_EFILE;
}
}
status = ares__lookup_hostaliases(name, s);
if (status != ARES_ENOTFOUND) {
return status;
}
}

@ -110,6 +110,54 @@ ares_bool_t ares_str_isnum(const char *str)
return ARES_TRUE;
}
void ares__str_rtrim(char *str)
{
size_t len;
size_t i;
if (str == NULL) {
return;
}
len = ares_strlen(str);
for (i = len; i > 0; i--) {
if (!ares__isspace(str[i - 1])) {
break;
}
}
str[i] = 0;
}
void ares__str_ltrim(char *str)
{
size_t i;
size_t len;
if (str == NULL) {
return;
}
for (i = 0; str[i] != 0 && ares__isspace(str[i]); i++) {
/* Do nothing */
}
if (i == 0) {
return;
}
len = ares_strlen(str);
if (i != len) {
memmove(str, str + i, len - i);
}
str[len - i] = 0;
}
void ares__str_trim(char *str)
{
ares__str_ltrim(str);
ares__str_rtrim(str);
}
/* tolower() is locale-specific. Use a lookup table fast conversion that only
* operates on ASCII */
static const unsigned char ares__tolower_lookup[] = {
@ -151,3 +199,71 @@ ares_bool_t ares__memeq_ci(const unsigned char *ptr, const unsigned char *val,
}
return ARES_TRUE;
}
ares_bool_t ares__isspace(int ch)
{
switch (ch) {
case '\r':
case '\t':
case ' ':
case '\v':
case '\f':
case '\n':
return ARES_TRUE;
default:
break;
}
return ARES_FALSE;
}
ares_bool_t ares__isprint(int ch)
{
if (ch >= 0x20 && ch <= 0x7E) {
return ARES_TRUE;
}
return ARES_FALSE;
}
/* Character set allowed by hostnames. This is to include the normal
* domain name character set plus:
* - underscores which are used in SRV records.
* - Forward slashes such as are used for classless in-addr.arpa
* delegation (CNAMEs)
* - Asterisks may be used for wildcard domains in CNAMEs as seen in the
* real world.
* While RFC 2181 section 11 does state not to do validation,
* that applies to servers, not clients. Vulnerabilities have been
* reported when this validation is not performed. Security is more
* important than edge-case compatibility (which is probably invalid
* anyhow). */
ares_bool_t ares__is_hostnamech(int ch)
{
/* [A-Za-z0-9-*._/]
* Don't use isalnum() as it is locale-specific
*/
if (ch >= 'A' && ch <= 'Z') {
return ARES_TRUE;
}
if (ch >= 'a' && ch <= 'z') {
return ARES_TRUE;
}
if (ch >= '0' && ch <= '9') {
return ARES_TRUE;
}
if (ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '*') {
return ARES_TRUE;
}
return ARES_FALSE;
}
ares_bool_t ares__is_hostname(const char *str)
{
size_t i;
for (i = 0; str[i] != 0; i++) {
if (!ares__is_hostnamech(str[i])) {
return ARES_FALSE;
}
}
return ARES_TRUE;
}

@ -24,8 +24,8 @@
*
* SPDX-License-Identifier: MIT
*/
#ifndef HEADER_CARES_STRDUP_H
#define HEADER_CARES_STRDUP_H
#ifndef __ARES_STR_H
#define __ARES_STR_H
#include "ares_setup.h"
#include "ares.h"
@ -48,8 +48,19 @@ size_t ares_strcpy(char *dest, const char *src, size_t dest_size);
ares_bool_t ares_str_isnum(const char *str);
void ares__str_ltrim(char *str);
void ares__str_rtrim(char *str);
void ares__str_trim(char *str);
unsigned char ares__tolower(unsigned char c);
ares_bool_t ares__memeq_ci(const unsigned char *ptr, const unsigned char *val,
size_t len);
#endif /* HEADER_CARES_STRDUP_H */
ares_bool_t ares__isspace(int ch);
ares_bool_t ares__isprint(int ch);
ares_bool_t ares__is_hostnamech(int ch);
ares_bool_t ares__is_hostname(const char *str);
#endif /* __ARES_STR_H */

@ -94,7 +94,7 @@ char **ares__strsplit(const char *in, const char *delms, size_t *num_elm)
status = ares__buf_split(
buf, (const unsigned char *)delms, ares_strlen(delms),
ARES_BUF_SPLIT_NO_DUPLICATES | ARES_BUF_SPLIT_CASE_INSENSITIVE, &llist);
ARES_BUF_SPLIT_NO_DUPLICATES | ARES_BUF_SPLIT_CASE_INSENSITIVE, 0, &llist);
if (status != ARES_SUCCESS) {
goto done;
}

@ -1059,6 +1059,10 @@ static ares_status_t ares_sysconfig_apply(ares_channel_t *channel,
channel->rotate = sysconfig->rotate;
}
if (sysconfig->usevc) {
channel->flags |= ARES_FLAG_USEVC;
}
return ARES_SUCCESS;
}

@ -134,7 +134,7 @@ static ares_status_t parse_sort(ares__buf_t *buf, struct apattern *pat)
ares__buf_tag(buf);
/* Consume ip address */
if (ares__buf_consume_charset(buf, ip_charset, sizeof(ip_charset)) == 0) {
if (ares__buf_consume_charset(buf, ip_charset, sizeof(ip_charset) - 1) == 0) {
return ARES_EBADSTR;
}
@ -162,8 +162,8 @@ static ares_status_t parse_sort(ares__buf_t *buf, struct apattern *pat)
ares__buf_tag(buf);
/* Consume mask */
if (ares__buf_consume_charset(buf, ipv4_charset, sizeof(ipv4_charset)) ==
0) {
if (ares__buf_consume_charset(buf, ipv4_charset,
sizeof(ipv4_charset) - 1) == 0) {
return ARES_EBADSTR;
}
@ -241,7 +241,7 @@ ares_status_t ares__parse_sortlist(struct apattern **sortlist, size_t *nsort,
/* Split on space or semicolon */
status = ares__buf_split(buf, (const unsigned char *)" ;", 2,
ARES_BUF_SPLIT_NONE, &list);
ARES_BUF_SPLIT_NONE, 0, &list);
if (status != ARES_SUCCESS) {
goto done;
}
@ -282,7 +282,8 @@ done:
return status;
}
static ares_status_t config_search(ares_sysconfig_t *sysconfig, const char *str)
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 */
@ -296,410 +297,518 @@ static ares_status_t config_search(ares_sysconfig_t *sysconfig, const char *str)
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 config_domain(ares_sysconfig_t *sysconfig, char *str)
static ares_status_t buf_fetch_string(ares__buf_t *buf, char *str,
size_t str_len)
{
char *q;
/* Set a single search domain. */
q = str;
while (*q && !ISSPACE(*q)) {
q++;
}
*q = '\0';
ares_status_t status;
ares__buf_tag(buf);
ares__buf_consume(buf, ares__buf_len(buf));
return config_search(sysconfig, str);
status = ares__buf_tag_fetch_string(buf, str, str_len);
return status;
}
static ares_status_t config_lookup(ares_sysconfig_t *sysconfig, const char *str,
const char *bindch, const char *altbindch,
const char *filech)
static ares_status_t config_lookup(ares_sysconfig_t *sysconfig,
ares__buf_t *buf, const char *separators)
{
char lookups[3];
char *l;
const char *p;
ares_bool_t found;
if (altbindch == NULL) {
altbindch = bindch;
ares_status_t status;
char lookupstr[32];
size_t lookupstr_cnt = 0;
ares__llist_t *lookups = NULL;
ares__llist_node_t *node;
size_t separators_len = ares_strlen(separators);
status = ares__buf_split(buf, (const unsigned char *)separators,
separators_len, ARES_BUF_SPLIT_TRIM, 0, &lookups);
if (status != ARES_SUCCESS) {
goto done;
}
/* Set the lookup order. Only the first letter of each work
* is relevant, and it has to be "b" for DNS or "f" for the
* host file. Ignore everything else.
*/
l = lookups;
p = str;
found = ARES_FALSE;
while (*p) {
if ((*p == *bindch || *p == *altbindch || *p == *filech) &&
l < lookups + 2) {
if (*p == *bindch || *p == *altbindch) {
*l++ = 'b';
} else {
*l++ = 'f';
}
found = ARES_TRUE;
memset(lookupstr, 0, sizeof(lookupstr));
for (node = ares__llist_node_first(lookups); node != NULL;
node = ares__llist_node_next(node)) {
char value[128];
char ch;
ares__buf_t *valbuf = ares__llist_node_val(node);
status = buf_fetch_string(valbuf, value, sizeof(value));
if (status != ARES_SUCCESS) {
continue;
}
while (*p && !ISSPACE(*p) && (*p != ',')) {
p++;
if (strcasecmp(value, "dns") == 0 || strcasecmp(value, "bind") == 0 ||
strcasecmp(value, "resolv") == 0 || strcasecmp(value, "resolve") == 0) {
ch = 'b';
} else if (strcasecmp(value, "files") == 0 ||
strcasecmp(value, "file") == 0 ||
strcasecmp(value, "local") == 0) {
ch = 'f';
} else {
continue;
}
while (*p && (ISSPACE(*p) || (*p == ','))) {
p++;
/* Look for a duplicate and ignore */
if (memchr(lookupstr, ch, lookupstr_cnt) == NULL) {
lookupstr[lookupstr_cnt++] = ch;
}
}
if (!found) {
return ARES_ENOTINITIALIZED;
if (lookupstr_cnt) {
ares_free(sysconfig->lookups);
sysconfig->lookups = ares_strdup(lookupstr);
if (sysconfig->lookups == NULL) {
return ARES_ENOMEM;
}
}
*l = '\0';
ares_free(sysconfig->lookups);
sysconfig->lookups = ares_strdup(lookups);
if (sysconfig->lookups == NULL) {
return ARES_ENOMEM;
status = ARES_SUCCESS;
done:
if (status != ARES_ENOMEM) {
status = ARES_SUCCESS;
}
return ARES_SUCCESS;
ares__llist_destroy(lookups);
return status;
}
static const char *try_option(const char *p, const char *q, const char *opt)
static ares_status_t process_option(ares_sysconfig_t *sysconfig,
ares__buf_t *option)
{
size_t len = ares_strlen(opt);
return ((size_t)(q - p) >= len && !strncmp(p, opt, len)) ? &p[len] : NULL;
ares__llist_t *kv = NULL;
char key[32] = "";
char val[32] = "";
unsigned int valint = 0;
ares_status_t status;
/* Split on : */
status = ares__buf_split(option, (const unsigned char *)":", 1,
ARES_BUF_SPLIT_TRIM, 2, &kv);
if (status != ARES_SUCCESS) {
goto done;
}
status = buf_fetch_string(ares__llist_first_val(kv), key, sizeof(key));
if (status != ARES_SUCCESS) {
goto done;
}
if (ares__llist_len(kv) == 2) {
status = buf_fetch_string(ares__llist_last_val(kv), val, sizeof(val));
if (status != ARES_SUCCESS) {
goto done;
}
valint = (unsigned int)strtoul(val, NULL, 10);
}
if (strcmp(key, "ndots") == 0) {
if (valint == 0) {
return ARES_EFORMERR;
}
sysconfig->ndots = valint;
} else if (strcmp(key, "retrans") == 0 || strcmp(key, "timeout") == 0) {
if (valint == 0) {
return ARES_EFORMERR;
}
sysconfig->timeout_ms = valint * 1000;
} else if (strcmp(key, "retry") == 0 || strcmp(key, "attempts") == 0) {
if (valint == 0) {
return ARES_EFORMERR;
}
sysconfig->tries = valint;
} else if (strcmp(key, "rotate") == 0) {
sysconfig->rotate = ARES_TRUE;
} else if (strcmp(key, "use-vc") == 0 || strcmp(key, "usevc") == 0) {
sysconfig->usevc = ARES_TRUE;
}
done:
ares__llist_destroy(kv);
return status;
}
static ares_status_t set_options(ares_sysconfig_t *sysconfig, const char *str)
{
const char *p;
const char *q;
const char *val;
ares__buf_t *buf = NULL;
ares__llist_t *options = NULL;
ares_status_t status;
ares__llist_node_t *node;
if (str == NULL) {
return ARES_SUCCESS;
buf = ares__buf_create_const((const unsigned char *)str, ares_strlen(str));
if (buf == NULL) {
return ARES_ENOMEM;
}
p = str;
while (*p) {
q = p;
while (*q && !ISSPACE(*q)) {
q++;
}
val = try_option(p, q, "ndots:");
if (val) {
sysconfig->ndots = strtoul(val, NULL, 10);
}
status = ares__buf_split(buf, (const unsigned char *)" \t", 2,
ARES_BUF_SPLIT_TRIM, 0, &options);
if (status != ARES_SUCCESS) {
goto done;
}
// Outdated option.
val = try_option(p, q, "retrans:");
if (val) {
sysconfig->timeout_ms = strtoul(val, NULL, 10);
}
for (node = ares__llist_node_first(options); node != NULL;
node = ares__llist_node_next(node)) {
ares__buf_t *valbuf = ares__llist_node_val(node);
val = try_option(p, q, "timeout:");
if (val) {
sysconfig->timeout_ms = strtoul(val, NULL, 10) * 1000;
status = process_option(sysconfig, valbuf);
/* Out of memory is the only fatal condition */
if (status == ARES_ENOMEM) {
goto done;
}
}
// Outdated option.
val = try_option(p, q, "retry:");
if (val) {
sysconfig->tries = strtoul(val, NULL, 10);
}
status = ARES_SUCCESS;
val = try_option(p, q, "attempts:");
if (val) {
sysconfig->tries = strtoul(val, NULL, 10);
}
done:
ares__llist_destroy(options);
ares__buf_destroy(buf);
return status;
}
val = try_option(p, q, "rotate");
if (val) {
sysconfig->rotate = ARES_TRUE;
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;
}
status = config_search(sysconfig, temp, 1);
ares_free(temp);
if (status != ARES_SUCCESS) {
return status;
}
}
p = q;
while (ISSPACE(*p)) {
p++;
res_options = getenv("RES_OPTIONS");
if (res_options) {
status = set_options(sysconfig, res_options);
if (status != ARES_SUCCESS) {
return status;
}
}
return ARES_SUCCESS;
}
static char *try_config(char *s, const char *opt, char scc)
/* 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. */
static ares_status_t parse_resolvconf_line(ares_sysconfig_t *sysconfig,
ares__buf_t *line)
{
size_t len;
char *p;
char *q;
char option[32];
char value[512];
ares_status_t status = ARES_SUCCESS;
if (!s || !opt) {
/* no line or no option */
return NULL; /* LCOV_EXCL_LINE */
/* 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;
}
/* Hash '#' character is always used as primary comment char, additionally
a not-NUL secondary comment char will be considered when specified. */
ares__buf_tag(line);
/* trim line comment */
p = s;
if (scc) {
while (*p && (*p != '#') && (*p != scc)) {
p++;
}
} else {
while (*p && (*p != '#')) {
p++;
}
/* Shouldn't be possible, but if it happens, ignore the line. */
if (ares__buf_consume_nonwhitespace(line) == 0) {
return ARES_SUCCESS;
}
*p = '\0';
/* trim trailing whitespace */
q = p - 1;
while ((q >= s) && ISSPACE(*q)) {
q--;
status = ares__buf_tag_fetch_string(line, option, sizeof(option));
if (status != ARES_SUCCESS) {
return ARES_SUCCESS;
}
*++q = '\0';
/* skip leading whitespace */
p = s;
while (*p && ISSPACE(*p)) {
p++;
}
ares__buf_consume_whitespace(line, ARES_TRUE);
if (!*p) {
/* empty line */
return NULL;
status = buf_fetch_string(line, value, sizeof(value));
if (status != ARES_SUCCESS) {
return ARES_SUCCESS;
}
if ((len = ares_strlen(opt)) == 0) {
/* empty option */
return NULL; /* LCOV_EXCL_LINE */
ares__str_trim(value);
if (*value == 0) {
return ARES_SUCCESS;
}
if (strncmp(p, opt, len) != 0) {
/* line and option do not match */
return NULL;
/* 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 (strcmp(option, "domain") == 0) {
/* Domain is legacy, don't overwrite an existing config set by search */
if (sysconfig->domains == NULL) {
status = config_search(sysconfig, value, 1);
}
} else if (strcmp(option, "lookup") == 0 ||
strcmp(option, "hostresorder") == 0) {
ares__buf_tag_rollback(line);
status = config_lookup(sysconfig, line, " \t");
} else if (strcmp(option, "search") == 0) {
status = config_search(sysconfig, value, 0);
} else if (strcmp(option, "nameserver") == 0) {
status =
ares__sconfig_append_fromstr(&sysconfig->sconfig, value, ARES_TRUE);
} else if (strcmp(option, "sortlist") == 0) {
/* 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 (strcmp(option, "options") == 0) {
status = set_options(sysconfig, value);
}
/* skip over given option name */
p += len;
return status;
}
if (!*p) {
/* no option value */
return NULL; /* LCOV_EXCL_LINE */
/* 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(ares_sysconfig_t *sysconfig,
ares__buf_t *line)
{
char option[32];
ares__buf_t *buf;
ares_status_t status = ARES_SUCCESS;
ares__llist_t *sects = NULL;
/* Ignore lines beginning with a comment */
if (ares__buf_begins_with(line, (const unsigned char *)"#", 1)) {
return ARES_SUCCESS;
}
if ((opt[len - 1] != ':') && (opt[len - 1] != '=') && !ISSPACE(*p)) {
/* whitespace between option name and value is mandatory
for given option names which do not end with ':' or '=' */
return NULL;
/* database : values (space delimited) */
status = ares__buf_split(line, (const unsigned char *)":", 1,
ARES_BUF_SPLIT_TRIM, 2, &sects);
if (status != ARES_SUCCESS || ares__llist_len(sects) != 2) {
goto done;
}
/* skip over whitespace */
while (*p && ISSPACE(*p)) {
p++;
buf = ares__llist_first_val(sects);
status = buf_fetch_string(buf, option, sizeof(option));
if (status != ARES_SUCCESS) {
goto done;
}
if (!*p) {
/* no option value */
return NULL;
/* Only support "hosts:" */
if (strcmp(option, "hosts") != 0) {
goto done;
}
/* return pointer to option value */
return p;
/* Values are space separated */
buf = ares__llist_last_val(sects);
status = config_lookup(sysconfig, buf, " \t");
done:
ares__llist_destroy(sects);
if (status != ARES_ENOMEM) {
status = ARES_SUCCESS;
}
return status;
}
ares_status_t ares__init_by_environment(ares_sysconfig_t *sysconfig)
/* 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(ares_sysconfig_t *sysconfig,
ares__buf_t *line)
{
const char *localdomain;
const char *res_options;
ares_status_t status;
char option[32];
ares__buf_t *buf;
ares_status_t status = ARES_SUCCESS;
ares__llist_t *sects = NULL;
localdomain = getenv("LOCALDOMAIN");
if (localdomain) {
char *temp = ares_strdup(localdomain);
if (temp == NULL) {
return ARES_ENOMEM;
}
status = config_domain(sysconfig, temp);
ares_free(temp);
if (status != ARES_SUCCESS) {
return status;
}
/* Ignore lines beginning with a comment */
if (ares__buf_begins_with(line, (const unsigned char *)"#", 1)) {
return ARES_SUCCESS;
}
res_options = getenv("RES_OPTIONS");
if (res_options) {
status = set_options(sysconfig, res_options);
if (status != ARES_SUCCESS) {
return status;
}
/* database = values (comma delimited)*/
status = ares__buf_split(line, (const unsigned char *)"=", 1,
ARES_BUF_SPLIT_TRIM, 2, &sects);
if (status != ARES_SUCCESS || ares__llist_len(sects) != 2) {
goto done;
}
return ARES_SUCCESS;
buf = ares__llist_first_val(sects);
status = buf_fetch_string(buf, option, sizeof(option));
if (status != ARES_SUCCESS) {
goto done;
}
/* Only support "hosts=" */
if (strcmp(option, "hosts") != 0) {
goto done;
}
/* Values are comma separated */
buf = ares__llist_last_val(sects);
status = config_lookup(sysconfig, buf, ",");
done:
ares__llist_destroy(sects);
if (status != ARES_ENOMEM) {
status = ARES_SUCCESS;
}
return status;
}
ares_status_t ares__init_sysconfig_files(const ares_channel_t *channel,
ares_sysconfig_t *sysconfig)
typedef ares_status_t (*line_callback_t)(ares_sysconfig_t *sysconfig,
ares__buf_t *line);
/* 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 char *filename,
ares_sysconfig_t *sysconfig,
line_callback_t cb)
{
char *p;
FILE *fp = NULL;
char *line = NULL;
size_t linesize = 0;
int error;
const char *resolvconf_path;
ares_status_t status = ARES_SUCCESS;
ares_status_t status = ARES_SUCCESS;
ares__llist_node_t *node;
ares__llist_t *lines = NULL;
ares__buf_t *buf = NULL;
/* Support path for resolvconf filename set by ares_init_options */
if (channel->resolvconf_path) {
resolvconf_path = channel->resolvconf_path;
} else {
resolvconf_path = PATH_RESOLV_CONF;
}
fp = fopen(resolvconf_path, "r");
if (fp) {
while ((status = ares__read_line(fp, &line, &linesize)) == ARES_SUCCESS) {
if ((p = try_config(line, "domain", ';'))) {
status = config_domain(sysconfig, p);
} else if ((p = try_config(line, "lookup", ';'))) {
status = config_lookup(sysconfig, p, "bind", NULL, "file");
} else if ((p = try_config(line, "search", ';'))) {
status = config_search(sysconfig, p);
} else if ((p = try_config(line, "nameserver", ';'))) {
status =
ares__sconfig_append_fromstr(&sysconfig->sconfig, p, ARES_TRUE);
} else if ((p = try_config(line, "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, p);
if (status != ARES_ENOMEM) {
status = ARES_SUCCESS;
}
} else if ((p = try_config(line, "options", ';'))) {
status = set_options(sysconfig, p);
} else {
status = ARES_SUCCESS;
}
if (status != ARES_SUCCESS) {
fclose(fp);
goto done;
}
}
fclose(fp);
buf = ares__buf_create();
if (buf == NULL) {
status = ARES_ENOMEM;
goto done;
}
if (status != ARES_EOF) {
goto done;
}
} else {
error = ERRNO;
switch (error) {
case ENOENT:
case ESRCH:
break;
default:
DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
strerror(error)));
DEBUGF(fprintf(stderr, "Error opening file: %s\n", PATH_RESOLV_CONF));
status = ARES_EFILE;
goto done;
}
status = ares__buf_load_file(filename, buf);
if (status != ARES_SUCCESS) {
goto done;
}
status = ares__buf_split(buf, (const unsigned char *)"\n", 1,
ARES_BUF_SPLIT_TRIM, 0, &lines);
if (status != ARES_SUCCESS) {
goto done;
}
/* Many systems (Solaris, Linux, BSD's) use nsswitch.conf */
fp = fopen("/etc/nsswitch.conf", "r");
if (fp) {
while ((status = ares__read_line(fp, &line, &linesize)) == ARES_SUCCESS) {
if ((p = try_config(line, "hosts:", '\0'))) {
(void)config_lookup(sysconfig, p, "dns", "resolve", "files");
}
}
fclose(fp);
if (status != ARES_EOF) {
for (node = ares__llist_node_first(lines); node != NULL;
node = ares__llist_node_next(node)) {
ares__buf_t *line = ares__llist_node_val(node);
status = cb(sysconfig, line);
if (status != ARES_SUCCESS) {
goto done;
}
} else {
error = ERRNO;
switch (error) {
case ENOENT:
case ESRCH:
break;
default:
DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
strerror(error)));
DEBUGF(
fprintf(stderr, "Error opening file: %s\n", "/etc/nsswitch.conf"));
break;
}
/* ignore error, maybe we will get luck in next if clause */
}
done:
ares__buf_destroy(buf);
ares__llist_destroy(lines);
/* Linux / GNU libc 2.x and possibly others have host.conf */
fp = fopen("/etc/host.conf", "r");
if (fp) {
while ((status = ares__read_line(fp, &line, &linesize)) == ARES_SUCCESS) {
if ((p = try_config(line, "order", '\0'))) {
/* ignore errors */
(void)config_lookup(sysconfig, p, "bind", NULL, "hosts");
}
}
fclose(fp);
if (status != ARES_EOF) {
goto done;
}
} else {
error = ERRNO;
switch (error) {
case ENOENT:
case ESRCH:
break;
default:
DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
strerror(error)));
DEBUGF(fprintf(stderr, "Error opening file: %s\n", "/etc/host.conf"));
break;
}
return status;
}
ares_status_t ares__init_sysconfig_files(const ares_channel_t *channel,
ares_sysconfig_t *sysconfig)
{
ares_status_t status = ARES_SUCCESS;
/* Resolv.conf */
status = process_config_lines((channel->resolvconf_path != NULL)
? channel->resolvconf_path
: PATH_RESOLV_CONF,
sysconfig, parse_resolvconf_line);
if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
goto done;
}
/* ignore error, maybe we will get luck in next if clause */
/* Nsswitch.conf */
status =
process_config_lines("/etc/nsswitch.conf", sysconfig, parse_nsswitch_line);
if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
goto done;
}
/* netsvc.conf */
status =
process_config_lines("/etc/netsvc.conf", sysconfig, parse_svcconf_line);
if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
goto done;
}
/* Tru64 uses /etc/svc.conf */
fp = fopen("/etc/svc.conf", "r");
if (fp) {
while ((status = ares__read_line(fp, &line, &linesize)) == ARES_SUCCESS) {
if ((p = try_config(line, "hosts=", '\0'))) {
/* ignore errors */
(void)config_lookup(sysconfig, p, "bind", NULL, "local");
}
}
fclose(fp);
if (status != ARES_EOF) {
goto done;
}
} else {
error = ERRNO;
switch (error) {
case ENOENT:
case ESRCH:
break;
default:
DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
strerror(error)));
DEBUGF(fprintf(stderr, "Error opening file: %s\n", "/etc/svc.conf"));
break;
}
/* ignore error */
/* svc.conf */
status = process_config_lines("/etc/svc.conf", sysconfig, parse_svcconf_line);
if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
goto done;
}
status = ARES_SUCCESS;
done:
ares_free(line);
return status;
}

@ -266,8 +266,8 @@ static ares_status_t parse_nameserver(ares__buf_t *buf, ares_sconfig_t *sconfig)
} else {
/* IPv6 */
const unsigned char ipv6_charset[] = "ABCDEFabcdef0123456789.:";
if (ares__buf_consume_charset(buf, ipv6_charset, sizeof(ipv6_charset)) ==
0) {
if (ares__buf_consume_charset(buf, ipv6_charset, sizeof(ipv6_charset)-1)
== 0) {
return ARES_EBADSTR;
}
}
@ -318,8 +318,8 @@ static ares_status_t parse_nameserver(ares__buf_t *buf, ares_sconfig_t *sconfig)
ares__buf_tag(buf);
if (ares__buf_consume_charset(buf, iface_charset, sizeof(iface_charset)) ==
0) {
if (ares__buf_consume_charset(buf, iface_charset, sizeof(iface_charset)-1)
== 0) {
return ARES_EBADSTR;
}
@ -463,7 +463,7 @@ ares_status_t ares__sconfig_append_fromstr(ares__llist_t **sconfig,
}
status = ares__buf_split(buf, (const unsigned char *)" ,", 2,
ARES_BUF_SPLIT_NONE, &list);
ARES_BUF_SPLIT_NONE, 0, &list);
if (status != ARES_SUCCESS) {
goto done;
}

@ -512,22 +512,6 @@ CONTAINED_TEST_F(LibraryTest, ContainerMyHostsInit,
return HasFailure();
}
NameContentList hostconf = {
{"/etc/resolv.conf", "nameserver 1.2.3.4\n"
"sortlist1.2.3.4\n" // malformed line
"search first.com second.com\n"},
{"/etc/host.conf", "order bind hosts\n"}};
CONTAINED_TEST_F(LibraryTest, ContainerHostConfInit,
"myhostname", "mydomainname.org", hostconf) {
ares_channel_t *channel = nullptr;
EXPECT_EQ(ARES_SUCCESS, ares_init(&channel));
EXPECT_EQ(std::string("bf"), std::string(channel->lookups));
ares_destroy(channel);
return HasFailure();
}
NameContentList svcconf = {
{"/etc/resolv.conf", "nameserver 1.2.3.4\n"
"search first.com second.com\n"},
@ -589,15 +573,7 @@ CONTAINED_TEST_F(LibraryTest, ContainerNsswitchConfNotReadable,
ares_destroy(channel);
return HasFailure();
}
CONTAINED_TEST_F(LibraryTest, ContainerHostConfNotReadable,
"myhostname", "mydomainname.org", hostconf) {
ares_channel_t *channel = nullptr;
// Unavailable /etc/host.conf falls back to defaults.
MakeUnreadable hide("/etc/host.conf");
EXPECT_EQ(ARES_SUCCESS, ares_init(&channel));
ares_destroy(channel);
return HasFailure();
}
CONTAINED_TEST_F(LibraryTest, ContainerSvcConfNotReadable,
"myhostname", "mydomainname.org", svcconf) {
ares_channel_t *channel = nullptr;

@ -284,49 +284,6 @@ TEST_F(LibraryTest, MallocDataFail) {
EXPECT_EQ(nullptr, ares_malloc_data(ARES_DATATYPE_MX_REPLY));
}
TEST_F(LibraryTest, ReadLine) {
TempFile temp("abcde\n0123456789\nXYZ\n012345678901234567890\n\n");
FILE *fp = fopen(temp.filename(), "r");
size_t bufsize = 4;
char *buf = (char *)ares_malloc(bufsize);
EXPECT_EQ(ARES_SUCCESS, ares__read_line(fp, &buf, &bufsize));
EXPECT_EQ("abcde", std::string(buf));
EXPECT_EQ(ARES_SUCCESS, ares__read_line(fp, &buf, &bufsize));
EXPECT_EQ("0123456789", std::string(buf));
EXPECT_EQ(ARES_SUCCESS, ares__read_line(fp, &buf, &bufsize));
EXPECT_EQ("XYZ", std::string(buf));
SetAllocFail(1);
EXPECT_EQ(ARES_ENOMEM, ares__read_line(fp, &buf, &bufsize));
EXPECT_EQ(nullptr, buf);
fclose(fp);
ares_free(buf);
}
TEST_F(LibraryTest, ReadLineNoBuf) {
TempFile temp("abcde\n0123456789\nXYZ\n012345678901234567890");
FILE *fp = fopen(temp.filename(), "r");
size_t bufsize = 0;
char *buf = nullptr;
SetAllocFail(1);
EXPECT_EQ(ARES_ENOMEM, ares__read_line(fp, &buf, &bufsize));
EXPECT_EQ(ARES_SUCCESS, ares__read_line(fp, &buf, &bufsize));
EXPECT_EQ("abcde", std::string(buf));
EXPECT_EQ(ARES_SUCCESS, ares__read_line(fp, &buf, &bufsize));
EXPECT_EQ("0123456789", std::string(buf));
EXPECT_EQ(ARES_SUCCESS, ares__read_line(fp, &buf, &bufsize));
EXPECT_EQ("XYZ", std::string(buf));
EXPECT_EQ(ARES_SUCCESS, ares__read_line(fp, &buf, &bufsize));
EXPECT_EQ("012345678901234567890", std::string(buf));
fclose(fp);
ares_free(buf);
}
TEST_F(FileChannelTest, GetAddrInfoHostsPositive) {
TempFile hostsfile("1.2.3.4 example.com \n"
" 2.3.4.5\tgoogle.com www.google.com\twww2.google.com\n"

Loading…
Cancel
Save