You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ji...@apache.org on 2015/06/30 17:26:19 UTC

svn commit: r1688474 [12/21] - in /httpd/httpd/trunk/modules/http2: ./ m4/ mod-h2.xcodeproj/ mod-h2.xcodeproj/project.xcworkspace/ mod-h2.xcodeproj/project.xcworkspace/xcshareddata/ mod-h2.xcodeproj/xcuserdata/ mod-h2.xcodeproj/xcuserdata/sei.xcuserdat...

Added: httpd/httpd/trunk/modules/http2/sandbox/httpd/mod_ssl-alpn/ssl_engine_io.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/sandbox/httpd/mod_ssl-alpn/ssl_engine_io.c?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/sandbox/httpd/mod_ssl-alpn/ssl_engine_io.c (added)
+++ httpd/httpd/trunk/modules/http2/sandbox/httpd/mod_ssl-alpn/ssl_engine_io.c Tue Jun 30 15:26:16 2015
@@ -0,0 +1,2098 @@
+/* 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 "mod_ssl.h"
+#include "apr_date.h"
+
+/*  _________________________________________________________________
+**
+**  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;
+    }
+    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->ptr);
+    apr_bucket *e;
+
+    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->shutdown = 1;
+    bio->init = 1;
+    bio->num = -1;
+    bio->ptr = 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_read(BIO *bio, char *out, int outl)
+{
+    /* this is never called */
+    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->ptr);
+    apr_bucket *e;
+
+    /* Abort early if the client has initiated a renegotiation. */
+    if (outctx->filter_ctx->config->reneg_state == RENEG_ABORT) {
+        outctx->rc = APR_ECONNABORTED;
+        return -1;
+    }
+
+    /* when handshaking we'll have a small number of bytes.
+     * max size SSL will pass us here is about 16k.
+     * (16413 bytes to be exact)
+     */
+    BIO_clear_retry_flags(bio);
+
+    /* 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);
+
+    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->ptr);
+
+    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->shutdown;
+        break;
+      case BIO_CTRL_SET_CLOSE:
+        bio->shutdown = (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;
+}
+
+static int bio_filter_out_gets(BIO *bio, char *buf, int size)
+{
+    /* this is never called */
+    return -1;
+}
+
+static int bio_filter_out_puts(BIO *bio, const char *str)
+{
+    /* this is never called */
+    return -1;
+}
+
+static BIO_METHOD bio_filter_out_method = {
+    BIO_TYPE_MEM,
+    "APR output filter",
+    bio_filter_out_write,
+    bio_filter_out_read,     /* read is never called */
+    bio_filter_out_puts,     /* puts is never called */
+    bio_filter_out_gets,     /* gets is never called */
+    bio_filter_out_ctrl,
+    bio_filter_create,
+    bio_filter_destroy,
+    NULL
+};
+
+typedef struct {
+    int length;
+    char *value;
+} 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;
+    int alpn_finished;  /* 1 if ALPN has finished, 0 otherwise */
+} 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.
+ */
+
+/* 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(char_buffer_t *buffer, char *in, int inl)
+{
+    if (!buffer->length) {
+        return 0;
+    }
+
+    if (buffer->length > inl) {
+        /* we have have enough to fill the caller's buffer */
+        memmove(in, buffer->value, inl);
+        buffer->value += inl;
+        buffer->length -= inl;
+    }
+    else {
+        /* swallow remainder of the buffer */
+        memmove(in, buffer->value, buffer->length);
+        inl = buffer->length;
+        buffer->value = NULL;
+        buffer->length = 0;
+    }
+
+    return inl;
+}
+
+static int char_buffer_write(char_buffer_t *buffer, char *in, int inl)
+{
+    buffer->value = in;
+    buffer->length = inl;
+    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->ptr);
+    apr_read_type_e block = inctx->block;
+
+    inctx->rc = APR_SUCCESS;
+
+    /* OpenSSL catches this case, so should we. */
+    if (!in)
+        return 0;
+
+    /* Abort early if the client has initiated a renegotiation. */
+    if (inctx->filter_ctx->config->reneg_state == RENEG_ABORT) {
+        inctx->rc = APR_ECONNABORTED;
+        return -1;
+    }
+
+    /* In theory, OpenSSL should flush as necessary, but it is known
+     * not to do so correctly in some cases; see PR 46952.
+     *
+     * Historically, this flush call was performed only for an SSLv2
+     * connection or for a proxy connection.  Calling _out_flush
+     * should be very cheap in cases where it is unnecessary (and no
+     * output is buffered) so the performance impact of doing it
+     * unconditionally should be minimal.
+     */
+    if (bio_filter_out_flush(inctx->bio_out) < 0) {
+        bio_filter_out_ctx_t *outctx = inctx->bio_out->ptr;
+        inctx->rc = outctx->rc;
+        return -1;
+    }
+
+    BIO_clear_retry_flags(bio);
+
+    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 (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 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 */
+    NULL,                       /* ctrl is never called */
+    bio_filter_create,
+    bio_filter_destroy,
+    NULL
+};
+
+
+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;
+    apr_size_t bytes = 0;
+    int rc;
+
+    *len = 0;
+
+    /* If we have something leftover from last time, try that first. */
+    if ((bytes = char_buffer_read(&inctx->cbuf, buf, wanted))) {
+        *len = bytes;
+        if (inctx->mode == AP_MODE_SPECULATIVE) {
+            /* We want to rollback this read. */
+            if (inctx->cbuf.length > 0) {
+                inctx->cbuf.value -= bytes;
+                inctx->cbuf.length += bytes;
+            } else {
+                char_buffer_write(&inctx->cbuf, buf, (int)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;
+        }
+
+        /* 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->cbuf, buf, rc);
+            }
+            return inctx->rc;
+        }
+        else 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;
+                }
+                else {
+                    inctx->rc = APR_EOF;
+                }
+                break;
+            }
+        }
+        else /* (rc < 0) */ {
+            int ssl_err = SSL_get_error(inctx->filter_ctx->pssl, rc);
+            conn_rec *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 {
+                    ap_log_cerror(APLOG_MARK, APLOG_INFO, inctx->rc, c, APLOGNO(01991)
+                                  "SSL input filter read failed.");
+                }
+            }
+            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 (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->cbuf, 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->cbuf, 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;
+    }
+
+    outctx = (bio_filter_out_ctx_t *)filter_ctx->pbioWrite->ptr;
+    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;
+        }
+        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
+ * recevied 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, ap_filter_t *f)
+{
+    bio_filter_in_ctx_t *inctx = f->ctx;
+    SSL_free(inctx->ssl);
+    sslconn->ssl = NULL;
+    inctx->ssl = NULL;
+    inctx->filter_ctx->pssl = NULL;
+}
+
+static apr_status_t ssl_io_filter_error(ap_filter_t *f,
+                                        apr_bucket_brigade *bb,
+                                        apr_status_t status)
+{
+    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);
+
+            sslconn->non_ssl_request = NON_SSL_SEND_HDR_SEP;
+            ssl_io_filter_disable(sslconn, f);
+
+            /* fake the request line */
+            bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc);
+            send_eos = 0;
+            break;
+
+    case MODSSL_ERROR_BAD_GATEWAY:
+        bucket = ap_bucket_error_create(HTTP_BAD_REQUEST, NULL,
+                                        f->c->pool,
+                                        f->c->bucket_alloc);
+        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c, APLOGNO(01997)
+                      "SSL handshake failed: sending 502");
+        break;
+
+    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 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) {
+        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!) */
+        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) */
+        shutdown_type = 0;
+        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!) */
+        shutdown_type = SSL_RECEIVED_SHUTDOWN;
+        type = "standard";
+        logno = APLOGNO(02001);
+        break;
+    }
+
+    SSL_set_shutdown(ssl, shutdown_type);
+    SSL_smart_shutdown(ssl);
+
+    /* and finally log the fact that we've closed the connection */
+    if (APLOG_CS_IS_LEVEL(c, mySrvFromConn(c), loglevel)) {
+        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;
+}
+
+/*
+ * 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 (sslconn->is_proxy) {
+#ifdef HAVE_TLSEXT
+        apr_ipsubnet_t *ip;
+#endif
+        const char *hostname_note = apr_table_get(c->notes,
+                                                  "proxy-request-hostname");
+        BOOL proxy_ssl_check_peer_ok = TRUE;
+        sc = mySrvConfig(server);
+
+#ifdef HAVE_TLSEXT
+        /*
+         * 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 &&
+            sc->proxy->protocol != SSL_PROTOCOL_SSLV3 &&
+            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
+
+        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 (sc->proxy_ssl_check_peer_expire != SSL_ENABLED_FALSE) {
+            if (!cert
+                || (X509_cmp_current_time(
+                     X509_get_notBefore(cert)) >= 0)
+                || (X509_cmp_current_time(
+                     X509_get_notAfter(cert)) <= 0)) {
+                proxy_ssl_check_peer_ok = FALSE;
+                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02004)
+                              "SSL Proxy: Peer certificate is expired");
+            }
+        }
+        if ((sc->proxy_ssl_check_peer_name != SSL_ENABLED_FALSE) &&
+            hostname_note) {
+            apr_table_unset(c->notes, "proxy-request-hostname");
+            if (!cert
+                || SSL_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 ((sc->proxy_ssl_check_peer_cn != SSL_ENABLED_FALSE) &&
+            hostname_note) {
+            const char *hostname;
+            int match = 0;
+
+            hostname = ssl_var_lookup(NULL, server, c, NULL,
+                                      "SSL_CLIENT_S_DN_CN");
+            apr_table_unset(c->notes, "proxy-request-hostname");
+
+            /* 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);
+            }
+        }
+
+        if (cert) {
+            X509_free(cert);
+        }
+
+        if (proxy_ssl_check_peer_ok != TRUE) {
+            /* 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 HTTP_BAD_GATEWAY;
+        }
+
+        apr_table_setn(c->notes, "SSL_connect_rv", "ok");
+        return APR_SUCCESS;
+    }
+
+    if ((n = SSL_accept(filter_ctx->pssl)) <= 0) {
+        bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)
+                                     (filter_ctx->pbioRead->ptr);
+        bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)
+                                       (filter_ctx->pbioWrite->ptr);
+        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);
+        }
+        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);
+
+    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.
+         */
+        apr_bucket *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_HDR_SEP) {
+            apr_bucket *bucket = apr_bucket_immortal_create(CRLF, 2, f->c->bucket_alloc);
+            APR_BRIGADE_INSERT_TAIL(bb, bucket);
+            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(f, bb, status);
+    }
+
+    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;
+
+        /* 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 (inctx->cbuf.length
+            && (pos = memchr(inctx->cbuf.value, APR_ASCII_LF,
+                             inctx->cbuf.length)) != NULL) {
+            start = inctx->cbuf.value;
+            len = 1 + pos - start; /* +1 to include LF */
+            /* Buffer contents now consumed. */
+            inctx->cbuf.value += len;
+            inctx->cbuf.length -= 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(f, bb, status);
+    }
+
+    /* Create a transient bucket out of the decrypted data. */
+    if (len > 0) {
+        apr_bucket *bucket =
+            apr_bucket_transient_create(start, len, f->c->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(bb, bucket);
+    }
+
+#if defined(HAVE_TLS_ALPN) || defined(HAVE_TLS_NPN)
+	/* By this point, Application-Layer Protocol Negotiation (ALPN) should be 
+	 * completed (if our version of OpenSSL supports it). If we haven't already, 
+	 * find out which protocol was decided upon and inform other modules 
+	 * by calling alpn_proto_negotiated_hook. 
+	 */
+	if (!inctx->alpn_finished) {
+		SSLConnRec *sslconn = myConnConfig(f->c);
+		const unsigned char *next_proto = NULL;
+		unsigned next_proto_len = 0;
+		int n;
+		
+		if (sslconn->alpn_negofns) {
+	#ifdef HAVE_TLS_ALPN
+			SSL_get0_alpn_selected(inctx->ssl, &next_proto, &next_proto_len);
+			ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, f->c,
+						  APLOGNO(02306) "SSL ALPN negotiated protocol: '%*s'",
+						  next_proto_len, (const char*)next_proto);
+	#else
+			SSL_get0_next_proto_negotiated(
+										   inctx->ssl, &next_proto, &next_proto_len);
+			ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, f->c,
+						  APLOGNO(02306) "SSL NPN negotiated protocol: '%*s'",
+						  next_proto_len, (const char*)next_proto);
+	#endif
+			for (n = 0; n < sslconn->alpn_negofns->nelts; n++) {
+				ssl_alpn_proto_negotiated fn =
+				APR_ARRAY_IDX(sslconn->alpn_negofns, n, ssl_alpn_proto_negotiated);
+				
+				if (fn(f->c, (const char *)next_proto, next_proto_len) == DONE)
+				break;
+			}
+		}
+		inctx->alpn_finished = 1;
+	}
+#endif
+
+    return APR_SUCCESS;
+}
+
+
+/* ssl_io_filter_output() produces one SSL/TLS message per bucket
+ * passed down the output filter stack.  This results in a high
+ * overhead (network packets) for any output comprising many small
+ * buckets.  SSI page applied through the HTTP chunk filter, for
+ * example, may produce many brigades containing small buckets -
+ * [chunk-size CRLF] [chunk-data] [CRLF].
+ *
+ * The coalescing filter merges many small buckets into larger buckets
+ * where possible, allowing the SSL I/O output filter to handle them
+ * more efficiently. */
+
+#define COALESCE_BYTES (2048)
+
+struct coalesce_ctx {
+    char buffer[COALESCE_BYTES];
+    apr_size_t bytes; /* number of bytes of buffer used. */
+};
+
+static apr_status_t ssl_io_filter_coalesce(ap_filter_t *f,
+                                           apr_bucket_brigade *bb)
+{
+    apr_bucket *e, *last = NULL;
+    apr_size_t bytes = 0;
+    struct coalesce_ctx *ctx = f->ctx;
+    unsigned count = 0;
+
+    /* The brigade consists of zero-or-more small data buckets which
+     * can be coalesced (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
+             && (bytes + e->length) < COALESCE_BYTES
+             && (ctx == NULL
+                 || bytes + ctx->bytes + e->length < COALESCE_BYTES);
+         e = APR_BUCKET_NEXT(e)) {
+        last = e;
+        if (e->length) count++; /* don't count zero-length buckets */
+        bytes += e->length;
+    }
+
+    /* Coalesce the prefix, if:
+     * a) more than one bucket is found to coalesce, or
+     * b) the brigade contains only a single data bucket, or
+     * c)
+     */
+    if (bytes > 0
+        && (count > 1
+            || (count == 1 && APR_BUCKET_NEXT(last) == APR_BRIGADE_SENTINEL(bb)))) {
+        /* If coalescing some bytes, ensure a context has been
+         * created. */
+        if (!ctx) {
+            f->ctx = ctx = apr_palloc(f->c->pool, sizeof *ctx);
+            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", ctx->bytes, bytes);
+
+        /* 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 != last) {
+            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 bucket type during coalesce");
+                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 > sizeof ctx->buffer
+                    || (len + ctx->bytes > sizeof ctx->buffer)) {
+                    ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(02014)
+                                  "unexpected coalesced bucket data length");
+                    break; /* non-fatal error; break out */
+                }
+
+                memcpy(ctx->buffer + ctx->bytes, data, len);
+                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, our work here is done. */
+        return APR_SUCCESS;
+    }
+
+    /* If anything remains in the brigade, it must now be passed down
+     * the filter stack, first prepending anything that has been
+     * coalesced. */
+    if (ctx && ctx->bytes) {
+        apr_bucket *e;
+
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c,
+                      "coalesce: passing on %" APR_SIZE_T_FMT " bytes", ctx->bytes);
+
+        e = apr_bucket_transient_create(ctx->buffer, ctx->bytes, bb->bucket_alloc);
+        APR_BRIGADE_INSERT_HEAD(bb, e);
+        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_read_type_e rblock = APR_NONBLOCK_READ;
+
+    if (f->c->aborted) {
+        apr_brigade_cleanup(bb);
+        return APR_ECONNABORTED;
+    }
+
+    if (!filter_ctx->pssl) {
+        /* ssl_filter_io_shutdown was called */
+        return ap_pass_brigade(f->next, bb);
+    }
+
+    inctx = (bio_filter_in_ctx_t *)filter_ctx->pbioRead->ptr;
+    outctx = (bio_filter_out_ctx_t *)filter_ctx->pbioWrite->ptr;
+
+    /* 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(f, bb, status);
+    }
+
+    while (!APR_BRIGADE_EMPTY(bb)) {
+        apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
+
+        /* If it is a flush or EOS, we need to pass this down.
+         * These types do not require translation by OpenSSL.
+         */
+        if (APR_BUCKET_IS_EOS(bucket) || APR_BUCKET_IS_FLUSH(bucket)) {
+            if (bio_filter_out_flush(filter_ctx->pbioWrite) < 0) {
+                status = outctx->rc;
+                break;
+            }
+
+            if (APR_BUCKET_IS_EOS(bucket)) {
+                /*
+                 * By definition, nothing can come after EOS.
+                 * which also means we can pass the rest of this brigade
+                 * without creating a new one since it only contains the
+                 * EOS bucket.
+                 */
+
+                if ((status = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
+                    return status;
+                }
+                break;
+            }
+            else {
+                /* bio_filter_out_flush() already passed down a flush bucket
+                 * if there was any data to be flushed.
+                 */
+                apr_bucket_delete(bucket);
+            }
+        }
+        else if (AP_BUCKET_IS_EOC(bucket)) {
+            /* The EOC bucket indicates connection closure, so SSL
+             * shutdown must now be performed.  */
+            ssl_filter_io_shutdown(filter_ctx, f->c, 0);
+            if ((status = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
+                return status;
+            }
+            break;
+        }
+        else {
+            /* filter output */
+            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;
+                continue; /* and try again with a blocking read. */
+            }
+
+            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) {
+                break;
+            }
+        }
+    }
+
+    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 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;
+
+    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)) {
+        /* Suprisingly (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) {
+        apr_bucket *e;
+
+        /* 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 {
+            apr_bucket *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)) {
+        apr_bucket *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 void 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);
+
+    filter_ctx->pbioRead = BIO_new(&bio_filter_in_method);
+    filter_ctx->pbioRead->ptr = (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;
+    inctx->cbuf.length = 0;
+    inctx->bb = apr_brigade_create(c->pool, c->bucket_alloc);
+    inctx->block = APR_BLOCK_READ;
+    inctx->pool = c->pool;
+    inctx->filter_ctx = filter_ctx;
+    inctx->alpn_finished = 0;
+}
+
+/* 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. */
+void ssl_io_filter_init(conn_rec *c, request_rec *r, SSL *ssl)
+{
+    ssl_filter_ctx_t *filter_ctx;
+
+    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);
+
+    filter_ctx->pbioWrite       = BIO_new(&bio_filter_out_method);
+    filter_ctx->pbioWrite->ptr  = (void *)bio_filter_out_ctx_new(filter_ctx, c);
+
+    /* write is non blocking for the benefit of async mpm */
+    if (c->cs) {
+        BIO_set_nbio(filter_ctx->pbioWrite, 1);
+    }
+
+    ssl_io_input_add_filter(filter_ctx, c, r, ssl);
+
+    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);
+
+    if (APLOG_CS_IS_LEVEL(c, mySrvFromConn(c), APLOG_TRACE4)) {
+        BIO_set_callback(SSL_get_rbio(ssl), ssl_io_data_cb);
+        BIO_set_callback_arg(SSL_get_rbio(ssl), (void *)ssl);
+    }
+
+    return;
+}
+
+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(server_rec *srvr,
+                             const char *s,
+                             long len)
+{
+    char buf[256];
+    char tmp[64];
+    int i, j, rows, trunc;
+    unsigned char ch;
+
+    trunc = 0;
+    for(; (len > 0) && ((s[len-1] == ' ') || (s[len-1] == '\0')); len--)
+        trunc++;
+    rows = (len / DUMP_WIDTH);
+    if ((rows * DUMP_WIDTH) < len)
+        rows++;
+    ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, srvr,
+            "+-------------------------------------------------------------------------+");
+    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 *)(s) + i * DUMP_WIDTH, j);
+        ap_xlate_proto_from_ascii(ebcdic_text, j);
+#endif /* APR_CHARSET_EBCDIC */
+        apr_snprintf(tmp, sizeof(tmp), "| %04x: ", i * DUMP_WIDTH);
+        apr_cpystrn(buf, tmp, sizeof(buf));
+        for (j = 0; j < DUMP_WIDTH; j++) {
+            if (((i * DUMP_WIDTH) + j) >= len)
+                apr_cpystrn(buf+strlen(buf), "   ", sizeof(buf)-strlen(buf));
+            else {
+                ch = ((unsigned char)*((char *)(s) + i * DUMP_WIDTH + j)) & 0xff;
+                apr_snprintf(tmp, sizeof(tmp), "%02x%c", ch , j==7 ? '-' : ' ');
+                apr_cpystrn(buf+strlen(buf), tmp, sizeof(buf)-strlen(buf));
+            }
+        }
+        apr_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf));
+        for (j = 0; j < DUMP_WIDTH; j++) {
+            if (((i * DUMP_WIDTH) + j) >= len)
+                apr_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf));
+            else {
+                ch = ((unsigned char)*((char *)(s) + i * DUMP_WIDTH + j)) & 0xff;
+#if APR_CHARSET_EBCDIC
+                apr_snprintf(tmp, sizeof(tmp), "%c", (ch >= 0x20 && ch <= 0x7F) ? ebcdic_text[j] : '.');
+#else /* APR_CHARSET_EBCDIC */
+                apr_snprintf(tmp, sizeof(tmp), "%c", ((ch >= ' ') && (ch <= '~')) ? ch : '.');
+#endif /* APR_CHARSET_EBCDIC */
+                apr_cpystrn(buf+strlen(buf), tmp, sizeof(buf)-strlen(buf));
+            }
+        }
+        apr_cpystrn(buf+strlen(buf), " |", sizeof(buf)-strlen(buf));
+        ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, srvr,
+                     "%s", buf);
+    }
+    if (trunc > 0)
+        ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, srvr,
+                "| %04ld - <SPACES/NULS>", len + trunc);
+    ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, srvr,
+            "+-------------------------------------------------------------------------+");
+    return;
+}
+
+long ssl_io_data_cb(BIO *bio, int cmd,
+                    const char *argp,
+                    int argi, long argl, long rc)
+{
+    SSL *ssl;
+    conn_rec *c;
+    server_rec *s;
+
+    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 (rc >= 0) {
+            ap_log_cserror(APLOG_MARK, APLOG_TRACE4, 0, c, s,
+                    "%s: %s %ld/%d bytes %s BIO#%pp [mem: %pp] %s",
+                    SSL_LIBRARY_NAME,
+                    (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"),
+                    rc, argi, (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "to" : "from"),
+                    bio, argp,
+                    (argp != NULL ? "(BIO dump follows)" : "(Oops, no memory buffer?)"));
+            if ((argp != NULL) && APLOG_CS_IS_LEVEL(c, s, APLOG_TRACE7))
+                ssl_io_data_dump(s, argp, rc);
+        }
+        else {
+            ap_log_cserror(APLOG_MARK, APLOG_TRACE4, 0, c, s,
+                    "%s: I/O error, %d bytes expected to %s on BIO#%pp [mem: %pp]",
+                    SSL_LIBRARY_NAME, argi,
+                    (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"),
+                    bio, argp);
+        }
+    }
+    return rc;
+}