diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c index 1c00260783..635c52eaaa 100644 --- a/modules/http/http_protocol.c +++ b/modules/http/http_protocol.c @@ -690,7 +690,9 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode ctx->b = apr_brigade_split(b, e); } else { - ctx->b = NULL; + if (!APR_BRIGADE_EMPTY(ctx->b)) { + ctx->b = NULL; /*XXX*/ + } } apr_brigade_length(b, 1, &total); *readbytes -= total; diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c index e49c5971f7..28117e6a30 100644 --- a/modules/ssl/mod_ssl.c +++ b/modules/ssl/mod_ssl.c @@ -488,20 +488,19 @@ static void ssl_register_hooks(apr_pool_t *p) ap_hook_default_port (ssl_hook_default_port, NULL,NULL, APR_HOOK_MIDDLE); ap_hook_handler (ssl_hook_Handler, NULL,NULL, APR_HOOK_MIDDLE); ap_hook_pre_config (ssl_hook_pre_config, NULL,NULL, APR_HOOK_MIDDLE); - ap_hook_fixups (ssl_hook_Fixup, NULL,NULL, APR_HOOK_MIDDLE); - #if 0 /* XXX - Work in progress */ ap_hook_child_init (ssl_init_Child, NULL,NULL, APR_HOOK_MIDDLE); ap_hook_process_connection (ssl_hook_process_connection, NULL,NULL, APR_HOOK_MIDDLE); ap_hook_post_read_request (ssl_hook_post_read_request, NULL,NULL, APR_HOOK_MIDDLE); +#endif ap_hook_translate_name(ssl_hook_Translate, NULL,NULL, APR_HOOK_MIDDLE); ap_hook_check_user_id (ssl_hook_UserCheck, NULL,NULL, APR_HOOK_MIDDLE); ap_hook_fixups (ssl_hook_Fixup, NULL,NULL, APR_HOOK_MIDDLE); ap_hook_access_checker(ssl_hook_Access, NULL,NULL, APR_HOOK_MIDDLE); ap_hook_auth_checker (ssl_hook_Auth, NULL,NULL, APR_HOOK_MIDDLE); - +#if 0 /* XXX - Work in progress */ ap_hook_open_logs (ssl_hook_open_logs, NULL,NULL, APR_HOOK_MIDDLE); ap_hook_quick_handler (ssl_hook_quick_handler, NULL,NULL, APR_HOOK_MIDDLE); ap_hook_log_transaction(ssl_hook_fixer_upper, NULL,NULL, APR_HOOK_MIDDLE); diff --git a/modules/ssl/ssl_engine_io.c b/modules/ssl/ssl_engine_io.c index fc2abb8454..678ee1ea70 100644 --- a/modules/ssl/ssl_engine_io.c +++ b/modules/ssl/ssl_engine_io.c @@ -220,6 +220,9 @@ static apr_status_t churn_output(SSLFilterRec *pRec) return APR_SUCCESS; } +#define bio_is_renegotiating(bio) \ +(((int)BIO_get_callback_arg(bio)) == SSL_ST_RENEGOTIATE) + static apr_status_t churn (SSLFilterRec *pRec, apr_read_type_e eReadType, apr_off_t *readbytes) { @@ -282,13 +285,22 @@ static apr_status_t churn (SSLFilterRec *pRec, assert(n >= 0 && (apr_size_t)n == len); + if (bio_is_renegotiating(pRec->pbioRead)) { + /* we're doing renegotiation in the access phase */ + if (len >= *readbytes) { + /* trick ap_http_filter into leaving us alone */ + *readbytes = 0; + break; /* SSL_renegotiate will take it from here */ + } + } + if ((ret = ssl_hook_process_connection(pRec)) != APR_SUCCESS) { /* if this is the case, ssl connection has been shutdown * and pRec->pssl has been freed */ return ret; } - + /* pass along all of the current BIO */ while ((n = ssl_io_hook_read(pRec->pssl, (unsigned char *)buf, sizeof(buf))) > 0) @@ -334,23 +346,35 @@ apr_status_t ssl_io_filter_Output(ap_filter_t *f,apr_bucket_brigade *pbbIn) APLOG_MARK,APLOG_ERR,ret,NULL, "Error in churn_output"); return ret; } - - if ((ret = ssl_hook_CloseConnection (pRec)) != APR_SUCCESS) - ap_log_error(APLOG_MARK,APLOG_ERR,ret,NULL, - "Error in ssl_hook_CloseConnection"); break; } - if(APR_BUCKET_IS_FLUSH(pbktIn)) { - continue; - } + if (!APR_BUCKET_IS_FLUSH(pbktIn)) { + /* read filter */ + apr_bucket_read(pbktIn,&data,&len,APR_BLOCK_READ); - /* read filter */ - apr_bucket_read(pbktIn,&data,&len,APR_BLOCK_READ); + /* write SSL */ + n = ssl_io_hook_write(pRec->pssl, (unsigned char *)data, len); - /* write SSL */ - n = ssl_io_hook_write(pRec->pssl, (unsigned char *)data, len); - assert (n == len); + if (n != len) { + conn_rec *c = (conn_rec *)SSL_get_app_data(pRec->pssl); + char *reason = "reason unknown"; + + /* XXX: probably a better way to determine this */ + if (SSL_total_renegotiations(pRec->pssl)) { + reason = "likely due to failed renegotiation"; + } + + ssl_log(c->base_server, SSL_LOG_ERROR, + "failed to write %d of %d bytes (%s)", + n > 0 ? len - n : len, len, reason); + + return APR_EINVAL; + } + } + /* else fallthrough to flush the current wbio + * likely triggered by renegotiation in ssl_hook_Access + */ /* churn the state machine */ ret=churn_output(pRec); @@ -388,8 +412,15 @@ apr_status_t ssl_io_filter_Input(ap_filter_t *f,apr_bucket_brigade *pbbOut, apr_status_t ssl_io_filter_cleanup (void *data) { - SSL *ssl = (SSL *)data; - return APR_SUCCESS; + apr_status_t ret; + SSLFilterRec *pRec = (SSLFilterRec *)data; + + if ((ret = ssl_hook_CloseConnection(pRec)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, ret, NULL, + "Error in ssl_hook_CloseConnection"); + } + + return ret; } void ssl_io_filter_init(conn_rec *c, SSL *ssl) @@ -406,7 +437,7 @@ void ssl_io_filter_init(conn_rec *c, SSL *ssl) SSL_set_bio(ssl, filter->pbioRead, filter->pbioWrite); filter->pssl = ssl; - apr_pool_cleanup_register(c->pool, (void*)ssl, + apr_pool_cleanup_register(c->pool, (void*)filter, ssl_io_filter_cleanup, apr_pool_cleanup_null); return; diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c index 14a29fa005..2b954882d3 100644 --- a/modules/ssl/ssl_engine_kernel.c +++ b/modules/ssl/ssl_engine_kernel.c @@ -573,6 +573,84 @@ int ssl_hook_Handler(request_rec *r) return HTTP_BAD_REQUEST; } +typedef long bio_hook_t (BIO *, int, const char *, int, long, long); + +static void bio_hook_set(BIO *b, bio_hook_t *hook, void *data) +{ + while (b) { + BIO_set_callback(b, hook); + BIO_set_callback_arg(b, data); + b = BIO_next(b); + } +} + +/* XXX: save/restore current callbacks if any? */ +#define ssl_bio_hooks_set(ssl, hook, data) \ + bio_hook_set(SSL_get_wbio(ssl), hook, data); \ + bio_hook_set(SSL_get_rbio(ssl), hook, data) + +#define ssl_bio_renegotiate_hook_set(ssl, data) \ +ssl_bio_hooks_set(ssl, ssl_renegotiate_hook, data) + +#define ssl_bio_hooks_unset(ssl) \ +ssl_bio_hooks_set(ssl, NULL, NULL) + +#define bio_mem_length(b) ((BUF_MEM *)b->ptr)->length + +/* if we need to renegotiate in the access phase + * data needs to be pushed to / pulled from the filter chain + * otherwise, a BIO_write just sits in memory and theres nothing + * to BIO_read + */ + +static long ssl_renegotiate_hook(BIO *bio, int cmd, const char *argp, + int argi, long argl, long rc) +{ + request_rec *r = (request_rec *)BIO_get_callback_arg(bio); + SSL *ssl; + + int is_failed_read = (cmd == (BIO_CB_READ|BIO_CB_RETURN) && (rc == -1)); + int is_flush = ((cmd == BIO_CB_CTRL) && (argi == BIO_CTRL_FLUSH)); + + if (is_flush || is_failed_read) { + ssl = (SSL *)apr_table_get(r->connection->notes, "ssl"); + /* disable this callback to prevent recursion + * and leave a "note" so the input filter leaves the rbio + * as-as + */ + ssl_bio_hooks_set(ssl, NULL, (void*)SSL_ST_RENEGOTIATE); + } + else { + return rc; + } + + if (is_flush) { + /* flush what was written into wbio to the client */ + ssl_log(r->server, SSL_LOG_DEBUG, + "flushing %d bytes to the client", + bio_mem_length(bio)); + ap_rflush(r); + } + else { + /* force read from the client socket */ + apr_bucket_brigade *bb = apr_brigade_create(r->connection->pool); + apr_off_t bytes = argi; + ap_get_brigade(r->input_filters, bb, + AP_MODE_BLOCKING, &bytes); + + rc = BIO_read(bio, (void *)argp, argi); + + ssl_log(r->server, SSL_LOG_DEBUG, + "retry read: wanted %d, got %d, %d remain\n", + argi, rc, bio_mem_length(bio)); + } + + /* reset this bio hook for further read/writes */ + ssl_bio_renegotiate_hook_set(ssl, r); + + return rc; +} + /* * Access Handler */ @@ -982,15 +1060,22 @@ int ssl_hook_Access(request_rec *r) SSL_set_session_id_context(ssl, (unsigned char *)&(r->main), sizeof(r->main)); else SSL_set_session_id_context(ssl, (unsigned char *)&r, sizeof(r)); + /* will need to push to / pull from filters to renegotiate */ + ssl_bio_renegotiate_hook_set(ssl, r); SSL_renegotiate(ssl); SSL_do_handshake(ssl); + if (SSL_get_state(ssl) != SSL_ST_OK) { ssl_log(r->server, SSL_LOG_ERROR, "Re-negotiation request failed"); + ssl_bio_hooks_unset(ssl); return HTTP_FORBIDDEN; } ssl_log(r->server, SSL_LOG_INFO, "Awaiting re-negotiation handshake"); SSL_set_state(ssl, SSL_ST_ACCEPT); SSL_do_handshake(ssl); + + ssl_bio_hooks_unset(ssl); + if (SSL_get_state(ssl) != SSL_ST_OK) { ssl_log(r->server, SSL_LOG_ERROR, "Re-negotiation handshake failed: Not accepted by client!?");