On 2023/06/30 15:29:07 +0200, Omar Polo <o...@omarpolo.com> wrote: > duh. sorry for the dumb question, it was obvious. Here's a better > diff. > > I've made strictier the syntax checks for IPv6 (after the closing ']' > there's the optional port but then nothing else) and joined together > the cases for the host:port or hostname alone to simplify the validity > check. > > last thing I'm unsure if whether we should log as-is the original Host > in case of error, as it can contain anything. > > Thanks!
now with a 100% correct return value in the ipv6 case. The memmove rewinds buf while start points at buf+1. i've called getaddrinfo() with buf, but we still return `start', so that needs to be fixed. diff /usr/src commit - 9832f5035d799ae12e9f848cff5650481b673260 path + /usr/src blob - 091cc86fec946a4a9d422c04a48f5cbfae015ce0 file + usr.sbin/httpd/server_http.c --- usr.sbin/httpd/server_http.c +++ usr.sbin/httpd/server_http.c @@ -23,6 +23,7 @@ #include <sys/tree.h> #include <sys/stat.h> +#include <netdb.h> #include <netinet/in.h> #include <arpa/inet.h> @@ -831,11 +832,49 @@ char * return (buf); } +static int +valid_domain(char *name, const char **errstr) +{ + size_t i; + unsigned char c, last = '\0'; + + *errstr = NULL; + + if (*name == '\0') { + *errstr = "empty domain name"; + return 0; + } + if (!isalpha((unsigned char)name[0]) && + !isdigit((unsigned char)name[0])) { + *errstr = "domain name starts with an invalid character"; + return 0; + } + for (i = 0; name[i] != '\0'; ++i) { + c = tolower((unsigned char)name[i]); + name[i] = c; + if (last == '.' && c == '.') { + *errstr = "domain name contains consecutive separators"; + return 0; + } + if (c != '.' && c != '-' && !isalnum(c) && + c != '_') /* technically invalid, but common */ { + *errstr = "domain name contains invalid characters"; + return 0; + } + last = c; + } + if (name[i - 1] == '.') + name[i - 1] = '\0'; + return 1; +} + char * server_http_parsehost(char *host, char *buf, size_t len, int *portval) { + struct addrinfo hints, *ai; char *start, *end, *port; const char *errstr = NULL; + int err; if (strlcpy(buf, host, len) >= len) { log_debug("%s: host name too long", __func__); @@ -849,18 +888,38 @@ server_http_parsehost(char *host, char *buf, size_t le /* Address enclosed in [] with port, eg. [2001:db8::1]:80 */ start++; *end++ = '\0'; - if ((port = strchr(end, ':')) == NULL || *port == '\0') - port = NULL; - else - port++; + if (*end == ':') { + *end++ = '\0'; + port = end; + } else if (*end != '\0') { + log_debug("%s: gibberish after IPv6 address: %s", + __func__, host); + return (NULL); + } + memmove(buf, start, strlen(start) + 1); - } else if ((end = strchr(start, ':')) != NULL) { - /* Name or address with port, eg. www.example.com:80 */ - *end++ = '\0'; - port = end; + start = buf; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + err = getaddrinfo(start, NULL, &hints, &ai); + if (err != 0) { + log_debug("%s: %s: %s", __func__, gai_strerror(err), + host); + return (NULL); + } + freeaddrinfo(ai); } else { - /* Name or address with default port, eg. www.example.com */ - port = NULL; + /* Name or address with optional port */ + if ((end = strchr(start, ':')) != NULL) { + *end++ = '\0'; + port = end; + } + + if (!valid_domain(start, &errstr)) { + log_debug("%s: %s: %s", __func__, errstr, host); + return (NULL); + } } if (port != NULL) {