Sync with trunk head (r49139)
[reactos.git] / dll / win32 / winhttp / net.c
index 897ccbe..d709c31 100644 (file)
 #endif
 #ifdef HAVE_OPENSSL_SSL_H
 # include <openssl/ssl.h>
+# include <openssl/opensslv.h>
 #undef FAR
 #undef DSA
 #endif
 
+#define NONAMELESSUNION
+
 #include "wine/debug.h"
 #include "wine/library.h"
 
 
 WINE_DEFAULT_DEBUG_CHANNEL(winhttp);
 
-#define DEFAULT_SEND_TIMEOUT        30
-#define DEFAULT_RECEIVE_TIMEOUT     30
-#define RESPONSE_TIMEOUT            30
-
 #ifndef HAVE_GETADDRINFO
 
 /* critical section to protect non-reentrant gethostbyname() */
@@ -80,17 +79,35 @@ static CRITICAL_SECTION cs_gethostbyname = { &critsect_debug, -1, 0, 0, 0, 0 };
 
 #include <openssl/err.h>
 
+static CRITICAL_SECTION init_ssl_cs;
+static CRITICAL_SECTION_DEBUG init_ssl_cs_debug =
+{
+    0, 0, &init_ssl_cs,
+    { &init_ssl_cs_debug.ProcessLocksList,
+      &init_ssl_cs_debug.ProcessLocksList },
+    0, 0, { (DWORD_PTR)(__FILE__ ": init_ssl_cs") }
+};
+static CRITICAL_SECTION init_ssl_cs = { &init_ssl_cs_debug, -1, 0, 0, 0, 0 };
+
 static void *libssl_handle;
 static void *libcrypto_handle;
 
+#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER> 0x1000000)
+static const SSL_METHOD *method;
+#else
 static SSL_METHOD *method;
+#endif
 static SSL_CTX *ctx;
+static int hostname_idx;
+static int error_idx;
+static int conn_idx;
 
 #define MAKE_FUNCPTR(f) static typeof(f) * p##f
 
 MAKE_FUNCPTR( SSL_library_init );
 MAKE_FUNCPTR( SSL_load_error_strings );
 MAKE_FUNCPTR( SSLv23_method );
+MAKE_FUNCPTR( SSL_CTX_free );
 MAKE_FUNCPTR( SSL_CTX_new );
 MAKE_FUNCPTR( SSL_new );
 MAKE_FUNCPTR( SSL_free );
@@ -99,21 +116,49 @@ MAKE_FUNCPTR( SSL_connect );
 MAKE_FUNCPTR( SSL_shutdown );
 MAKE_FUNCPTR( SSL_write );
 MAKE_FUNCPTR( SSL_read );
-MAKE_FUNCPTR( SSL_get_verify_result );
+MAKE_FUNCPTR( SSL_get_error );
+MAKE_FUNCPTR( SSL_get_ex_new_index );
+MAKE_FUNCPTR( SSL_get_ex_data );
+MAKE_FUNCPTR( SSL_set_ex_data );
+MAKE_FUNCPTR( SSL_get_ex_data_X509_STORE_CTX_idx );
 MAKE_FUNCPTR( SSL_get_peer_certificate );
-MAKE_FUNCPTR( SSL_CTX_get_timeout );
-MAKE_FUNCPTR( SSL_CTX_set_timeout );
 MAKE_FUNCPTR( SSL_CTX_set_default_verify_paths );
-
-MAKE_FUNCPTR( BIO_new_fp );
+MAKE_FUNCPTR( SSL_CTX_set_verify );
+MAKE_FUNCPTR( SSL_get_current_cipher );
+MAKE_FUNCPTR( SSL_CIPHER_get_bits );
+
+MAKE_FUNCPTR( CRYPTO_num_locks );
+MAKE_FUNCPTR( CRYPTO_set_id_callback );
+MAKE_FUNCPTR( CRYPTO_set_locking_callback );
+MAKE_FUNCPTR( ERR_free_strings );
 MAKE_FUNCPTR( ERR_get_error );
 MAKE_FUNCPTR( ERR_error_string );
+MAKE_FUNCPTR( X509_STORE_CTX_get_ex_data );
 MAKE_FUNCPTR( i2d_X509 );
+MAKE_FUNCPTR( sk_value );
+MAKE_FUNCPTR( sk_num );
 #undef MAKE_FUNCPTR
 
+static CRITICAL_SECTION *ssl_locks;
+static unsigned int num_ssl_locks;
+
+static unsigned long ssl_thread_id(void)
+{
+    return GetCurrentThreadId();
+}
+
+static void ssl_lock_callback(int mode, int type, const char *file, int line)
+{
+    if (mode & CRYPTO_LOCK)
+        EnterCriticalSection( &ssl_locks[type] );
+    else
+        LeaveCriticalSection( &ssl_locks[type] );
+}
+
 #endif
 
 /* translate a unix error code into a winsock error code */
+#if 0
 static int sock_get_error( int err )
 {
 #if !defined(__MINGW32__) && !defined (_MSC_VER)
@@ -179,24 +224,222 @@ static int sock_get_error( int err )
 #endif
     return err;
 }
+#else
+#define sock_get_error(x) WSAGetLastError()
+#endif
+
+#ifdef SONAME_LIBSSL
+static PCCERT_CONTEXT X509_to_cert_context(X509 *cert)
+{
+    unsigned char *buffer, *p;
+    int len;
+    BOOL malloc = FALSE;
+    PCCERT_CONTEXT ret;
+
+    p = NULL;
+    if ((len = pi2d_X509( cert, &p )) < 0) return NULL;
+    /*
+     * SSL 0.9.7 and above malloc the buffer if it is null.
+     * however earlier version do not and so we would need to alloc the buffer.
+     *
+     * see the i2d_X509 man page for more details.
+     */
+    if (!p)
+    {
+        if (!(buffer = heap_alloc( len ))) return NULL;
+        p = buffer;
+        len = pi2d_X509( cert, &p );
+    }
+    else
+    {
+        buffer = p;
+        malloc = TRUE;
+    }
+
+    ret = CertCreateCertificateContext( X509_ASN_ENCODING, buffer, len );
+
+    if (malloc) free( buffer );
+    else heap_free( buffer );
+
+    return ret;
+}
+
+static DWORD netconn_verify_cert( PCCERT_CONTEXT cert, HCERTSTORE store,
+                                  WCHAR *server, DWORD security_flags )
+{
+    BOOL ret;
+    CERT_CHAIN_PARA chainPara = { sizeof(chainPara), { 0 } };
+    PCCERT_CHAIN_CONTEXT chain;
+    char oid_server_auth[] = szOID_PKIX_KP_SERVER_AUTH;
+    char *server_auth[] = { oid_server_auth };
+    DWORD err = ERROR_SUCCESS;
+
+    TRACE("verifying %s\n", debugstr_w( server ));
+    chainPara.RequestedUsage.Usage.cUsageIdentifier = 1;
+    chainPara.RequestedUsage.Usage.rgpszUsageIdentifier = server_auth;
+    if ((ret = CertGetCertificateChain( NULL, cert, NULL, store, &chainPara, 0,
+                                        NULL, &chain )))
+    {
+        if (chain->TrustStatus.dwErrorStatus)
+        {
+            static const DWORD supportedErrors =
+                CERT_TRUST_IS_NOT_TIME_VALID |
+                CERT_TRUST_IS_UNTRUSTED_ROOT |
+                CERT_TRUST_IS_NOT_VALID_FOR_USAGE;
+
+            if (chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_TIME_VALID)
+            {
+                if (!(security_flags & SECURITY_FLAG_IGNORE_CERT_DATE_INVALID))
+                    err = ERROR_WINHTTP_SECURE_CERT_DATE_INVALID;
+            }
+            else if (chain->TrustStatus.dwErrorStatus &
+                     CERT_TRUST_IS_UNTRUSTED_ROOT)
+            {
+                if (!(security_flags & SECURITY_FLAG_IGNORE_UNKNOWN_CA))
+                    err = ERROR_WINHTTP_SECURE_INVALID_CA;
+            }
+            else if ((chain->TrustStatus.dwErrorStatus &
+                      CERT_TRUST_IS_OFFLINE_REVOCATION) ||
+                     (chain->TrustStatus.dwErrorStatus &
+                      CERT_TRUST_REVOCATION_STATUS_UNKNOWN))
+                err = ERROR_WINHTTP_SECURE_CERT_REV_FAILED;
+            else if (chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_REVOKED)
+                err = ERROR_WINHTTP_SECURE_CERT_REVOKED;
+            else if (chain->TrustStatus.dwErrorStatus &
+                CERT_TRUST_IS_NOT_VALID_FOR_USAGE)
+            {
+                if (!(security_flags & SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE))
+                    err = ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE;
+            }
+            else if (chain->TrustStatus.dwErrorStatus & ~supportedErrors)
+                err = ERROR_WINHTTP_SECURE_INVALID_CERT;
+        }
+        if (!err)
+        {
+            CERT_CHAIN_POLICY_PARA policyPara;
+            SSL_EXTRA_CERT_CHAIN_POLICY_PARA sslExtraPolicyPara;
+            CERT_CHAIN_POLICY_STATUS policyStatus;
+            CERT_CHAIN_CONTEXT chainCopy;
+
+            /* Clear chain->TrustStatus.dwErrorStatus so
+             * CertVerifyCertificateChainPolicy will verify additional checks
+             * rather than stopping with an existing, ignored error.
+             */
+            memcpy(&chainCopy, chain, sizeof(chainCopy));
+            chainCopy.TrustStatus.dwErrorStatus = 0;
+            sslExtraPolicyPara.u.cbSize = sizeof(sslExtraPolicyPara);
+            sslExtraPolicyPara.dwAuthType = AUTHTYPE_SERVER;
+            sslExtraPolicyPara.pwszServerName = server;
+            policyPara.cbSize = sizeof(policyPara);
+            policyPara.dwFlags = 0;
+            policyPara.pvExtraPolicyPara = &sslExtraPolicyPara;
+            ret = CertVerifyCertificateChainPolicy( CERT_CHAIN_POLICY_SSL,
+                                                    &chainCopy, &policyPara,
+                                                    &policyStatus );
+            /* Any error in the policy status indicates that the
+             * policy couldn't be verified.
+             */
+            if (ret && policyStatus.dwError)
+            {
+                if (policyStatus.dwError == CERT_E_CN_NO_MATCH)
+                {
+                    if (!(security_flags & SECURITY_FLAG_IGNORE_CERT_CN_INVALID))
+                        err = ERROR_WINHTTP_SECURE_CERT_CN_INVALID;
+                }
+                else
+                    err = ERROR_WINHTTP_SECURE_INVALID_CERT;
+            }
+        }
+        CertFreeCertificateChain( chain );
+    }
+    else
+        err = ERROR_WINHTTP_SECURE_CHANNEL_ERROR;
+    TRACE("returning %08x\n", err);
+    return err;
+}
+
+static int netconn_secure_verify( int preverify_ok, X509_STORE_CTX *ctx )
+{
+    SSL *ssl;
+    WCHAR *server;
+    BOOL ret = FALSE;
+    netconn_t *conn;
+    HCERTSTORE store = CertOpenStore( CERT_STORE_PROV_MEMORY, 0, 0,
+     CERT_STORE_CREATE_NEW_FLAG, NULL );
+
+    ssl = pX509_STORE_CTX_get_ex_data( ctx, pSSL_get_ex_data_X509_STORE_CTX_idx() );
+    server = pSSL_get_ex_data( ssl, hostname_idx );
+    conn = pSSL_get_ex_data( ssl, conn_idx );
+    if (store)
+    {
+        X509 *cert;
+        int i;
+        PCCERT_CONTEXT endCert = NULL;
+
+        ret = TRUE;
+        for (i = 0; ret && i < psk_num((struct stack_st *)ctx->chain); i++)
+        {
+            PCCERT_CONTEXT context;
+
+            cert = (X509 *)psk_value((struct stack_st *)ctx->chain, i);
+            if ((context = X509_to_cert_context( cert )))
+            {
+                if (i == 0)
+                    ret = CertAddCertificateContextToStore( store, context,
+                        CERT_STORE_ADD_ALWAYS, &endCert );
+                else
+                    ret = CertAddCertificateContextToStore( store, context,
+                        CERT_STORE_ADD_ALWAYS, NULL );
+                CertFreeCertificateContext( context );
+            }
+        }
+        if (!endCert) ret = FALSE;
+        if (ret)
+        {
+            DWORD_PTR err = netconn_verify_cert( endCert, store, server,
+                                                 conn->security_flags );
+
+            if (err)
+            {
+                pSSL_set_ex_data( ssl, error_idx, (void *)err );
+                ret = FALSE;
+            }
+        }
+        CertFreeCertificateContext( endCert );
+        CertCloseStore( store, 0 );
+    }
+    return ret;
+}
+#endif
 
 BOOL netconn_init( netconn_t *conn, BOOL secure )
 {
+#if defined(SONAME_LIBSSL) && defined(SONAME_LIBCRYPTO)
+    int i;
+#endif
+
     conn->socket = -1;
     if (!secure) return TRUE;
 
 #if defined(SONAME_LIBSSL) && defined(SONAME_LIBCRYPTO)
-    if (libssl_handle) return TRUE;
+    EnterCriticalSection( &init_ssl_cs );
+    if (libssl_handle)
+    {
+        LeaveCriticalSection( &init_ssl_cs );
+        return TRUE;
+    }
     if (!(libssl_handle = wine_dlopen( SONAME_LIBSSL, RTLD_NOW, NULL, 0 )))
     {
         ERR("Trying to use SSL but couldn't load %s. Expect trouble.\n", SONAME_LIBSSL);
         set_last_error( ERROR_WINHTTP_SECURE_CHANNEL_ERROR );
+        LeaveCriticalSection( &init_ssl_cs );
         return FALSE;
     }
     if (!(libcrypto_handle = wine_dlopen( SONAME_LIBCRYPTO, RTLD_NOW, NULL, 0 )))
     {
         ERR("Trying to use SSL but couldn't load %s. Expect trouble.\n", SONAME_LIBCRYPTO);
         set_last_error( ERROR_WINHTTP_SECURE_CHANNEL_ERROR );
+        LeaveCriticalSection( &init_ssl_cs );
         return FALSE;
     }
 #define LOAD_FUNCPTR(x) \
@@ -204,11 +447,13 @@ BOOL netconn_init( netconn_t *conn, BOOL secure )
     { \
         ERR("Failed to load symbol %s\n", #x); \
         set_last_error( ERROR_WINHTTP_SECURE_CHANNEL_ERROR ); \
+        LeaveCriticalSection( &init_ssl_cs ); \
         return FALSE; \
     }
     LOAD_FUNCPTR( SSL_library_init );
     LOAD_FUNCPTR( SSL_load_error_strings );
     LOAD_FUNCPTR( SSLv23_method );
+    LOAD_FUNCPTR( SSL_CTX_free );
     LOAD_FUNCPTR( SSL_CTX_new );
     LOAD_FUNCPTR( SSL_new );
     LOAD_FUNCPTR( SSL_free );
@@ -217,11 +462,16 @@ BOOL netconn_init( netconn_t *conn, BOOL secure )
     LOAD_FUNCPTR( SSL_shutdown );
     LOAD_FUNCPTR( SSL_write );
     LOAD_FUNCPTR( SSL_read );
-    LOAD_FUNCPTR( SSL_get_verify_result );
+    LOAD_FUNCPTR( SSL_get_error );
+    LOAD_FUNCPTR( SSL_get_ex_new_index );
+    LOAD_FUNCPTR( SSL_get_ex_data );
+    LOAD_FUNCPTR( SSL_set_ex_data );
+    LOAD_FUNCPTR( SSL_get_ex_data_X509_STORE_CTX_idx );
     LOAD_FUNCPTR( SSL_get_peer_certificate );
-    LOAD_FUNCPTR( SSL_CTX_get_timeout );
-    LOAD_FUNCPTR( SSL_CTX_set_timeout );
     LOAD_FUNCPTR( SSL_CTX_set_default_verify_paths );
+    LOAD_FUNCPTR( SSL_CTX_set_verify );
+    LOAD_FUNCPTR( SSL_get_current_cipher );
+    LOAD_FUNCPTR( SSL_CIPHER_get_bits );
 #undef LOAD_FUNCPTR
 
 #define LOAD_FUNCPTR(x) \
@@ -229,19 +479,72 @@ BOOL netconn_init( netconn_t *conn, BOOL secure )
     { \
         ERR("Failed to load symbol %s\n", #x); \
         set_last_error( ERROR_WINHTTP_SECURE_CHANNEL_ERROR ); \
+        LeaveCriticalSection( &init_ssl_cs ); \
         return FALSE; \
     }
-    LOAD_FUNCPTR( BIO_new_fp );
+    LOAD_FUNCPTR( CRYPTO_num_locks );
+    LOAD_FUNCPTR( CRYPTO_set_id_callback );
+    LOAD_FUNCPTR( CRYPTO_set_locking_callback );
+    LOAD_FUNCPTR( ERR_free_strings );
     LOAD_FUNCPTR( ERR_get_error );
     LOAD_FUNCPTR( ERR_error_string );
+    LOAD_FUNCPTR( X509_STORE_CTX_get_ex_data );
     LOAD_FUNCPTR( i2d_X509 );
+    LOAD_FUNCPTR( sk_value );
+    LOAD_FUNCPTR( sk_num );
 #undef LOAD_FUNCPTR
 
     pSSL_library_init();
     pSSL_load_error_strings();
-    pBIO_new_fp( stderr, BIO_NOCLOSE );
 
     method = pSSLv23_method();
+    ctx = pSSL_CTX_new( method );
+    if (!pSSL_CTX_set_default_verify_paths( ctx ))
+    {
+        ERR("SSL_CTX_set_default_verify_paths failed: %s\n", pERR_error_string( pERR_get_error(), 0 ));
+        set_last_error( ERROR_OUTOFMEMORY );
+        LeaveCriticalSection( &init_ssl_cs );
+        return FALSE;
+    }
+    hostname_idx = pSSL_get_ex_new_index( 0, (void *)"hostname index", NULL, NULL, NULL );
+    if (hostname_idx == -1)
+    {
+        ERR("SSL_get_ex_new_index failed: %s\n", pERR_error_string( pERR_get_error(), 0 ));
+        set_last_error( ERROR_OUTOFMEMORY );
+        LeaveCriticalSection( &init_ssl_cs );
+        return FALSE;
+    }
+    error_idx = pSSL_get_ex_new_index( 0, (void *)"error index", NULL, NULL, NULL );
+    if (error_idx == -1)
+    {
+        ERR("SSL_get_ex_new_index failed: %s\n", pERR_error_string( pERR_get_error(), 0 ));
+        set_last_error( ERROR_OUTOFMEMORY );
+        LeaveCriticalSection( &init_ssl_cs );
+        return FALSE;
+    }
+    conn_idx = pSSL_get_ex_new_index( 0, (void *)"netconn index", NULL, NULL, NULL );
+    if (conn_idx == -1)
+    {
+        ERR("SSL_get_ex_new_index failed: %s\n", pERR_error_string( pERR_get_error(), 0 ));
+        set_last_error( ERROR_OUTOFMEMORY );
+        LeaveCriticalSection( &init_ssl_cs );
+        return FALSE;
+    }
+    pSSL_CTX_set_verify( ctx, SSL_VERIFY_PEER, netconn_secure_verify );
+
+    pCRYPTO_set_id_callback(ssl_thread_id);
+    num_ssl_locks = pCRYPTO_num_locks();
+    ssl_locks = HeapAlloc(GetProcessHeap(), 0, num_ssl_locks * sizeof(CRITICAL_SECTION));
+    if (!ssl_locks)
+    {
+        set_last_error( ERROR_OUTOFMEMORY );
+        LeaveCriticalSection( &init_ssl_cs );
+        return FALSE;
+    }
+    for (i = 0; i < num_ssl_locks; i++) InitializeCriticalSection( &ssl_locks[i] );
+    pCRYPTO_set_locking_callback(ssl_lock_callback);
+
+    LeaveCriticalSection( &init_ssl_cs );
 #else
     WARN("SSL support not compiled in.\n");
     set_last_error( ERROR_WINHTTP_SECURE_CHANNEL_ERROR );
@@ -250,6 +553,29 @@ BOOL netconn_init( netconn_t *conn, BOOL secure )
     return TRUE;
 }
 
+void netconn_unload( void )
+{
+#if defined(SONAME_LIBSSL) && defined(SONAME_LIBCRYPTO)
+    if (libcrypto_handle)
+    {
+        pERR_free_strings();
+        wine_dlclose( libcrypto_handle, NULL, 0 );
+    }
+    if (libssl_handle)
+    {
+        if (ctx)
+            pSSL_CTX_free( ctx );
+        wine_dlclose( libssl_handle, NULL, 0 );
+    }
+    if (ssl_locks)
+    {
+        int i;
+        for (i = 0; i < num_ssl_locks; i++) DeleteCriticalSection( &ssl_locks[i] );
+        HeapFree( GetProcessHeap(), 0, ssl_locks );
+    }
+#endif
+}
+
 BOOL netconn_connected( netconn_t *conn )
 {
     return (conn->socket != -1);
@@ -295,58 +621,87 @@ BOOL netconn_close( netconn_t *conn )
     return TRUE;
 }
 
-BOOL netconn_connect( netconn_t *conn, const struct sockaddr *sockaddr, unsigned int addr_len )
+BOOL netconn_connect( netconn_t *conn, const struct sockaddr *sockaddr, unsigned int addr_len, int timeout )
 {
-    if (connect( conn->socket, sockaddr, addr_len ) == -1)
+    BOOL ret = FALSE;
+    int res = 0, state;
+
+    if (timeout > 0)
     {
-        WARN("unable to connect to host (%s)\n", strerror(errno));
-        set_last_error( sock_get_error( errno ) );
-        return FALSE;
+        state = 1;
+        ioctlsocket( conn->socket, FIONBIO, &state );
     }
-    return TRUE;
-}
+    if (connect( conn->socket, sockaddr, addr_len ) < 0)
+    {
+        res = sock_get_error( errno );
+        if (res == WSAEWOULDBLOCK || res == WSAEINPROGRESS)
+        {
+            fd_set outfd;
+            struct timeval tv;
 
-BOOL netconn_secure_connect( netconn_t *conn )
-{
-#ifdef SONAME_LIBSSL
-    X509 *cert;
-    long res;
+            FD_ZERO(&outfd);
+            FD_SET(conn->socket, &outfd);
 
-    ctx = pSSL_CTX_new( method );
-    if (!pSSL_CTX_set_default_verify_paths( ctx ))
+            tv.tv_sec = 0;
+            tv.tv_usec = timeout * 1000;
+
+            if (select( 0, NULL, &outfd, NULL, &tv ) > 0)
+                ret = TRUE;
+            else
+                res = sock_get_error( errno );
+        }
+    }
+    else
+        ret = TRUE;
+    if (timeout > 0)
     {
-        ERR("SSL_CTX_set_default_verify_paths failed: %s\n", pERR_error_string( pERR_get_error(), 0 ));
-        set_last_error( ERROR_OUTOFMEMORY );
-        return FALSE;
+        state = 0;
+        ioctlsocket( conn->socket, FIONBIO, &state );
     }
+    if (!ret)
+    {
+        WARN("unable to connect to host (%d)\n", res);
+        set_last_error( res );
+    }
+    return ret;
+}
+
+BOOL netconn_secure_connect( netconn_t *conn, WCHAR *hostname )
+{
+#ifdef SONAME_LIBSSL
     if (!(conn->ssl_conn = pSSL_new( ctx )))
     {
         ERR("SSL_new failed: %s\n", pERR_error_string( pERR_get_error(), 0 ));
         set_last_error( ERROR_OUTOFMEMORY );
         goto fail;
     }
-    if (!pSSL_set_fd( conn->ssl_conn, conn->socket ))
+    if (!pSSL_set_ex_data( conn->ssl_conn, hostname_idx, hostname ))
     {
-        ERR("SSL_set_fd failed: %s\n", pERR_error_string( pERR_get_error(), 0 ));
+        ERR("SSL_set_ex_data failed: %s\n", pERR_error_string( pERR_get_error(), 0 ));
         set_last_error( ERROR_WINHTTP_SECURE_CHANNEL_ERROR );
         goto fail;
     }
-    if (pSSL_connect( conn->ssl_conn ) <= 0)
+    if (!pSSL_set_ex_data( conn->ssl_conn, conn_idx, conn ))
     {
-        ERR("SSL_connect failed: %s\n", pERR_error_string( pERR_get_error(), 0 ));
+        ERR("SSL_set_ex_data failed: %s\n", pERR_error_string( pERR_get_error(), 0 ));
         set_last_error( ERROR_WINHTTP_SECURE_CHANNEL_ERROR );
-        goto fail;
+        return FALSE;
     }
-    if (!(cert = pSSL_get_peer_certificate( conn->ssl_conn )))
+    if (!pSSL_set_fd( conn->ssl_conn, conn->socket ))
     {
-        ERR("No certificate for server: %s\n", pERR_error_string( pERR_get_error(), 0 ));
+        ERR("SSL_set_fd failed: %s\n", pERR_error_string( pERR_get_error(), 0 ));
         set_last_error( ERROR_WINHTTP_SECURE_CHANNEL_ERROR );
         goto fail;
     }
-    if ((res = pSSL_get_verify_result( conn->ssl_conn )) != X509_V_OK)
+    if (pSSL_connect( conn->ssl_conn ) <= 0)
     {
-        /* FIXME: we should set an error and return, but we only print an error at the moment */
-        ERR("couldn't verify server certificate (%ld)\n", res);
+        DWORD err;
+
+        err = (DWORD_PTR)pSSL_get_ex_data( conn->ssl_conn, error_idx );
+        if (!err) err = ERROR_WINHTTP_SECURE_CHANNEL_ERROR;
+        ERR("couldn't verify server certificate (%d)\n", err);
+        set_last_error( err );
+        goto fail;
     }
     TRACE("established SSL connection\n");
     conn->secure = TRUE;
@@ -387,6 +742,8 @@ BOOL netconn_send( netconn_t *conn, const void *msg, size_t len, int flags, int
 
 BOOL netconn_recv( netconn_t *conn, void *buf, size_t len, int flags, int *recvd )
 {
+    int ret;
+
     *recvd = 0;
     if (!netconn_connected( conn )) return FALSE;
     if (!len) return TRUE;
@@ -425,19 +782,29 @@ BOOL netconn_recv( netconn_t *conn, void *buf, size_t len, int flags, int *recvd
             /* check if we have enough data from the peek buffer */
             if (!(flags & MSG_WAITALL) || (*recvd == len)) return TRUE;
         }
-        *recvd += pSSL_read( conn->ssl_conn, (char *)buf + *recvd, len - *recvd );
+        ret = pSSL_read( conn->ssl_conn, (char *)buf + *recvd, len - *recvd );
+        if (ret < 0)
+            return FALSE;
+
+        /* check if EOF was received */
+        if (!ret && (pSSL_get_error( conn->ssl_conn, ret ) == SSL_ERROR_ZERO_RETURN ||
+                     pSSL_get_error( conn->ssl_conn, ret ) == SSL_ERROR_SYSCALL ))
+        {
+            netconn_close( conn );
+            return TRUE;
+        }
         if (flags & MSG_PEEK) /* must copy into buffer */
         {
-            conn->peek_len = *recvd;
-            if (!*recvd)
+            conn->peek_len = ret;
+            if (!ret)
             {
                 heap_free( conn->peek_msg_mem );
                 conn->peek_msg_mem = NULL;
                 conn->peek_msg = NULL;
             }
-            else memcpy( conn->peek_msg, buf, *recvd );
+            else memcpy( conn->peek_msg, buf, ret );
         }
-        if (*recvd < 1 && len) return FALSE;
+        *recvd = ret;
         return TRUE;
 #else
         return FALSE;
@@ -474,7 +841,6 @@ BOOL netconn_query_data_available( netconn_t *conn, DWORD *available )
 
 BOOL netconn_get_next_line( netconn_t *conn, char *buffer, DWORD *buflen )
 {
-    struct timeval tv;
     fd_set infd;
     BOOL ret = FALSE;
     DWORD recvd = 0;
@@ -484,11 +850,6 @@ BOOL netconn_get_next_line( netconn_t *conn, char *buffer, DWORD *buflen )
     if (conn->secure)
     {
 #ifdef SONAME_LIBSSL
-        long timeout;
-
-        timeout = pSSL_CTX_get_timeout( ctx );
-        pSSL_CTX_set_timeout( ctx, DEFAULT_RECEIVE_TIMEOUT );
-
         while (recvd < *buflen)
         {
             int dummy;
@@ -504,7 +865,6 @@ BOOL netconn_get_next_line( netconn_t *conn, char *buffer, DWORD *buflen )
             }
             if (buffer[recvd] != '\r') recvd++;
         }
-        pSSL_CTX_set_timeout( ctx, timeout );
         if (ret)
         {
             buffer[recvd++] = 0;
@@ -516,16 +876,23 @@ BOOL netconn_get_next_line( netconn_t *conn, char *buffer, DWORD *buflen )
         return FALSE;
 #endif
     }
-
+    
     FD_ZERO(&infd);
     FD_SET(conn->socket, &infd);
-    tv.tv_sec=RESPONSE_TIMEOUT;
-    tv.tv_usec=0;
+    
     while (recvd < *buflen)
     {
-        if (select(conn->socket+1,&infd,NULL,NULL,&tv) > 0)
+        int res;
+        struct timeval tv, *ptv;
+        socklen_t len = sizeof(tv);
+
+        if ((res = getsockopt( conn->socket, SOL_SOCKET, SO_RCVTIMEO, (void*)&tv, &len ) != -1))
+            ptv = &tv;
+        else
+            ptv = NULL;
+            
+        if (select( 0, &infd, NULL, NULL, ptv ) > 0)
         {
-            int res;
             if ((res = recv( conn->socket, &buffer[recvd], 1, 0 )) <= 0)
             {
                 if (res == -1) set_last_error( sock_get_error( errno ) );
@@ -570,7 +937,7 @@ DWORD netconn_set_timeout( netconn_t *netconn, BOOL send, int value )
     return ERROR_SUCCESS;
 }
 
-BOOL netconn_resolve( WCHAR *hostnameW, INTERNET_PORT port, struct sockaddr_in *sa )
+static DWORD resolve_hostname( WCHAR *hostnameW, INTERNET_PORT port, struct sockaddr *sa, socklen_t *sa_len )
 {
     char *hostname;
 #ifdef HAVE_GETADDRINFO
@@ -578,27 +945,53 @@ BOOL netconn_resolve( WCHAR *hostnameW, INTERNET_PORT port, struct sockaddr_in *
     int ret;
 #else
     struct hostent *he;
+    struct sockaddr_in *sin = (struct sockaddr_in *)sa;
 #endif
 
-    if (!(hostname = strdupWA( hostnameW ))) return FALSE;
+    if (!(hostname = strdupWA( hostnameW ))) return ERROR_OUTOFMEMORY;
 
 #ifdef HAVE_GETADDRINFO
     memset( &hints, 0, sizeof(struct addrinfo) );
+    /* Prefer IPv4 to IPv6 addresses, since some web servers do not listen on
+     * their IPv6 addresses even though they have IPv6 addresses in the DNS.
+     */
     hints.ai_family = AF_INET;
 
     ret = getaddrinfo( hostname, NULL, &hints, &res );
-    heap_free( hostname );
     if (ret != 0)
     {
-        TRACE("failed to get address of %s (%s)\n", debugstr_w(hostnameW), gai_strerror(ret));
-        return FALSE;
+        TRACE("failed to get IPv4 address of %s (%s), retrying with IPv6\n", debugstr_w(hostnameW), gai_strerror(ret));
+        hints.ai_family = AF_INET6;
+        ret = getaddrinfo( hostname, NULL, &hints, &res );
+        if (ret != 0)
+        {
+            TRACE("failed to get address of %s (%s)\n", debugstr_w(hostnameW), gai_strerror(ret));
+            heap_free( hostname );
+            return ERROR_WINHTTP_NAME_NOT_RESOLVED;
+        }
+    }
+    heap_free( hostname );
+    if (*sa_len < res->ai_addrlen)
+    {
+        WARN("address too small\n");
+        freeaddrinfo( res );
+        return ERROR_WINHTTP_NAME_NOT_RESOLVED;
+    }
+    *sa_len = res->ai_addrlen;
+    memcpy( sa, res->ai_addr, res->ai_addrlen );
+    /* Copy port */
+    switch (res->ai_family)
+    {
+    case AF_INET:
+        ((struct sockaddr_in *)sa)->sin_port = htons( port );
+        break;
+    case AF_INET6:
+        ((struct sockaddr_in6 *)sa)->sin6_port = htons( port );
+        break;
     }
-    memset( sa, 0, sizeof(struct sockaddr_in) );
-    memcpy( &sa->sin_addr, &((struct sockaddr_in *)res->ai_addr)->sin_addr, sizeof(struct in_addr) );
-    sa->sin_family = res->ai_family;
-    sa->sin_port = htons( port );
 
     freeaddrinfo( res );
+    return ERROR_SUCCESS;
 #else
     EnterCriticalSection( &cs_gethostbyname );
 
@@ -608,57 +1001,99 @@ BOOL netconn_resolve( WCHAR *hostnameW, INTERNET_PORT port, struct sockaddr_in *
     {
         TRACE("failed to get address of %s (%d)\n", debugstr_w(hostnameW), h_errno);
         LeaveCriticalSection( &cs_gethostbyname );
-        return FALSE;
+        return ERROR_WINHTTP_NAME_NOT_RESOLVED;
+    }
+    if (*sa_len < sizeof(struct sockaddr_in))
+    {
+        WARN("address too small\n");
+        LeaveCriticalSection( &cs_gethostbyname );
+        return ERROR_WINHTTP_NAME_NOT_RESOLVED;
     }
+    *sa_len = sizeof(struct sockaddr_in);
     memset( sa, 0, sizeof(struct sockaddr_in) );
-    memcpy( &sa->sin_addr, he->h_addr, he->h_length );
-    sa->sin_family = he->h_addrtype;
-    sa->sin_port = htons( port );
+    memcpy( &sin->sin_addr, he->h_addr, he->h_length );
+    sin->sin_family = he->h_addrtype;
+    sin->sin_port = htons( port );
 
     LeaveCriticalSection( &cs_gethostbyname );
+    return ERROR_SUCCESS;
 #endif
-    return TRUE;
 }
 
-const void *netconn_get_certificate( netconn_t *conn )
+struct resolve_args
 {
-#ifdef SONAME_LIBSSL
-    X509 *cert;
-    unsigned char *buffer, *p;
-    int len;
-    BOOL malloc = FALSE;
-    const CERT_CONTEXT *ret;
+    WCHAR           *hostname;
+    INTERNET_PORT    port;
+    struct sockaddr *sa;
+    socklen_t       *sa_len;
+};
 
-    if (!conn->secure) return NULL;
+static DWORD CALLBACK resolve_proc( LPVOID arg )
+{
+    struct resolve_args *ra = arg;
+    return resolve_hostname( ra->hostname, ra->port, ra->sa, ra->sa_len );
+}
 
-    if (!(cert = pSSL_get_peer_certificate( conn->ssl_conn ))) return NULL;
-    p = NULL;
-    if ((len = pi2d_X509( cert, &p )) < 0) return NULL;
-    /*
-     * SSL 0.9.7 and above malloc the buffer if it is null.
-     * however earlier version do not and so we would need to alloc the buffer.
-     *
-     * see the i2d_X509 man page for more details.
-     */
-    if (!p)
+BOOL netconn_resolve( WCHAR *hostname, INTERNET_PORT port, struct sockaddr *sa, socklen_t *sa_len, int timeout )
+{
+    DWORD ret;
+
+    if (timeout)
     {
-        if (!(buffer = heap_alloc( len ))) return NULL;
-        p = buffer;
-        len = pi2d_X509( cert, &p );
+        DWORD status;
+        HANDLE thread;
+        struct resolve_args ra;
+
+        ra.hostname = hostname;
+        ra.port     = port;
+        ra.sa       = sa;
+        ra.sa_len   = sa_len;
+
+        thread = CreateThread( NULL, 0, resolve_proc, &ra, 0, NULL );
+        if (!thread) return FALSE;
+
+        status = WaitForSingleObject( thread, timeout );
+        if (status == WAIT_OBJECT_0) GetExitCodeThread( thread, &ret );
+        else ret = ERROR_WINHTTP_TIMEOUT;
+        CloseHandle( thread );
     }
-    else
+    else ret = resolve_hostname( hostname, port, sa, sa_len );
+
+    if (ret)
     {
-        buffer = p;
-        malloc = TRUE;
+        set_last_error( ret );
+        return FALSE;
     }
+    return TRUE;
+}
 
-    ret = CertCreateCertificateContext( X509_ASN_ENCODING, buffer, len );
+const void *netconn_get_certificate( netconn_t *conn )
+{
+#ifdef SONAME_LIBSSL
+    X509 *cert;
+    const CERT_CONTEXT *ret;
 
-    if (malloc) free( buffer );
-    else heap_free( buffer );
+    if (!conn->secure) return NULL;
 
+    if (!(cert = pSSL_get_peer_certificate( conn->ssl_conn ))) return NULL;
+    ret = X509_to_cert_context( cert );
     return ret;
 #else
     return NULL;
 #endif
 }
+
+int netconn_get_cipher_strength( netconn_t *conn )
+{
+#ifdef SONAME_LIBSSL
+    SSL_CIPHER *cipher;
+    int bits = 0;
+
+    if (!conn->secure) return 0;
+    if (!(cipher = pSSL_get_current_cipher( conn->ssl_conn ))) return 0;
+    pSSL_CIPHER_get_bits( cipher, &bits );
+    return bits;
+#else
+    return 0;
+#endif
+}