| /* Licensed to the Apache Software Foundation (ASF) under one or more | |
| * contributor license agreements. See the NOTICE file distributed with | |
| * this work for additional information regarding copyright ownership. | |
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |
| * (the "License"); you may not use this file except in compliance with | |
| * the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| /* _ _ | |
| * _ __ ___ ___ __| | ___ ___| | mod_ssl | |
| * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL | |
| * | | | | | | (_) | (_| | \__ \__ \ | | |
| * |_| |_| |_|\___/ \__,_|___|___/___/_| | |
| * |_____| | |
| * ssl_engine_io.c | |
| * I/O Functions | |
| */ | |
| /* ``MY HACK: This universe. | |
| Just one little problem: | |
| core keeps dumping.'' | |
| -- Unknown */ | |
| #include "ssl_private.h" | |
| #include "apr_date.h" | |
| APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, proxy_post_handshake, | |
| (conn_rec *c,SSL *ssl), | |
| (c,ssl),OK,DECLINED); | |
| /* _________________________________________________________________ | |
| ** | |
| ** I/O Hooks | |
| ** _________________________________________________________________ | |
| */ | |
| /* This file is designed to be the bridge between OpenSSL and httpd. | |
| * However, we really don't expect anyone (let alone ourselves) to | |
| * remember what is in this file. So, first, a quick overview. | |
| * | |
| * In this file, you will find: | |
| * - ssl_io_filter_input (Apache input filter) | |
| * - ssl_io_filter_output (Apache output filter) | |
| * | |
| * - bio_filter_in_* (OpenSSL input filter) | |
| * - bio_filter_out_* (OpenSSL output filter) | |
| * | |
| * The input chain is roughly: | |
| * | |
| * ssl_io_filter_input->ssl_io_input_read->SSL_read->... | |
| * ...->bio_filter_in_read->ap_get_brigade/next-httpd-filter | |
| * | |
| * In mortal terminology, we do the following: | |
| * - Receive a request for data to the SSL input filter | |
| * - Call a helper function once we know we should perform a read | |
| * - Call OpenSSL's SSL_read() | |
| * - SSL_read() will then call bio_filter_in_read | |
| * - bio_filter_in_read will then try to fetch data from the next httpd filter | |
| * - bio_filter_in_read will flatten that data and return it to SSL_read | |
| * - SSL_read will then decrypt the data | |
| * - ssl_io_input_read will then receive decrypted data as a char* and | |
| * ensure that there were no read errors | |
| * - The char* is placed in a brigade and returned | |
| * | |
| * Since connection-level input filters in httpd need to be able to | |
| * handle AP_MODE_GETLINE calls (namely identifying LF-terminated strings), | |
| * ssl_io_input_getline which will handle this special case. | |
| * | |
| * Due to AP_MODE_GETLINE and AP_MODE_SPECULATIVE, we may sometimes have | |
| * 'leftover' decoded data which must be setaside for the next read. That | |
| * is currently handled by the char_buffer_{read|write} functions. So, | |
| * ssl_io_input_read may be able to fulfill reads without invoking | |
| * SSL_read(). | |
| * | |
| * Note that the filter context of ssl_io_filter_input and bio_filter_in_* | |
| * are shared as bio_filter_in_ctx_t. | |
| * | |
| * Note that the filter is by choice limited to reading at most | |
| * AP_IOBUFSIZE (8192 bytes) per call. | |
| * | |
| */ | |
| /* this custom BIO allows us to hook SSL_write directly into | |
| * an apr_bucket_brigade and use transient buckets with the SSL | |
| * malloc-ed buffer, rather than copying into a mem BIO. | |
| * also allows us to pass the brigade as data is being written | |
| * rather than buffering up the entire response in the mem BIO. | |
| * | |
| * when SSL needs to flush (e.g. SSL_accept()), it will call BIO_flush() | |
| * which will trigger a call to bio_filter_out_ctrl() -> bio_filter_out_flush(). | |
| * so we only need to flush the output ourselves if we receive an | |
| * EOS or FLUSH bucket. this was not possible with the mem BIO where we | |
| * had to flush all over the place not really knowing when it was required | |
| * to do so. | |
| */ | |
| typedef struct { | |
| SSL *pssl; | |
| BIO *pbioRead; | |
| BIO *pbioWrite; | |
| ap_filter_t *pInputFilter; | |
| ap_filter_t *pOutputFilter; | |
| SSLConnRec *config; | |
| } ssl_filter_ctx_t; | |
| typedef struct { | |
| ssl_filter_ctx_t *filter_ctx; | |
| conn_rec *c; | |
| apr_bucket_brigade *bb; /* Brigade used as a buffer. */ | |
| apr_status_t rc; | |
| } bio_filter_out_ctx_t; | |
| static bio_filter_out_ctx_t *bio_filter_out_ctx_new(ssl_filter_ctx_t *filter_ctx, | |
| conn_rec *c) | |
| { | |
| bio_filter_out_ctx_t *outctx = apr_palloc(c->pool, sizeof(*outctx)); | |
| outctx->filter_ctx = filter_ctx; | |
| outctx->c = c; | |
| outctx->bb = apr_brigade_create(c->pool, c->bucket_alloc); | |
| return outctx; | |
| } | |
| /* Pass an output brigade down the filter stack; returns 1 on success | |
| * or -1 on failure. */ | |
| static int bio_filter_out_pass(bio_filter_out_ctx_t *outctx) | |
| { | |
| AP_DEBUG_ASSERT(!APR_BRIGADE_EMPTY(outctx->bb)); | |
| outctx->rc = ap_pass_brigade(outctx->filter_ctx->pOutputFilter->next, | |
| outctx->bb); | |
| /* Fail if the connection was reset: */ | |
| if (outctx->rc == APR_SUCCESS && outctx->c->aborted) { | |
| outctx->rc = APR_ECONNRESET; | |
| } | |
| apr_brigade_cleanup(outctx->bb); | |
| return (outctx->rc == APR_SUCCESS) ? 1 : -1; | |
| } | |
| /* Send a FLUSH bucket down the output filter stack; returns 1 on | |
| * success, -1 on failure. */ | |
| static int bio_filter_out_flush(BIO *bio) | |
| { | |
| bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio); | |
| apr_bucket *e; | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, outctx->c, | |
| "bio_filter_out_write: flush"); | |
| AP_DEBUG_ASSERT(APR_BRIGADE_EMPTY(outctx->bb)); | |
| e = apr_bucket_flush_create(outctx->bb->bucket_alloc); | |
| APR_BRIGADE_INSERT_TAIL(outctx->bb, e); | |
| return bio_filter_out_pass(outctx); | |
| } | |
| static int bio_filter_create(BIO *bio) | |
| { | |
| BIO_set_shutdown(bio, 1); | |
| BIO_set_init(bio, 1); | |
| #if MODSSL_USE_OPENSSL_PRE_1_1_API | |
| /* No setter method for OpenSSL 1.1.0 available, | |
| * but I can't find any functional use of the | |
| * "num" field there either. | |
| */ | |
| bio->num = -1; | |
| #endif | |
| BIO_set_data(bio, NULL); | |
| return 1; | |
| } | |
| static int bio_filter_destroy(BIO *bio) | |
| { | |
| if (bio == NULL) { | |
| return 0; | |
| } | |
| /* nothing to free here. | |
| * apache will destroy the bucket brigade for us | |
| */ | |
| return 1; | |
| } | |
| static int bio_filter_out_write(BIO *bio, const char *in, int inl) | |
| { | |
| bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio); | |
| apr_bucket *e; | |
| int need_flush; | |
| BIO_clear_retry_flags(bio); | |
| #ifndef SSL_OP_NO_RENEGOTIATION | |
| /* Abort early if the client has initiated a renegotiation. */ | |
| if (outctx->filter_ctx->config->reneg_state == RENEG_ABORT) { | |
| outctx->rc = APR_ECONNABORTED; | |
| return -1; | |
| } | |
| #endif | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, outctx->c, | |
| "bio_filter_out_write: %i bytes", inl); | |
| /* Use a transient bucket for the output data - any downstream | |
| * filter must setaside if necessary. */ | |
| e = apr_bucket_transient_create(in, inl, outctx->bb->bucket_alloc); | |
| APR_BRIGADE_INSERT_TAIL(outctx->bb, e); | |
| /* In theory, OpenSSL should flush as necessary, but it is known | |
| * not to do so correctly in some cases (< 0.9.8m; see PR 46952), | |
| * or on the proxy/client side (after ssl23_client_hello(), e.g. | |
| * ssl/proxy.t test suite). | |
| * | |
| * Historically, this flush call was performed only for an SSLv2 | |
| * connection or for a proxy connection. Calling _out_flush can | |
| * be expensive in cases where requests/responses are pipelined, | |
| * so limit the performance impact to handshake time. | |
| */ | |
| #if OPENSSL_VERSION_NUMBER < 0x0009080df | |
| need_flush = !SSL_is_init_finished(outctx->filter_ctx->pssl); | |
| #else | |
| need_flush = SSL_in_connect_init(outctx->filter_ctx->pssl); | |
| #endif | |
| if (need_flush) { | |
| e = apr_bucket_flush_create(outctx->bb->bucket_alloc); | |
| APR_BRIGADE_INSERT_TAIL(outctx->bb, e); | |
| } | |
| if (bio_filter_out_pass(outctx) < 0) { | |
| return -1; | |
| } | |
| return inl; | |
| } | |
| static long bio_filter_out_ctrl(BIO *bio, int cmd, long num, void *ptr) | |
| { | |
| long ret = 1; | |
| bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio); | |
| switch (cmd) { | |
| case BIO_CTRL_RESET: | |
| case BIO_CTRL_EOF: | |
| case BIO_C_SET_BUF_MEM_EOF_RETURN: | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, outctx->c, | |
| "output bio: unhandled control %d", cmd); | |
| ret = 0; | |
| break; | |
| case BIO_CTRL_WPENDING: | |
| case BIO_CTRL_PENDING: | |
| case BIO_CTRL_INFO: | |
| ret = 0; | |
| break; | |
| case BIO_CTRL_GET_CLOSE: | |
| ret = (long)BIO_get_shutdown(bio); | |
| break; | |
| case BIO_CTRL_SET_CLOSE: | |
| BIO_set_shutdown(bio, (int)num); | |
| break; | |
| case BIO_CTRL_FLUSH: | |
| ret = bio_filter_out_flush(bio); | |
| break; | |
| case BIO_CTRL_DUP: | |
| ret = 1; | |
| break; | |
| /* N/A */ | |
| case BIO_C_SET_BUF_MEM: | |
| case BIO_C_GET_BUF_MEM_PTR: | |
| /* we don't care */ | |
| case BIO_CTRL_PUSH: | |
| case BIO_CTRL_POP: | |
| default: | |
| ret = 0; | |
| break; | |
| } | |
| return ret; | |
| } | |
| typedef struct { | |
| apr_bucket *b; | |
| apr_bucket_brigade *bb; | |
| } char_buffer_t; | |
| typedef struct { | |
| SSL *ssl; | |
| BIO *bio_out; | |
| ap_filter_t *f; | |
| apr_status_t rc; | |
| ap_input_mode_t mode; | |
| apr_read_type_e block; | |
| apr_bucket_brigade *bb; | |
| char_buffer_t cbuf; | |
| apr_pool_t *pool; | |
| char buffer[AP_IOBUFSIZE]; | |
| ssl_filter_ctx_t *filter_ctx; | |
| } bio_filter_in_ctx_t; | |
| /* | |
| * this char_buffer api might seem silly, but we don't need to copy | |
| * any of this data and we need to remember the length. | |
| */ | |
| static void char_buffer_insert(bio_filter_in_ctx_t *inctx) | |
| { | |
| char_buffer_t *buf = &inctx->cbuf; | |
| ap_filter_t *f = inctx->filter_ctx->pInputFilter; | |
| /* set the bucket at the top of the filter's pending data */ | |
| ap_filter_reinstate_brigade(f, buf->bb, NULL); | |
| APR_BRIGADE_INSERT_HEAD(buf->bb, buf->b); | |
| ap_filter_adopt_brigade(f, buf->bb); | |
| } | |
| static void char_buffer_consume(bio_filter_in_ctx_t *inctx, int inl) | |
| { | |
| apr_bucket *b = inctx->cbuf.b; | |
| b->data = (char *)b->data + inl; | |
| b->length -= inl; | |
| if (!b->length) { | |
| APR_BUCKET_REMOVE(b); | |
| APR_BUCKET_INIT(b); | |
| } | |
| else if (APR_BUCKET_NEXT(b) == b) { | |
| /* rollbacks might get us here (inl < 0) */ | |
| char_buffer_insert(inctx); | |
| } | |
| } | |
| /* Copy up to INL bytes from the char_buffer BUFFER into IN. Note | |
| * that due to the strange way this API is designed/used, the | |
| * char_buffer object is used to cache a segment of inctx->buffer, and | |
| * then this function called to copy (part of) that segment to the | |
| * beginning of inctx->buffer. So the segments to copy cannot be | |
| * presumed to be non-overlapping, and memmove must be used. */ | |
| static int char_buffer_read(bio_filter_in_ctx_t *inctx, char *in, int inl) | |
| { | |
| char_buffer_t *buf = &inctx->cbuf; | |
| int avail = buf->b ? buf->b->length : 0; | |
| if (!avail) { | |
| return 0; | |
| } | |
| if (inl > avail) { | |
| inl = avail; | |
| } | |
| memmove(in, buf->b->data, inl); | |
| char_buffer_consume(inctx, inl); | |
| return inl; | |
| } | |
| static int char_buffer_write(bio_filter_in_ctx_t *inctx, char *in, int inl) | |
| { | |
| char_buffer_t *buf = &inctx->cbuf; | |
| if (buf->b) { | |
| AP_DEBUG_ASSERT(APR_BUCKET_NEXT(buf->b) == buf->b); | |
| } | |
| else { | |
| ap_filter_t *f = inctx->filter_ctx->pInputFilter; | |
| buf->b = apr_bucket_immortal_create("", 0, f->c->bucket_alloc); | |
| buf->bb = apr_brigade_create(f->c->pool, f->c->bucket_alloc); | |
| } | |
| buf->b->data = in; | |
| buf->b->length = inl; | |
| if (buf->b->length) { | |
| char_buffer_insert(inctx); | |
| } | |
| return inl; | |
| } | |
| /* This function will read from a brigade and discard the read buckets as it | |
| * proceeds. It will read at most *len bytes. | |
| */ | |
| static apr_status_t brigade_consume(apr_bucket_brigade *bb, | |
| apr_read_type_e block, | |
| char *c, apr_size_t *len) | |
| { | |
| apr_size_t actual = 0; | |
| apr_status_t status = APR_SUCCESS; | |
| while (!APR_BRIGADE_EMPTY(bb)) { | |
| apr_bucket *b = APR_BRIGADE_FIRST(bb); | |
| const char *str; | |
| apr_size_t str_len; | |
| apr_size_t consume; | |
| /* Justin points out this is an http-ism that might | |
| * not fit if brigade_consume is added to APR. Perhaps | |
| * apr_bucket_read(eos_bucket) should return APR_EOF? | |
| * Then this becomes mainline instead of a one-off. | |
| */ | |
| if (APR_BUCKET_IS_EOS(b)) { | |
| status = APR_EOF; | |
| break; | |
| } | |
| /* The reason I'm not offering brigade_consume yet | |
| * across to apr-util is that the following call | |
| * illustrates how borked that API really is. For | |
| * this sort of case (caller provided buffer) it | |
| * would be much more trivial for apr_bucket_consume | |
| * to do all the work that follows, based on the | |
| * particular characteristics of the bucket we are | |
| * consuming here. | |
| */ | |
| status = apr_bucket_read(b, &str, &str_len, block); | |
| if (status != APR_SUCCESS) { | |
| if (APR_STATUS_IS_EOF(status)) { | |
| /* This stream bucket was consumed */ | |
| apr_bucket_delete(b); | |
| continue; | |
| } | |
| break; | |
| } | |
| if (str_len > 0) { | |
| /* Do not block once some data has been consumed */ | |
| block = APR_NONBLOCK_READ; | |
| /* Assure we don't overflow. */ | |
| consume = (str_len + actual > *len) ? *len - actual : str_len; | |
| memcpy(c, str, consume); | |
| c += consume; | |
| actual += consume; | |
| if (consume >= b->length) { | |
| /* This physical bucket was consumed */ | |
| apr_bucket_delete(b); | |
| } | |
| else { | |
| /* Only part of this physical bucket was consumed */ | |
| b->start += consume; | |
| b->length -= consume; | |
| } | |
| } | |
| else if (b->length == 0) { | |
| apr_bucket_delete(b); | |
| } | |
| /* This could probably be actual == *len, but be safe from stray | |
| * photons. */ | |
| if (actual >= *len) { | |
| break; | |
| } | |
| } | |
| *len = actual; | |
| return status; | |
| } | |
| /* | |
| * this is the function called by SSL_read() | |
| */ | |
| static int bio_filter_in_read(BIO *bio, char *in, int inlen) | |
| { | |
| apr_size_t inl = inlen; | |
| bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio); | |
| apr_read_type_e block = inctx->block; | |
| inctx->rc = APR_SUCCESS; | |
| /* OpenSSL catches this case, so should we. */ | |
| if (!in) | |
| return 0; | |
| BIO_clear_retry_flags(bio); | |
| #ifndef SSL_OP_NO_RENEGOTIATION | |
| /* Abort early if the client has initiated a renegotiation. */ | |
| if (inctx->filter_ctx->config->reneg_state == RENEG_ABORT) { | |
| inctx->rc = APR_ECONNABORTED; | |
| return -1; | |
| } | |
| #endif | |
| if (!inctx->bb) { | |
| inctx->rc = APR_EOF; | |
| return -1; | |
| } | |
| if (APR_BRIGADE_EMPTY(inctx->bb)) { | |
| inctx->rc = ap_get_brigade(inctx->f->next, inctx->bb, | |
| AP_MODE_READBYTES, block, | |
| inl); | |
| /* If the read returns EAGAIN or success with an empty | |
| * brigade, return an error after setting the retry flag; | |
| * SSL_read() will then return -1, and SSL_get_error() will | |
| * indicate SSL_ERROR_WANT_READ. */ | |
| if (APR_STATUS_IS_EAGAIN(inctx->rc) || APR_STATUS_IS_EINTR(inctx->rc) | |
| || (inctx->rc == APR_SUCCESS && APR_BRIGADE_EMPTY(inctx->bb))) { | |
| BIO_set_retry_read(bio); | |
| return -1; | |
| } | |
| if (block == APR_BLOCK_READ | |
| && APR_STATUS_IS_TIMEUP(inctx->rc) | |
| && APR_BRIGADE_EMPTY(inctx->bb)) { | |
| /* don't give up, just return the timeout */ | |
| return -1; | |
| } | |
| if (inctx->rc != APR_SUCCESS) { | |
| /* Unexpected errors discard the brigade */ | |
| apr_brigade_cleanup(inctx->bb); | |
| inctx->bb = NULL; | |
| return -1; | |
| } | |
| } | |
| inctx->rc = brigade_consume(inctx->bb, block, in, &inl); | |
| if (inctx->rc == APR_SUCCESS) { | |
| return (int)inl; | |
| } | |
| if (APR_STATUS_IS_EAGAIN(inctx->rc) | |
| || APR_STATUS_IS_EINTR(inctx->rc)) { | |
| BIO_set_retry_read(bio); | |
| return (int)inl; | |
| } | |
| /* Unexpected errors and APR_EOF clean out the brigade. | |
| * Subsequent calls will return APR_EOF. | |
| */ | |
| apr_brigade_cleanup(inctx->bb); | |
| inctx->bb = NULL; | |
| if (APR_STATUS_IS_EOF(inctx->rc) && inl) { | |
| /* Provide the results of this read pass, | |
| * without resetting the BIO retry_read flag | |
| */ | |
| return (int)inl; | |
| } | |
| return -1; | |
| } | |
| static long bio_filter_in_ctrl(BIO *bio, int cmd, long num, void *ptr) | |
| { | |
| bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio); | |
| switch (cmd) { | |
| #ifdef BIO_CTRL_EOF | |
| case BIO_CTRL_EOF: | |
| return inctx->rc == APR_EOF; | |
| #endif | |
| default: | |
| break; | |
| } | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, inctx->f->c, | |
| "input bio: unhandled control %d", cmd); | |
| return 0; | |
| } | |
| #if MODSSL_USE_OPENSSL_PRE_1_1_API | |
| static BIO_METHOD bio_filter_out_method = { | |
| BIO_TYPE_MEM, | |
| "APR output filter", | |
| bio_filter_out_write, | |
| NULL, /* read is never called */ | |
| NULL, /* puts is never called */ | |
| NULL, /* gets is never called */ | |
| bio_filter_out_ctrl, | |
| bio_filter_create, | |
| bio_filter_destroy, | |
| NULL | |
| }; | |
| static BIO_METHOD bio_filter_in_method = { | |
| BIO_TYPE_MEM, | |
| "APR input filter", | |
| NULL, /* write is never called */ | |
| bio_filter_in_read, | |
| NULL, /* puts is never called */ | |
| NULL, /* gets is never called */ | |
| bio_filter_in_ctrl, /* ctrl is called for EOF check */ | |
| bio_filter_create, | |
| bio_filter_destroy, | |
| NULL | |
| }; | |
| #else | |
| static BIO_METHOD *bio_filter_out_method = NULL; | |
| static BIO_METHOD *bio_filter_in_method = NULL; | |
| void init_bio_methods(void) | |
| { | |
| bio_filter_out_method = BIO_meth_new(BIO_TYPE_MEM, "APR output filter"); | |
| BIO_meth_set_write(bio_filter_out_method, &bio_filter_out_write); | |
| BIO_meth_set_ctrl(bio_filter_out_method, &bio_filter_out_ctrl); | |
| BIO_meth_set_create(bio_filter_out_method, &bio_filter_create); | |
| BIO_meth_set_destroy(bio_filter_out_method, &bio_filter_destroy); | |
| bio_filter_in_method = BIO_meth_new(BIO_TYPE_MEM, "APR input filter"); | |
| BIO_meth_set_read(bio_filter_in_method, &bio_filter_in_read); | |
| BIO_meth_set_ctrl(bio_filter_in_method, &bio_filter_in_ctrl); /* ctrl is never called */ | |
| BIO_meth_set_create(bio_filter_in_method, &bio_filter_create); | |
| BIO_meth_set_destroy(bio_filter_in_method, &bio_filter_destroy); | |
| } | |
| void free_bio_methods(void) | |
| { | |
| BIO_meth_free(bio_filter_out_method); | |
| BIO_meth_free(bio_filter_in_method); | |
| } | |
| #endif | |
| static apr_status_t ssl_io_input_read(bio_filter_in_ctx_t *inctx, | |
| char *buf, | |
| apr_size_t *len) | |
| { | |
| apr_size_t wanted = *len; | |
| int bytes, rc; | |
| *len = 0; | |
| /* If we have something leftover from last time, try that first. */ | |
| if ((bytes = char_buffer_read(inctx, buf, wanted))) { | |
| *len = bytes; | |
| if (inctx->mode == AP_MODE_SPECULATIVE) { | |
| /* We want to rollback this read. */ | |
| char_buffer_consume(inctx, -bytes); | |
| return APR_SUCCESS; | |
| } | |
| /* This could probably be *len == wanted, but be safe from stray | |
| * photons. | |
| */ | |
| if (*len >= wanted) { | |
| return APR_SUCCESS; | |
| } | |
| if (inctx->mode == AP_MODE_GETLINE) { | |
| if (memchr(buf, APR_ASCII_LF, *len)) { | |
| return APR_SUCCESS; | |
| } | |
| } | |
| else { | |
| /* Down to a nonblock pattern as we have some data already | |
| */ | |
| inctx->block = APR_NONBLOCK_READ; | |
| } | |
| } | |
| while (1) { | |
| if (!inctx->filter_ctx->pssl) { | |
| /* Ensure a non-zero error code is returned */ | |
| if (inctx->rc == APR_SUCCESS) { | |
| inctx->rc = APR_EGENERAL; | |
| } | |
| break; | |
| } | |
| /* We rely on SSL_get_error() after the read, which requires an empty | |
| * error queue before the read in order to work properly. | |
| */ | |
| ERR_clear_error(); | |
| /* SSL_read may not read because we haven't taken enough data | |
| * from the stack. This is where we want to consider all of | |
| * the blocking and SPECULATIVE semantics | |
| */ | |
| rc = SSL_read(inctx->filter_ctx->pssl, buf + bytes, wanted - bytes); | |
| if (rc > 0) { | |
| *len += rc; | |
| if (inctx->mode == AP_MODE_SPECULATIVE) { | |
| /* We want to rollback this read. */ | |
| char_buffer_write(inctx, buf, rc); | |
| } | |
| return inctx->rc; | |
| } | |
| else /* (rc <= 0) */ { | |
| int ssl_err; | |
| conn_rec *c; | |
| if (rc == 0) { | |
| /* If EAGAIN, we will loop given a blocking read, | |
| * otherwise consider ourselves at EOF. | |
| */ | |
| if (APR_STATUS_IS_EAGAIN(inctx->rc) | |
| || APR_STATUS_IS_EINTR(inctx->rc)) { | |
| /* Already read something, return APR_SUCCESS instead. | |
| * On win32 in particular, but perhaps on other kernels, | |
| * a blocking call isn't 'always' blocking. | |
| */ | |
| if (*len > 0) { | |
| inctx->rc = APR_SUCCESS; | |
| break; | |
| } | |
| if (inctx->block == APR_NONBLOCK_READ) { | |
| break; | |
| } | |
| } | |
| else { | |
| if (*len > 0) { | |
| inctx->rc = APR_SUCCESS; | |
| break; | |
| } | |
| } | |
| } | |
| ssl_err = SSL_get_error(inctx->filter_ctx->pssl, rc); | |
| c = (conn_rec*)SSL_get_app_data(inctx->filter_ctx->pssl); | |
| if (ssl_err == SSL_ERROR_WANT_READ) { | |
| /* | |
| * If OpenSSL wants to read more, and we were nonblocking, | |
| * report as an EAGAIN. Otherwise loop, pulling more | |
| * data from network filter. | |
| * | |
| * (This is usually the case when the client forces an SSL | |
| * renegotiation which is handled implicitly by OpenSSL.) | |
| */ | |
| inctx->rc = APR_EAGAIN; | |
| if (*len > 0) { | |
| inctx->rc = APR_SUCCESS; | |
| break; | |
| } | |
| if (inctx->block == APR_NONBLOCK_READ) { | |
| break; | |
| } | |
| continue; /* Blocking and nothing yet? Try again. */ | |
| } | |
| else if (ssl_err == SSL_ERROR_SYSCALL) { | |
| if (APR_STATUS_IS_EAGAIN(inctx->rc) | |
| || APR_STATUS_IS_EINTR(inctx->rc)) { | |
| /* Already read something, return APR_SUCCESS instead. */ | |
| if (*len > 0) { | |
| inctx->rc = APR_SUCCESS; | |
| break; | |
| } | |
| if (inctx->block == APR_NONBLOCK_READ) { | |
| break; | |
| } | |
| continue; /* Blocking and nothing yet? Try again. */ | |
| } | |
| else if (APR_STATUS_IS_TIMEUP(inctx->rc)) { | |
| /* just return it, the calling layer might be fine with it, | |
| and we do not want to bloat the log. */ | |
| } | |
| else { | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, inctx->rc, c, APLOGNO(01991) | |
| "SSL input filter read failed."); | |
| } | |
| } | |
| else if (rc == 0 && ssl_err == SSL_ERROR_ZERO_RETURN) { | |
| inctx->rc = APR_EOF; | |
| break; | |
| } | |
| else /* if (ssl_err == SSL_ERROR_SSL) */ { | |
| /* | |
| * Log SSL errors and any unexpected conditions. | |
| */ | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, inctx->rc, c, APLOGNO(01992) | |
| "SSL library error %d reading data", ssl_err); | |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, mySrvFromConn(c)); | |
| } | |
| if (rc == 0) { | |
| inctx->rc = APR_EOF; | |
| break; | |
| } | |
| if (inctx->rc == APR_SUCCESS) { | |
| inctx->rc = APR_EGENERAL; | |
| } | |
| break; | |
| } | |
| } | |
| return inctx->rc; | |
| } | |
| /* Read a line of input from the SSL input layer into buffer BUF of | |
| * length *LEN; updating *len to reflect the length of the line | |
| * including the LF character. */ | |
| static apr_status_t ssl_io_input_getline(bio_filter_in_ctx_t *inctx, | |
| char *buf, | |
| apr_size_t *len) | |
| { | |
| const char *pos = NULL; | |
| apr_status_t status; | |
| apr_size_t tmplen = *len, buflen = *len, offset = 0; | |
| *len = 0; | |
| /* | |
| * in most cases we get all the headers on the first SSL_read. | |
| * however, in certain cases SSL_read will only get a partial | |
| * chunk of the headers, so we try to read until LF is seen. | |
| */ | |
| while (tmplen > 0) { | |
| status = ssl_io_input_read(inctx, buf + offset, &tmplen); | |
| if (status != APR_SUCCESS) { | |
| if (APR_STATUS_IS_EAGAIN(status) && (*len > 0)) { | |
| /* Save the part of the line we already got */ | |
| char_buffer_write(inctx, buf, *len); | |
| } | |
| return status; | |
| } | |
| *len += tmplen; | |
| if ((pos = memchr(buf, APR_ASCII_LF, *len))) { | |
| break; | |
| } | |
| offset += tmplen; | |
| tmplen = buflen - offset; | |
| } | |
| if (pos) { | |
| char *value; | |
| int length; | |
| apr_size_t bytes = pos - buf; | |
| bytes += 1; | |
| value = buf + bytes; | |
| length = *len - bytes; | |
| char_buffer_write(inctx, value, length); | |
| *len = bytes; | |
| } | |
| return APR_SUCCESS; | |
| } | |
| static apr_status_t ssl_filter_write(ap_filter_t *f, | |
| const char *data, | |
| apr_size_t len) | |
| { | |
| ssl_filter_ctx_t *filter_ctx = f->ctx; | |
| bio_filter_out_ctx_t *outctx; | |
| int res; | |
| /* write SSL */ | |
| if (filter_ctx->pssl == NULL) { | |
| return APR_EGENERAL; | |
| } | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, f->c, | |
| "ssl_filter_write: %"APR_SIZE_T_FMT" bytes", len); | |
| /* We rely on SSL_get_error() after the write, which requires an empty error | |
| * queue before the write in order to work properly. | |
| */ | |
| ERR_clear_error(); | |
| outctx = (bio_filter_out_ctx_t *)BIO_get_data(filter_ctx->pbioWrite); | |
| res = SSL_write(filter_ctx->pssl, (unsigned char *)data, len); | |
| if (res < 0) { | |
| int ssl_err = SSL_get_error(filter_ctx->pssl, res); | |
| conn_rec *c = (conn_rec*)SSL_get_app_data(outctx->filter_ctx->pssl); | |
| if (ssl_err == SSL_ERROR_WANT_WRITE) { | |
| /* | |
| * If OpenSSL wants to write more, and we were nonblocking, | |
| * report as an EAGAIN. Otherwise loop, pushing more | |
| * data at the network filter. | |
| * | |
| * (This is usually the case when the client forces an SSL | |
| * renegotiation which is handled implicitly by OpenSSL.) | |
| */ | |
| outctx->rc = APR_EAGAIN; | |
| } | |
| else if (ssl_err == SSL_ERROR_WANT_READ) { | |
| /* | |
| * If OpenSSL wants to read during write, and we were | |
| * nonblocking, set the sense explicitly to read and | |
| * report as an EAGAIN. | |
| * | |
| * (This is usually the case when the client forces an SSL | |
| * renegotiation which is handled implicitly by OpenSSL.) | |
| */ | |
| outctx->c->cs->sense = CONN_SENSE_WANT_READ; | |
| outctx->rc = APR_EAGAIN; | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, outctx->c, | |
| "Want read during nonblocking write"); | |
| } | |
| else if (ssl_err == SSL_ERROR_SYSCALL) { | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, outctx->rc, c, APLOGNO(01993) | |
| "SSL output filter write failed."); | |
| } | |
| else /* if (ssl_err == SSL_ERROR_SSL) */ { | |
| /* | |
| * Log SSL errors | |
| */ | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, outctx->rc, c, APLOGNO(01994) | |
| "SSL library error %d writing data", ssl_err); | |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, mySrvFromConn(c)); | |
| } | |
| if (outctx->rc == APR_SUCCESS) { | |
| outctx->rc = APR_EGENERAL; | |
| } | |
| } | |
| else if ((apr_size_t)res != len) { | |
| conn_rec *c = f->c; | |
| char *reason = "reason unknown"; | |
| /* XXX: probably a better way to determine this */ | |
| if (SSL_total_renegotiations(filter_ctx->pssl)) { | |
| reason = "likely due to failed renegotiation"; | |
| } | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, outctx->rc, c, APLOGNO(01995) | |
| "failed to write %" APR_SSIZE_T_FMT | |
| " of %" APR_SIZE_T_FMT " bytes (%s)", | |
| len - (apr_size_t)res, len, reason); | |
| outctx->rc = APR_EGENERAL; | |
| } | |
| return outctx->rc; | |
| } | |
| /* Just use a simple request. Any request will work for this, because | |
| * we use a flag in the conn_rec->conn_vector now. The fake request just | |
| * gets the request back to the Apache core so that a response can be sent. | |
| * Since we use an HTTP/1.x request, we also have to inject the empty line | |
| * that terminates the headers, or the core will read more data from the | |
| * socket. | |
| */ | |
| #define HTTP_ON_HTTPS_PORT \ | |
| "GET / HTTP/1.0" CRLF | |
| #define HTTP_ON_HTTPS_PORT_BUCKET(alloc) \ | |
| apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, \ | |
| sizeof(HTTP_ON_HTTPS_PORT) - 1, \ | |
| alloc) | |
| /* Custom apr_status_t error code, used when a plain HTTP request is | |
| * received on an SSL port. */ | |
| #define MODSSL_ERROR_HTTP_ON_HTTPS (APR_OS_START_USERERR + 0) | |
| /* Custom apr_status_t error code, used when the proxy cannot | |
| * establish an outgoing SSL connection. */ | |
| #define MODSSL_ERROR_BAD_GATEWAY (APR_OS_START_USERERR + 1) | |
| static void ssl_io_filter_disable(SSLConnRec *sslconn, | |
| bio_filter_in_ctx_t *inctx) | |
| { | |
| SSL_free(inctx->ssl); | |
| sslconn->ssl = NULL; | |
| inctx->ssl = NULL; | |
| inctx->filter_ctx->pssl = NULL; | |
| } | |
| static apr_status_t ssl_io_filter_error(bio_filter_in_ctx_t *inctx, | |
| apr_bucket_brigade *bb, | |
| apr_status_t status, | |
| int is_init) | |
| { | |
| ap_filter_t *f = inctx->f; | |
| SSLConnRec *sslconn = myConnConfig(f->c); | |
| apr_bucket *bucket; | |
| int send_eos = 1; | |
| switch (status) { | |
| case MODSSL_ERROR_HTTP_ON_HTTPS: | |
| /* log the situation */ | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c, APLOGNO(01996) | |
| "SSL handshake failed: HTTP spoken on HTTPS port; " | |
| "trying to send HTML error page"); | |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, sslconn->server); | |
| ssl_io_filter_disable(sslconn, inctx); | |
| f->c->keepalive = AP_CONN_CLOSE; | |
| if (is_init) { | |
| sslconn->non_ssl_request = NON_SSL_SEND_REQLINE; | |
| return AP_FILTER_ERROR; | |
| } | |
| sslconn->non_ssl_request = NON_SSL_SEND_HDR_SEP; | |
| /* fake the request line */ | |
| bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc); | |
| send_eos = 0; | |
| break; | |
| case MODSSL_ERROR_BAD_GATEWAY: | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c, APLOGNO(01997) | |
| "SSL handshake failed: sending 502"); | |
| f->c->aborted = 1; | |
| return APR_EGENERAL; | |
| default: | |
| return status; | |
| } | |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); | |
| if (send_eos) { | |
| bucket = apr_bucket_eos_create(f->c->bucket_alloc); | |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); | |
| } | |
| return APR_SUCCESS; | |
| } | |
| static const char ssl_io_filter[] = "SSL/TLS Filter"; | |
| static const char ssl_io_buffer[] = "SSL/TLS Buffer"; | |
| static const char ssl_io_coalesce[] = "SSL/TLS Coalescing Filter"; | |
| /* | |
| * Close the SSL part of the socket connection | |
| * (called immediately _before_ the socket is closed) | |
| * or called with | |
| */ | |
| static void ssl_filter_io_shutdown(ssl_filter_ctx_t *filter_ctx, | |
| conn_rec *c, int abortive) | |
| { | |
| SSL *ssl = filter_ctx->pssl; | |
| const char *type = ""; | |
| SSLConnRec *sslconn = myConnConfig(c); | |
| int quiet_shutdown; | |
| int shutdown_type; | |
| int loglevel = APLOG_DEBUG; | |
| const char *logno; | |
| if (!ssl) { | |
| return; | |
| } | |
| /* | |
| * Now close the SSL layer of the connection. We've to take | |
| * the TLSv1 standard into account here: | |
| * | |
| * | 7.2.1. Closure alerts | |
| * | | |
| * | The client and the server must share knowledge that the connection is | |
| * | ending in order to avoid a truncation attack. Either party may | |
| * | initiate the exchange of closing messages. | |
| * | | |
| * | close_notify | |
| * | This message notifies the recipient that the sender will not send | |
| * | any more messages on this connection. The session becomes | |
| * | unresumable if any connection is terminated without proper | |
| * | close_notify messages with level equal to warning. | |
| * | | |
| * | Either party may initiate a close by sending a close_notify alert. | |
| * | Any data received after a closure alert is ignored. | |
| * | | |
| * | Each party is required to send a close_notify alert before closing | |
| * | the write side of the connection. It is required that the other party | |
| * | respond with a close_notify alert of its own and close down the | |
| * | connection immediately, discarding any pending writes. It is not | |
| * | required for the initiator of the close to wait for the responding | |
| * | close_notify alert before closing the read side of the connection. | |
| * | |
| * This means we've to send a close notify message, but haven't to wait | |
| * for the close notify of the client. Actually we cannot wait for the | |
| * close notify of the client because some clients (including Netscape | |
| * 4.x) don't send one, so we would hang. | |
| */ | |
| /* | |
| * exchange close notify messages, but allow the user | |
| * to force the type of handshake via SetEnvIf directive | |
| */ | |
| if (abortive) { | |
| quiet_shutdown = 1; | |
| shutdown_type = SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN; | |
| type = "abortive"; | |
| logno = APLOGNO(01998); | |
| loglevel = APLOG_INFO; | |
| } | |
| else switch (sslconn->shutdown_type) { | |
| case SSL_SHUTDOWN_TYPE_UNCLEAN: | |
| /* perform no close notify handshake at all | |
| (violates the SSL/TLS standard!) */ | |
| quiet_shutdown = 1; | |
| shutdown_type = SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN; | |
| type = "unclean"; | |
| logno = APLOGNO(01999); | |
| break; | |
| case SSL_SHUTDOWN_TYPE_ACCURATE: | |
| /* send close notify and wait for clients close notify | |
| (standard compliant, but usually causes connection hangs) */ | |
| quiet_shutdown = 0; | |
| shutdown_type = SSL_get_shutdown(ssl); | |
| type = "accurate"; | |
| logno = APLOGNO(02000); | |
| break; | |
| default: | |
| /* | |
| * case SSL_SHUTDOWN_TYPE_UNSET: | |
| * case SSL_SHUTDOWN_TYPE_STANDARD: | |
| */ | |
| /* send close notify, but don't wait for clients close notify | |
| (standard compliant and safe, so it's the DEFAULT!) */ | |
| quiet_shutdown = 0; | |
| shutdown_type = SSL_get_shutdown(ssl) | SSL_RECEIVED_SHUTDOWN; | |
| type = "standard"; | |
| logno = APLOGNO(02001); | |
| break; | |
| } | |
| if (quiet_shutdown) { | |
| SSL_set_quiet_shutdown(ssl, 1); | |
| } | |
| SSL_set_shutdown(ssl, shutdown_type); | |
| modssl_smart_shutdown(ssl); | |
| /* and finally log the fact that we've closed the connection */ | |
| if (APLOG_CS_IS_LEVEL(c, mySrvFromConn(c), loglevel)) { | |
| /* Intentional no APLOGNO */ | |
| /* logno provides APLOGNO */ | |
| ap_log_cserror(APLOG_MARK, loglevel, 0, c, mySrvFromConn(c), | |
| "%sConnection closed to child %ld with %s shutdown " | |
| "(server %s)", | |
| logno, c->id, type, | |
| ssl_util_vhostid(c->pool, mySrvFromConn(c))); | |
| } | |
| /* deallocate the SSL connection */ | |
| if (sslconn->client_cert) { | |
| X509_free(sslconn->client_cert); | |
| sslconn->client_cert = NULL; | |
| } | |
| SSL_free(ssl); | |
| sslconn->ssl = NULL; | |
| filter_ctx->pssl = NULL; /* so filters know we've been shutdown */ | |
| if (abortive) { | |
| /* prevent any further I/O */ | |
| c->aborted = 1; | |
| } | |
| } | |
| static apr_status_t ssl_io_filter_cleanup(void *data) | |
| { | |
| ssl_filter_ctx_t *filter_ctx = data; | |
| if (filter_ctx->pssl) { | |
| conn_rec *c = (conn_rec *)SSL_get_app_data(filter_ctx->pssl); | |
| SSLConnRec *sslconn = myConnConfig(c); | |
| SSL_free(filter_ctx->pssl); | |
| sslconn->ssl = filter_ctx->pssl = NULL; | |
| } | |
| return APR_SUCCESS; | |
| } | |
| /* Return non-zero if the certificate is not temporally valid at the | |
| * current time, i.e. it is expired or not yet valid. */ | |
| static int cert_time_invalid(const X509 *cert) | |
| { | |
| #if OPENSSL_VERSION_NUMBER >= 0x40000000L | |
| return X509_check_certificate_times(NULL, cert, NULL) != 1; | |
| #else | |
| return X509_cmp_current_time(X509_get_notBefore(cert)) >= 0 | |
| || X509_cmp_current_time(X509_get_notAfter(cert)) <= 0; | |
| #endif | |
| } | |
| /* | |
| * The hook is NOT registered with ap_hook_process_connection. Instead, it is | |
| * called manually from the churn () before it tries to read any data. | |
| * There is some problem if I accept conn_rec *. Still investigating.. | |
| * Adv. if conn_rec * can be accepted is we can hook this function using the | |
| * ap_hook_process_connection hook. | |
| */ | |
| /* Perform the SSL handshake (whether in client or server mode), if | |
| * necessary, for the given connection. */ | |
| static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx) | |
| { | |
| conn_rec *c = (conn_rec *)SSL_get_app_data(filter_ctx->pssl); | |
| SSLConnRec *sslconn = myConnConfig(c); | |
| SSLSrvConfigRec *sc; | |
| X509 *cert; | |
| int n; | |
| int ssl_err; | |
| long verify_result; | |
| server_rec *server; | |
| if (SSL_is_init_finished(filter_ctx->pssl)) { | |
| return APR_SUCCESS; | |
| } | |
| server = sslconn->server; | |
| if (c->outgoing) { | |
| #ifdef HAVE_TLSEXT | |
| apr_ipsubnet_t *ip; | |
| #endif | |
| const char *hostname_note = apr_table_get(c->notes, | |
| "proxy-request-hostname"); | |
| #ifdef HAVE_TLS_ALPN | |
| const char *alpn_note; | |
| apr_array_header_t *alpn_proposed = NULL; | |
| int alpn_empty_ok = 1; | |
| #endif | |
| BOOL proxy_ssl_check_peer_ok = TRUE; | |
| int post_handshake_rc = OK; | |
| SSLDirConfigRec *dc; | |
| dc = sslconn->dc; | |
| sc = mySrvConfig(server); | |
| #ifdef HAVE_TLSEXT | |
| #ifdef HAVE_TLS_ALPN | |
| alpn_note = apr_table_get(c->notes, "proxy-request-alpn-protos"); | |
| if (alpn_note) { | |
| char *protos, *s, *p, *last, *proto; | |
| apr_size_t len; | |
| /* Transform the note into a protocol formatted byte array: | |
| * (len-byte proto-char+)* | |
| * We need the remote server to agree on one of these, unless 'http/1.1' | |
| * is also among our proposals. Because pre-ALPN remotes will speak this. | |
| */ | |
| alpn_proposed = apr_array_make(c->pool, 3, sizeof(const char*)); | |
| alpn_empty_ok = 0; | |
| s = protos = apr_pcalloc(c->pool, strlen(alpn_note)+1); | |
| p = apr_pstrdup(c->pool, alpn_note); | |
| while ((p = apr_strtok(p, ", ", &last))) { | |
| len = last - p - (*last? 1 : 0); | |
| if (len > 255) { | |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03309) | |
| "ALPN proxy protocol identifier too long: %s", | |
| p); | |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, server); | |
| return APR_EGENERAL; | |
| } | |
| proto = apr_pstrndup(c->pool, p, len); | |
| APR_ARRAY_PUSH(alpn_proposed, const char*) = proto; | |
| if (!strcmp("http/1.1", proto)) { | |
| alpn_empty_ok = 1; | |
| } | |
| *s++ = (unsigned char)len; | |
| while (len--) { | |
| *s++ = *p++; | |
| } | |
| p = NULL; | |
| } | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, | |
| "setting alpn protos from '%s', protolen=%d", | |
| alpn_note, (int)(s - protos)); | |
| if (protos != s && SSL_set_alpn_protos(filter_ctx->pssl, | |
| (unsigned char *)protos, | |
| s - protos)) { | |
| ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(03310) | |
| "error setting alpn protos from '%s'", alpn_note); | |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_WARNING, server); | |
| /* If ALPN was requested and we cannot do it, we must fail */ | |
| return MODSSL_ERROR_BAD_GATEWAY; | |
| } | |
| } | |
| #endif /* defined HAVE_TLS_ALPN */ | |
| /* | |
| * Enable SNI for backend requests. Make sure we don't do it for | |
| * pure SSLv3 connections, and also prevent IP addresses | |
| * from being included in the SNI extension. (OpenSSL would simply | |
| * pass them on, but RFC 6066 is quite clear on this: "Literal | |
| * IPv4 and IPv6 addresses are not permitted".) | |
| */ | |
| if (hostname_note && | |
| #ifndef OPENSSL_NO_SSL3 | |
| dc->proxy->protocol != SSL_PROTOCOL_SSLV3 && | |
| #endif | |
| apr_ipsubnet_create(&ip, hostname_note, NULL, | |
| c->pool) != APR_SUCCESS) { | |
| if (SSL_set_tlsext_host_name(filter_ctx->pssl, hostname_note)) { | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c, | |
| "SNI extension for SSL Proxy request set to '%s'", | |
| hostname_note); | |
| } else { | |
| ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(02002) | |
| "Failed to set SNI extension for SSL Proxy " | |
| "request to '%s'", hostname_note); | |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_WARNING, server); | |
| } | |
| } | |
| #endif /* defined HAVE_TLSEXT */ | |
| if ((n = SSL_connect(filter_ctx->pssl)) <= 0) { | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02003) | |
| "SSL Proxy connect failed"); | |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server); | |
| /* ensure that the SSL structures etc are freed, etc: */ | |
| ssl_filter_io_shutdown(filter_ctx, c, 1); | |
| apr_table_setn(c->notes, "SSL_connect_rv", "err"); | |
| return MODSSL_ERROR_BAD_GATEWAY; | |
| } | |
| cert = SSL_get_peer_certificate(filter_ctx->pssl); | |
| if (dc->proxy->ssl_check_peer_expire != FALSE) { | |
| if (!cert || cert_time_invalid(cert)) { | |
| proxy_ssl_check_peer_ok = FALSE; | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02004) | |
| "SSL Proxy: Peer certificate is expired"); | |
| } | |
| } | |
| if ((dc->proxy->ssl_check_peer_name != FALSE) && | |
| ((dc->proxy->ssl_check_peer_cn != FALSE) || | |
| (dc->proxy->ssl_check_peer_name == TRUE)) && | |
| hostname_note) { | |
| if (!cert | |
| || modssl_X509_match_name(c->pool, cert, hostname_note, | |
| TRUE, server) == FALSE) { | |
| proxy_ssl_check_peer_ok = FALSE; | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02411) | |
| "SSL Proxy: Peer certificate does not match " | |
| "for hostname %s", hostname_note); | |
| } | |
| } | |
| else if ((dc->proxy->ssl_check_peer_cn == TRUE) && | |
| hostname_note) { | |
| const char *hostname; | |
| int match = 0; | |
| hostname = ssl_var_lookup(c->pool, server, c, NULL, | |
| "SSL_CLIENT_S_DN_CN"); | |
| /* Do string match or simplest wildcard match if that | |
| * fails. */ | |
| match = strcasecmp(hostname, hostname_note) == 0; | |
| if (!match && strncmp(hostname, "*.", 2) == 0) { | |
| const char *p = ap_strchr_c(hostname_note, '.'); | |
| match = p && strcasecmp(p, hostname + 1) == 0; | |
| } | |
| if (!match) { | |
| proxy_ssl_check_peer_ok = FALSE; | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02005) | |
| "SSL Proxy: Peer certificate CN mismatch:" | |
| " Certificate CN: %s Requested hostname: %s", | |
| hostname, hostname_note); | |
| } | |
| } | |
| #ifdef HAVE_TLS_ALPN | |
| /* If we proposed ALPN protocol(s), we need to check if the server | |
| * agreed to one of them. While <https://www.rfc-editor.org/rfc/rfc7301.txt> | |
| * chapter 3.2 says the server SHALL error the handshake in such a case, | |
| * the reality is that some servers fall back to their default, e.g. http/1.1. | |
| * (we also do this right now) | |
| * We need to treat this as an error for security reasons. | |
| */ | |
| if (alpn_proposed && alpn_proposed->nelts > 0) { | |
| const char *selected; | |
| unsigned int slen; | |
| SSL_get0_alpn_selected(filter_ctx->pssl, (const unsigned char**)&selected, &slen); | |
| if (!selected || !slen) { | |
| /* No ALPN selection reported by the remote server. This could mean | |
| * it does not support ALPN (old server) or that it does not support | |
| * any of our proposals (Apache itself up to 2.4.48 at least did that). */ | |
| if (!alpn_empty_ok) { | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10273) | |
| "SSL Proxy: Peer did not select any of our ALPN protocols [%s].", | |
| alpn_note); | |
| proxy_ssl_check_peer_ok = FALSE; | |
| } | |
| } | |
| else { | |
| const char *proto; | |
| int i, found = 0; | |
| for (i = 0; !found && i < alpn_proposed->nelts; ++i) { | |
| proto = APR_ARRAY_IDX(alpn_proposed, i, const char *); | |
| found = !strncmp(selected, proto, slen); | |
| } | |
| if (!found) { | |
| /* From a conforming peer, this should never happen, | |
| * but life always finds a way... */ | |
| proto = apr_pstrndup(c->pool, selected, slen); | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10274) | |
| "SSL Proxy: Peer proposed ALPN protocol %s which is none " | |
| "of our proposals [%s].", proto, alpn_note); | |
| proxy_ssl_check_peer_ok = FALSE; | |
| } | |
| } | |
| } | |
| #endif | |
| if (proxy_ssl_check_peer_ok == TRUE) { | |
| /* another chance to fail */ | |
| post_handshake_rc = ssl_run_proxy_post_handshake(c, filter_ctx->pssl); | |
| } | |
| if (cert) { | |
| X509_free(cert); | |
| } | |
| if (proxy_ssl_check_peer_ok != TRUE | |
| || (post_handshake_rc != OK && post_handshake_rc != DECLINED)) { | |
| /* ensure that the SSL structures etc are freed, etc: */ | |
| ssl_filter_io_shutdown(filter_ctx, c, 1); | |
| apr_table_setn(c->notes, "SSL_connect_rv", "err"); | |
| return MODSSL_ERROR_BAD_GATEWAY; | |
| } | |
| apr_table_setn(c->notes, "SSL_connect_rv", "ok"); | |
| return APR_SUCCESS; | |
| } | |
| /* We rely on SSL_get_error() after the accept, which requires an empty | |
| * error queue before the accept in order to work properly. | |
| */ | |
| ERR_clear_error(); | |
| if ((n = SSL_accept(filter_ctx->pssl)) <= 0) { | |
| bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *) | |
| BIO_get_data(filter_ctx->pbioRead); | |
| bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *) | |
| BIO_get_data(filter_ctx->pbioWrite); | |
| apr_status_t rc = inctx->rc ? inctx->rc : outctx->rc ; | |
| ssl_err = SSL_get_error(filter_ctx->pssl, n); | |
| if (ssl_err == SSL_ERROR_ZERO_RETURN) { | |
| /* | |
| * The case where the connection was closed before any data | |
| * was transferred. That's not a real error and can occur | |
| * sporadically with some clients. | |
| */ | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, rc, c, APLOGNO(02006) | |
| "SSL handshake stopped: connection was closed"); | |
| } | |
| else if (ssl_err == SSL_ERROR_WANT_READ) { | |
| /* | |
| * This is in addition to what was present earlier. It is | |
| * borrowed from openssl_state_machine.c [mod_tls]. | |
| * TBD. | |
| */ | |
| outctx->rc = APR_EAGAIN; | |
| return APR_EAGAIN; | |
| } | |
| else if (ERR_GET_LIB(ERR_peek_error()) == ERR_LIB_SSL && | |
| ERR_GET_REASON(ERR_peek_error()) == SSL_R_HTTP_REQUEST) { | |
| /* | |
| * The case where OpenSSL has recognized a HTTP request: | |
| * This means the client speaks plain HTTP on our HTTPS port. | |
| * ssl_io_filter_error will disable the ssl filters when it | |
| * sees this status code. | |
| */ | |
| return MODSSL_ERROR_HTTP_ON_HTTPS; | |
| } | |
| else if (ssl_err == SSL_ERROR_SYSCALL) { | |
| ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rc, c, APLOGNO(02007) | |
| "SSL handshake interrupted by system " | |
| "[Hint: Stop button pressed in browser?!]"); | |
| } | |
| else /* if (ssl_err == SSL_ERROR_SSL) */ { | |
| /* | |
| * Log SSL errors and any unexpected conditions. | |
| */ | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, rc, c, APLOGNO(02008) | |
| "SSL library error %d in handshake " | |
| "(server %s)", ssl_err, | |
| ssl_util_vhostid(c->pool, server)); | |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server); | |
| } | |
| if (inctx->rc == APR_SUCCESS) { | |
| inctx->rc = APR_EGENERAL; | |
| } | |
| ssl_filter_io_shutdown(filter_ctx, c, 1); | |
| return inctx->rc; | |
| } | |
| sc = mySrvConfig(sslconn->server); | |
| /* | |
| * Check for failed client authentication | |
| */ | |
| verify_result = SSL_get_verify_result(filter_ctx->pssl); | |
| if ((verify_result != X509_V_OK) || | |
| sslconn->verify_error) | |
| { | |
| if (ssl_verify_error_is_optional(verify_result) && | |
| (sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA)) | |
| { | |
| /* leaving this log message as an error for the moment, | |
| * according to the mod_ssl docs: | |
| * "level optional_no_ca is actually against the idea | |
| * of authentication (but can be used to establish | |
| * SSL test pages, etc.)" | |
| * optional_no_ca doesn't appear to work as advertised | |
| * in 1.x | |
| */ | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02009) | |
| "SSL client authentication failed, " | |
| "accepting certificate based on " | |
| "\"SSLVerifyClient optional_no_ca\" " | |
| "configuration"); | |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server); | |
| /* on session resumption ssl_callback_SSLVerify() | |
| * will not be called, therefore we have to set it here | |
| */ | |
| sslconn->verify_info = "GENEROUS"; | |
| } | |
| else { | |
| const char *error = sslconn->verify_error ? | |
| sslconn->verify_error : | |
| X509_verify_cert_error_string(verify_result); | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02010) | |
| "SSL client authentication failed: %s", | |
| error ? error : "unknown"); | |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server); | |
| ssl_filter_io_shutdown(filter_ctx, c, 1); | |
| return APR_ECONNABORTED; | |
| } | |
| } | |
| /* | |
| * Remember the peer certificate's DN | |
| */ | |
| if ((cert = SSL_get_peer_certificate(filter_ctx->pssl))) { | |
| if (sslconn->client_cert) { | |
| X509_free(sslconn->client_cert); | |
| } | |
| sslconn->client_cert = cert; | |
| sslconn->client_dn = NULL; | |
| } | |
| /* | |
| * Make really sure that when a peer certificate | |
| * is required we really got one... (be paranoid) | |
| */ | |
| if ((sc->server->auth.verify_mode == SSL_CVERIFY_REQUIRE) && | |
| !sslconn->client_cert) | |
| { | |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02011) | |
| "No acceptable peer certificate available"); | |
| ssl_filter_io_shutdown(filter_ctx, c, 1); | |
| return APR_ECONNABORTED; | |
| } | |
| return APR_SUCCESS; | |
| } | |
| static apr_status_t ssl_io_filter_input(ap_filter_t *f, | |
| apr_bucket_brigade *bb, | |
| ap_input_mode_t mode, | |
| apr_read_type_e block, | |
| apr_off_t readbytes) | |
| { | |
| apr_status_t status; | |
| bio_filter_in_ctx_t *inctx = f->ctx; | |
| const char *start = inctx->buffer; /* start of block to return */ | |
| apr_size_t len = sizeof(inctx->buffer); /* length of block to return */ | |
| int is_init = (mode == AP_MODE_INIT); | |
| apr_bucket *bucket; | |
| if (f->c->aborted) { | |
| /* XXX: Ok, if we aborted, we ARE at the EOS. We also have | |
| * aborted. This 'double protection' is probably redundant, | |
| * but also effective against just about anything. | |
| */ | |
| bucket = apr_bucket_eos_create(f->c->bucket_alloc); | |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); | |
| return APR_ECONNABORTED; | |
| } | |
| if (!inctx->ssl) { | |
| SSLConnRec *sslconn = myConnConfig(f->c); | |
| if (sslconn->non_ssl_request == NON_SSL_SEND_REQLINE) { | |
| bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc); | |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); | |
| if (mode != AP_MODE_SPECULATIVE) { | |
| sslconn->non_ssl_request = NON_SSL_SEND_HDR_SEP; | |
| } | |
| return APR_SUCCESS; | |
| } | |
| if (sslconn->non_ssl_request == NON_SSL_SEND_HDR_SEP) { | |
| bucket = apr_bucket_immortal_create(CRLF, 2, f->c->bucket_alloc); | |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); | |
| if (mode != AP_MODE_SPECULATIVE) { | |
| sslconn->non_ssl_request = NON_SSL_SET_ERROR_MSG; | |
| } | |
| return APR_SUCCESS; | |
| } | |
| return ap_get_brigade(f->next, bb, mode, block, readbytes); | |
| } | |
| /* XXX: we don't currently support anything other than these modes. */ | |
| if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE && | |
| mode != AP_MODE_SPECULATIVE && mode != AP_MODE_INIT) { | |
| return APR_ENOTIMPL; | |
| } | |
| inctx->mode = mode; | |
| inctx->block = block; | |
| /* XXX: we could actually move ssl_io_filter_handshake to an | |
| * ap_hook_process_connection but would still need to call it for | |
| * AP_MODE_INIT for protocols that may upgrade the connection | |
| * rather than have SSLEngine On configured. | |
| */ | |
| if ((status = ssl_io_filter_handshake(inctx->filter_ctx)) != APR_SUCCESS) { | |
| return ssl_io_filter_error(inctx, bb, status, is_init); | |
| } | |
| if (is_init) { | |
| /* protocol module needs to handshake before sending | |
| * data to client (e.g. NNTP or FTP) | |
| */ | |
| return APR_SUCCESS; | |
| } | |
| if (inctx->mode == AP_MODE_READBYTES || | |
| inctx->mode == AP_MODE_SPECULATIVE) { | |
| /* Protected from truncation, readbytes < MAX_SIZE_T | |
| * FIXME: No, it's *not* protected. -- jre */ | |
| if (readbytes < len) { | |
| len = (apr_size_t)readbytes; | |
| } | |
| status = ssl_io_input_read(inctx, inctx->buffer, &len); | |
| } | |
| else if (inctx->mode == AP_MODE_GETLINE) { | |
| const char *pos; | |
| bucket = inctx->cbuf.b; | |
| /* Satisfy the read directly out of the buffer if possible; | |
| * invoking ssl_io_input_getline will mean the entire buffer | |
| * is copied once (unnecessarily) for each GETLINE call. */ | |
| if (bucket && bucket->length | |
| && (pos = memchr(bucket->data, APR_ASCII_LF, | |
| bucket->length)) != NULL) { | |
| start = bucket->data; | |
| len = 1 + pos - start; /* +1 to include LF */ | |
| /* Buffer contents now consumed. */ | |
| char_buffer_consume(inctx, len); | |
| status = APR_SUCCESS; | |
| } | |
| else { | |
| /* Otherwise fall back to the hard way. */ | |
| status = ssl_io_input_getline(inctx, inctx->buffer, &len); | |
| } | |
| } | |
| else { | |
| /* We have no idea what you are talking about, so return an error. */ | |
| status = APR_ENOTIMPL; | |
| } | |
| /* It is possible for mod_ssl's BIO to be used outside of the | |
| * direct control of mod_ssl's input or output filter -- notably, | |
| * when mod_ssl initiates a renegotiation. Switching the BIO mode | |
| * back to "blocking" here ensures such operations don't fail with | |
| * SSL_ERROR_WANT_READ. */ | |
| inctx->block = APR_BLOCK_READ; | |
| /* Handle custom errors. */ | |
| if (status != APR_SUCCESS) { | |
| return ssl_io_filter_error(inctx, bb, status, 0); | |
| } | |
| /* Create a transient bucket out of the decrypted data. */ | |
| if (len > 0) { | |
| bucket = | |
| apr_bucket_transient_create(start, len, f->c->bucket_alloc); | |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); | |
| } | |
| return APR_SUCCESS; | |
| } | |
| /* ssl_io_filter_output() produces one SSL/TLS record per bucket | |
| * passed down the output filter stack. This results in a high | |
| * overhead (more network packets & TLS processing) for any output | |
| * comprising many small buckets. SSI output passed through the HTTP | |
| * chunk filter, for example, may produce many brigades containing | |
| * small buckets - [chunk-size CRLF] [chunk-data] [CRLF]. | |
| * | |
| * Sending HTTP response headers as a separate TLS record to the | |
| * response body also reveals information to a network observer (the | |
| * size of headers) which can be significant. | |
| * | |
| * The coalescing filter merges data buckets with the aim of producing | |
| * fewer, larger TLS records - without copying/buffering all content | |
| * and introducing unnecessary overhead. | |
| * | |
| * ### This buffering could be probably be done more comprehensively | |
| * ### in ssl_io_filter_output itself. | |
| */ | |
| /* apr_brigade_write() allocates buckets of APR_BUCKET_BUFF_SIZE bytes, | |
| * capping our buffer to that avoids creating a second bucket partially | |
| * filled (which would defeat the purpose of coalescing). | |
| */ | |
| #define COALESCE_BYTES ((apr_size_t)APR_BUCKET_BUFF_SIZE) | |
| struct coalesce_ctx { | |
| apr_bucket_brigade *buffer; | |
| apr_size_t bytes; /* number of bytes in buffer. */ | |
| }; | |
| static apr_status_t ssl_io_filter_coalesce(ap_filter_t *f, | |
| apr_bucket_brigade *bb) | |
| { | |
| apr_bucket *e, *upto; | |
| apr_size_t bytes = 0; | |
| struct coalesce_ctx *ctx = f->ctx; | |
| unsigned count = 0; | |
| if (ctx) { | |
| ap_filter_reinstate_brigade(f, ctx->buffer, NULL); | |
| } | |
| else { | |
| ctx = f->ctx = apr_pcalloc(f->c->pool, sizeof(*ctx)); | |
| ctx->buffer = apr_brigade_create(f->c->pool, f->c->bucket_alloc); | |
| } | |
| /* The brigade consists of zero-or-more small data buckets which | |
| * can be coalesced (referred to as the "prefix"), followed by the | |
| * remainder of the brigade. | |
| * | |
| * Find the last bucket - if any - of that prefix. count gives | |
| * the number of buckets in the prefix. The "prefix" must contain | |
| * only data buckets with known length, and must be of a total | |
| * size which fits into the buffer. | |
| * | |
| * N.B.: The process here could be repeated throughout the brigade | |
| * (coalesce any run of consecutive data buckets) but this would | |
| * add significant complexity, particularly to memory | |
| * management. */ | |
| for (e = APR_BRIGADE_FIRST(bb); | |
| e != APR_BRIGADE_SENTINEL(bb) | |
| && !APR_BUCKET_IS_METADATA(e) | |
| && e->length != (apr_size_t)-1 | |
| && e->length <= COALESCE_BYTES | |
| && (ctx->bytes + bytes + e->length) <= COALESCE_BYTES; | |
| e = APR_BUCKET_NEXT(e)) { | |
| /* don't count zero-length buckets */ | |
| if (e->length) { | |
| bytes += e->length; | |
| count++; | |
| } | |
| } | |
| /* If there is room remaining and the next bucket is a data | |
| * bucket, try to include it in the prefix to coalesce. For a | |
| * typical [HEAP] [FILE] HTTP response brigade, this handles | |
| * merging the headers and the start of the body into a single TLS | |
| * record. */ | |
| if (bytes + ctx->bytes > 0 | |
| && bytes + ctx->bytes < COALESCE_BYTES | |
| && e != APR_BRIGADE_SENTINEL(bb) | |
| && !APR_BUCKET_IS_METADATA(e)) { | |
| apr_status_t rv = APR_SUCCESS; | |
| /* For an indeterminate length bucket (PIPE/CGI/...), try a | |
| * non-blocking read to have it morph into a HEAP. If the | |
| * read fails with EAGAIN, it is harmless to try a split | |
| * anyway, split is ENOTIMPL for most PIPE-like buckets. */ | |
| if (e->length == (apr_size_t)-1) { | |
| const char *discard; | |
| apr_size_t ignore; | |
| rv = apr_bucket_read(e, &discard, &ignore, APR_NONBLOCK_READ); | |
| if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) { | |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(10232) | |
| "coalesce failed to read from %s bucket", | |
| e->type->name); | |
| return AP_FILTER_ERROR; | |
| } | |
| } | |
| if (rv == APR_SUCCESS) { | |
| /* If the read above made the bucket morph, it may now fit | |
| * entirely within the buffer. Otherwise, split it so it does | |
| * fit. */ | |
| if (e->length > COALESCE_BYTES | |
| || e->length + ctx->bytes + bytes > COALESCE_BYTES) { | |
| rv = apr_bucket_split(e, COALESCE_BYTES - ctx->bytes - bytes); | |
| } | |
| if (rv == APR_SUCCESS && e->length == 0) { | |
| /* As above, don't count in the prefix if the bucket is | |
| * now zero-length. */ | |
| } | |
| else if (rv == APR_SUCCESS) { | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, | |
| "coalesce: adding %" APR_SIZE_T_FMT " bytes " | |
| "from split %s bucket, total %" APR_SIZE_T_FMT, | |
| e->length, e->type->name, bytes + ctx->bytes); | |
| count++; | |
| bytes += e->length; | |
| e = APR_BUCKET_NEXT(e); | |
| } | |
| else if (rv != APR_ENOTIMPL) { | |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(10233) | |
| "coalesce: failed to split data bucket"); | |
| return AP_FILTER_ERROR; | |
| } | |
| } | |
| } | |
| /* The prefix is zero or more buckets. upto now points to the | |
| * bucket AFTER the end of the prefix, which may be the brigade | |
| * sentinel. */ | |
| upto = e; | |
| /* Coalesce the prefix, if any of the following are true: | |
| * | |
| * a) the prefix is more than one bucket | |
| * OR | |
| * b) the prefix is the entire brigade, which is a single bucket | |
| * AND the prefix length is smaller than the buffer size, | |
| * OR | |
| * c) the prefix is a single bucket | |
| * AND there is buffered data from a previous pass. | |
| * | |
| * The aim with (b) is to buffer a small bucket so it can be | |
| * coalesced with future invocations of this filter. e.g. three | |
| * calls each with a single 100 byte HEAP bucket should get | |
| * coalesced together. But an invocation with a 8192 byte HEAP | |
| * should pass through untouched. | |
| */ | |
| if (bytes > 0 | |
| && (count > 1 | |
| || (upto == APR_BRIGADE_SENTINEL(bb) && bytes < COALESCE_BYTES) | |
| || (ctx->bytes > 0))) { | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, | |
| "coalesce: have %" APR_SIZE_T_FMT " bytes, " | |
| "adding %" APR_SIZE_T_FMT " more (buckets=%u)", | |
| ctx->bytes, bytes, count); | |
| /* Iterate through the prefix segment. For non-fatal errors | |
| * in this loop it is safe to break out and fall back to the | |
| * normal path of sending the buffer + remaining buckets in | |
| * brigade. */ | |
| e = APR_BRIGADE_FIRST(bb); | |
| while (e != upto) { | |
| apr_size_t len; | |
| const char *data; | |
| apr_bucket *next; | |
| if (APR_BUCKET_IS_METADATA(e) | |
| || e->length == (apr_size_t)-1) { | |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(02012) | |
| "unexpected %s bucket during coalesce", | |
| e->type->name); | |
| break; /* non-fatal error; break out */ | |
| } | |
| if (e->length) { | |
| apr_status_t rv; | |
| /* A blocking read should be fine here for a | |
| * known-length data bucket, rather than the usual | |
| * non-block/flush/block. */ | |
| rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ); | |
| if (rv) { | |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(02013) | |
| "coalesce failed to read from data bucket"); | |
| return AP_FILTER_ERROR; | |
| } | |
| /* Be paranoid. */ | |
| if (len > COALESCE_BYTES | |
| || (len + ctx->bytes > COALESCE_BYTES)) { | |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(02014) | |
| "unexpected coalesced bucket data length"); | |
| break; /* non-fatal error; break out */ | |
| } | |
| rv = apr_brigade_write(ctx->buffer, NULL, NULL, data, len); | |
| if (rv) { | |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(10270) | |
| "coalesce failed to write buffered data"); | |
| return AP_FILTER_ERROR; | |
| } | |
| ctx->bytes += len; | |
| } | |
| next = APR_BUCKET_NEXT(e); | |
| apr_bucket_delete(e); | |
| e = next; | |
| } | |
| } | |
| if (APR_BRIGADE_EMPTY(bb)) { | |
| /* If the brigade is now empty, setaside the buffer for the next | |
| * call or write completion */ | |
| return ap_filter_setaside_brigade(f, ctx->buffer); | |
| } | |
| /* If anything remains in the brigade, it must now be passed down | |
| * the filter stack, first prepending anything that has been | |
| * coalesced. */ | |
| if (!APR_BRIGADE_EMPTY(ctx->buffer)) { | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, | |
| "coalesce: passing on %" APR_SIZE_T_FMT " bytes", | |
| ctx->bytes); | |
| APR_BRIGADE_PREPEND(bb, ctx->buffer); | |
| ctx->bytes = 0; /* buffer now emptied. */ | |
| } | |
| return ap_pass_brigade(f->next, bb); | |
| } | |
| static apr_status_t ssl_io_filter_output(ap_filter_t *f, | |
| apr_bucket_brigade *bb) | |
| { | |
| apr_status_t status = APR_SUCCESS; | |
| ssl_filter_ctx_t *filter_ctx = f->ctx; | |
| bio_filter_in_ctx_t *inctx; | |
| bio_filter_out_ctx_t *outctx; | |
| apr_bucket *flush_upto = NULL; | |
| apr_read_type_e rblock = APR_NONBLOCK_READ; | |
| if (f->c->aborted) { | |
| apr_brigade_cleanup(bb); | |
| return APR_ECONNABORTED; | |
| } | |
| /* Reinstate any buffered content */ | |
| ap_filter_reinstate_brigade(f, bb, &flush_upto); | |
| if (!filter_ctx->pssl) { | |
| /* ssl_filter_io_shutdown was called */ | |
| return ap_pass_brigade(f->next, bb); | |
| } | |
| inctx = (bio_filter_in_ctx_t *)BIO_get_data(filter_ctx->pbioRead); | |
| outctx = (bio_filter_out_ctx_t *)BIO_get_data(filter_ctx->pbioWrite); | |
| /* When we are the writer, we must initialize the inctx | |
| * mode so that we block for any required ssl input, because | |
| * output filtering is always nonblocking. | |
| */ | |
| inctx->mode = AP_MODE_READBYTES; | |
| inctx->block = APR_BLOCK_READ; | |
| if ((status = ssl_io_filter_handshake(filter_ctx)) != APR_SUCCESS) { | |
| return ssl_io_filter_error(inctx, bb, status, 0); | |
| } | |
| while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) { | |
| apr_bucket *bucket = APR_BRIGADE_FIRST(bb); | |
| /* if the core has set aside data, back off and try later */ | |
| if (!flush_upto) { | |
| if (ap_filter_should_yield(f->next)) { | |
| break; | |
| } | |
| } | |
| else if (flush_upto == bucket) { | |
| flush_upto = NULL; | |
| } | |
| if (APR_BUCKET_IS_METADATA(bucket)) { | |
| /* Pass through metadata buckets untouched. EOC is | |
| * special; terminate the SSL layer first. */ | |
| if (AP_BUCKET_IS_EOC(bucket)) { | |
| ssl_filter_io_shutdown(filter_ctx, f->c, 0); | |
| } | |
| /* Metadata buckets are passed one per brigade; it might | |
| * be more efficient (but also more complex) to use | |
| * outctx->bb as a true buffer and interleave these with | |
| * data buckets. */ | |
| APR_BUCKET_REMOVE(bucket); | |
| APR_BRIGADE_INSERT_TAIL(outctx->bb, bucket); | |
| if (bio_filter_out_pass(outctx) < 0) { | |
| status = outctx->rc; | |
| } | |
| } | |
| else { | |
| /* Filter a data bucket. */ | |
| const char *data; | |
| apr_size_t len; | |
| status = apr_bucket_read(bucket, &data, &len, rblock); | |
| if (APR_STATUS_IS_EAGAIN(status)) { | |
| /* No data available: flush... */ | |
| if (bio_filter_out_flush(filter_ctx->pbioWrite) < 0) { | |
| status = outctx->rc; | |
| break; | |
| } | |
| rblock = APR_BLOCK_READ; | |
| /* and try again with a blocking read. */ | |
| status = APR_SUCCESS; | |
| continue; | |
| } | |
| rblock = APR_NONBLOCK_READ; | |
| if (!APR_STATUS_IS_EOF(status) && (status != APR_SUCCESS)) { | |
| break; | |
| } | |
| status = ssl_filter_write(f, data, len); | |
| apr_bucket_delete(bucket); | |
| } | |
| } | |
| if (status == APR_SUCCESS) { | |
| status = ap_filter_setaside_brigade(f, bb); | |
| } | |
| return status; | |
| } | |
| struct modssl_buffer_ctx { | |
| apr_bucket_brigade *bb; | |
| }; | |
| int ssl_io_buffer_fill(request_rec *r, apr_size_t maxlen) | |
| { | |
| conn_rec *c = r->connection; | |
| struct modssl_buffer_ctx *ctx; | |
| apr_bucket_brigade *tempb; | |
| apr_off_t total = 0; /* total length buffered */ | |
| int eos = 0; /* non-zero once EOS is seen */ | |
| /* Create the context which will be passed to the input filter; | |
| * containing a setaside pool and a brigade which constrain the | |
| * lifetime of the buffered data. */ | |
| ctx = apr_palloc(r->pool, sizeof *ctx); | |
| ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc); | |
| /* ... and a temporary brigade. */ | |
| tempb = apr_brigade_create(r->pool, c->bucket_alloc); | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c, "filling buffer, max size " | |
| "%" APR_SIZE_T_FMT " bytes", maxlen); | |
| do { | |
| apr_status_t rv; | |
| apr_bucket *e, *next; | |
| /* The request body is read from the protocol-level input | |
| * filters; the buffering filter will reinject it from that | |
| * level, allowing content/resource filters to run later, if | |
| * necessary. */ | |
| rv = ap_get_brigade(r->proto_input_filters, tempb, AP_MODE_READBYTES, | |
| APR_BLOCK_READ, 8192); | |
| if (rv) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02015) | |
| "could not read request body for SSL buffer"); | |
| return ap_map_http_request_error(rv, HTTP_INTERNAL_SERVER_ERROR); | |
| } | |
| /* Iterate through the returned brigade: setaside each bucket | |
| * into the context's pool and move it into the brigade. */ | |
| for (e = APR_BRIGADE_FIRST(tempb); | |
| e != APR_BRIGADE_SENTINEL(tempb) && !eos; e = next) { | |
| const char *data; | |
| apr_size_t len; | |
| next = APR_BUCKET_NEXT(e); | |
| if (APR_BUCKET_IS_EOS(e)) { | |
| eos = 1; | |
| } else if (!APR_BUCKET_IS_METADATA(e)) { | |
| rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ); | |
| if (rv != APR_SUCCESS) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02016) | |
| "could not read bucket for SSL buffer"); | |
| return HTTP_INTERNAL_SERVER_ERROR; | |
| } | |
| total += len; | |
| } | |
| rv = apr_bucket_setaside(e, r->pool); | |
| if (rv != APR_SUCCESS) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02017) | |
| "could not setaside bucket for SSL buffer"); | |
| return HTTP_INTERNAL_SERVER_ERROR; | |
| } | |
| APR_BUCKET_REMOVE(e); | |
| APR_BRIGADE_INSERT_TAIL(ctx->bb, e); | |
| } | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c, | |
| "total of %" APR_OFF_T_FMT " bytes in buffer, eos=%d", | |
| total, eos); | |
| /* Fail if this exceeds the maximum buffer size. */ | |
| if (total > maxlen) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02018) | |
| "request body exceeds maximum size (%" APR_SIZE_T_FMT | |
| ") for SSL buffer", maxlen); | |
| return HTTP_REQUEST_ENTITY_TOO_LARGE; | |
| } | |
| } while (!eos); | |
| apr_brigade_destroy(tempb); | |
| /* After consuming all protocol-level input, remove all protocol-level | |
| * filters. It should strictly only be necessary to remove filters | |
| * at exactly ftype == AP_FTYPE_PROTOCOL, since this filter will | |
| * precede all > AP_FTYPE_PROTOCOL anyway. */ | |
| while (r->proto_input_filters->frec->ftype < AP_FTYPE_CONNECTION) { | |
| ap_remove_input_filter(r->proto_input_filters); | |
| } | |
| /* Insert the filter which will supply the buffered content. */ | |
| ap_add_input_filter(ssl_io_buffer, ctx, r, c); | |
| return 0; | |
| } | |
| /* This input filter supplies the buffered request body to the caller | |
| * from the brigade stored in f->ctx. Note that the placement of this | |
| * filter in the filter stack is important; it must be the first | |
| * r->proto_input_filter; lower-typed filters will not be preserved | |
| * across internal redirects (see PR 43738). */ | |
| static apr_status_t ssl_io_filter_buffer(ap_filter_t *f, | |
| apr_bucket_brigade *bb, | |
| ap_input_mode_t mode, | |
| apr_read_type_e block, | |
| apr_off_t bytes) | |
| { | |
| struct modssl_buffer_ctx *ctx = f->ctx; | |
| apr_status_t rv; | |
| apr_bucket *e, *d; | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, | |
| "read from buffered SSL brigade, mode %d, " | |
| "%" APR_OFF_T_FMT " bytes", | |
| mode, bytes); | |
| if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) { | |
| return APR_ENOTIMPL; | |
| } | |
| if (APR_BRIGADE_EMPTY(ctx->bb)) { | |
| /* Surprisingly (and perhaps, wrongly), the request body can be | |
| * pulled from the input filter stack more than once; a | |
| * handler may read it, and ap_discard_request_body() will | |
| * attempt to do so again after *every* request. So input | |
| * filters must be prepared to give up an EOS if invoked after | |
| * initially reading the request. The HTTP_IN filter does this | |
| * with its ->eos_sent flag. */ | |
| APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(f->c->bucket_alloc)); | |
| return APR_SUCCESS; | |
| } | |
| if (mode == AP_MODE_READBYTES) { | |
| /* Partition the buffered brigade. */ | |
| rv = apr_brigade_partition(ctx->bb, bytes, &e); | |
| if (rv && rv != APR_INCOMPLETE) { | |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(02019) | |
| "could not partition buffered SSL brigade"); | |
| ap_remove_input_filter(f); | |
| return rv; | |
| } | |
| /* If the buffered brigade contains less then the requested | |
| * length, just pass it all back. */ | |
| if (rv == APR_INCOMPLETE) { | |
| APR_BRIGADE_CONCAT(bb, ctx->bb); | |
| } else { | |
| d = APR_BRIGADE_FIRST(ctx->bb); | |
| e = APR_BUCKET_PREV(e); | |
| /* Unsplice the partitioned segment and move it into the | |
| * passed-in brigade; no convenient way to do this with | |
| * the APR_BRIGADE_* macros. */ | |
| APR_RING_UNSPLICE(d, e, link); | |
| APR_RING_SPLICE_HEAD(&bb->list, d, e, apr_bucket, link); | |
| APR_BRIGADE_CHECK_CONSISTENCY(bb); | |
| APR_BRIGADE_CHECK_CONSISTENCY(ctx->bb); | |
| } | |
| } | |
| else { | |
| /* Split a line into the passed-in brigade. */ | |
| rv = apr_brigade_split_line(bb, ctx->bb, block, bytes); | |
| if (rv) { | |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(02020) | |
| "could not split line from buffered SSL brigade"); | |
| ap_remove_input_filter(f); | |
| return rv; | |
| } | |
| } | |
| if (APR_BRIGADE_EMPTY(ctx->bb)) { | |
| e = APR_BRIGADE_LAST(bb); | |
| /* Ensure that the brigade is terminated by an EOS if the | |
| * buffered request body has been entirely consumed. */ | |
| if (e == APR_BRIGADE_SENTINEL(bb) || !APR_BUCKET_IS_EOS(e)) { | |
| e = apr_bucket_eos_create(f->c->bucket_alloc); | |
| APR_BRIGADE_INSERT_TAIL(bb, e); | |
| } | |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, | |
| "buffered SSL brigade exhausted"); | |
| /* Note that the filter must *not* be removed here; it may be | |
| * invoked again, see comment above. */ | |
| } | |
| return APR_SUCCESS; | |
| } | |
| /* The request_rec pointer is passed in here only to ensure that the | |
| * filter chain is modified correctly when doing a TLS upgrade. It | |
| * must *not* be used otherwise. */ | |
| static apr_status_t ssl_io_input_add_filter(ssl_filter_ctx_t *filter_ctx, conn_rec *c, | |
| request_rec *r, SSL *ssl) | |
| { | |
| bio_filter_in_ctx_t *inctx; | |
| inctx = apr_palloc(c->pool, sizeof(*inctx)); | |
| filter_ctx->pInputFilter = ap_add_input_filter(ssl_io_filter, inctx, r, c); | |
| #if MODSSL_USE_OPENSSL_PRE_1_1_API | |
| filter_ctx->pbioRead = BIO_new(&bio_filter_in_method); | |
| #else | |
| filter_ctx->pbioRead = BIO_new(bio_filter_in_method); | |
| #endif | |
| if(filter_ctx->pbioRead == NULL) { | |
| return APR_ENOMEM; | |
| } | |
| BIO_set_data(filter_ctx->pbioRead, (void *)inctx); | |
| inctx->ssl = ssl; | |
| inctx->bio_out = filter_ctx->pbioWrite; | |
| inctx->f = filter_ctx->pInputFilter; | |
| inctx->rc = APR_SUCCESS; | |
| inctx->mode = AP_MODE_READBYTES; | |
| memset(&inctx->cbuf, 0, sizeof(inctx->cbuf)); | |
| inctx->bb = apr_brigade_create(c->pool, c->bucket_alloc); | |
| inctx->block = APR_BLOCK_READ; | |
| inctx->pool = c->pool; | |
| inctx->filter_ctx = filter_ctx; | |
| return APR_SUCCESS; | |
| } | |
| /* The request_rec pointer is passed in here only to ensure that the | |
| * filter chain is modified correctly when doing a TLS upgrade. It | |
| * must *not* be used otherwise. */ | |
| apr_status_t ssl_io_filter_init(conn_rec *c, request_rec *r, SSL *ssl) | |
| { | |
| ssl_filter_ctx_t *filter_ctx; | |
| apr_status_t rv = APR_SUCCESS; | |
| filter_ctx = apr_palloc(c->pool, sizeof(ssl_filter_ctx_t)); | |
| filter_ctx->config = myConnConfig(c); | |
| ap_add_output_filter(ssl_io_coalesce, NULL, r, c); | |
| filter_ctx->pOutputFilter = ap_add_output_filter(ssl_io_filter, | |
| filter_ctx, r, c); | |
| #if MODSSL_USE_OPENSSL_PRE_1_1_API | |
| filter_ctx->pbioWrite = BIO_new(&bio_filter_out_method); | |
| #else | |
| filter_ctx->pbioWrite = BIO_new(bio_filter_out_method); | |
| #endif | |
| if(filter_ctx->pbioWrite == NULL) { | |
| return APR_ENOMEM; | |
| } | |
| BIO_set_data(filter_ctx->pbioWrite, (void *)bio_filter_out_ctx_new(filter_ctx, c)); | |
| rv = ssl_io_input_add_filter(filter_ctx, c, r, ssl); | |
| if(rv != APR_SUCCESS) { | |
| return rv; | |
| } | |
| SSL_set_bio(ssl, filter_ctx->pbioRead, filter_ctx->pbioWrite); | |
| filter_ctx->pssl = ssl; | |
| apr_pool_cleanup_register(c->pool, (void*)filter_ctx, | |
| ssl_io_filter_cleanup, apr_pool_cleanup_null); | |
| modssl_set_io_callbacks(ssl, c, mySrvFromConn(c)); | |
| return APR_SUCCESS; | |
| } | |
| void ssl_io_filter_register(apr_pool_t *p) | |
| { | |
| ap_register_input_filter (ssl_io_filter, ssl_io_filter_input, NULL, AP_FTYPE_CONNECTION + 5); | |
| ap_register_output_filter (ssl_io_coalesce, ssl_io_filter_coalesce, NULL, AP_FTYPE_CONNECTION + 4); | |
| ap_register_output_filter (ssl_io_filter, ssl_io_filter_output, NULL, AP_FTYPE_CONNECTION + 5); | |
| ap_register_input_filter (ssl_io_buffer, ssl_io_filter_buffer, NULL, AP_FTYPE_PROTOCOL); | |
| return; | |
| } | |
| /* _________________________________________________________________ | |
| ** | |
| ** I/O Data Debugging | |
| ** _________________________________________________________________ | |
| */ | |
| #define DUMP_WIDTH 16 | |
| static void ssl_io_data_dump(conn_rec *c, server_rec *s, | |
| const char *b, int len) | |
| { | |
| char buf[256]; | |
| int i, j, rows, trunc, pos; | |
| unsigned char ch; | |
| trunc = 0; | |
| for (; (len > 0) && ((b[len-1] == ' ') || (b[len-1] == '\0')); len--) | |
| trunc++; | |
| rows = (len / DUMP_WIDTH); | |
| if ((rows * DUMP_WIDTH) < len) | |
| rows++; | |
| ap_log_cserror(APLOG_MARK, APLOG_TRACE7, 0, c, s, | |
| "+-------------------------------------------------------------------------+"); | |
| for (i = 0 ; i < rows; i++) { | |
| #if APR_CHARSET_EBCDIC | |
| char ebcdic_text[DUMP_WIDTH]; | |
| j = DUMP_WIDTH; | |
| if ((i * DUMP_WIDTH + j) > len) | |
| j = len % DUMP_WIDTH; | |
| if (j == 0) | |
| j = DUMP_WIDTH; | |
| memcpy(ebcdic_text,(char *)(b) + i * DUMP_WIDTH, j); | |
| ap_xlate_proto_from_ascii(ebcdic_text, j); | |
| #endif /* APR_CHARSET_EBCDIC */ | |
| pos = 0; | |
| pos += apr_snprintf(buf, sizeof(buf)-pos, "| %04x: ", i * DUMP_WIDTH); | |
| for (j = 0; j < DUMP_WIDTH; j++) { | |
| if (((i * DUMP_WIDTH) + j) >= len) | |
| pos += apr_snprintf(buf+pos, sizeof(buf)-pos, " "); | |
| else { | |
| ch = ((unsigned char)*((char *)(b) + i * DUMP_WIDTH + j)) & 0xff; | |
| pos += apr_snprintf(buf+pos, sizeof(buf)-pos, "%02x%c", ch , j==7 ? '-' : ' '); | |
| } | |
| } | |
| pos += apr_snprintf(buf+pos, sizeof(buf)-pos, " "); | |
| for (j = 0; j < DUMP_WIDTH; j++) { | |
| if (((i * DUMP_WIDTH) + j) >= len) | |
| pos += apr_snprintf(buf+pos, sizeof(buf)-pos, " "); | |
| else { | |
| ch = ((unsigned char)*((char *)(b) + i * DUMP_WIDTH + j)) & 0xff; | |
| #if APR_CHARSET_EBCDIC | |
| pos += apr_snprintf(buf+pos, sizeof(buf)-pos, "%c", (ch >= 0x20 && ch <= 0x7F) ? ebcdic_text[j] : '.'); | |
| #else /* APR_CHARSET_EBCDIC */ | |
| pos += apr_snprintf(buf+pos, sizeof(buf)-pos, "%c", ((ch >= ' ') && (ch <= '~')) ? ch : '.'); | |
| #endif /* APR_CHARSET_EBCDIC */ | |
| } | |
| } | |
| pos += apr_snprintf(buf+pos, sizeof(buf)-pos, " |"); | |
| ap_log_cserror(APLOG_MARK, APLOG_TRACE7, 0, c, s, "%s", buf); | |
| } | |
| if (trunc > 0) | |
| ap_log_cserror(APLOG_MARK, APLOG_TRACE7, 0, c, s, | |
| "| %04d - <SPACES/NULS>", len + trunc); | |
| ap_log_cserror(APLOG_MARK, APLOG_TRACE7, 0, c, s, | |
| "+-------------------------------------------------------------------------+"); | |
| } | |
| #define MODSSL_IO_DUMP_MAX APR_UINT16_MAX | |
| #if OPENSSL_VERSION_NUMBER >= 0x30000000L | |
| static long modssl_io_cb(BIO *bio, int cmd, const char *argp, | |
| size_t len, int argi, long argl, int rc, | |
| size_t *processed) | |
| #else | |
| static long modssl_io_cb(BIO *bio, int cmd, const char *argp, | |
| int argi, long argl, long rc) | |
| #endif | |
| { | |
| SSL *ssl; | |
| conn_rec *c; | |
| server_rec *s; | |
| /* unused */ | |
| #if OPENSSL_VERSION_NUMBER >= 0x30000000L | |
| (void)argi; | |
| #endif | |
| (void)argl; | |
| if ((ssl = (SSL *)BIO_get_callback_arg(bio)) == NULL) | |
| return rc; | |
| if ((c = (conn_rec *)SSL_get_app_data(ssl)) == NULL) | |
| return rc; | |
| s = mySrvFromConn(c); | |
| if ( cmd == (BIO_CB_WRITE|BIO_CB_RETURN) | |
| || cmd == (BIO_CB_READ |BIO_CB_RETURN) ) { | |
| #if OPENSSL_VERSION_NUMBER >= 0x30000000L | |
| apr_size_t requested_len = len; | |
| /* | |
| * On OpenSSL >= 3 rc uses the meaning of the BIO_read_ex and | |
| * BIO_write_ex functions return value and not the one of | |
| * BIO_read and BIO_write. Hence 0 indicates an error. | |
| */ | |
| int ok = (rc > 0); | |
| #else | |
| apr_size_t requested_len = (apr_size_t)argi; | |
| int ok = (rc >= 0); | |
| #endif | |
| if (ok) { | |
| #if OPENSSL_VERSION_NUMBER >= 0x30000000L | |
| apr_size_t actual_len = *processed; | |
| #else | |
| apr_size_t actual_len = (apr_size_t)rc; | |
| #endif | |
| const char *dump = ""; | |
| if (APLOG_CS_IS_LEVEL(c, s, APLOG_TRACE7)) { | |
| if (argp == NULL) | |
| dump = "(Oops, no memory buffer?)"; | |
| else if (actual_len > MODSSL_IO_DUMP_MAX) | |
| dump = "(BIO dump follows, truncated to " | |
| APR_STRINGIFY(MODSSL_IO_DUMP_MAX) ")"; | |
| else | |
| dump = "(BIO dump follows)"; | |
| } | |
| ap_log_cserror(APLOG_MARK, APLOG_TRACE4, 0, c, s, | |
| "%s: %s %" APR_SIZE_T_FMT "/%" APR_SIZE_T_FMT | |
| " bytes %s BIO#%pp [mem: %pp] %s", | |
| MODSSL_LIBRARY_NAME, | |
| (cmd & BIO_CB_WRITE) ? "write" : "read", | |
| actual_len, requested_len, | |
| (cmd & BIO_CB_WRITE) ? "to" : "from", | |
| bio, argp, dump); | |
| /* | |
| * *dump will only be != '\0' if | |
| * APLOG_CS_IS_LEVEL(c, s, APLOG_TRACE7) | |
| */ | |
| if (*dump != '\0' && argp != NULL) { | |
| int dump_len = (actual_len >= MODSSL_IO_DUMP_MAX | |
| ? MODSSL_IO_DUMP_MAX | |
| : actual_len); | |
| ssl_io_data_dump(c, s, argp, dump_len); | |
| } | |
| } | |
| else { | |
| ap_log_cserror(APLOG_MARK, APLOG_TRACE4, 0, c, s, | |
| "%s: I/O error, %" APR_SIZE_T_FMT | |
| " bytes expected to %s on BIO#%pp [mem: %pp]", | |
| MODSSL_LIBRARY_NAME, requested_len, | |
| (cmd & BIO_CB_WRITE) ? "write" : "read", | |
| bio, argp); | |
| } | |
| } | |
| return rc; | |
| } | |
| static APR_INLINE void set_bio_callback(BIO *bio, void *arg) | |
| { | |
| #if OPENSSL_VERSION_NUMBER >= 0x30000000L | |
| BIO_set_callback_ex(bio, modssl_io_cb); | |
| #else | |
| BIO_set_callback(bio, modssl_io_cb); | |
| #endif | |
| BIO_set_callback_arg(bio, arg); | |
| } | |
| void modssl_set_io_callbacks(SSL *ssl, conn_rec *c, server_rec *s) | |
| { | |
| BIO *rbio, *wbio; | |
| if (!APLOG_CS_IS_LEVEL(c, s, APLOG_TRACE4)) | |
| return; | |
| rbio = SSL_get_rbio(ssl); | |
| wbio = SSL_get_wbio(ssl); | |
| if (rbio) { | |
| set_bio_callback(rbio, ssl); | |
| } | |
| if (wbio && wbio != rbio) { | |
| set_bio_callback(wbio, ssl); | |
| } | |
| } |