|
|
|
@ -140,9 +140,13 @@ static ares_bool_t ares_server_blacklisted(const struct ares_addr *addr) |
|
|
|
|
/* Parse address and port in these formats, either ipv4 or ipv6 addresses
|
|
|
|
|
* are allowed: |
|
|
|
|
* ipaddr |
|
|
|
|
* ipv4addr:port |
|
|
|
|
* [ipaddr] |
|
|
|
|
* [ipaddr]:port |
|
|
|
|
* |
|
|
|
|
* TODO: in the future we need to add % and # syntax modifications to support |
|
|
|
|
* the link-local interface name and domain, respectively. |
|
|
|
|
* |
|
|
|
|
* If a port is not specified, will set port to 0. |
|
|
|
|
* |
|
|
|
|
* Will fail if an IPv6 nameserver as detected by |
|
|
|
@ -150,84 +154,106 @@ static ares_bool_t ares_server_blacklisted(const struct ares_addr *addr) |
|
|
|
|
* |
|
|
|
|
* Returns an error code on failure, else ARES_SUCCESS |
|
|
|
|
*/ |
|
|
|
|
static ares_status_t parse_dnsaddrport(const char *str, size_t len, |
|
|
|
|
|
|
|
|
|
static ares_status_t parse_dnsaddrport(ares__buf_t *buf, |
|
|
|
|
struct ares_addr *host, |
|
|
|
|
unsigned short *port) |
|
|
|
|
{ |
|
|
|
|
char ipaddr[INET6_ADDRSTRLEN] = ""; |
|
|
|
|
char ipport[6] = ""; |
|
|
|
|
size_t mylen; |
|
|
|
|
const char *addr_start = NULL; |
|
|
|
|
const char *addr_end = NULL; |
|
|
|
|
const char *port_start = NULL; |
|
|
|
|
const char *port_end = NULL; |
|
|
|
|
|
|
|
|
|
/* Must start with [, hex digit or : */ |
|
|
|
|
if (len == 0 || (*str != '[' && !isxdigit(*str) && *str != ':')) { |
|
|
|
|
return ARES_EBADSTR; |
|
|
|
|
} |
|
|
|
|
ares_status_t status; |
|
|
|
|
char ipaddr[INET6_ADDRSTRLEN] = ""; |
|
|
|
|
size_t addrlen; |
|
|
|
|
|
|
|
|
|
*port = 0; |
|
|
|
|
|
|
|
|
|
/* If it starts with a bracket, must end with a bracket */ |
|
|
|
|
if (*str == '[') { |
|
|
|
|
const char *ptr; |
|
|
|
|
addr_start = str + 1; |
|
|
|
|
ptr = memchr(addr_start, ']', len - 1); |
|
|
|
|
if (ptr == NULL) { |
|
|
|
|
/* Consume any leading whitespace */ |
|
|
|
|
ares__buf_consume_whitespace(buf, ARES_TRUE); |
|
|
|
|
|
|
|
|
|
/* pop off IP address. If it is in [ ] then it can be ipv4 or ipv6. If
|
|
|
|
|
* not, ipv4 only */ |
|
|
|
|
if (ares__buf_begins_with(buf, (const unsigned char *)"[", 1)) { |
|
|
|
|
/* Consume [ */ |
|
|
|
|
ares__buf_consume(buf, 1); |
|
|
|
|
|
|
|
|
|
ares__buf_tag(buf); |
|
|
|
|
|
|
|
|
|
/* Consume until ] */ |
|
|
|
|
if (ares__buf_consume_until_charset(buf, (const unsigned char *)"]", 1, ARES_TRUE) == 0) { |
|
|
|
|
return ARES_EBADSTR; |
|
|
|
|
} |
|
|
|
|
addr_end = ptr - 1; |
|
|
|
|
|
|
|
|
|
/* Try to pull off port */ |
|
|
|
|
if ((size_t)(ptr - str) < len) { |
|
|
|
|
ptr++; |
|
|
|
|
if (*ptr != ':') { |
|
|
|
|
status = ares__buf_tag_fetch_string(buf, ipaddr, sizeof(ipaddr)); |
|
|
|
|
if (status != ARES_SUCCESS) { |
|
|
|
|
return status; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Skip over ] */ |
|
|
|
|
ares__buf_consume(buf, 1); |
|
|
|
|
} else { |
|
|
|
|
size_t offset; |
|
|
|
|
|
|
|
|
|
/* Not in [ ], see if '.' is in first 4 characters, if it is, then its ipv4,
|
|
|
|
|
* otherwise treat as ipv6 */ |
|
|
|
|
ares__buf_tag(buf); |
|
|
|
|
|
|
|
|
|
offset = ares__buf_consume_until_charset(buf, (const unsigned char *)".", 1, ARES_TRUE); |
|
|
|
|
ares__buf_tag_rollback(buf); |
|
|
|
|
ares__buf_tag(buf); |
|
|
|
|
|
|
|
|
|
if (offset > 0 && offset < 4) { |
|
|
|
|
/* IPv4 */ |
|
|
|
|
if (ares__buf_consume_charset(buf, (const unsigned char *)"0123456789.", 11) == 0) { |
|
|
|
|
return ARES_EBADSTR; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Missing port number */ |
|
|
|
|
if ((size_t)(ptr - str) == len) { |
|
|
|
|
} else { |
|
|
|
|
/* IPv6 */ |
|
|
|
|
const unsigned char ipv6_charset[] = "ABCDEFabcdef0123456789.:"; |
|
|
|
|
if (ares__buf_consume_charset(buf, ipv6_charset, sizeof(ipv6_charset)) == 0) { |
|
|
|
|
return ARES_EBADSTR; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
port_start = ptr + 1; |
|
|
|
|
port_end = str + (len - 1); |
|
|
|
|
status = ares__buf_tag_fetch_string(buf, ipaddr, sizeof(ipaddr)); |
|
|
|
|
if (status != ARES_SUCCESS) { |
|
|
|
|
return status; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
addr_start = str; |
|
|
|
|
addr_end = str + (len - 1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
mylen = (size_t)(addr_end - addr_start) + 1; |
|
|
|
|
/* Larger than buffer with null term */ |
|
|
|
|
if (mylen + 1 > sizeof(ipaddr)) { |
|
|
|
|
/* Convert ip address from string to network byte order */ |
|
|
|
|
host->family = AF_UNSPEC; |
|
|
|
|
if (ares_dns_pton(ipaddr, host, &addrlen) == NULL) { |
|
|
|
|
return ARES_EBADSTR; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
memset(ipaddr, 0, sizeof(ipaddr)); |
|
|
|
|
memcpy(ipaddr, addr_start, mylen); |
|
|
|
|
/* Pull off port */ |
|
|
|
|
if (ares__buf_begins_with(buf, (const unsigned char *)":", 1)) { |
|
|
|
|
char portstr[6]; |
|
|
|
|
|
|
|
|
|
/* Consume : */ |
|
|
|
|
ares__buf_consume(buf, 1); |
|
|
|
|
|
|
|
|
|
ares__buf_tag(buf); |
|
|
|
|
|
|
|
|
|
if (port_start) { |
|
|
|
|
mylen = (size_t)(port_end - port_start) + 1; |
|
|
|
|
/* Larger than buffer with null term */ |
|
|
|
|
if (mylen + 1 > sizeof(ipport)) { |
|
|
|
|
/* Read numbers */ |
|
|
|
|
if (ares__buf_consume_charset(buf, (const unsigned char *)"0123456789", 10) == 0) { |
|
|
|
|
return ARES_EBADSTR; |
|
|
|
|
} |
|
|
|
|
memset(ipport, 0, sizeof(ipport)); |
|
|
|
|
memcpy(ipport, port_start, mylen); |
|
|
|
|
} else { |
|
|
|
|
snprintf(ipport, sizeof(ipport), "0"); |
|
|
|
|
|
|
|
|
|
status = ares__buf_tag_fetch_string(buf, portstr, sizeof(portstr)); |
|
|
|
|
if (status != ARES_SUCCESS) { |
|
|
|
|
return status; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
*port = (unsigned short)atoi(portstr); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Convert textual address to binary format. */ |
|
|
|
|
if (ares_inet_pton(AF_INET, ipaddr, &host->addr.addr4) == 1) { |
|
|
|
|
host->family = AF_INET; |
|
|
|
|
} else if (ares_inet_pton(AF_INET6, ipaddr, &host->addr.addr6) == 1) { |
|
|
|
|
host->family = AF_INET6; |
|
|
|
|
} else { |
|
|
|
|
/* Consume any trailing whitespace so we can bail out if there is something
|
|
|
|
|
* after we didn't read */ |
|
|
|
|
ares__buf_consume_whitespace(buf, ARES_TRUE); |
|
|
|
|
|
|
|
|
|
if (ares__buf_len(buf) != 0) { |
|
|
|
|
return ARES_EBADSTR; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
*port = (unsigned short)atoi(ipport); |
|
|
|
|
return ARES_SUCCESS; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -295,48 +321,56 @@ fail: |
|
|
|
|
* Returns an error code on failure, else ARES_SUCCESS. |
|
|
|
|
*/ |
|
|
|
|
ares_status_t ares__sconfig_append_fromstr(ares__llist_t **sconfig, |
|
|
|
|
const char *str) |
|
|
|
|
const char *str, |
|
|
|
|
ares_bool_t ignore_invalid) |
|
|
|
|
{ |
|
|
|
|
struct ares_addr host; |
|
|
|
|
const char *p; |
|
|
|
|
const char *txtaddr; |
|
|
|
|
ares_status_t status; |
|
|
|
|
ares_status_t status = ARES_SUCCESS; |
|
|
|
|
ares__buf_t *buf = NULL; |
|
|
|
|
ares__llist_t *list = NULL; |
|
|
|
|
ares__llist_node_t *node; |
|
|
|
|
|
|
|
|
|
/* On Windows, there may be more than one nameserver specified in the same
|
|
|
|
|
* registry key, so we parse input as a space or comma separated list. |
|
|
|
|
*/ |
|
|
|
|
for (p = str; p;) { |
|
|
|
|
unsigned short port; |
|
|
|
|
|
|
|
|
|
/* Skip whitespace and commas. */ |
|
|
|
|
while (*p && (ISSPACE(*p) || (*p == ','))) { |
|
|
|
|
p++; |
|
|
|
|
} |
|
|
|
|
if (!*p) { |
|
|
|
|
/* No more input, done. */ |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
buf = ares__buf_create_const((const unsigned char *)str, ares_strlen(str)); |
|
|
|
|
if (buf == NULL) { |
|
|
|
|
status = ARES_ENOMEM; |
|
|
|
|
goto done; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Pointer to start of IPv4 or IPv6 address part. */ |
|
|
|
|
txtaddr = p; |
|
|
|
|
status = ares__buf_split(buf, (const unsigned char *)" ,", 2, |
|
|
|
|
ARES_BUF_SPLIT_NONE, &list); |
|
|
|
|
if (status != ARES_SUCCESS) { |
|
|
|
|
goto done; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Advance past this address. */ |
|
|
|
|
while (*p && !ISSPACE(*p) && (*p != ',')) { |
|
|
|
|
p++; |
|
|
|
|
} |
|
|
|
|
for (node = ares__llist_node_first(list); node != NULL; |
|
|
|
|
node = ares__llist_node_next(node)) { |
|
|
|
|
ares__buf_t *entry = ares__llist_node_val(node); |
|
|
|
|
struct ares_addr host; |
|
|
|
|
unsigned short port; |
|
|
|
|
|
|
|
|
|
if (parse_dnsaddrport(txtaddr, (size_t)(p - txtaddr), &host, &port) != |
|
|
|
|
ARES_SUCCESS) { |
|
|
|
|
continue; |
|
|
|
|
status = parse_dnsaddrport(entry, &host, &port); |
|
|
|
|
if (status != ARES_SUCCESS) { |
|
|
|
|
if (ignore_invalid) { |
|
|
|
|
continue; |
|
|
|
|
} else { |
|
|
|
|
goto done; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
status = ares__sconfig_append(sconfig, &host, port, port); |
|
|
|
|
if (status != ARES_SUCCESS) { |
|
|
|
|
return status; |
|
|
|
|
goto done; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ARES_SUCCESS; |
|
|
|
|
status = ARES_SUCCESS; |
|
|
|
|
|
|
|
|
|
done: |
|
|
|
|
ares__llist_destroy(list); |
|
|
|
|
ares__buf_destroy(buf); |
|
|
|
|
return status; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static unsigned short ares__sconfig_get_port(const ares_channel_t *channel, |
|
|
|
@ -895,165 +929,45 @@ int ares_set_servers_ports(ares_channel_t *channel, |
|
|
|
|
|
|
|
|
|
/* Incoming string format: host[:port][,host[:port]]... */ |
|
|
|
|
/* IPv6 addresses with ports require square brackets [fe80::1]:53 */ |
|
|
|
|
static ares_status_t set_servers_csv(ares_channel_t *channel, const char *_csv, |
|
|
|
|
int use_port) |
|
|
|
|
static ares_status_t set_servers_csv(ares_channel_t *channel, const char *_csv) |
|
|
|
|
{ |
|
|
|
|
size_t i; |
|
|
|
|
char *csv = NULL; |
|
|
|
|
char *ptr; |
|
|
|
|
const char *start_host; |
|
|
|
|
int cc = 0; |
|
|
|
|
ares_status_t status = ARES_SUCCESS; |
|
|
|
|
struct ares_addr_port_node *servers = NULL; |
|
|
|
|
struct ares_addr_port_node *last = NULL; |
|
|
|
|
|
|
|
|
|
if (ares_library_initialized() != ARES_SUCCESS) { |
|
|
|
|
return ARES_ENOTINITIALIZED; /* LCOV_EXCL_LINE: n/a on non-WinSock */ |
|
|
|
|
} |
|
|
|
|
ares_status_t status; |
|
|
|
|
ares__llist_t *slist = NULL; |
|
|
|
|
|
|
|
|
|
if (!channel) { |
|
|
|
|
if (channel == NULL) { |
|
|
|
|
return ARES_ENODATA; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* NOTE: lock is in ares__servers_update() */ |
|
|
|
|
|
|
|
|
|
i = ares_strlen(_csv); |
|
|
|
|
if (i == 0) { |
|
|
|
|
if (ares_strlen(_csv) == 0) { |
|
|
|
|
/* blank all servers */ |
|
|
|
|
return (ares_status_t)ares_set_servers_ports(channel, NULL); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
csv = ares_malloc(i + 2); |
|
|
|
|
if (!csv) { |
|
|
|
|
return ARES_ENOMEM; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ares_strcpy(csv, _csv, i + 2); |
|
|
|
|
if (csv[i - 1] != ',') { /* make parsing easier by ensuring ending ',' */ |
|
|
|
|
csv[i] = ','; |
|
|
|
|
csv[i + 1] = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
start_host = csv; |
|
|
|
|
for (ptr = csv; *ptr; ptr++) { |
|
|
|
|
if (*ptr == ':') { |
|
|
|
|
/* count colons to determine if we have an IPv6 number or IPv4 with
|
|
|
|
|
port */ |
|
|
|
|
cc++; |
|
|
|
|
} else if (*ptr == '[') { |
|
|
|
|
/* move start_host if an open square bracket is found wrapping an IPv6
|
|
|
|
|
address */ |
|
|
|
|
start_host = ptr + 1; |
|
|
|
|
} else if (*ptr == ',') { |
|
|
|
|
char *pp = ptr - 1; |
|
|
|
|
char *p = ptr; |
|
|
|
|
int port = 0; |
|
|
|
|
struct in_addr in4; |
|
|
|
|
struct ares_in6_addr in6; |
|
|
|
|
struct ares_addr_port_node *s = NULL; |
|
|
|
|
|
|
|
|
|
*ptr = 0; /* null terminate host:port string */ |
|
|
|
|
/* Got an entry..see if the port was specified. */ |
|
|
|
|
if (cc > 0) { |
|
|
|
|
while (pp > start_host) { |
|
|
|
|
/* a single close square bracket followed by a colon, ']:' indicates
|
|
|
|
|
an IPv6 address with port */ |
|
|
|
|
if ((*pp == ']') && (*p == ':')) { |
|
|
|
|
break; /* found port */ |
|
|
|
|
} |
|
|
|
|
/* a single colon, ':' indicates an IPv4 address with port */ |
|
|
|
|
if ((*pp == ':') && (cc == 1)) { |
|
|
|
|
break; /* found port */ |
|
|
|
|
} |
|
|
|
|
if (!(ISDIGIT(*pp) || (*pp == ':'))) { |
|
|
|
|
/* Found end of digits before we found :, so wasn't a port */ |
|
|
|
|
/* must allow ':' for IPv6 case of ']:' indicates we found a port */ |
|
|
|
|
pp = p = ptr; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
pp--; |
|
|
|
|
p--; |
|
|
|
|
} |
|
|
|
|
if ((pp != start_host) && ((pp + 1) < ptr)) { |
|
|
|
|
/* Found it. Parse over the port number */ |
|
|
|
|
/* when an IPv6 address is wrapped with square brackets the port
|
|
|
|
|
starts at pp + 2 */ |
|
|
|
|
if (*pp == ']') { |
|
|
|
|
p++; /* move p before ':' */ |
|
|
|
|
} |
|
|
|
|
/* p will point to the start of the port */ |
|
|
|
|
port = (int)strtol(p, NULL, 10); |
|
|
|
|
*pp = 0; /* null terminate host */ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
/* resolve host, try ipv4 first, rslt is in network byte order */ |
|
|
|
|
if (!ares_inet_pton(AF_INET, start_host, &in4)) { |
|
|
|
|
/* Ok, try IPv6 then */ |
|
|
|
|
if (!ares_inet_pton(AF_INET6, start_host, &in6)) { |
|
|
|
|
status = ARES_EBADSTR; |
|
|
|
|
goto out; |
|
|
|
|
} |
|
|
|
|
/* was ipv6, add new server */ |
|
|
|
|
s = ares_malloc(sizeof(*s)); |
|
|
|
|
if (!s) { |
|
|
|
|
status = ARES_ENOMEM; |
|
|
|
|
goto out; |
|
|
|
|
} |
|
|
|
|
s->family = AF_INET6; |
|
|
|
|
memcpy(&s->addr, &in6, sizeof(struct ares_in6_addr)); |
|
|
|
|
} else { |
|
|
|
|
/* was ipv4, add new server */ |
|
|
|
|
s = ares_malloc(sizeof(*s)); |
|
|
|
|
if (!s) { |
|
|
|
|
status = ARES_ENOMEM; |
|
|
|
|
goto out; |
|
|
|
|
} |
|
|
|
|
s->family = AF_INET; |
|
|
|
|
memcpy(&s->addr, &in4, sizeof(struct in_addr)); |
|
|
|
|
} |
|
|
|
|
if (s) { |
|
|
|
|
s->udp_port = use_port ? port : 0; |
|
|
|
|
s->tcp_port = s->udp_port; |
|
|
|
|
s->next = NULL; |
|
|
|
|
if (last) { |
|
|
|
|
last->next = s; |
|
|
|
|
/* need to move last to maintain the linked list */ |
|
|
|
|
last = last->next; |
|
|
|
|
} else { |
|
|
|
|
servers = s; |
|
|
|
|
last = s; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Set up for next one */ |
|
|
|
|
start_host = ptr + 1; |
|
|
|
|
cc = 0; |
|
|
|
|
} |
|
|
|
|
status = ares__sconfig_append_fromstr(&slist, _csv, ARES_FALSE); |
|
|
|
|
if (status != ARES_SUCCESS) { |
|
|
|
|
ares__llist_destroy(slist); |
|
|
|
|
return status; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
status = (ares_status_t)ares_set_servers_ports(channel, servers); |
|
|
|
|
/* NOTE: lock is in ares__servers_update() */ |
|
|
|
|
status = ares__servers_update(channel, slist, ARES_TRUE); |
|
|
|
|
|
|
|
|
|
out: |
|
|
|
|
if (csv) { |
|
|
|
|
ares_free(csv); |
|
|
|
|
} |
|
|
|
|
while (servers) { |
|
|
|
|
struct ares_addr_port_node *s = servers; |
|
|
|
|
servers = servers->next; |
|
|
|
|
ares_free(s); |
|
|
|
|
} |
|
|
|
|
ares__llist_destroy(slist); |
|
|
|
|
|
|
|
|
|
return status; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* We'll go ahead and honor ports anyhow */ |
|
|
|
|
int ares_set_servers_csv(ares_channel_t *channel, const char *_csv) |
|
|
|
|
{ |
|
|
|
|
/* NOTE: lock is in ares__servers_update() */ |
|
|
|
|
return (int)set_servers_csv(channel, _csv, ARES_FALSE); |
|
|
|
|
return (int)set_servers_csv(channel, _csv); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int ares_set_servers_ports_csv(ares_channel_t *channel, const char *_csv) |
|
|
|
|
{ |
|
|
|
|
/* NOTE: lock is in ares__servers_update() */ |
|
|
|
|
return (int)set_servers_csv(channel, _csv, ARES_TRUE); |
|
|
|
|
return (int)set_servers_csv(channel, _csv); |
|
|
|
|
} |
|
|
|
|