fix simultaneously connecting to multiple hosts in non-IPv6 builds

we need to deep-copy the struct hostent data, as otherwise the
concurrent connects will overwrite each other's lookup results.

this is a rather hypothetical fix, as the bug currently affects only
channels connecting two IMAP accounts, and only if the first host's
first address asynchronously fails to connect.
This commit is contained in:
Oswald Buddenhagen 2020-07-27 22:48:41 +02:00
parent 3651c30296
commit 813ad67c56
2 changed files with 30 additions and 24 deletions

View File

@ -430,6 +430,32 @@ socket_close_internal( conn_t *sock )
sock->fd = -1; sock->fd = -1;
} }
#ifndef HAVE_IPV6
struct addr_info {
struct addr_info *ai_next;
struct sockaddr_in ai_addr[1];
};
#define freeaddrinfo(ai) free( ai )
static struct addr_info *
init_addrinfo( struct hostent *he )
{
uint naddr = 0;
for (char **addr = he->h_addr_list; *addr; addr++)
naddr++;
struct addr_info *caddr = nfcalloc( naddr * sizeof(struct addrinfo) );
struct addr_info *ret, **caddrp = &ret;
for (char **addr = he->h_addr_list; *addr; addr++, caddr++) {
caddr->ai_addr->sin_family = AF_INET;
memcpy( &caddr->ai_addr->sin_addr.s_addr, *addr, sizeof(struct in_addr) );
*caddrp = caddr;
caddrp = &caddr->ai_next;
}
return ret;
}
#endif
void void
socket_connect( conn_t *sock, void (*cb)( int ok, void *aux ) ) socket_connect( conn_t *sock, void (*cb)( int ok, void *aux ) )
{ {
@ -479,8 +505,6 @@ socket_connect( conn_t *sock, void (*cb)( int ok, void *aux ) )
return; return;
} }
info( "\vok\n" ); info( "\vok\n" );
sock->curr_addr = sock->addrs;
#else #else
struct hostent *he; struct hostent *he;
@ -493,8 +517,9 @@ socket_connect( conn_t *sock, void (*cb)( int ok, void *aux ) )
} }
info( "\vok\n" ); info( "\vok\n" );
sock->curr_addr = he->h_addr_list; sock->addrs = init_addrinfo( he );
#endif #endif
sock->curr_addr = sock->addrs;
socket_connect_one( sock ); socket_connect_one( sock );
} }
} }
@ -506,16 +531,10 @@ socket_connect_one( conn_t *sock )
#ifdef HAVE_IPV6 #ifdef HAVE_IPV6
struct addrinfo *ai; struct addrinfo *ai;
#else #else
struct { struct addr_info *ai;
struct sockaddr_in ai_addr[1];
} ai[1];
#endif #endif
#ifdef HAVE_IPV6
if (!(ai = sock->curr_addr)) { if (!(ai = sock->curr_addr)) {
#else
if (!*sock->curr_addr) {
#endif
error( "No working address found for %s\n", sock->conf->host ); error( "No working address found for %s\n", sock->conf->host );
socket_connect_bail( sock ); socket_connect_bail( sock );
return; return;
@ -532,11 +551,6 @@ socket_connect_one( conn_t *sock )
#endif #endif
{ {
struct sockaddr_in *in = ((struct sockaddr_in *)ai->ai_addr); struct sockaddr_in *in = ((struct sockaddr_in *)ai->ai_addr);
#ifndef HAVE_IPV6
memset( in, 0, sizeof(*in) );
in->sin_family = AF_INET;
in->sin_addr.s_addr = *((int *)*sock->curr_addr);
#endif
in->sin_port = htons( sock->conf->port ); in->sin_port = htons( sock->conf->port );
nfasprintf( &sock->name, "%s (%s:%hu)", nfasprintf( &sock->name, "%s (%s:%hu)",
sock->conf->host, inet_ntoa( in->sin_addr ), sock->conf->port ); sock->conf->host, inet_ntoa( in->sin_addr ), sock->conf->port );
@ -579,11 +593,7 @@ socket_connect_next( conn_t *conn )
sys_error( "Cannot connect to %s", conn->name ); sys_error( "Cannot connect to %s", conn->name );
free( conn->name ); free( conn->name );
conn->name = 0; conn->name = 0;
#ifdef HAVE_IPV6
conn->curr_addr = conn->curr_addr->ai_next; conn->curr_addr = conn->curr_addr->ai_next;
#else
conn->curr_addr++;
#endif
socket_connect_one( conn ); socket_connect_one( conn );
} }
@ -597,12 +607,10 @@ socket_connect_failed( conn_t *conn )
static void static void
socket_connected( conn_t *conn ) socket_connected( conn_t *conn )
{ {
#ifdef HAVE_IPV6
if (conn->addrs) { if (conn->addrs) {
freeaddrinfo( conn->addrs ); freeaddrinfo( conn->addrs );
conn->addrs = 0; conn->addrs = 0;
} }
#endif
conf_notifier( &conn->notify, 0, POLLIN ); conf_notifier( &conn->notify, 0, POLLIN );
socket_expect_read( conn, 0 ); socket_expect_read( conn, 0 );
conn->state = SCK_READY; conn->state = SCK_READY;
@ -612,12 +620,10 @@ socket_connected( conn_t *conn )
static void static void
socket_cleanup_names( conn_t *conn ) socket_cleanup_names( conn_t *conn )
{ {
#ifdef HAVE_IPV6
if (conn->addrs) { if (conn->addrs) {
freeaddrinfo( conn->addrs ); freeaddrinfo( conn->addrs );
conn->addrs = 0; conn->addrs = 0;
} }
#endif
free( conn->name ); free( conn->name );
conn->name = 0; conn->name = 0;
} }

View File

@ -73,7 +73,7 @@ typedef struct {
#ifdef HAVE_IPV6 #ifdef HAVE_IPV6
struct addrinfo *addrs, *curr_addr; /* needed during connect */ struct addrinfo *addrs, *curr_addr; /* needed during connect */
#else #else
char **curr_addr; /* needed during connect */ struct addr_info *addrs, *curr_addr; /* needed during connect */
#endif #endif
char *name; char *name;
#ifdef HAVE_LIBSSL #ifdef HAVE_LIBSSL