diff --git a/lib/nss.c b/lib/nss.c index 6e8d242..f5c69e6 100644 --- a/lib/nss.c +++ b/lib/nss.c @@ -844,6 +844,36 @@ static SECStatus SelectClientCert(void *arg, PRFileDesc *sock, return SECSuccess; } +/* This function is supposed to decide, which error codes should be used + * to conclude server is TLS intolerant. + * + * taken from xulrunner - nsNSSIOLayer.cpp + */ +static PRBool +isTLSIntoleranceError(PRInt32 err) +{ + switch (err) { + case SSL_ERROR_BAD_MAC_ALERT: + case SSL_ERROR_BAD_MAC_READ: + case SSL_ERROR_HANDSHAKE_FAILURE_ALERT: + case SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT: + case SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE: + case SSL_ERROR_ILLEGAL_PARAMETER_ALERT: + case SSL_ERROR_NO_CYPHER_OVERLAP: + case SSL_ERROR_BAD_SERVER: + case SSL_ERROR_BAD_BLOCK_PADDING: + case SSL_ERROR_UNSUPPORTED_VERSION: + case SSL_ERROR_PROTOCOL_VERSION_ALERT: + case SSL_ERROR_RX_MALFORMED_FINISHED: + case SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE: + case SSL_ERROR_DECODE_ERROR_ALERT: + case SSL_ERROR_RX_UNKNOWN_ALERT: + return PR_TRUE; + default: + return PR_FALSE; + } +} + /** * Global SSL init * @@ -1081,7 +1111,11 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) switch (data->set.ssl.version) { default: case CURL_SSLVERSION_DEFAULT: - ssl3 = tlsv1 = PR_TRUE; + ssl3 = PR_TRUE; + if (data->state.ssl_connect_retry) + infof(data, "TLS disabled due to previous handshake failure\n"); + else + tlsv1 = PR_TRUE; break; case CURL_SSLVERSION_TLSv1: tlsv1 = PR_TRUE; @@ -1104,6 +1138,9 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) if(SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, ssl2) != SECSuccess) goto error; + /* reset the flag to avoid an infinite loop */ + data->state.ssl_connect_retry = FALSE; + /* enable all ciphers from enable_ciphers_by_default */ cipher_to_enable = enable_ciphers_by_default; while (SSL_NULL_WITH_NULL_NULL != *cipher_to_enable) { @@ -1280,10 +1317,21 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) return CURLE_OK; error: + /* reset the flag to avoid an infinite loop */ + data->state.ssl_connect_retry = FALSE; + err = PR_GetError(); infof(data, "NSS error %d\n", err); if(model) PR_Close(model); + + if (ssl3 && tlsv1 && isTLSIntoleranceError(err)) { + /* schedule reconnect through Curl_retry_request() */ + data->state.ssl_connect_retry = TRUE; + infof(data, "Error in TLS handshake, trying SSLv3...\n"); + return CURLE_OK; + } + return curlerr; } diff --git a/lib/transfer.c b/lib/transfer.c index 1f69706..c3a1976 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -2572,10 +2572,11 @@ CURLcode Curl_retry_request(struct connectdata *conn, if(data->set.upload && !(conn->protocol&PROT_HTTP)) return CURLE_OK; - if((data->req.bytecount + + if(/* workaround for broken TLS servers */ data->state.ssl_connect_retry || + ((data->req.bytecount + data->req.headerbytecount == 0) && conn->bits.reuse && - !data->set.opt_no_body) { + !data->set.opt_no_body)) { /* We got no data, we attempted to re-use a connection and yet we want a "body". This might happen if the connection was left alive when we were done using it before, but that was closed when we wanted to read from diff --git a/lib/urldata.h b/lib/urldata.h index b9e5c24..b181e3f 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1331,6 +1331,9 @@ struct UrlState { } proto; /* current user of this SessionHandle instance, or NULL */ struct connectdata *current_conn; + + /* if true, force SSL connection retry (workaround for certain servers) */ + bool ssl_connect_retry; };