Index: outgoing.c =================================================================== --- outgoing.c (revision 2419) +++ outgoing.c (working copy) @@ -446,6 +446,13 @@ apr_status_t serf__open_connections(serf_context_t return status; } + status = serf_config_set_string(conn->config, + SERF_CONFIG_CONN_PIPELINING, + (conn->max_outstanding_requests != 1 && + conn->pipelining == 1) ? "Y" : "N"); + if (status) + return status; + /* Flag our pollset as dirty now that we have a new socket. */ conn->dirty_conn = 1; ctx->dirty_pollset = 1; @@ -1247,6 +1254,20 @@ static apr_status_t read_from_connection(serf_conn goto error; } + /* This connection uses HTTP pipelining and the server asked for a + renegotiation (e.g. to access the requested resource a specific + client certificate is required). + Because of a known problem in OpenSSL this won't work most of the + time, so as a workaround, when the server asks for a renegotiation + on a connection using HTTP pipelining, we reset the connection, + disable pipelining and reconnect to the server. */ + if (status == SERF_ERROR_SSL_NEGOTIATE_IN_PROGRESS) { + serf__connection_set_pipelining(conn, 0); + reset_connection(conn, 1); + status = APR_SUCCESS; + goto error; + } + /* If our response handler says it can't do anything more, we now * treat that as a success. */ Index: serf.h =================================================================== --- serf.h (revision 2419) +++ serf.h (working copy) @@ -110,6 +110,10 @@ typedef struct serf_config_t serf_config_t; /* SSL handshake failed */ #define SERF_ERROR_SSL_SETUP_FAILED (SERF_ERROR_START + 72) +/* Serf-internal error code, raised when the server initiates SSL renegotiation + on a connection that uses HTTP pipelining. */ +#define SERF_ERROR_SSL_NEGOTIATE_IN_PROGRESS (SERF_ERROR_START + 73) + /* General authentication related errors */ #define SERF_ERROR_AUTHN_FAILED (SERF_ERROR_START + 90) @@ -1190,9 +1194,10 @@ typedef enum { #define SERF_CONFIG_HOST_NAME SERF_CONFIG_PER_HOST | 0x000001 #define SERF_CONFIG_HOST_PORT SERF_CONFIG_PER_HOST | 0x000002 -#define SERF_CONFIG_CONN_LOCALIP SERF_CONFIG_PER_CONNECTION | 0x000003 -#define SERF_CONFIG_CONN_REMOTEIP SERF_CONFIG_PER_CONNECTION | 0x000004 -#define SERF_CONFIG_CTX_LOGBATON SERF_CONFIG_PER_CONTEXT | 0x000005 +#define SERF_CONFIG_CONN_LOCALIP SERF_CONFIG_PER_CONNECTION | 0x000001 +#define SERF_CONFIG_CONN_REMOTEIP SERF_CONFIG_PER_CONNECTION | 0x000002 +#define SERF_CONFIG_CONN_PIPELINING SERF_CONFIG_PER_CONNECTION | 0x000003 +#define SERF_CONFIG_CTX_LOGBATON SERF_CONFIG_PER_CONTEXT | 0x000001 /* Configuration values stored in the configuration store: Index: test/MockHTTPinC/MockHTTP_server.c =================================================================== --- test/MockHTTPinC/MockHTTP_server.c (revision 2419) +++ test/MockHTTPinC/MockHTTP_server.c (working copy) @@ -214,7 +214,7 @@ static apr_status_t setupTCPServer(mhServCtx_t *ct pfd.desc_type = APR_POLL_SOCKET; pfd.desc.s = ctx->skt; - pfd.reqevents = APR_POLLIN; + pfd.reqevents = APR_POLLIN | APR_POLLHUP | APR_POLLERR; STATUSERR(apr_pollset_add(ctx->pollset, &pfd)); } @@ -1552,7 +1552,7 @@ apr_status_t _mhRunServerLoop(mhServCtx_t *ctx) cctx = initClientCtx(ctx->pool, ctx, cskt, ctx->type); pfd.desc_type = APR_POLL_SOCKET; pfd.desc.s = cskt; - pfd.reqevents = APR_POLLIN | APR_POLLOUT; + pfd.reqevents = APR_POLLIN | APR_POLLOUT | APR_POLLHUP | APR_POLLERR; pfd.client_data = cctx; STATUSERR(apr_pollset_add(ctx->pollset, &pfd)); cctx->reqevents = pfd.reqevents; @@ -1588,6 +1588,7 @@ apr_status_t _mhRunServerLoop(mhServCtx_t *ctx) if (status == APR_EOF) { /* Close the socket and an associated proxy skt */ closeAndRemoveClientCtx(ctx, cctx); + break; } } } @@ -2434,6 +2435,8 @@ static apr_status_t initSSLCtx(_mhClientCtx_t *cct cctx->ssl_ctx = ssl_ctx; ssl_ctx->bio_read_status = APR_SUCCESS; + _mhLog(MH_VERBOSE, cctx->skt, "Initializing SSL context.\n"); + /* Init OpenSSL globally */ if (!init_done) { Index: test/test_ssl.c =================================================================== --- test/test_ssl.c (revision 2419) +++ test/test_ssl.c (working copy) @@ -1755,10 +1755,23 @@ static void test_ssl_renegotiate(CuTest *tc) create_new_request(tb, &handler_ctx[0], "GET", "/index1.html", 1); - run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, - handler_ctx, tb->pool); + status = run_client_and_mock_servers_loops(tb, 1, handler_ctx, + tb->pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); create_new_request(tb, &handler_ctx[1], "GET", "/index2.html", 2); + + status = run_client_and_mock_servers_loops(tb, num_requests, handler_ctx, + tb->pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + + /* Check that the requests were sent and reveived by the server */ + /* Note: the test server will have received the first request twice, so + we can't check for VerifyAllRequestsReceivedInOrder here. */ + Verify(tb->mh) + CuAssert(tc, ErrorMessage, VerifyAllRequestsReceived); + EndVerify + CuAssertIntEquals(tc, num_requests, tb->handled_requests->nelts); } static void test_ssl_missing_client_certificate(CuTest *tc) @@ -2136,10 +2149,7 @@ CuSuite *test_ssl(void) SUITE_ADD_TEST(suite, test_ssl_server_cert_with_san_nul_byte); SUITE_ADD_TEST(suite, test_ssl_server_cert_with_cnsan_nul_byte); SUITE_ADD_TEST(suite, test_ssl_server_cert_with_san_and_empty_cb); -#if 0 - /* WIP: Test hangs */ SUITE_ADD_TEST(suite, test_ssl_renegotiate); -#endif return suite; } Index: buckets/ssl_buckets.c =================================================================== --- buckets/ssl_buckets.c (revision 2419) +++ buckets/ssl_buckets.c (working copy) @@ -179,6 +179,9 @@ struct serf_ssl_context_t { requests. */ apr_status_t fatal_err; + /* Flag is set to 1 when a renegotiation is in progress. */ + int renegotiation; + serf_config_t *config; }; @@ -278,6 +281,25 @@ apps_ssl_info_callback(const SSL *s, int where, in } #endif + +/* Listens for the SSL renegotiate ciphers alert and report it back to the + serf context. */ +static void +detect_renegotiate(const SSL *s, int where, int ret) +{ +#ifdef SERF_LOGGING_ENABLED + apps_ssl_info_callback(s, where, ret); +#endif + + /* The server asked to renegotiate the SSL session. */ + if (SSL_state(s) == SSL_ST_RENEGOTIATE) { + serf_ssl_context_t *ssl_ctx = SSL_get_app_data(s); + + ssl_ctx->renegotiation = 1; + ssl_ctx->fatal_err = SERF_ERROR_SSL_NEGOTIATE_IN_PROGRESS; + } +} + static void log_ssl_error(serf_ssl_context_t *ctx) { unsigned long e = ERR_get_error(); @@ -294,6 +316,11 @@ static int bio_bucket_read(BIO *bio, char *in, int apr_status_t status; apr_size_t len; + /* The server initiated a renegotiation and we were instructed to report + that as an error asap. */ + if (ctx->renegotiation) + return -1; + serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, "bio_bucket_read called for %d bytes\n", inlen); @@ -328,6 +355,11 @@ static int bio_bucket_write(BIO *bio, const char * serf_ssl_context_t *ctx = bio->ptr; serf_bucket_t *tmp; + /* The server initiated a renegotiation and we were instructed to report + that as an error asap. */ + if (ctx->renegotiation) + return -1; + serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, "bio_bucket_write called for %d bytes\n", inl); @@ -1446,6 +1478,7 @@ static serf_ssl_context_t *ssl_init_context(serf_b ssl_ctx->cached_cert_pw = 0; ssl_ctx->pending_err = APR_SUCCESS; ssl_ctx->fatal_err = APR_SUCCESS; + ssl_ctx->renegotiation = 0; ssl_ctx->cert_callback = NULL; ssl_ctx->cert_pw_callback = NULL; @@ -2092,6 +2125,8 @@ static apr_status_t serf_ssl_set_config(serf_bucke ssl_context_t *ctx = bucket->data; serf_ssl_context_t *ssl_ctx = ctx->ssl_ctx; apr_status_t err_status = APR_SUCCESS; + const char *pipelining; + apr_status_t status; ssl_ctx->config = config; @@ -2111,6 +2146,15 @@ static apr_status_t serf_ssl_set_config(serf_bucke } } + status = serf_config_get_string(config, SERF_CONFIG_CONN_PIPELINING, + &pipelining); + if (status) + return status; + + if (strcmp(pipelining, "Y") == 0) { + SSL_CTX_set_info_callback(ssl_ctx->ctx, detect_renegotiate); + } + return err_status; }