1
0
mirror of https://github.com/moparisthebest/curl synced 2024-08-13 17:03:50 -04:00
curl/ares/ares_init.c
Daniel Stenberg 864f1a3366 - Nico Stappenbelt reported that when processing domain and search lines in
the resolv.conf file, the first entry encountered is processed and used as
  the search list. According to the manual pages for both Linux, Solaris and
  Tru64, the last entry of either a domain or a search field is used.
2004-05-11 21:12:10 +00:00

834 lines
21 KiB
C

/* Copyright 1998 by the Massachusetts Institute of Technology.
*
* Permission to use, copy, modify, and distribute this
* software and its documentation for any purpose and without
* fee is hereby granted, provided that the above copyright
* notice appear in all copies and that both that copyright
* notice and this permission notice appear in supporting
* documentation, and that the name of M.I.T. not be used in
* advertising or publicity pertaining to distribution of the
* software without specific, written prior permission.
* M.I.T. makes no representations about the suitability of
* this software for any purpose. It is provided "as is"
* without express or implied warranty.
*/
#include <sys/types.h>
#ifdef WIN32
#include "nameser.h"
#else
#include <sys/param.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <arpa/nameser.h>
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>
#include "ares.h"
#include "ares_private.h"
static int init_by_options(ares_channel channel, struct ares_options *options,
int optmask);
static int init_by_environment(ares_channel channel);
static int init_by_resolv_conf(ares_channel channel);
static int init_by_defaults(ares_channel channel);
static int config_domain(ares_channel channel, char *str);
static int config_lookup(ares_channel channel, const char *str);
static int config_nameserver(struct server_state **servers, int *nservers,
char *str);
static int config_sortlist(struct apattern **sortlist, int *nsort,
const char *str);
static int set_search(ares_channel channel, const char *str);
static int set_options(ares_channel channel, const char *str);
static char *try_config(char *s, const char *opt);
static const char *try_option(const char *p, const char *q, const char *opt);
static int ip_addr(const char *s, int len, struct in_addr *addr);
static void natural_mask(struct apattern *pat);
int ares_init(ares_channel *channelptr)
{
return ares_init_options(channelptr, NULL, 0);
}
int ares_init_options(ares_channel *channelptr, struct ares_options *options,
int optmask)
{
ares_channel channel;
int i, status;
struct server_state *server;
struct timeval tv;
channel = malloc(sizeof(struct ares_channeldata));
if (!channel)
return ARES_ENOMEM;
/* Set everything to distinguished values so we know they haven't
* been set yet.
*/
channel->flags = -1;
channel->timeout = -1;
channel->tries = -1;
channel->ndots = -1;
channel->udp_port = -1;
channel->tcp_port = -1;
channel->nservers = -1;
channel->ndomains = -1;
channel->nsort = -1;
channel->lookups = NULL;
channel->queries = NULL;
/* Initialize configuration by each of the four sources, from highest
* precedence to lowest.
*/
status = init_by_options(channel, options, optmask);
if (status == ARES_SUCCESS)
status = init_by_environment(channel);
if (status == ARES_SUCCESS)
status = init_by_resolv_conf(channel);
if (status == ARES_SUCCESS)
status = init_by_defaults(channel);
if (status != ARES_SUCCESS)
{
/* Something failed; clean up memory we may have allocated. */
if (channel->nservers != -1)
free(channel->servers);
if (channel->ndomains != -1)
{
for (i = 0; i < channel->ndomains; i++)
free(channel->domains[i]);
free(channel->domains);
}
if (channel->nsort != -1)
free(channel->sortlist);
if(channel->lookups)
free(channel->lookups);
free(channel);
return status;
}
/* Trim to one server if ARES_FLAG_PRIMARY is set. */
if ((channel->flags & ARES_FLAG_PRIMARY) && channel->nservers > 1)
channel->nservers = 1;
/* Initialize server states. */
for (i = 0; i < channel->nservers; i++)
{
server = &channel->servers[i];
server->udp_socket = -1;
server->tcp_socket = -1;
server->tcp_lenbuf_pos = 0;
server->tcp_buffer = NULL;
server->qhead = NULL;
server->qtail = NULL;
}
/* Choose a somewhat random query ID. The main point is to avoid
* collisions with stale queries. An attacker trying to spoof a DNS
* answer also has to guess the query ID, but it's only a 16-bit
* field, so there's not much to be done about that.
*/
gettimeofday(&tv, NULL);
channel->next_id = (unsigned short)
(tv.tv_sec ^ tv.tv_usec ^ getpid()) & 0xffff;
channel->queries = NULL;
*channelptr = channel;
return ARES_SUCCESS;
}
static int init_by_options(ares_channel channel, struct ares_options *options,
int optmask)
{
int i;
/* Easy stuff. */
if ((optmask & ARES_OPT_FLAGS) && channel->flags == -1)
channel->flags = options->flags;
if ((optmask & ARES_OPT_TIMEOUT) && channel->timeout == -1)
channel->timeout = options->timeout;
if ((optmask & ARES_OPT_TRIES) && channel->tries == -1)
channel->tries = options->tries;
if ((optmask & ARES_OPT_NDOTS) && channel->ndots == -1)
channel->ndots = options->ndots;
if ((optmask & ARES_OPT_UDP_PORT) && channel->udp_port == -1)
channel->udp_port = options->udp_port;
if ((optmask & ARES_OPT_TCP_PORT) && channel->tcp_port == -1)
channel->tcp_port = options->tcp_port;
/* Copy the servers, if given. */
if ((optmask & ARES_OPT_SERVERS) && channel->nservers == -1)
{
channel->servers =
malloc(options->nservers * sizeof(struct server_state));
if (!channel->servers && options->nservers != 0)
return ARES_ENOMEM;
for (i = 0; i < options->nservers; i++)
channel->servers[i].addr = options->servers[i];
channel->nservers = options->nservers;
}
/* Copy the domains, if given. Keep channel->ndomains consistent so
* we can clean up in case of error.
*/
if ((optmask & ARES_OPT_DOMAINS) && channel->ndomains == -1)
{
channel->domains = malloc(options->ndomains * sizeof(char *));
if (!channel->domains && options->ndomains != 0)
return ARES_ENOMEM;
for (i = 0; i < options->ndomains; i++)
{
channel->ndomains = i;
channel->domains[i] = strdup(options->domains[i]);
if (!channel->domains[i])
return ARES_ENOMEM;
}
channel->ndomains = options->ndomains;
}
/* Set lookups, if given. */
if ((optmask & ARES_OPT_LOOKUPS) && !channel->lookups)
{
channel->lookups = strdup(options->lookups);
if (!channel->lookups)
return ARES_ENOMEM;
}
return ARES_SUCCESS;
}
static int init_by_environment(ares_channel channel)
{
const char *localdomain, *res_options;
int status;
localdomain = getenv("LOCALDOMAIN");
if (localdomain && channel->ndomains == -1)
{
status = set_search(channel, localdomain);
if (status != ARES_SUCCESS)
return status;
}
res_options = getenv("RES_OPTIONS");
if (res_options)
{
status = set_options(channel, res_options);
if (status != ARES_SUCCESS)
return status;
}
return ARES_SUCCESS;
}
#ifdef WIN32
static int get_res_size_nt(HKEY hKey, char *subkey, int *size)
{
return RegQueryValueEx(hKey, subkey, 0, NULL, NULL, size);
}
/* Warning: returns a dynamically allocated buffer, the user MUST
* use free() if the function returns 1
*/
static int get_res_nt(HKEY hKey, char *subkey, char **obuf)
{
/* Test for the size we need */
int size = 0;
int result;
result = RegQueryValueEx(hKey, subkey, 0, NULL, NULL, &size);
if ((result != ERROR_SUCCESS && result != ERROR_MORE_DATA) || !size)
return 0;
*obuf = malloc(size+1);
if (RegQueryValueEx(hKey, subkey, 0, NULL, *obuf, &size) != ERROR_SUCCESS)
{
free(*obuf);
return 0;
}
if (size == 1)
{
free(*obuf);
return 0;
}
return 1;
}
static int get_res_interfaces_nt(HKEY hKey, char *subkey, char **obuf)
{
char enumbuf[39]; /* GUIDs are 38 chars + 1 for NULL */
int enum_size = 39;
int idx = 0;
HKEY hVal;
while (RegEnumKeyEx(hKey, idx++, enumbuf, &enum_size, 0,
NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS)
{
enum_size = 39;
if (RegOpenKeyEx(hKey, enumbuf, 0, KEY_QUERY_VALUE, &hVal) !=
ERROR_SUCCESS)
continue;
if (!get_res_nt(hVal, subkey, obuf))
RegCloseKey(hVal);
else
{
RegCloseKey(hVal);
return 1;
}
}
return 0;
}
#endif
static int init_by_resolv_conf(ares_channel channel)
{
char *line = NULL;
int status, nservers = 0, nsort = 0;
struct server_state *servers = NULL;
struct apattern *sortlist = NULL;
#ifdef WIN32
/*
NameServer Registry:
On Windows 9X, the DNS server can be found in:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\MSTCP\NameServer
On Windows NT/2000/XP/2003:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\NameServer
or
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DhcpNameServer
or
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\{AdapterID}\
NameServer
or
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\{AdapterID}\
DhcpNameServer
*/
HKEY mykey;
HKEY subkey;
DWORD data_type;
DWORD bytes;
DWORD result;
DWORD keysize = MAX_PATH;
status = ARES_EFILE;
if (IsNT)
{
if (RegOpenKeyEx(
HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0,
KEY_READ, &mykey
) == ERROR_SUCCESS)
{
RegOpenKeyEx(mykey, "Interfaces", 0,
KEY_QUERY_VALUE|KEY_ENUMERATE_SUB_KEYS, &subkey);
if (get_res_nt(mykey, NAMESERVER, &line))
{
status = config_nameserver(&servers, &nservers, line);
free(line);
}
else if (get_res_nt(mykey, DHCPNAMESERVER, &line))
{
status = config_nameserver(&servers, &nservers, line);
free(line);
}
/* Try the interfaces */
else if (get_res_interfaces_nt(subkey, NAMESERVER, &line))
{
status = config_nameserver(&servers, &nservers, line);
free(line);
}
else if (get_res_interfaces_nt(subkey, DHCPNAMESERVER, &line))
{
status = config_nameserver(&servers, &nservers, line);
free(line);
}
RegCloseKey(subkey);
RegCloseKey(mykey);
}
}
else
{
if (RegOpenKeyEx(
HKEY_LOCAL_MACHINE, WIN_NS_9X, 0,
KEY_READ, &mykey
) == ERROR_SUCCESS)
{
if ((result = RegQueryValueEx(
mykey, NAMESERVER, NULL, &data_type,
NULL, &bytes
)
) == ERROR_SUCCESS ||
result == ERROR_MORE_DATA)
{
if (bytes)
{
line = (char *)malloc(bytes+1);
if (RegQueryValueEx(mykey, NAMESERVER, NULL, &data_type,
(unsigned char *)line, &bytes) ==
ERROR_SUCCESS)
{
status = config_nameserver(&servers, &nservers, line);
}
free(line);
}
}
}
RegCloseKey(mykey);
}
if (status != ARES_EFILE)
{
/*
if (!channel->lookups) {
status = config_lookup(channel, "file bind");
}
*/
status = ARES_EOF;
}
#elif defined(riscos)
/* Under RISC OS, name servers are listed in the
system variable Inet$Resolvers, space separated. */
line = getenv("Inet$Resolvers");
status = ARES_EFILE;
if (line) {
char *resolvers = strdup(line), *pos, *space;
if (!resolvers)
return ARES_ENOMEM;
pos = resolvers;
do {
space = strchr(pos, ' ');
if (space)
*space = 0;
status = config_nameserver(&servers, &nservers, pos);
if (status != ARES_SUCCESS)
break;
pos = space + 1;
} while (space);
if (status == ARES_SUCCESS)
status = ARES_EOF;
free(resolvers);
}
#else
{
char *p;
FILE *fp;
int linesize;
fp = fopen(PATH_RESOLV_CONF, "r");
if (!fp)
return (errno == ENOENT) ? ARES_SUCCESS : ARES_EFILE;
while ((status = ares__read_line(fp, &line, &linesize)) == ARES_SUCCESS)
{
if ((p = try_config(line, "domain")))
status = config_domain(channel, p);
else if ((p = try_config(line, "lookup")) && !channel->lookups)
status = config_lookup(channel, p);
else if ((p = try_config(line, "search")))
status = set_search(channel, p);
else if ((p = try_config(line, "nameserver")) && channel->nservers == -1)
status = config_nameserver(&servers, &nservers, p);
else if ((p = try_config(line, "sortlist")) && channel->nsort == -1)
status = config_sortlist(&sortlist, &nsort, p);
else if ((p = try_config(line, "options")))
status = set_options(channel, p);
else
status = ARES_SUCCESS;
if (status != ARES_SUCCESS)
break;
}
free(line);
fclose(fp);
}
#endif
/* Handle errors. */
if (status != ARES_EOF)
{
if (servers != NULL) free(servers);
if (sortlist != NULL) free(sortlist);
return status;
}
/* If we got any name server entries, fill them in. */
if (servers)
{
channel->servers = servers;
channel->nservers = nservers;
}
/* If we got any sortlist entries, fill them in. */
if (sortlist)
{
channel->sortlist = sortlist;
channel->nsort = nsort;
}
return ARES_SUCCESS;
}
static int init_by_defaults(ares_channel channel)
{
char hostname[MAXHOSTNAMELEN + 1];
if (channel->flags == -1)
channel->flags = 0;
if (channel->timeout == -1)
channel->timeout = DEFAULT_TIMEOUT;
if (channel->tries == -1)
channel->tries = DEFAULT_TRIES;
if (channel->ndots == -1)
channel->ndots = 1;
if (channel->udp_port == -1)
channel->udp_port = htons(NAMESERVER_PORT);
if (channel->tcp_port == -1)
channel->tcp_port = htons(NAMESERVER_PORT);
if (channel->nservers == -1)
{
/* If nobody specified servers, try a local named. */
channel->servers = malloc(sizeof(struct server_state));
if (!channel->servers)
return ARES_ENOMEM;
channel->servers[0].addr.s_addr = htonl(INADDR_LOOPBACK);
channel->nservers = 1;
}
if (channel->ndomains == -1)
{
/* Derive a default domain search list from the kernel hostname,
* or set it to empty if the hostname isn't helpful.
*/
if (gethostname(hostname, sizeof(hostname)) == -1
|| !strchr(hostname, '.'))
{
channel->domains = malloc(0);
channel->ndomains = 0;
}
else
{
channel->domains = malloc(sizeof(char *));
if (!channel->domains)
return ARES_ENOMEM;
channel->ndomains = 0;
channel->domains[0] = strdup(strchr(hostname, '.') + 1);
if (!channel->domains[0])
return ARES_ENOMEM;
channel->ndomains = 1;
}
}
if (channel->nsort == -1)
{
channel->sortlist = NULL;
channel->nsort = 0;
}
if (!channel->lookups)
{
channel->lookups = strdup("bf");
if (!channel->lookups)
return ARES_ENOMEM;
}
return ARES_SUCCESS;
}
static int config_domain(ares_channel channel, char *str)
{
char *q;
/* Set a single search domain. */
q = str;
while (*q && !isspace((unsigned char)*q))
q++;
*q = 0;
return set_search(channel, str);
}
static int config_lookup(ares_channel channel, const char *str)
{
char lookups[3], *l;
const char *p;
/* 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;
while (*p)
{
if ((*p == 'b' || *p == 'f') && l < lookups + 2)
*l++ = *p;
while (*p && !isspace((unsigned char)*p))
p++;
while (isspace((unsigned char)*p))
p++;
}
*l = 0;
channel->lookups = strdup(lookups);
return (channel->lookups) ? ARES_SUCCESS : ARES_ENOMEM;
}
static int config_nameserver(struct server_state **servers, int *nservers,
char *str)
{
struct in_addr addr;
struct server_state *newserv;
/* On Windows, there may be more than one nameserver specified in the same
* registry key, so we parse it as a space or comma seperated list.
*/
#ifdef WIN32
char *p = str;
char *begin = str;
int more = 1;
while (more)
{
more = 0;
while (*p && !isspace(*p) && *p != ',')
p++;
if (*p)
{
*p = 0;
more = 1;
}
/* Skip multiple spaces or trailing spaces */
if (!*begin)
{
begin = ++p;
continue;
}
/* This is the part that actually sets the nameserver */
addr.s_addr = inet_addr(begin);
if (addr.s_addr == INADDR_NONE)
continue;
newserv = realloc(*servers, (*nservers + 1) * sizeof(struct server_state));
if (!newserv)
return ARES_ENOMEM;
newserv[*nservers].addr = addr;
*servers = newserv;
(*nservers)++;
if (!more)
break;
begin = ++p;
}
#else
/* Add a nameserver entry, if this is a valid address. */
addr.s_addr = inet_addr(str);
if (addr.s_addr == INADDR_NONE)
return ARES_SUCCESS;
newserv = realloc(*servers, (*nservers + 1) * sizeof(struct server_state));
if (!newserv)
return ARES_ENOMEM;
newserv[*nservers].addr = addr;
*servers = newserv;
(*nservers)++;
#endif
return ARES_SUCCESS;
}
static int config_sortlist(struct apattern **sortlist, int *nsort,
const char *str)
{
struct apattern pat, *newsort;
const char *q;
/* Add sortlist entries. */
while (*str && *str != ';')
{
q = str;
while (*q && *q != '/' && *q != ';' && !isspace((unsigned char)*q))
q++;
if (ip_addr(str, (int)(q - str), &pat.addr) == 0)
{
/* We have a pattern address; now determine the mask. */
if (*q == '/')
{
str = q + 1;
while (*q && *q != ';' && !isspace((unsigned char)*q))
q++;
if (ip_addr(str, (int)(q - str), &pat.mask) != 0)
natural_mask(&pat);
}
else
natural_mask(&pat);
/* Add this pattern to our list. */
newsort = realloc(*sortlist, (*nsort + 1) * sizeof(struct apattern));
if (!newsort)
return ARES_ENOMEM;
newsort[*nsort] = pat;
*sortlist = newsort;
(*nsort)++;
}
else
{
while (*q && *q != ';' && !isspace((unsigned char)*q))
q++;
}
str = q;
while (isspace((unsigned char)*str))
str++;
}
return ARES_SUCCESS;
}
static int set_search(ares_channel channel, const char *str)
{
int n;
const char *p, *q;
if(channel->ndomains != -1) {
/* if we already have some domains present, free them first */
for(n=0; n < channel->ndomains; n++)
free(channel->domains[n]);
free(channel->domains);
channel->ndomains = -1;
}
/* Count the domains given. */
n = 0;
p = str;
while (*p)
{
while (*p && !isspace((unsigned char)*p))
p++;
while (isspace((unsigned char)*p))
p++;
n++;
}
channel->domains = malloc(n * sizeof(char *));
if (!channel->domains && n)
return ARES_ENOMEM;
/* Now copy the domains. */
n = 0;
p = str;
while (*p)
{
channel->ndomains = n;
q = p;
while (*q && !isspace((unsigned char)*q))
q++;
channel->domains[n] = malloc(q - p + 1);
if (!channel->domains[n])
return ARES_ENOMEM;
memcpy(channel->domains[n], p, q - p);
channel->domains[n][q - p] = 0;
p = q;
while (isspace((unsigned char)*p))
p++;
n++;
}
channel->ndomains = n;
return ARES_SUCCESS;
}
static int set_options(ares_channel channel, const char *str)
{
const char *p, *q, *val;
p = str;
while (*p)
{
q = p;
while (*q && !isspace((unsigned char)*q))
q++;
val = try_option(p, q, "ndots:");
if (val && channel->ndots == -1)
channel->ndots = atoi(val);
val = try_option(p, q, "retrans:");
if (val && channel->timeout == -1)
channel->timeout = atoi(val);
val = try_option(p, q, "retry:");
if (val && channel->tries == -1)
channel->tries = atoi(val);
p = q;
while (isspace((unsigned char)*p))
p++;
}
return ARES_SUCCESS;
}
static char *try_config(char *s, const char *opt)
{
size_t len;
len = strlen(opt);
if (strncmp(s, opt, len) != 0 || !isspace((unsigned char)s[len]))
return NULL;
s += len;
while (isspace((unsigned char)*s))
s++;
return s;
}
static const char *try_option(const char *p, const char *q, const char *opt)
{
size_t len = strlen(opt);
return ((size_t)(q - p) > len && !strncmp(p, opt, len)) ? &p[len] : NULL;
}
static int ip_addr(const char *s, int len, struct in_addr *addr)
{
char ipbuf[16];
/* Four octets and three periods yields at most 15 characters. */
if (len > 15)
return -1;
memcpy(ipbuf, s, len);
ipbuf[len] = 0;
addr->s_addr = inet_addr(ipbuf);
if (addr->s_addr == INADDR_NONE && strcmp(ipbuf, "255.255.255.255") != 0)
return -1;
return 0;
}
static void natural_mask(struct apattern *pat)
{
struct in_addr addr;
/* Store a host-byte-order copy of pat in a struct in_addr. Icky,
* but portable.
*/
addr.s_addr = ntohl(pat->addr.s_addr);
/* This is out of date in the CIDR world, but some people might
* still rely on it.
*/
if (IN_CLASSA(addr.s_addr))
pat->mask.s_addr = htonl(IN_CLASSA_NET);
else if (IN_CLASSB(addr.s_addr))
pat->mask.s_addr = htonl(IN_CLASSB_NET);
else
pat->mask.s_addr = htonl(IN_CLASSC_NET);
}