host names: allow trailing dot in name resolve, then strip it

Delays stripping of trailing dots to after resolving the hostname.

Fixes #3022
Closes #3222
This commit is contained in:
Tobias Hintze 2018-11-02 21:24:14 +01:00 committed by Daniel Stenberg
parent 2366697806
commit 5b4cce2e36
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
3 changed files with 61 additions and 33 deletions

View File

@ -167,7 +167,8 @@ problems may have been fixed or changed somewhat since this was written!
When given a URL with a trailing dot for the host name part: When given a URL with a trailing dot for the host name part:
"https://example.com./", libcurl will strip off the dot and use the name "https://example.com./", libcurl will strip off the dot and use the name
without a dot internally and send it dot-less in HTTP Host: headers and in without a dot internally and send it dot-less in HTTP Host: headers and in
the TLS SNI field. the TLS SNI field. For the purpose of resolving the name to an address
the hostname is used as is without any change.
The HTTP part violates RFC 7230 section 5.4 but the SNI part is accordance The HTTP part violates RFC 7230 section 5.4 but the SNI part is accordance
with RFC 6066 section 3. with RFC 6066 section 3.
@ -186,10 +187,10 @@ problems may have been fixed or changed somewhat since this was written!
Our current approach allows a knowing client to send a custom HTTP header Our current approach allows a knowing client to send a custom HTTP header
with the dot added. with the dot added.
It can also be noted that while adding a trailing dot to the host name in In a few cases there is a difference in name resolving to IP addresses with
most (all?) cases will make the name resolve to the same set of IP addresses, a trailing dot, but it can be noted that many HTTP servers will not happily
many HTTP servers will not happily accept the trailing dot there unless that accept the trailing dot there unless that has been specifically configured
has been specifically configured to be a fine virtual host. to be a fine virtual host.
If URLs with trailing dots for host names become more popular or even just If URLs with trailing dots for host names become more popular or even just
used more than for just plain fun experiments, I'm sure we will have reason used more than for just plain fun experiments, I'm sure we will have reason

View File

@ -127,7 +127,7 @@ bool curl_win32_idn_to_ascii(const char *in, char **out);
#include "memdebug.h" #include "memdebug.h"
static void conn_free(struct connectdata *conn); static void conn_free(struct connectdata *conn);
static void free_fixed_hostname(struct hostname *host); static void free_idnconverted_hostname(struct hostname *host);
static unsigned int get_protocol_family(unsigned int protocol); static unsigned int get_protocol_family(unsigned int protocol);
/* Some parts of the code (e.g. chunked encoding) assume this buffer has at /* Some parts of the code (e.g. chunked encoding) assume this buffer has at
@ -708,6 +708,7 @@ static void conn_free(struct connectdata *conn)
Curl_safefree(conn->trailer); Curl_safefree(conn->trailer);
Curl_safefree(conn->host.rawalloc); /* host name buffer */ Curl_safefree(conn->host.rawalloc); /* host name buffer */
Curl_safefree(conn->conn_to_host.rawalloc); /* host name buffer */ Curl_safefree(conn->conn_to_host.rawalloc); /* host name buffer */
Curl_safefree(conn->hostname_resolve);
Curl_safefree(conn->secondaryhostname); Curl_safefree(conn->secondaryhostname);
Curl_safefree(conn->http_proxy.host.rawalloc); /* http proxy name buffer */ Curl_safefree(conn->http_proxy.host.rawalloc); /* http proxy name buffer */
Curl_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name buffer */ Curl_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name buffer */
@ -788,10 +789,10 @@ CURLcode Curl_disconnect(struct Curl_easy *data,
infof(data, "Closing connection %ld\n", conn->connection_id); infof(data, "Closing connection %ld\n", conn->connection_id);
Curl_conncache_remove_conn(conn, TRUE); Curl_conncache_remove_conn(conn, TRUE);
free_fixed_hostname(&conn->host); free_idnconverted_hostname(&conn->host);
free_fixed_hostname(&conn->conn_to_host); free_idnconverted_hostname(&conn->conn_to_host);
free_fixed_hostname(&conn->http_proxy.host); free_idnconverted_hostname(&conn->http_proxy.host);
free_fixed_hostname(&conn->socks_proxy.host); free_idnconverted_hostname(&conn->socks_proxy.host);
DEBUGASSERT(conn->data == data); DEBUGASSERT(conn->data == data);
/* this assumes that the pointer is still there after the connection was /* this assumes that the pointer is still there after the connection was
@ -1679,11 +1680,23 @@ static bool is_ASCII_name(const char *hostname)
} }
/* /*
* Perform any necessary IDN conversion of hostname * Strip single trailing dot in the hostname,
* primarily for SNI and http host header.
*/ */
static CURLcode fix_hostname(struct connectdata *conn, struct hostname *host) static void strip_trailing_dot(struct hostname *host)
{ {
size_t len; size_t len;
len = strlen(host->name);
if(len && (host->name[len-1] == '.'))
host->name[len-1] = 0;
}
/*
* Perform any necessary IDN conversion of hostname
*/
static CURLcode idnconvert_hostname(struct connectdata *conn,
struct hostname *host)
{
struct Curl_easy *data = conn->data; struct Curl_easy *data = conn->data;
#ifndef USE_LIBIDN2 #ifndef USE_LIBIDN2
@ -1696,12 +1709,6 @@ static CURLcode fix_hostname(struct connectdata *conn, struct hostname *host)
/* set the name we use to display the host name */ /* set the name we use to display the host name */
host->dispname = host->name; host->dispname = host->name;
len = strlen(host->name);
if(len && (host->name[len-1] == '.'))
/* strip off a single trailing dot if present, primarily for SNI but
there's no use for it */
host->name[len-1] = 0;
/* Check name for non-ASCII and convert hostname to ACE form if we can */ /* Check name for non-ASCII and convert hostname to ACE form if we can */
if(!is_ASCII_name(host->name)) { if(!is_ASCII_name(host->name)) {
#ifdef USE_LIBIDN2 #ifdef USE_LIBIDN2
@ -1756,9 +1763,9 @@ static CURLcode fix_hostname(struct connectdata *conn, struct hostname *host)
} }
/* /*
* Frees data allocated by fix_hostname() * Frees data allocated by idnconvert_hostname()
*/ */
static void free_fixed_hostname(struct hostname *host) static void free_idnconverted_hostname(struct hostname *host)
{ {
#if defined(USE_LIBIDN2) #if defined(USE_LIBIDN2)
if(host->encalloc) { if(host->encalloc) {
@ -3373,7 +3380,7 @@ static CURLcode resolve_server(struct Curl_easy *data,
*************************************************************/ *************************************************************/
if(conn->bits.reuse) if(conn->bits.reuse)
/* We're reusing the connection - no need to resolve anything, and /* We're reusing the connection - no need to resolve anything, and
fix_hostname() was called already in create_conn() for the re-use idnconvert_hostname() was called already in create_conn() for the re-use
case. */ case. */
*async = FALSE; *async = FALSE;
@ -3428,7 +3435,10 @@ static CURLcode resolve_server(struct Curl_easy *data,
conn->port = conn->remote_port; conn->port = conn->remote_port;
/* Resolve target host right on */ /* Resolve target host right on */
rc = Curl_resolv_timeout(conn, connhost->name, (int)conn->port, conn->hostname_resolve = strdup(connhost->name);
if(!conn->hostname_resolve)
return CURLE_OUT_OF_MEMORY;
rc = Curl_resolv_timeout(conn, conn->hostname_resolve, (int)conn->port,
&hostaddr, timeout_ms); &hostaddr, timeout_ms);
if(rc == CURLRESOLV_PENDING) if(rc == CURLRESOLV_PENDING)
*async = TRUE; *async = TRUE;
@ -3449,7 +3459,10 @@ static CURLcode resolve_server(struct Curl_easy *data,
&conn->socks_proxy.host : &conn->http_proxy.host; &conn->socks_proxy.host : &conn->http_proxy.host;
/* resolve proxy */ /* resolve proxy */
rc = Curl_resolv_timeout(conn, host->name, (int)conn->port, conn->hostname_resolve = strdup(host->name);
if(!conn->hostname_resolve)
return CURLE_OUT_OF_MEMORY;
rc = Curl_resolv_timeout(conn, conn->hostname_resolve, (int)conn->port,
&hostaddr, timeout_ms); &hostaddr, timeout_ms);
if(rc == CURLRESOLV_PENDING) if(rc == CURLRESOLV_PENDING)
@ -3479,8 +3492,8 @@ static CURLcode resolve_server(struct Curl_easy *data,
static void reuse_conn(struct connectdata *old_conn, static void reuse_conn(struct connectdata *old_conn,
struct connectdata *conn) struct connectdata *conn)
{ {
free_fixed_hostname(&old_conn->http_proxy.host); free_idnconverted_hostname(&old_conn->http_proxy.host);
free_fixed_hostname(&old_conn->socks_proxy.host); free_idnconverted_hostname(&old_conn->socks_proxy.host);
free(old_conn->http_proxy.host.rawalloc); free(old_conn->http_proxy.host.rawalloc);
free(old_conn->socks_proxy.host.rawalloc); free(old_conn->socks_proxy.host.rawalloc);
@ -3524,14 +3537,18 @@ static void reuse_conn(struct connectdata *old_conn,
/* host can change, when doing keepalive with a proxy or if the case is /* host can change, when doing keepalive with a proxy or if the case is
different this time etc */ different this time etc */
free_fixed_hostname(&conn->host); free_idnconverted_hostname(&conn->host);
free_fixed_hostname(&conn->conn_to_host); free_idnconverted_hostname(&conn->conn_to_host);
Curl_safefree(conn->host.rawalloc); Curl_safefree(conn->host.rawalloc);
Curl_safefree(conn->conn_to_host.rawalloc); Curl_safefree(conn->conn_to_host.rawalloc);
conn->host = old_conn->host; conn->host = old_conn->host;
conn->conn_to_host = old_conn->conn_to_host; conn->conn_to_host = old_conn->conn_to_host;
conn->conn_to_port = old_conn->conn_to_port; conn->conn_to_port = old_conn->conn_to_port;
conn->remote_port = old_conn->remote_port; conn->remote_port = old_conn->remote_port;
Curl_safefree(conn->hostname_resolve);
conn->hostname_resolve = old_conn->hostname_resolve;
old_conn->hostname_resolve = NULL;
/* persist connection info in session handle */ /* persist connection info in session handle */
Curl_persistconninfo(conn); Curl_persistconninfo(conn);
@ -3681,30 +3698,30 @@ static CURLcode create_conn(struct Curl_easy *data,
goto out; goto out;
/************************************************************* /*************************************************************
* IDN-fix the hostnames * IDN-convert the hostnames
*************************************************************/ *************************************************************/
result = fix_hostname(conn, &conn->host); result = idnconvert_hostname(conn, &conn->host);
if(result) if(result)
goto out; goto out;
if(conn->bits.conn_to_host) { if(conn->bits.conn_to_host) {
result = fix_hostname(conn, &conn->conn_to_host); result = idnconvert_hostname(conn, &conn->conn_to_host);
if(result) if(result)
goto out; goto out;
} }
if(conn->bits.httpproxy) { if(conn->bits.httpproxy) {
result = fix_hostname(conn, &conn->http_proxy.host); result = idnconvert_hostname(conn, &conn->http_proxy.host);
if(result) if(result)
goto out; goto out;
} }
if(conn->bits.socksproxy) { if(conn->bits.socksproxy) {
result = fix_hostname(conn, &conn->socks_proxy.host); result = idnconvert_hostname(conn, &conn->socks_proxy.host);
if(result) if(result)
goto out; goto out;
} }
/************************************************************* /*************************************************************
* Check whether the host and the "connect to host" are equal. * Check whether the host and the "connect to host" are equal.
* Do this after the hostnames have been IDN-fixed. * Do this after the hostnames have been IDN-converted.
*************************************************************/ *************************************************************/
if(conn->bits.conn_to_host && if(conn->bits.conn_to_host &&
strcasecompare(conn->conn_to_host.name, conn->host.name)) { strcasecompare(conn->conn_to_host.name, conn->host.name)) {
@ -4032,6 +4049,15 @@ static CURLcode create_conn(struct Curl_easy *data,
*************************************************************/ *************************************************************/
result = resolve_server(data, conn, async); result = resolve_server(data, conn, async);
/* Strip trailing dots. resolve_server copied the name. */
strip_trailing_dot(&conn->host);
if(conn->bits.httpproxy)
strip_trailing_dot(&conn->http_proxy.host);
if(conn->bits.socksproxy)
strip_trailing_dot(&conn->socks_proxy.host);
if(conn->bits.conn_to_host)
strip_trailing_dot(&conn->conn_to_host);
out: out:
return result; return result;
} }

View File

@ -828,6 +828,7 @@ struct connectdata {
int socktype; /* SOCK_STREAM or SOCK_DGRAM */ int socktype; /* SOCK_STREAM or SOCK_DGRAM */
struct hostname host; struct hostname host;
char *hostname_resolve; /* host name to resolve to address, allocated */
char *secondaryhostname; /* secondary socket host name (ftp) */ char *secondaryhostname; /* secondary socket host name (ftp) */
struct hostname conn_to_host; /* the host to connect to. valid only if struct hostname conn_to_host; /* the host to connect to. valid only if
bits.conn_to_host is set */ bits.conn_to_host is set */