diff --git a/src/lib/ares_init.c b/src/lib/ares_init.c index 3e2033cd..0bc71592 100644 --- a/src/lib/ares_init.c +++ b/src/lib/ares_init.c @@ -73,7 +73,7 @@ static int init_by_defaults(ares_channel channel); #ifndef WATT32 static int config_nameserver(struct server_state **servers, int *nservers, - char *str); + const char *str); #endif static int set_search(ares_channel channel, const char *str); static int set_options(ares_channel channel, const char *str); @@ -1313,18 +1313,28 @@ static int init_by_resolv_conf(ares_channel channel) int nscount = res_getservers(&res, addr, MAXNS); int i; for (i = 0; i < nscount; ++i) { - char str[INET6_ADDRSTRLEN]; + char ipaddr[INET6_ADDRSTRLEN] = ""; + char ipaddr_port[INET6_ADDRSTRLEN + 8]; /* [%s]:NNNNN */ + unsigned short port = 0; int config_status; sa_family_t family = addr[i].sin.sin_family; if (family == AF_INET) { - ares_inet_ntop(family, &addr[i].sin.sin_addr, str, sizeof(str)); + ares_inet_ntop(family, &addr[i].sin.sin_addr, ipaddr, sizeof(ipaddr)); + port = ntohs(addr[i].sin.sin_port); } else if (family == AF_INET6) { - ares_inet_ntop(family, &addr[i].sin6.sin6_addr, str, sizeof(str)); + ares_inet_ntop(family, &addr[i].sin6.sin6_addr, ipaddr, sizeof(ipaddr)); + port = ntohs(addr[i].sin6.sin6_port); } else { continue; } - config_status = config_nameserver(&servers, &nservers, str); + if (port) { + snprintf(ipaddr_port, sizeof(ipaddr_port), "[%s]:%u", ipaddr, port); + } else { + snprintf(ipaddr_port, sizeof(ipaddr_port), "%s", ipaddr); + } + + config_status = config_nameserver(&servers, &nservers, ipaddr_port); if (config_status != ARES_SUCCESS) { status = config_status; break; @@ -1831,8 +1841,112 @@ static int ares_ipv6_server_blacklisted(const unsigned char ipaddr[16]) return 0; } -/* Add the IPv4 or IPv6 nameservers in str (separated by commas) to the - * servers list, updating servers and nservers as required. +/* Parse address and port in these formats, either ipv4 or ipv6 addresses + * are allowed: + * ipaddr + * [ipaddr] + * [ipaddr]:port + * + * If a port is not specified, will set port to 0. + * + * Will fail if an IPv6 nameserver as detected by + * ares_ipv6_server_blacklisted() + * + * Returns an error code on failure, else ARES_SUCCESS + */ +static int parse_dnsaddrport(const char *str, size_t len, + 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; + } + + /* 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) { + return ARES_EBADSTR; + } + addr_end = ptr-1; + + /* Try to pull off port */ + if ((size_t)(ptr - str) < len) { + ptr++; + if (*ptr != ':') { + return ARES_EBADSTR; + } + + /* Missing port number */ + if ((size_t)(ptr - str) == len) { + return ARES_EBADSTR; + } + + port_start = ptr+1; + port_end = str+(len-1); + } + } else { + addr_start = str; + addr_end = str+(len-1); + } + + mylen = (addr_end-addr_start)+1; + /* Larger than buffer with null term */ + if (mylen+1 > sizeof(ipaddr)) { + return ARES_EBADSTR; + } + + memset(ipaddr, 0, sizeof(ipaddr)); + memcpy(ipaddr, addr_start, mylen); + + if (port_start) { + mylen = (port_end-port_start)+1; + /* Larger than buffer with null term */ + if (mylen+1 > sizeof(ipport)) { + return ARES_EBADSTR; + } + memset(ipport, 0, sizeof(ipport)); + memcpy(ipport, port_start, mylen); + } else { + snprintf(ipport, sizeof(ipport), "0"); + } + + /* Convert textual address to binary format. */ + if (ares_inet_pton(AF_INET, ipaddr, &host->addrV4) == 1) { + host->family = AF_INET; + } else if (ares_inet_pton(AF_INET6, ipaddr, &host->addrV6) == 1 + /* Silently skip blacklisted IPv6 servers. */ + && !ares_ipv6_server_blacklisted( + (const unsigned char *)&host->addrV6)) { + host->family = AF_INET6; + } else { + return ARES_EBADSTR; + } + + *port = (unsigned short)atoi(ipport); + return ARES_SUCCESS; +} + +/* Add the IPv4 or IPv6 nameservers in str (separated by commas or spaces) to + * the servers list, updating servers and nservers as required. + * + * If a nameserver is encapsulated in [ ] it may optionally include a port + * suffix, e.g.: + * [127.0.0.1]:59591 + * + * The extended format is required to support OpenBSD's resolv.conf format: + * https://man.openbsd.org/OpenBSD-5.1/resolv.conf.5 + * As well as MacOS libresolv that may include a non-default port number. * * This will silently ignore blacklisted IPv6 nameservers as detected by * ares_ipv6_server_blacklisted(). @@ -1840,16 +1954,18 @@ static int ares_ipv6_server_blacklisted(const unsigned char ipaddr[16]) * Returns an error code on failure, else ARES_SUCCESS. */ static int config_nameserver(struct server_state **servers, int *nservers, - char *str) + const char *str) { struct ares_addr host; struct server_state *newserv; - char *p, *txtaddr; + const char *p, *txtaddr; /* On Windows, there may be more than one nameserver specified in the same * registry key, so we parse input as a space or comma seperated list. */ for (p = str; p;) { + unsigned short port; + /* Skip whitespace and commas. */ while (*p && (ISSPACE(*p) || (*p == ','))) p++; @@ -1863,23 +1979,11 @@ static int config_nameserver(struct server_state **servers, int *nservers, /* Advance past this address. */ while (*p && !ISSPACE(*p) && (*p != ',')) p++; - if (*p) - /* Null terminate this address. */ - *p++ = '\0'; - else - /* Reached end of input, done when this address is processed. */ - p = NULL; - /* Convert textual address to binary format. */ - if (ares_inet_pton(AF_INET, txtaddr, &host.addrV4) == 1) - host.family = AF_INET; - else if (ares_inet_pton(AF_INET6, txtaddr, &host.addrV6) == 1 - /* Silently skip blacklisted IPv6 servers. */ - && !ares_ipv6_server_blacklisted( - (const unsigned char *)&host.addrV6)) - host.family = AF_INET6; - else + if (parse_dnsaddrport(txtaddr, p-txtaddr, &host, &port) != + ARES_SUCCESS) { continue; + } /* Resize servers state array. */ newserv = ares_realloc(*servers, (*nservers + 1) * @@ -1889,8 +1993,8 @@ static int config_nameserver(struct server_state **servers, int *nservers, /* Store address data. */ newserv[*nservers].addr.family = host.family; - newserv[*nservers].addr.udp_port = 0; - newserv[*nservers].addr.tcp_port = 0; + newserv[*nservers].addr.udp_port = htons(port); + newserv[*nservers].addr.tcp_port = htons(port); if (host.family == AF_INET) memcpy(&newserv[*nservers].addr.addrV4, &host.addrV4, sizeof(host.addrV4));