/* 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; }