From 09d667c49383edb980bcac88de1173c452066128 Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Sun, 26 Jul 2020 16:45:11 -0700 Subject: [PATCH] fixes #844 WebSocket wildcard host errors fixes #1224 wss fails on IPV6 address This fixes bugs and inconsistencies in the way addresses are handled for HTTP (and consequently websocket). The Host: address line needs to look at numeric IPs and treat wildcards as if they are not specified, and needs to understand the IPv6 address format using brackets (e.g. [::1]:80). --- src/core/platform.h | 6 ++ src/platform/posix/posix_resolv_gai.c | 111 +++++++++++++++++++++++++- src/platform/windows/win_resolv.c | 104 +++++++++++++++++++++++- src/supplemental/http/http_server.c | 110 +++++++++++++++++++------ src/supplemental/tcp/tcp.c | 2 +- 5 files changed, 305 insertions(+), 28 deletions(-) diff --git a/src/core/platform.h b/src/core/platform.h index 70420061e..c6f4ef305 100644 --- a/src/core/platform.h +++ b/src/core/platform.h @@ -339,6 +339,12 @@ extern void nni_tcp_resolv(const char *, const char *, int, int, nni_aio *); // service names using UDP. extern void nni_udp_resolv(const char *, const char *, int, int, nni_aio *); +// nni_parse_ip parses an IP address, without a port. +extern int nni_parse_ip(const char *, nng_sockaddr *); + +// nni_parse_ip_port parses an IP address with an optional port appended. +extern int nni_parse_ip_port(const char *, nng_sockaddr *); + // // IPC (UNIX Domain Sockets & Named Pipes) Support. // diff --git a/src/platform/posix/posix_resolv_gai.c b/src/platform/posix/posix_resolv_gai.c index 5cec05708..883130456 100644 --- a/src/platform/posix/posix_resolv_gai.c +++ b/src/platform/posix/posix_resolv_gai.c @@ -1,5 +1,5 @@ // -// Copyright 2019 Staysail Systems, Inc. +// Copyright 2020 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // // This software is supplied under the terms of the MIT License, a @@ -257,8 +257,9 @@ resolv_ip(const char *host, const char *serv, int passive, int family, // NB: must remain valid until this is completed. So we have to // keep our own copy. - if (host != NULL && nni_strnlen(host, sizeof(item->name_buf)) >= - sizeof(item->name_buf)) { + if (host != NULL && + nni_strnlen(host, sizeof(item->name_buf)) >= + sizeof(item->name_buf)) { NNI_FREE_STRUCT(item); nni_aio_finish_error(aio, NNG_EADDRINVAL); return; @@ -353,6 +354,110 @@ resolv_worker(void *unused) nni_mtx_unlock(&resolv_mtx); } +int +parse_ip(const char *addr, nng_sockaddr *sa, bool want_port) +{ + struct addrinfo hints; + struct addrinfo *results; + int rv; + bool v6 = false; + bool wrapped = false; + char * port; + char * host; + char * buf; + size_t buf_len; + + if (addr == NULL) { + addr = ""; + } + + buf_len = strlen(addr) + 1; + if ((buf = nni_alloc(buf_len)) == NULL) { + return (NNG_ENOMEM); + } + memcpy(buf, addr, buf_len); + host = buf; + if (*host == '[') { + v6 = true; + wrapped = true; + host++; + } else { + char *s; + for (s = host; *s != '\0'; s++) { + if (*s == '.') { + break; + } + if (*s == ':') { + v6 = true; + break; + } + } + } + for (port = host; *port != '\0'; port++) { + if (wrapped) { + if (*port == ']') { + *port++ = '\0'; + wrapped = false; + break; + } + } else if (!v6) { + if (*port == ':') { + break; + } + } + } + + if (wrapped) { + // Never got the closing bracket. + rv = NNG_EADDRINVAL; + goto done; + } + + if ((!want_port) && (*port != '\0')) { + rv = NNG_EADDRINVAL; + goto done; + } else if (*port == ':') { + *port++ = '\0'; + } + + if (*port == '\0') { + port = "0"; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICSERV | AI_NUMERICHOST | AI_PASSIVE; + if (v6) { + hints.ai_family = AF_INET6; + } +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif + + rv = getaddrinfo(host, port, &hints, &results); + if ((rv != 0) || (results == NULL)) { + rv = nni_plat_errno(rv); + goto done; + } + nni_posix_sockaddr2nn(sa, (void *) results->ai_addr); + freeaddrinfo(results); + +done: + nni_free(buf, buf_len); + return (rv); +} + +int +nni_parse_ip(const char *addr, nni_sockaddr *sa) +{ + return (parse_ip(addr, sa, false)); +} + +int +nni_parse_ip_port(const char *addr, nni_sockaddr *sa) +{ + return (parse_ip(addr, sa, true)); +} + int nni_posix_resolv_sysinit(void) { diff --git a/src/platform/windows/win_resolv.c b/src/platform/windows/win_resolv.c index ff356700a..d80b5ddf3 100644 --- a/src/platform/windows/win_resolv.c +++ b/src/platform/windows/win_resolv.c @@ -1,5 +1,5 @@ // -// Copyright 2019 Staysail Systems, Inc. +// Copyright 2020 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // // This software is supplied under the terms of the MIT License, a @@ -316,6 +316,108 @@ resolv_worker(void *notused) nni_mtx_unlock(&resolv_mtx); } +int +parse_ip(const char *addr, nng_sockaddr *sa, bool want_port) +{ + struct addrinfo hints; + struct addrinfo *results; + int rv; + bool v6 = false; + bool wrapped = false; + char * port; + char * host; + char * buf; + size_t buf_len; + + if (addr == NULL) { + addr = ""; + } + + buf_len = strlen(addr) + 1; + if ((buf = nni_alloc(buf_len)) == NULL) { + return (NNG_ENOMEM); + } + memcpy(buf, addr, buf_len); + host = buf; + if (*host == '[') { + v6 = true; + wrapped = true; + host++; + } else { + char *s; + for (s = host; *s != '\0'; s++) { + if (*s == '.') { + break; + } + if (*s == ':') { + v6 = true; + break; + } + } + } + for (port = host; *port != '\0'; port++) { + if (wrapped) { + if (*port == ']') { + *port++ = '\0'; + wrapped = false; + break; + } + } else if (!v6) { + if (*port == ':') { + break; + } + } + } + + if (wrapped) { + // Never got the closing bracket. + rv = NNG_EADDRINVAL; + goto done; + } + + if ((!want_port) && (*port != '\0')) { + rv = NNG_EADDRINVAL; + goto done; + } else if (*port == ':') { + *port++ = '\0'; + } + + if (*port == '\0') { + port = "0"; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = + AI_ADDRCONFIG | AI_NUMERICSERV | AI_NUMERICHOST | AI_PASSIVE; + if (v6) { + hints.ai_family = AF_INET6; + } + + rv = getaddrinfo(host, port, &hints, &results); + if ((rv != 0) || (results == NULL)) { + rv = nni_win_error(rv); + goto done; + } + nni_win_sockaddr2nn(sa, (void *) results->ai_addr); + freeaddrinfo(results); + +done: + nni_free(buf, buf_len); + return (rv); +} + +int +nni_parse_ip(const char *addr, nni_sockaddr *sa) +{ + return (parse_ip(addr, sa, false)); +} + +int +nni_parse_ip_port(const char *addr, nni_sockaddr *sa) +{ + return (parse_ip(addr, sa, true)); +} + int nni_win_resolv_sysinit(void) { diff --git a/src/supplemental/http/http_server.c b/src/supplemental/http/http_server.c index 1b90c172e..e711c2a2f 100644 --- a/src/supplemental/http/http_server.c +++ b/src/supplemental/http/http_server.c @@ -36,6 +36,8 @@ struct nng_http_handler { char * uri; char * method; char * host; + nng_sockaddr host_addr; + bool host_ip; bool tree; bool tree_exclusive; nni_atomic_u64 ref; @@ -203,11 +205,35 @@ nni_http_handler_set_host(nni_http_handler *h, const char *host) if (nni_atomic_get_bool(&h->busy) != 0) { return (NNG_EBUSY); } - if (host == NULL) { + if ((host == NULL) || (strcmp(host, "*") == 0) || + strcmp(host, "") == 0) { nni_strfree(h->host); h->host = NULL; return (0); } + if (nni_parse_ip(host, &h->host_addr) == 0) { + uint8_t wild[16] = { 0 }; + + // Check for wild card addresses. + switch (h->host_addr.s_family) { + case NNG_AF_INET: + if (h->host_addr.s_in.sa_addr == 0) { + nni_strfree(h->host); + h->host = NULL; + return (0); + } + break; + case NNG_AF_INET6: + if (memcmp(h->host_addr.s_in6.sa_addr, wild, 16) == + 0) { + nni_strfree(h->host); + h->host = NULL; + return (0); + } + break; + } + h->host_ip = true; + } if ((dup = nni_strdup(host)) == NULL) { return (NNG_ENOMEM); } @@ -472,6 +498,64 @@ nni_http_hijack(nni_http_conn *conn) return (0); } +static bool +http_handler_host_match(nni_http_handler *h, const char *host) +{ + nng_sockaddr sa; + size_t len; + + if (h->host == NULL) { + return (true); + } + if (host == NULL) { + // Virtual hosts not possible under HTTP/1.0 + return (false); + } + if (h->host_ip) { + if (nni_parse_ip_port(host, &sa) != 0) { + return (false); + } + switch (h->host_addr.s_family) { + case NNG_AF_INET: + if ((sa.s_in.sa_family != NNG_AF_INET) || + (sa.s_in.sa_addr != h->host_addr.s_in.sa_addr)) { + return (false); + } + return (true); + case NNG_AF_INET6: + if (sa.s_in6.sa_family != NNG_AF_INET6) { + return (false); + } + if (memcmp(sa.s_in6.sa_addr, + h->host_addr.s_in6.sa_addr, 16) != 0) { + return (false); + } + return (true); + } + } + + len = strlen(h->host); + + if ((nni_strncasecmp(host, h->host, len) != 0)) { + return (false); + } + + // At least the first part matches. If the ending + // part is a lone "." (legal in DNS), or a port + // number, we match it. (We do not validate the + // port number.) Note that there may be false matches + // with IPv6 addresses, but addresses shouldn't be + // used with virtual hosts anyway. With both addresses + // and ports, a false match would be unlikely since + // they'd still have to *connect* using that info. + if ((host[len] != '\0') && (host[len] != ':') && + ((host[len] != '.') || (host[len + 1] != '\0'))) { + return (false); + } + + return (true); +} + static void http_sconn_rxdone(void *arg) { @@ -556,29 +640,9 @@ http_sconn_rxdone(void *arg) nni_mtx_lock(&s->mtx); NNI_LIST_FOREACH (&s->handlers, h) { size_t len; - if (h->host != NULL) { - if (host == NULL) { - // HTTP/1.0 cannot access virtual hosts. - continue; - } - len = strlen(h->host); - if ((nni_strncasecmp(host, h->host, len) != 0)) { - continue; - } - - // At least the first part matches. If the ending - // part is a lone "." (legal in DNS), or a port - // number, we match it. (We do not validate the - // port number.) Note that there may be false matches - // with IPv6 addresses, but addresses shouldn't be - // used with virtual hosts anyway. With both addresses - // and ports, a false match would be unlikely since - // they'd still have to *connect* using that info. - if ((host[len] != '\0') && (host[len] != ':') && - ((host[len] != '.') || (host[len + 1] != '\0'))) { - continue; - } + if (!http_handler_host_match(h, host)) { + continue; } len = strlen(h->uri); diff --git a/src/supplemental/tcp/tcp.c b/src/supplemental/tcp/tcp.c index dd6f28ff7..2fb7f56dd 100644 --- a/src/supplemental/tcp/tcp.c +++ b/src/supplemental/tcp/tcp.c @@ -439,7 +439,7 @@ nni_tcp_listener_alloc(nng_stream_listener **lp, const nng_url *url) h = url->u_hostname; // Wildcard special case, which means bind to INADDR_ANY. - if ((h != NULL) && ((strcmp(h, "*") == 0) || (strlen(h) == 0))) { + if ((h != NULL) && ((strcmp(h, "*") == 0) || (strcmp(h, "") == 0))) { h = NULL; } nni_tcp_resolv(h, url->u_port, af, 1, aio);