/*                      _             _
**  _ __ ___   ___   __| |    ___ ___| |  mod_ssl
** | '_ ` _ \ / _ \ / _` |   / __/ __| |  Apache Interface to OpenSSL
** | | | | | | (_) | (_| |   \__ \__ \ |  www.modssl.org
** |_| |_| |_|\___/ \__,_|___|___/___/_|  ftp.modssl.org
**                      |_____|
**  ssl_engine_io.c
**  I/O Functions
*/

/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 */
                             /* ``MY HACK: This universe.
                                  Just one little problem:
                                  core keeps dumping.''
                                            -- Unknown    */
#include "mod_ssl.h"

/*  _________________________________________________________________
**
**  I/O Hooks
**  _________________________________________________________________
*/

/* XXX THIS STUFF NEEDS A MAJOR CLEANUP -RSE XXX */

/* 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_bucket_ctrl() -> BIO_bucket_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 {
    SSLFilterRec *frec;
    conn_rec *c;
    apr_bucket_brigade *bb;
    apr_size_t length;
    char buffer[AP_IOBUFSIZE];
    apr_size_t blen;
} BIO_bucket_t;

static BIO_bucket_t *BIO_bucket_new(SSLFilterRec *frec, conn_rec *c)
{
    BIO_bucket_t *b = apr_palloc(c->pool, sizeof(*b));

    b->frec = frec;
    b->c = c;
    b->bb = apr_brigade_create(c->pool, c->bucket_alloc);
    b->blen = 0;
    b->length = 0;

    return b;
}

#define BIO_bucket_ptr(bio) (BIO_bucket_t *)bio->ptr

static int BIO_bucket_flush(BIO *bio)
{
    BIO_bucket_t *b = BIO_bucket_ptr(bio);
    apr_bucket *e;

    if (!(b->blen || b->length)) {
        return APR_SUCCESS;
    }

    if (b->blen) {
        e = apr_bucket_transient_create(b->buffer, b->blen,
                                        b->bb->bucket_alloc);
        /* we filled this buffer first so add it to the 
         * head of the brigade
         */
        APR_BRIGADE_INSERT_HEAD(b->bb, e);
        b->blen = 0;
    }

    b->length = 0;
    e = apr_bucket_flush_create(b->bb->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(b->bb, e);

    return ap_pass_brigade(b->frec->pOutputFilter->next, b->bb);
}

static int bio_bucket_new(BIO *bio)
{
    bio->shutdown = 1;
    bio->init = 1;
    bio->num = -1;
    bio->ptr = NULL;

    return 1;
}

static int bio_bucket_free(BIO *bio)
{
    if (bio == NULL) {
        return 0;
    }

    /* nothing to free here.
     * apache will destroy the bucket brigade for us
     */
    return 1;
}
	
static int bio_bucket_read(BIO *bio, char *out, int outl)
{
    /* this is never called */
    return -1;
}

static int bio_bucket_write(BIO *bio, const char *in, int inl)
{
    BIO_bucket_t *b = BIO_bucket_ptr(bio);

    /* 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);

    if (!b->length && (inl + b->blen < sizeof(b->buffer))) {
        /* the first two SSL_writes (of 1024 and 261 bytes)
         * need to be in the same packet (vec[0].iov_base)
         */
        /* XXX: could use apr_brigade_write() to make code look cleaner
         * but this way we avoid the malloc(APR_BUCKET_BUFF_SIZE)
         * and free() of it later
         */
        memcpy(&b->buffer[b->blen], in, inl);
        b->blen += inl;
    }
    else {
        /* pass along the encrypted data
         * need to flush since we're using SSL's malloc-ed buffer 
         * which will be overwritten once we leave here
         */
        apr_bucket *bucket = apr_bucket_transient_create(in, inl,
                                                         b->bb->bucket_alloc);

        b->length += inl;
        APR_BRIGADE_INSERT_TAIL(b->bb, bucket);

        BIO_bucket_flush(bio);
    }

    return inl;
}

static long bio_bucket_ctrl(BIO *bio, int cmd, long num, void *ptr)
{
    long ret = 1;
    char **pptr;

    BIO_bucket_t *b = BIO_bucket_ptr(bio);

    switch (cmd) {
      case BIO_CTRL_RESET:
        b->blen = b->length = 0;
        break;
      case BIO_CTRL_EOF:
        ret = (long)((b->blen + b->length) == 0);
        break;
      case BIO_C_SET_BUF_MEM_EOF_RETURN:
        b->blen = b->length = (apr_size_t)num;
        break;
      case BIO_CTRL_INFO:
        ret = (long)(b->blen + b->length);
        if (ptr) {
            pptr = (char **)ptr;
            *pptr = (char *)&(b->buffer[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_WPENDING:
        ret = 0L;
        break;
      case BIO_CTRL_PENDING:
        ret = (long)(b->blen + b->length);
        break;
      case BIO_CTRL_FLUSH:
        ret = (BIO_bucket_flush(bio) == APR_SUCCESS);
        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_bucket_gets(BIO *bio, char *buf, int size)
{
    /* this is never called */
    return -1;
}

static int bio_bucket_puts(BIO *bio, const char *str)
{
    /* this is never called */
    return -1;
}

static BIO_METHOD bio_bucket_method = {
    BIO_TYPE_MEM,
    "APR bucket brigade",
    bio_bucket_write,
    bio_bucket_read,
    bio_bucket_puts,
    bio_bucket_gets,
    bio_bucket_ctrl,
    bio_bucket_new,
    bio_bucket_free,
#ifdef OPENSSL_VERSION_NUMBER
    NULL /* sslc does not have the callback_ctrl field */
#endif
};

static BIO_METHOD *BIO_s_bucket(void)
{
    return &bio_bucket_method;
}

typedef struct {
    int length;
    char *value;
} char_buffer_t;

typedef struct {
    SSL *ssl;
    BIO *wbio;
    ap_filter_t *f;
    apr_status_t rc;
    ap_input_mode_t mode;
    apr_read_type_e block;
    apr_bucket_brigade *bb;
    apr_bucket *bucket;
    char_buffer_t cbuf;
} BIO_bucket_in_t;

typedef struct {
    BIO_bucket_in_t inbio;
    char_buffer_t cbuf;
    apr_pool_t *pool;
    char buffer[AP_IOBUFSIZE];
    SSLFilterRec *frec;
} ssl_io_input_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 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 */
        memcpy(in, buffer->value, inl);
        buffer->value += inl;
        buffer->length -= inl;
    }
    else {
        /* swallow remainder of the buffer */
        memcpy(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 is the function called by SSL_read()
 */
#define BIO_bucket_in_ptr(bio) (BIO_bucket_in_t *)bio->ptr

static int bio_bucket_in_read(BIO *bio, char *in, int inl)
{
    BIO_bucket_in_t *inbio = BIO_bucket_in_ptr(bio);
    SSLConnRec *sslconn = myConnConfig(inbio->f->c);
    int len = 0;

    /* XXX: flush here only required for SSLv2;
     * OpenSSL calls BIO_flush() at the appropriate times for
     * the other protocols.
     */
    if ((SSL_version(inbio->ssl) == SSL2_VERSION) || sslconn->is_proxy) {
        BIO_bucket_flush(inbio->wbio);
    }

    inbio->rc = APR_SUCCESS;
    
    /* first use data already read from socket if any */
    if ((len = char_buffer_read(&inbio->cbuf, in, inl))) {
        if ((len <= inl) || inbio->mode == AP_MODE_GETLINE) {
            return len;
        }
        inl -= len;
    }

    while (1) {
        const char *buf;
        apr_size_t buf_len = 0;

        if (inbio->bucket) {
            /* all of the data in this bucket has been read,
             * so we can delete it now.
             */
            apr_bucket_delete(inbio->bucket);
            inbio->bucket = NULL;
        }

        if (APR_BRIGADE_EMPTY(inbio->bb)) {
            /* We will always call with READBYTES even if the user wants
             * GETLINE.
             */
            inbio->rc = ap_get_brigade(inbio->f->next, inbio->bb,
                                       AP_MODE_READBYTES, inbio->block, 
                                       inl);

            if ((inbio->rc != APR_SUCCESS) || APR_BRIGADE_EMPTY(inbio->bb))
            {
                break;
            }
        }

        inbio->bucket = APR_BRIGADE_FIRST(inbio->bb);

        inbio->rc = apr_bucket_read(inbio->bucket,
                                    &buf, &buf_len, inbio->block);

        if (inbio->rc != APR_SUCCESS) {
            apr_bucket_delete(inbio->bucket);
            inbio->bucket = NULL;
            return len;
        }

        if (buf_len) {
            /* Protected against len > MAX_INT 
             */
            if ((len + (int)buf_len) >= inl || (int)buf_len < 0) {
                /* we have enough to fill the buffer.
                 * append if we have already written to the buffer.
                 */
                int nibble = inl - len;
                char *value = (char *)buf+nibble;

                int length = buf_len - nibble;
                memcpy(in + len, buf, nibble);

                char_buffer_write(&inbio->cbuf, value, length);
                len += nibble;

                break;
            }
            else {
                /* not enough data,
                 * save what we have and try to read more.
                 */
                memcpy(in + len, buf, buf_len);
                len += buf_len;
            }
        }

        if (inbio->mode == AP_MODE_GETLINE) {
            /* only read from the socket once in getline mode.
             * since callers buffer size is likely much larger than
             * the request headers.  caller can always come back for more
             * if first read didn't get all the headers.
             */
            break;
        }
    }

    return len;
}

static BIO_METHOD bio_bucket_in_method = {
    BIO_TYPE_MEM,
    "APR input bucket brigade",
    NULL,                       /* write is never called */
    bio_bucket_in_read,
    NULL,                       /* puts is never called */
    NULL,                       /* gets is never called */
    NULL,                       /* ctrl is never called */
    bio_bucket_new,
    bio_bucket_free,
#ifdef OPENSSL_VERSION_NUMBER
    NULL /* sslc does not have the callback_ctrl field */
#endif
};

static BIO_METHOD *BIO_s_in_bucket(void)
{
    return &bio_bucket_in_method;
}

static const char ssl_io_filter[] = "SSL/TLS Filter";

static int ssl_io_hook_read(SSL *ssl, char *buf, int len)
{
    int rc;

    if (ssl == NULL) {
        return -1;
    }

    rc = SSL_read(ssl, buf, len);

    if (rc <= 0) {
        int ssl_err = SSL_get_error(ssl, rc);

        if (ssl_err == SSL_ERROR_WANT_READ) {
            /*
             * Simulate an EINTR in case OpenSSL wants to read more.
             * (This is usually the case when the client forces an SSL
             * renegotation which is handled implicitly by OpenSSL.)
             */
            errno = EINTR;
            rc = 0; /* non fatal error */
        }
        else if (ssl_err == SSL_ERROR_SSL) {
            /*
             * Log SSL errors
             */
            conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server,
                    "SSL error on reading data");
            ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, c->base_server);
        }
    }

    return rc;
}

static int ssl_io_hook_write(SSL *ssl, unsigned char *buf, int len)
{
    int rc;

    if (ssl == NULL) {
        return -1;
    }

    rc = SSL_write(ssl, buf, len);

    if (rc < 0) {
        int ssl_err = SSL_get_error(ssl, rc);

        if (ssl_err == SSL_ERROR_WANT_WRITE) {
            /*
             * Simulate an EINTR in case OpenSSL wants to write more.
             */
            errno = EINTR;
        }
        else if (ssl_err == SSL_ERROR_SSL) {
            /*
             * Log SSL errors
             */
            conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server,
                    "SSL error on writing data");
            ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, c->base_server);
        }
        /*
         * XXX - Just trying to reflect the behaviour in 
         * openssl_state_machine.c [mod_tls]. TBD
         */
        rc = 0;
    }
    return rc;
}

static apr_status_t ssl_filter_write(ap_filter_t *f,
                                     const char *data,
                                     apr_size_t len)
{
    SSLFilterRec *ctx = f->ctx;
    apr_size_t n;

    /* write SSL */
    n = ssl_io_hook_write(ctx->pssl, (unsigned char *)data, len);

    if (n != len) {
        conn_rec *c = f->c;
        char *reason = "reason unknown";

        /* XXX: probably a better way to determine this */
        if (SSL_total_renegotiations(ctx->pssl)) {
            reason = "likely due to failed renegotiation";
        }

        ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server,
                "failed to write %d of %d bytes (%s)",
                n > 0 ? len - n : len, len, reason);

        return APR_EINVAL;
    }

    return APR_SUCCESS;
}

static apr_status_t ssl_io_filter_Output(ap_filter_t *f,
                                         apr_bucket_brigade *bb)
{
    apr_status_t status = APR_SUCCESS;
    SSLFilterRec *ctx = f->ctx;

    if (!ctx->pssl) {
        /* ssl_abort() has been called */
        return ap_pass_brigade(f->next, bb);
    }

    if ((status = ssl_hook_process_connection(ctx)) != APR_SUCCESS) {
        return 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 ((status = BIO_bucket_flush(ctx->pbioWrite)) != APR_SUCCESS) {
                return status;
            }

            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_bucket_flush() already passed down a flush bucket
                 * if there was any data to be flushed.
                 */
                apr_bucket_delete(bucket);
            }
        }
        else {
            /* read filter */
            const char *data;
            apr_size_t len;

            apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
            status = ssl_filter_write(f, data, len);
            apr_bucket_delete(bucket);

            if (status != APR_SUCCESS) {
                break;
            }
        }
    }

    return status;
}

/*
 * ctx->cbuf is leftover plaintext from ssl_io_input_getline,
 * use what we have there first if any,
 * then go for more by calling ssl_io_hook_read.
 */
static apr_status_t ssl_io_input_read(ssl_io_input_ctx_t *ctx,
                                      char *buf,
                                      apr_size_t *len)
{
    apr_size_t wanted = *len;
    apr_size_t bytes = 0;
    int rc;

    *len = 0;

    if ((bytes = char_buffer_read(&ctx->cbuf, buf, wanted))) {
        *len = bytes;
        if (ctx->inbio.mode == AP_MODE_SPECULATIVE) {
            /* We want to rollback this read. */
            ctx->cbuf.value -= bytes;
            ctx->cbuf.length += bytes;
            return APR_SUCCESS;
        } 
        if ((*len >= wanted) || ctx->inbio.mode == AP_MODE_GETLINE) {
            return APR_SUCCESS;
        }
    }

    rc = ssl_io_hook_read(ctx->frec->pssl, buf + bytes, wanted - bytes);

    if (rc > 0) {
        *len += rc;
        if (ctx->inbio.mode == AP_MODE_SPECULATIVE) {
            char_buffer_write(&ctx->cbuf, buf, rc);
        }
    }
    else if ((rc == 0) && (errno != EINTR)) {
        /* something other than SSL_ERROR_WANT_READ */
        return APR_EOF;
    }
    else if ((rc == -1) && (ctx->inbio.rc == APR_SUCCESS)) {
        /*
         * bucket read from socket was successful,
         * but there was an error within the ssl runtime.
         */
        return APR_EGENERAL;
    }

    return ctx->inbio.rc;
}

static apr_status_t ssl_io_input_getline(ssl_io_input_ctx_t *ctx,
                                         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(ctx, buf + offset, &tmplen);
        
        if (status != APR_SUCCESS) {
            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(&ctx->cbuf, value, length);

        *len = bytes;
    }

    return APR_SUCCESS;
}

/* 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.
 *
 * We should probably use a 0.9 request, but the BIO bucket code is calling
 * socket_bucket_read one extra time with all 0.9 requests from the client.
 * Until that is resolved, continue to use a 1.0 request, just like we
 * always have.
 */
#define HTTP_ON_HTTPS_PORT \
    "GET / HTTP/1.0"

#define HTTP_ON_HTTPS_PORT_BUCKET(alloc) \
    apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, \
                               sizeof(HTTP_ON_HTTPS_PORT) - 1, \
                               alloc)

static void ssl_io_filter_disable(ap_filter_t *f)
{
    ssl_io_input_ctx_t *ctx = f->ctx;
    ctx->inbio.ssl = NULL;
    ctx->frec->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;

    switch (status) {
      case HTTP_BAD_REQUEST:
            /* log the situation */
            ap_log_error(APLOG_MARK, APLOG_ERR, 0,
                         f->c->base_server,
                         "SSL handshake failed: HTTP spoken on HTTPS port; "
                         "trying to send HTML error page");
            ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, f->c->base_server);

            sslconn->non_ssl_request = 1;
            ssl_io_filter_disable(f);

            /* fake the request line */
            bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc);
            break;

      default:
        return status;
    }

    APR_BRIGADE_INSERT_TAIL(bb, bucket);

    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;
    ssl_io_input_ctx_t *ctx = f->ctx;

    apr_size_t len = sizeof(ctx->buffer);
    int is_init = (mode == AP_MODE_INIT);

    if (!ctx->inbio.ssl) {
        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;
    }

    ctx->inbio.mode = mode;
    ctx->inbio.block = block;

    /* XXX: we could actually move ssl_hook_process_connection 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.
     */
    status = ssl_hook_process_connection(ctx->frec);

    if (status != 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 (ctx->inbio.mode == AP_MODE_READBYTES || 
        ctx->inbio.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(ctx, ctx->buffer, &len);
    }
    else if (ctx->inbio.mode == AP_MODE_GETLINE) {
        status = ssl_io_input_getline(ctx, ctx->buffer, &len);
    }
    else {
        /* We have no idea what you are talking about, so return an error. */
        return APR_ENOTIMPL;
    }

    if (status != APR_SUCCESS) {
        return ssl_io_filter_error(f, bb, status);
    }

    if (len > 0) {
        apr_bucket *bucket =
            apr_bucket_transient_create(ctx->buffer, len, f->c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(bb, bucket);
    }

    return APR_SUCCESS;
}

static void ssl_io_input_add_filter(SSLFilterRec *frec, conn_rec *c,
                                    SSL *ssl)
{
    ssl_io_input_ctx_t *ctx;

    ctx = apr_palloc(c->pool, sizeof(*ctx));

    frec->pInputFilter = ap_add_input_filter(ssl_io_filter, ctx, NULL, c);

    frec->pbioRead = BIO_new(BIO_s_in_bucket());
    frec->pbioRead->ptr = (void *)&ctx->inbio;

    ctx->frec = frec;
    ctx->inbio.ssl = ssl;
    ctx->inbio.wbio = frec->pbioWrite;
    ctx->inbio.f = frec->pInputFilter;
    ctx->inbio.bb = apr_brigade_create(c->pool, c->bucket_alloc);
    ctx->inbio.bucket = NULL;
    ctx->inbio.cbuf.length = 0;

    ctx->cbuf.length = 0;

    ctx->pool = c->pool;
}

static apr_status_t ssl_io_filter_cleanup (void *data)
{
    apr_status_t ret;
    SSLFilterRec *pRec = (SSLFilterRec *)data;

    if (!pRec->pssl) {
        /* already been shutdown */
        return APR_SUCCESS;
    }

    if ((ret = ssl_hook_CloseConnection(pRec)) != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, ret, NULL,
                     "Error in ssl_hook_CloseConnection");
    }

    return ret;
}

void ssl_io_filter_init(conn_rec *c, SSL *ssl)
{
    SSLFilterRec *filter;

    filter = apr_palloc(c->pool, sizeof(SSLFilterRec));

    filter->pOutputFilter   = ap_add_output_filter(ssl_io_filter,
                                                   filter, NULL, c);

    filter->pbioWrite       = BIO_new(BIO_s_bucket());
    filter->pbioWrite->ptr  = (void *)BIO_bucket_new(filter, c);

    ssl_io_input_add_filter(filter, c, ssl);

    SSL_set_bio(ssl, filter->pbioRead, filter->pbioWrite);
    filter->pssl            = ssl;

    apr_pool_cleanup_register(c->pool, (void*)filter,
                              ssl_io_filter_cleanup, apr_pool_cleanup_null);

    if (c->base_server->loglevel >= APLOG_DEBUG) {
        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_filter, ssl_io_filter_Output, NULL, AP_FTYPE_CONNECTION + 5);
    return;
}

/*  _________________________________________________________________
**
**  I/O Data Debugging
**  _________________________________________________________________
*/

#define DUMP_WIDTH 16

static void ssl_io_data_dump(server_rec *srvr,
                             MODSSL_BIO_CB_ARG_TYPE *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_DEBUG, 0, srvr,
            "+-------------------------------------------------------------------------+");
    for(i = 0 ; i< rows; i++) {
        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;
                apr_snprintf(tmp, sizeof(tmp), "%c", ((ch >= ' ') && (ch <= '~')) ? ch : '.');
                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_DEBUG, 0, srvr,
                     "%s", buf);
    }
    if (trunc > 0)
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, srvr,
                "| %04ld - <SPACES/NULS>", len + trunc);
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, srvr,
            "+-------------------------------------------------------------------------+");
    return;
}

long ssl_io_data_cb(BIO *bio, int cmd,
                    MODSSL_BIO_CB_ARG_TYPE *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 = c->base_server;

    if (   cmd == (BIO_CB_WRITE|BIO_CB_RETURN)
        || cmd == (BIO_CB_READ |BIO_CB_RETURN) ) {
        if (rc >= 0) {
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
                    "%s: %s %ld/%d bytes %s BIO#%p [mem: %p] %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)
                ssl_io_data_dump(s, argp, rc);
        }
        else {
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
                    "%s: I/O error, %d bytes expected to %s on BIO#%p [mem: %p]",
                    SSL_LIBRARY_NAME, argi,
                    (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"),
                    bio, argp);
        }
    }
    return rc;
}
