blob: 3082f332457c221c36dd84d736c721cead11832a [file] [log] [blame]
/* MIT License
*
* Copyright (c) 1998 Massachusetts Institute of Technology
* Copyright (c) 2008 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_ARPA_INET_H
# include <arpa/inet.h>
#endif
#include "ares_data.h"
#include "ares_inet_net_pton.h"
void ares_destroy_options(struct ares_options *options)
{
int i;
ares_free(options->servers);
for (i = 0; options->domains && i < options->ndomains; i++) {
ares_free(options->domains[i]);
}
ares_free(options->domains);
ares_free(options->sortlist);
ares_free(options->lookups);
ares_free(options->resolvconf_path);
ares_free(options->hosts_path);
}
static struct in_addr *ares_save_opt_servers(const ares_channel_t *channel,
int *nservers)
{
ares_slist_node_t *snode;
struct in_addr *out =
ares_malloc_zero(ares_slist_len(channel->servers) * sizeof(*out));
*nservers = 0;
if (out == NULL) {
return NULL;
}
for (snode = ares_slist_node_first(channel->servers); snode != NULL;
snode = ares_slist_node_next(snode)) {
const ares_server_t *server = ares_slist_node_val(snode);
if (server->addr.family != AF_INET) {
continue;
}
memcpy(&out[*nservers], &server->addr.addr.addr4, sizeof(*out));
(*nservers)++;
}
return out;
}
/* Save options from initialized channel */
int ares_save_options(const ares_channel_t *channel,
struct ares_options *options, int *optmask)
{
size_t i;
/* NOTE: We can't zero the whole thing out, this is because the size of the
* struct ares_options changes over time, so if someone compiled
* with an older version, their struct size might be smaller and
* we might overwrite their memory! So using the optmask is critical
* here, as they could have only set options they knew about.
*
* Unfortunately ares_destroy_options() doesn't take an optmask, so
* there are a few pointers we *must* zero out otherwise we won't
* know if they were allocated or not
*/
options->servers = NULL;
options->domains = NULL;
options->sortlist = NULL;
options->lookups = NULL;
options->resolvconf_path = NULL;
options->hosts_path = NULL;
if (!ARES_CONFIG_CHECK(channel)) {
return ARES_ENODATA;
}
if (channel->optmask & ARES_OPT_FLAGS) {
options->flags = (int)channel->flags;
}
/* We convert ARES_OPT_TIMEOUT to ARES_OPT_TIMEOUTMS in
* ares_init_by_options() */
if (channel->optmask & ARES_OPT_TIMEOUTMS) {
options->timeout = (int)channel->timeout;
}
if (channel->optmask & ARES_OPT_TRIES) {
options->tries = (int)channel->tries;
}
if (channel->optmask & ARES_OPT_NDOTS) {
options->ndots = (int)channel->ndots;
}
if (channel->optmask & ARES_OPT_MAXTIMEOUTMS) {
options->maxtimeout = (int)channel->maxtimeout;
}
if (channel->optmask & ARES_OPT_UDP_PORT) {
options->udp_port = channel->udp_port;
}
if (channel->optmask & ARES_OPT_TCP_PORT) {
options->tcp_port = channel->tcp_port;
}
if (channel->optmask & ARES_OPT_SOCK_STATE_CB) {
options->sock_state_cb = channel->sock_state_cb;
options->sock_state_cb_data = channel->sock_state_cb_data;
}
if (channel->optmask & ARES_OPT_SERVERS) {
options->servers = ares_save_opt_servers(channel, &options->nservers);
if (options->servers == NULL) {
return ARES_ENOMEM;
}
}
if (channel->optmask & ARES_OPT_DOMAINS) {
options->domains = NULL;
if (channel->ndomains) {
options->domains = ares_malloc(channel->ndomains * sizeof(char *));
if (!options->domains) {
return ARES_ENOMEM;
}
for (i = 0; i < channel->ndomains; i++) {
options->domains[i] = ares_strdup(channel->domains[i]);
if (!options->domains[i]) {
options->ndomains = (int)i;
return ARES_ENOMEM;
}
}
}
options->ndomains = (int)channel->ndomains;
}
if (channel->optmask & ARES_OPT_LOOKUPS) {
options->lookups = ares_strdup(channel->lookups);
if (!options->lookups && channel->lookups) {
return ARES_ENOMEM;
}
}
if (channel->optmask & ARES_OPT_SORTLIST) {
options->sortlist = NULL;
if (channel->nsort) {
options->sortlist = ares_malloc(channel->nsort * sizeof(struct apattern));
if (!options->sortlist) {
return ARES_ENOMEM;
}
for (i = 0; i < channel->nsort; i++) {
options->sortlist[i] = channel->sortlist[i];
}
}
options->nsort = (int)channel->nsort;
}
if (channel->optmask & ARES_OPT_RESOLVCONF) {
options->resolvconf_path = ares_strdup(channel->resolvconf_path);
if (!options->resolvconf_path) {
return ARES_ENOMEM;
}
}
if (channel->optmask & ARES_OPT_HOSTS_FILE) {
options->hosts_path = ares_strdup(channel->hosts_path);
if (!options->hosts_path) {
return ARES_ENOMEM;
}
}
if (channel->optmask & ARES_OPT_SOCK_SNDBUF &&
channel->socket_send_buffer_size > 0) {
options->socket_send_buffer_size = channel->socket_send_buffer_size;
}
if (channel->optmask & ARES_OPT_SOCK_RCVBUF &&
channel->socket_receive_buffer_size > 0) {
options->socket_receive_buffer_size = channel->socket_receive_buffer_size;
}
if (channel->optmask & ARES_OPT_EDNSPSZ) {
options->ednspsz = (int)channel->ednspsz;
}
if (channel->optmask & ARES_OPT_UDP_MAX_QUERIES) {
options->udp_max_queries = (int)channel->udp_max_queries;
}
if (channel->optmask & ARES_OPT_QUERY_CACHE) {
options->qcache_max_ttl = channel->qcache_max_ttl;
}
if (channel->optmask & ARES_OPT_EVENT_THREAD) {
options->evsys = channel->evsys;
}
/* Set options for server failover behavior */
if (channel->optmask & ARES_OPT_SERVER_FAILOVER) {
options->server_failover_opts.retry_chance = channel->server_retry_chance;
options->server_failover_opts.retry_delay = channel->server_retry_delay;
}
*optmask = (int)channel->optmask;
return ARES_SUCCESS;
}
static ares_status_t ares_init_options_servers(ares_channel_t *channel,
const struct in_addr *servers,
size_t nservers)
{
ares_llist_t *slist = NULL;
ares_status_t status;
status = ares_in_addr_to_sconfig_llist(servers, nservers, &slist);
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
status = ares_servers_update(channel, slist, ARES_TRUE);
ares_llist_destroy(slist);
return status;
}
ares_status_t ares_init_by_options(ares_channel_t *channel,
const struct ares_options *options,
int optmask)
{
size_t i;
if (channel == NULL) {
return ARES_ENODATA; /* LCOV_EXCL_LINE: DefensiveCoding */
}
if (options == NULL) {
if (optmask != 0) {
return ARES_ENODATA; /* LCOV_EXCL_LINE: DefensiveCoding */
}
return ARES_SUCCESS;
}
/* Easy stuff. */
/* Event Thread requires threading support and is incompatible with socket
* state callbacks */
if (optmask & ARES_OPT_EVENT_THREAD) {
if (!ares_threadsafety()) {
return ARES_ENOTIMP;
}
if (optmask & ARES_OPT_SOCK_STATE_CB) {
return ARES_EFORMERR;
}
channel->evsys = options->evsys;
}
if (optmask & ARES_OPT_FLAGS) {
channel->flags = (unsigned int)options->flags;
}
if (optmask & ARES_OPT_TIMEOUTMS) {
/* Apparently some integrations were passing -1 to tell c-ares to use
* the default instead of just omitting the optmask */
if (options->timeout <= 0) {
optmask &= ~(ARES_OPT_TIMEOUTMS);
} else {
channel->timeout = (unsigned int)options->timeout;
}
} else if (optmask & ARES_OPT_TIMEOUT) {
optmask &= ~(ARES_OPT_TIMEOUT);
/* Apparently some integrations were passing -1 to tell c-ares to use
* the default instead of just omitting the optmask */
if (options->timeout > 0) {
/* Convert to milliseconds */
optmask |= ARES_OPT_TIMEOUTMS;
channel->timeout = (unsigned int)options->timeout * 1000;
}
}
if (optmask & ARES_OPT_TRIES) {
if (options->tries <= 0) {
optmask &= ~(ARES_OPT_TRIES);
} else {
channel->tries = (size_t)options->tries;
}
}
if (optmask & ARES_OPT_NDOTS) {
if (options->ndots < 0) {
optmask &= ~(ARES_OPT_NDOTS);
} else {
channel->ndots = (size_t)options->ndots;
}
}
if (optmask & ARES_OPT_MAXTIMEOUTMS) {
if (options->maxtimeout <= 0) {
optmask &= ~(ARES_OPT_MAXTIMEOUTMS);
} else {
channel->maxtimeout = (size_t)options->maxtimeout;
}
}
if (optmask & ARES_OPT_ROTATE) {
channel->rotate = ARES_TRUE;
}
if (optmask & ARES_OPT_NOROTATE) {
channel->rotate = ARES_FALSE;
}
if (optmask & ARES_OPT_UDP_PORT) {
channel->udp_port = options->udp_port;
}
if (optmask & ARES_OPT_TCP_PORT) {
channel->tcp_port = options->tcp_port;
}
if (optmask & ARES_OPT_SOCK_STATE_CB) {
channel->sock_state_cb = options->sock_state_cb;
channel->sock_state_cb_data = options->sock_state_cb_data;
}
if (optmask & ARES_OPT_SOCK_SNDBUF) {
if (options->socket_send_buffer_size <= 0) {
optmask &= ~(ARES_OPT_SOCK_SNDBUF);
} else {
channel->socket_send_buffer_size = options->socket_send_buffer_size;
}
}
if (optmask & ARES_OPT_SOCK_RCVBUF) {
if (options->socket_receive_buffer_size <= 0) {
optmask &= ~(ARES_OPT_SOCK_RCVBUF);
} else {
channel->socket_receive_buffer_size = options->socket_receive_buffer_size;
}
}
if (optmask & ARES_OPT_EDNSPSZ) {
if (options->ednspsz <= 0) {
optmask &= ~(ARES_OPT_EDNSPSZ);
} else {
channel->ednspsz = (size_t)options->ednspsz;
}
}
/* Copy the domains, if given. Keep channel->ndomains consistent so
* we can clean up in case of error.
*/
if (optmask & ARES_OPT_DOMAINS && options->ndomains > 0) {
channel->domains =
ares_malloc_zero((size_t)options->ndomains * sizeof(char *));
if (!channel->domains) {
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
channel->ndomains = (size_t)options->ndomains;
for (i = 0; i < (size_t)options->ndomains; i++) {
channel->domains[i] = ares_strdup(options->domains[i]);
if (!channel->domains[i]) {
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
}
/* Set lookups, if given. */
if (optmask & ARES_OPT_LOOKUPS) {
if (options->lookups == NULL) {
optmask &= ~(ARES_OPT_LOOKUPS);
} else {
channel->lookups = ares_strdup(options->lookups);
if (!channel->lookups) {
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
}
/* copy sortlist */
if (optmask & ARES_OPT_SORTLIST && options->nsort > 0) {
channel->nsort = (size_t)options->nsort;
channel->sortlist =
ares_malloc((size_t)options->nsort * sizeof(struct apattern));
if (!channel->sortlist) {
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
for (i = 0; i < (size_t)options->nsort; i++) {
channel->sortlist[i] = options->sortlist[i];
}
}
/* Set path for resolv.conf file, if given. */
if (optmask & ARES_OPT_RESOLVCONF) {
if (options->resolvconf_path == NULL) {
optmask &= ~(ARES_OPT_RESOLVCONF);
} else {
channel->resolvconf_path = ares_strdup(options->resolvconf_path);
if (channel->resolvconf_path == NULL) {
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
}
/* Set path for hosts file, if given. */
if (optmask & ARES_OPT_HOSTS_FILE) {
if (options->hosts_path == NULL) {
optmask &= ~(ARES_OPT_HOSTS_FILE);
} else {
channel->hosts_path = ares_strdup(options->hosts_path);
if (channel->hosts_path == NULL) {
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
}
if (optmask & ARES_OPT_UDP_MAX_QUERIES) {
if (options->udp_max_queries <= 0) {
optmask &= ~(ARES_OPT_UDP_MAX_QUERIES);
} else {
channel->udp_max_queries = (size_t)options->udp_max_queries;
}
}
/* As of c-ares 1.31.0, the Query Cache is on by default. The only way to
* disable it is to set options->qcache_max_ttl = 0 while specifying the
* ARES_OPT_QUERY_CACHE which will actually disable it completely. */
if (optmask & ARES_OPT_QUERY_CACHE) {
/* qcache_max_ttl is unsigned unlike the others */
channel->qcache_max_ttl = options->qcache_max_ttl;
} else {
optmask |= ARES_OPT_QUERY_CACHE;
channel->qcache_max_ttl = 3600;
}
/* Initialize the ipv4 servers if provided */
if (optmask & ARES_OPT_SERVERS) {
if (options->nservers <= 0) {
optmask &= ~(ARES_OPT_SERVERS);
} else {
ares_status_t status;
status = ares_init_options_servers(channel, options->servers,
(size_t)options->nservers);
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
}
/* Set fields for server failover behavior */
if (optmask & ARES_OPT_SERVER_FAILOVER) {
channel->server_retry_chance = options->server_failover_opts.retry_chance;
channel->server_retry_delay = options->server_failover_opts.retry_delay;
}
channel->optmask = (unsigned int)optmask;
return ARES_SUCCESS;
}