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

/* ====================================================================
 * Copyright (c) 1998-2000 Ralf S. Engelschall. 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. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by
 *     Ralf S. Engelschall <rse@engelschall.com> for use in the
 *     mod_ssl project (http://www.modssl.org/)."
 *
 * 4. The names "mod_ssl" must not be used to endorse or promote
 *    products derived from this software without prior written
 *    permission. For written permission, please contact
 *    rse@engelschall.com.
 *
 * 5. Products derived from this software may not be called "mod_ssl"
 *    nor may "mod_ssl" appear in their names without prior
 *    written permission of Ralf S. Engelschall.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by
 *     Ralf S. Engelschall <rse@engelschall.com> for use in the
 *     mod_ssl project (http://www.modssl.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY RALF S. ENGELSCHALL ``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 RALF S. ENGELSCHALL OR
 * HIS 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 Request Body Sucking and Re-Injection
**  _________________________________________________________________
*/

#ifndef SSL_CONSERVATIVE

/*
 * Background:
 *
 * 1. When the client sends a HTTP/HTTPS request, Apache's core code
 * reads only the request line ("METHOD /path HTTP/x.y") and the
 * attached MIME headers ("Foo: bar") up to the terminating line ("CR
 * LF"). An attached request body (for instance the data of a POST
 * method) is _NOT_ read. Instead it is read by mod_cgi's content
 * handler and directly passed to the CGI script.
 *
 * 2. mod_ssl supports per-directory re-configuration of SSL parameters.
 * This is implemented by performing an SSL renegotiation of the
 * re-configured parameters after the request is read, but before the
 * response is sent. In more detail: the renegotiation happens after the
 * request line and MIME headers were read, but _before_ the attached
 * request body is read. The reason simply is that in the HTTP protocol
 * usually there is no acknowledgment step between the headers and the
 * body (there is the 100-continue feature and the chunking facility
 * only), so Apache has no API hook for this step.
 *
 * 3. the problem now occurs when the client sends a POST request for
 * URL /foo via HTTPS the server and the server has SSL parameters
 * re-configured on a per-URL basis for /foo. Then mod_ssl has to
 * perform an SSL renegotiation after the request was read and before
 * the response is sent. But the problem is the pending POST body data
 * in the receive buffer of SSL (which Apache still has not read - it's
 * pending until mod_cgi sucks it in). When mod_ssl now tries to perform
 * the renegotiation the pending data leads to an I/O error.
 *
 * Solution Idea:
 *
 * There are only two solutions: Either to simply state that POST
 * requests to URLs with SSL re-configurations are not allowed, or to
 * renegotiate really after the _complete_ request (i.e. including
 * the POST body) was read. Obviously the latter would be preferred,
 * but it cannot be done easily inside Apache, because as already
 * mentioned, there is no API step between the body reading and the body
 * processing. And even when we mod_ssl would hook directly into the
 * loop of mod_cgi, we wouldn't solve the problem for other handlers, of
 * course. So the only general solution is to suck in the pending data
 * of the request body from the OpenSSL BIO into the Apache BUFF. Then
 * the renegotiation can be done and after this step Apache can proceed
 * processing the request as before.
 *
 * Solution Implementation:
 *
 * We cannot simply suck in the data via an SSL_read-based loop because of
 * HTTP chunking. Instead we _have_ to use the Apache API for this step which
 * is aware of HTTP chunking. So the trick is to suck in the pending request
 * data via the Apache API (which uses Apache's BUFF code and in the
 * background mod_ssl's I/O glue code) and re-inject it later into the Apache
 * BUFF code again. This way the data flows twice through the Apache BUFF, of
 * course. But this way the solution doesn't depend on any Apache specifics
 * and is fully transparent to Apache modules.
 */

struct ssl_io_suck_st {
    BOOL  active;
    char *bufptr;
    int   buflen;
    char *pendptr;
    int   pendlen;
};

/* prepare request_rec structure for input sucking */
static void ssl_io_suck_start(request_rec *r)
{
    struct ssl_io_suck_st *ss;

    ss = ap_ctx_get(r->ctx, "ssl::io::suck");
    if (ss == NULL) {
        ss = ap_palloc(r->pool, sizeof(struct ssl_io_suck_st));
        ap_ctx_set(r->ctx, "ssl::io::suck", ss);
        ss->buflen  = 8192;
        ss->bufptr  = ap_palloc(r->pool, ss->buflen);
    }
    ss->pendptr = ss->bufptr;
    ss->pendlen = 0;
    ss->active = FALSE;
    return;
}

/* record a sucked input chunk */
static void ssl_io_suck_record(request_rec *r, char *buf, int len)
{
    struct ssl_io_suck_st *ss;
    
    if ((ss = ap_ctx_get(r->ctx, "ssl::io::suck")) == NULL)
        return;
    if (((ss->bufptr + ss->buflen) - (ss->pendptr + ss->pendlen)) < len) {
        /* "expand" buffer: actually we cannot really expand the buffer
           here, because Apache's pool system doesn't support expanding chunks
           of memory. Instead we have to either reuse processed data or
           allocate a new chunk of memory in advance if we really need more
           memory. */
        int newlen;
        char *newptr;

        if ((  (ss->pendptr - ss->bufptr) 
             + ((ss->bufptr + ss->buflen) - (ss->pendptr + ss->pendlen)) ) >= len) {
            /* make memory available by reusing already processed data */
            memmove(ss->bufptr, ss->pendptr, ss->pendlen);
            ss->pendptr = ss->bufptr;
        }
        else {
            /* too bad, we have to allocate a new larger buffer */
            newlen = (ss->buflen * 2) + len;
            newptr = ap_palloc(r->pool, newlen);
            ss->bufptr  = newptr;
            ss->buflen  = newlen;
            memcpy(ss->bufptr, ss->pendptr, ss->pendlen);
            ss->pendptr = ss->bufptr;
        }
    }
    memcpy(ss->pendptr+ss->pendlen, buf, len);
    ss->pendlen += len;
    return;
}

/* finish request_rec after input sucking */
static void ssl_io_suck_end(request_rec *r)
{
    struct ssl_io_suck_st *ss;
    
    if ((ss = ap_ctx_get(r->ctx, "ssl::io::suck")) == NULL)
        return;
    ss->active = TRUE;
    r->read_body    = REQUEST_NO_BODY;
    r->read_length  = 0;
    r->read_chunked = 0;
    r->remaining    = 0;
    ap_bsetflag(r->connection->client, B_CHUNK, 0);
    return;
}

void ssl_io_suck(request_rec *r, SSL *ssl)
{
    int rc;
    int len;
    char *buf;
    int buflen;
    char c;
    int sucked;

    if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)) == OK) {
        if (ap_should_client_block(r)) {

            /* read client request block through Apache API */
            buflen = HUGE_STRING_LEN;
            buf = ap_palloc(r->pool, buflen);
            ap_hard_timeout("SSL I/O request body pre-sucking", r);
            sucked = 0;
            ssl_io_suck_start(r);
            while ((len = ap_get_client_block(r, buf, buflen)) > 0) {
                ssl_io_suck_record(r, buf, len);
                sucked += len;
            }
            ssl_io_suck_end(r);
            ap_kill_timeout(r);

            /* suck trailing data (usually CR LF) which 
               is still in the Apache BUFF layer */
            while (ap_bpeekc(r->connection->client) != EOF) {
                c = ap_bgetc(r->connection->client);
                ssl_io_suck_record(r, &c, 1);
                sucked++;
            }

            ssl_log(r->server, SSL_LOG_TRACE, 
                    "I/O: sucked %d bytes of input data from SSL/TLS I/O layer "
                    "for delayed injection into Apache I/O layer", sucked);
        }
    }
    return;
}
    
/* the SSL_read replacement routine which knows about the suck buffer */
static int ssl_io_suck_read(SSL *ssl, char *buf, int len)
{
    ap_ctx *actx;
    struct ssl_io_suck_st *ss;
    request_rec *r = NULL;
    int rv;

    actx = (ap_ctx *)SSL_get_app_data2(ssl);
    if (actx != NULL)
        r = (request_rec *)ap_ctx_get(actx, "ssl::request_rec");

    rv = -1;
    if (r != NULL) {
        ss = ap_ctx_get(r->ctx, "ssl::io::suck");
        if (ss != NULL) {
            if (ss->active && ss->pendlen > 0) {
                /* ok, there is pre-sucked data */
                len = (ss->pendlen > len ? len : ss->pendlen);
                memcpy(buf, ss->pendptr, len);
                ss->pendptr += len;
                ss->pendlen -= len;
                ssl_log(r->server, SSL_LOG_TRACE, 
                        "I/O: injecting %d bytes of pre-sucked data "
                        "into Apache I/O layer", len);
                rv = len;
            }
        }
    }
    if (rv == -1)
        rv = SSL_read(ssl, buf, len);
    return rv;
}

/* override SSL_read in the following code... */
#define SSL_read ssl_io_suck_read

#endif /* !SSL_CONSERVATIVE */

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

#ifndef NO_WRITEV
#include <sys/types.h>
#include <sys/uio.h>
#endif

static int ssl_io_hook_read(BUFF *fb, char *buf, int len);
static int ssl_io_hook_write(BUFF *fb, char *buf, int len);
#ifndef NO_WRITEV
static int ssl_io_hook_writev(BUFF *fb, const struct iovec *iov, int iovcnt);
#endif
#ifdef WIN32
static int ssl_io_hook_recvwithtimeout(BUFF *fb, char *buf, int len);
static int ssl_io_hook_sendwithtimeout(BUFF *fb, const char *buf, int len);
#endif /* WIN32 */

void ssl_io_register(void)
{
    ap_hook_register("ap::buff::read",   ssl_io_hook_read,  AP_HOOK_NOCTX);
    ap_hook_register("ap::buff::write",  ssl_io_hook_write, AP_HOOK_NOCTX);
#ifndef NO_WRITEV
    ap_hook_register("ap::buff::writev", ssl_io_hook_writev, AP_HOOK_NOCTX);
#endif
#ifdef WIN32
    ap_hook_register("ap::buff::recvwithtimeout",
                     ssl_io_hook_recvwithtimeout, AP_HOOK_NOCTX);
    ap_hook_register("ap::buff::sendwithtimeout",
                     ssl_io_hook_sendwithtimeout, AP_HOOK_NOCTX);
#endif
    return;
}

void ssl_io_unregister(void)
{
    ap_hook_unregister("ap::buff::read",   ssl_io_hook_read);
    ap_hook_unregister("ap::buff::write",  ssl_io_hook_write);
#ifndef NO_WRITEV
    ap_hook_unregister("ap::buff::writev", ssl_io_hook_writev);
#endif
#ifdef WIN32
    ap_hook_unregister("ap::buff::recvwithtimeout", ssl_io_hook_recvwithtimeout);
    ap_hook_unregister("ap::buff::sendwithtimeout", ssl_io_hook_sendwithtimeout);
#endif
    return;
}

static int ssl_io_hook_read(BUFF *fb, char *buf, int len)
{
    SSL *ssl;
    conn_rec *c;
    int rc;

    if ((ssl = ap_ctx_get(fb->ctx, "ssl")) != NULL) {
        rc = SSL_read(ssl, buf, len);
        /*
         * 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.)
         */
        if (rc < 0 && SSL_get_error(ssl, rc) == SSL_ERROR_WANT_READ)
            errno = EINTR;
        /*
         * Log SSL errors
         */
        if (rc < 0 && SSL_get_error(ssl, rc) == SSL_ERROR_SSL) {
            c = (conn_rec *)SSL_get_app_data(ssl);
            ssl_log(c->server, SSL_LOG_ERROR|SSL_ADD_SSLERR,
                    "SSL error on reading data");
        }
        /*
         * read(2) returns only the generic error number -1
         */
        if (rc < 0)
            rc = -1;
    }
    else
        rc = read(fb->fd_in, buf, len);
    return rc;
}

static int ssl_io_hook_write(BUFF *fb, char *buf, int len)
{
    SSL *ssl;
    conn_rec *c;
    int rc;

    if ((ssl = ap_ctx_get(fb->ctx, "ssl")) != NULL) {
        rc = SSL_write(ssl, buf, len);
        /*
         * Simulate an EINTR in case OpenSSL wants to write more.
         */
        if (rc < 0 && SSL_get_error(ssl, rc) == SSL_ERROR_WANT_WRITE)
            errno = EINTR;
        /*
         * Log SSL errors
         */
        if (rc < 0 && SSL_get_error(ssl, rc) == SSL_ERROR_SSL) {
            c = (conn_rec *)SSL_get_app_data(ssl);
            ssl_log(c->server, SSL_LOG_ERROR|SSL_ADD_SSLERR,
                    "SSL error on writing data");
        }
        /*
         * write(2) returns only the generic error number -1
         */
        if (rc < 0)
            rc = -1;
    }
    else
        rc = write(fb->fd, buf, len);
    return rc;
}

#ifndef NO_WRITEV
/* the prototype for our own SSL_writev() */
static int SSL_writev(SSL *, const struct iovec *, int);

static int ssl_io_hook_writev(BUFF *fb, const struct iovec *iov, int iovcnt)
{
    SSL *ssl;
    conn_rec *c;
    int rc;

    if ((ssl = ap_ctx_get(fb->ctx, "ssl")) != NULL) {
        rc = SSL_writev(ssl, iov, iovcnt);
        /*
         * Simulate an EINTR in case OpenSSL wants to write more.
         */
        if (rc < 0 && SSL_get_error(ssl, rc) == SSL_ERROR_WANT_WRITE)
            errno = EINTR;
        /*
         * Log SSL errors
         */
        if (rc < 0 && SSL_get_error(ssl, rc) == SSL_ERROR_SSL) {
            c = (conn_rec *)SSL_get_app_data(ssl);
            ssl_log(c->server, SSL_LOG_ERROR|SSL_ADD_SSLERR,
                    "SSL error on writing data");
        }
        /*
         * writev(2) returns only the generic error number -1
         */
        if (rc < 0)
            rc = -1;
    }
    else
        rc = writev(fb->fd, iov, iovcnt);
    return rc;
}
#endif

#ifdef WIN32

/* these two functions are exported from buff.c under WIN32 */
API_EXPORT(int) sendwithtimeout(int sock, const char *buf, int len, int flags);
API_EXPORT(int) recvwithtimeout(int sock, char *buf, int len, int flags);

/* and the prototypes for our SSL_xxx variants */
static int SSL_sendwithtimeout(BUFF *fb, const char *buf, int len);
static int SSL_recvwithtimeout(BUFF *fb, char *buf, int len);

static int ssl_io_hook_recvwithtimeout(BUFF *fb, char *buf, int len)
{
    SSL *ssl;
    int rc;

    if ((ssl = ap_ctx_get(fb->ctx, "ssl")) != NULL)
        rc = SSL_recvwithtimeout(fb, buf, len);
    else
        rc = recvwithtimeout(fb->fd, buf, len, 0);
    return rc;
}

static int ssl_io_hook_sendwithtimeout(BUFF *fb, const char *buf, int len)
{
    SSL *ssl;
    int rc;

    if ((ssl = ap_ctx_get(fb->ctx, "ssl")) != NULL)
        rc = SSL_sendwithtimeout(fb, buf, len);
    else
        rc = sendwithtimeout(fb->fd, buf, len, 0);
    return rc;
}

#endif /* WIN32 */

/*  _________________________________________________________________
**
**  Special Functions for OpenSSL
**  _________________________________________________________________
*/

#ifdef WIN32

static int SSL_sendwithtimeout(BUFF *fb, const char *buf, int len)
{
    int iostate = 1;
    fd_set fdset;
    struct timeval tv;
    int err = WSAEWOULDBLOCK;
    int rv;
    int retry;
    int sock = fb->fd;
    SSL *ssl;

    ssl = ap_ctx_get(fb->ctx, "ssl");

    if (!(tv.tv_sec = ap_check_alarm()))
        return (SSL_write(ssl, (char*)buf, len));

    rv = ioctlsocket(sock, FIONBIO, &iostate);
    iostate = 0;
    if (rv) {
        err = WSAGetLastError();
        ap_assert(0);
    }
    rv = SSL_write(ssl, (char*)buf, len);
    if (rv <= 0) {
        if (BIO_sock_should_retry(rv)) {
            do {
                retry=0;
                FD_ZERO(&fdset);
                FD_SET((unsigned int)sock, &fdset);
                tv.tv_usec = 0;
                rv = select(FD_SETSIZE, NULL, &fdset, NULL, &tv);
                if (rv == SOCKET_ERROR)
                    err = WSAGetLastError();
                else if (rv == 0) {
                    ioctlsocket(sock, FIONBIO, &iostate);
                    if(ap_check_alarm() < 0) {
                        WSASetLastError(EINTR); /* Simulate an alarm() */
                        return (SOCKET_ERROR);
                    }
                }
                else {
                    rv = SSL_write(ssl, (char*)buf, len);
                    if (BIO_sock_should_retry(rv)) {
                        ap_log_error(APLOG_MARK,APLOG_DEBUG, NULL,
                                     "select claimed we could write, "
                                     "but in fact we couldn't. "
                                     "This is a bug in Windows.");
                        retry=1;
                        Sleep(100);
                    }
                }
            } while(retry);
        }
    }
    ioctlsocket(sock, FIONBIO, &iostate);
    if (rv == SOCKET_ERROR)
        WSASetLastError(err);
    return (rv);
}

static int SSL_recvwithtimeout(BUFF *fb, char *buf, int len)
{
    int iostate = 1;
    fd_set fdset;
    struct timeval tv;
    int err = WSAEWOULDBLOCK;
    int rv;
    int sock = fb->fd_in;
    SSL *ssl;

    ssl = ap_ctx_get(fb->ctx, "ssl");

    if (!(tv.tv_sec = ap_check_alarm()))
        return (SSL_read(ssl, buf, len));

    rv = ioctlsocket(sock, FIONBIO, &iostate);
    iostate = 0;
    ap_assert(!rv);
    rv = SSL_read(ssl, buf, len);
    if (rv <= 0) {
        if (BIO_sock_should_retry(rv)) {
            FD_ZERO(&fdset);
            FD_SET((unsigned int)sock, &fdset);
            tv.tv_usec = 0;
            rv = select(FD_SETSIZE, &fdset, NULL, NULL, &tv);
            if (rv == SOCKET_ERROR)
                err = WSAGetLastError();
            else if (rv == 0) {
                ioctlsocket(sock, FIONBIO, &iostate);
                ap_check_alarm();
                WSASetLastError(WSAEWOULDBLOCK);
                return (SOCKET_ERROR);
            }
            else {
                rv = SSL_read(ssl, buf, len);
                if (rv == SOCKET_ERROR)
                    err = WSAGetLastError();
            }
        }
    }
    ioctlsocket(sock, FIONBIO, &iostate);
    if (rv == SOCKET_ERROR)
        WSASetLastError(err);
    return (rv);
}

#endif /*WIN32*/

/*
 * There is no SSL_writev() provided by OpenSSL. The reason is mainly because
 * OpenSSL has to fragment the data itself again for the SSL record layer, so a
 * writev() like interface makes not much sense.  What we do is to emulate it
 * to at least being able to use the write() like interface. But keep in mind
 * that the network I/O performance is not write() like, of course.
 */
#ifndef NO_WRITEV
static int SSL_writev(SSL *ssl, const struct iovec *iov, int iovcnt)
{
    int i;
    int n;
    int rc;

    rc = 0;
    for (i = 0; i < iovcnt; i++) {
        if ((n = SSL_write(ssl, iov[i].iov_base, iov[i].iov_len)) == -1) {
            rc = -1;
            break;
        }
        rc += n;
    }
    return rc;
}
#endif

/*  _________________________________________________________________
**
**  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++;
    ssl_log(srvr, SSL_LOG_DEBUG|SSL_NO_TIMESTAMP|SSL_NO_LEVELID,
            "+-------------------------------------------------------------------------+");
    for(i = 0 ; i< rows; i++) {
        ap_snprintf(tmp, sizeof(tmp), "| %04x: ", i * DUMP_WIDTH);
        ap_cpystrn(buf, tmp, sizeof(buf));
        for (j = 0; j < DUMP_WIDTH; j++) {
            if (((i * DUMP_WIDTH) + j) >= len)
                ap_cpystrn(buf+strlen(buf), "   ", sizeof(buf)-strlen(buf));
            else {
                ch = ((unsigned char)*((char *)(s) + i * DUMP_WIDTH + j)) & 0xff;
                ap_snprintf(tmp, sizeof(tmp), "%02x%c", ch , j==7 ? '-' : ' ');
                ap_cpystrn(buf+strlen(buf), tmp, sizeof(buf)-strlen(buf));
            }
        }
        ap_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf));
        for (j = 0; j < DUMP_WIDTH; j++) {
            if (((i * DUMP_WIDTH) + j) >= len)
                ap_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf));
            else {
                ch = ((unsigned char)*((char *)(s) + i * DUMP_WIDTH + j)) & 0xff;
                ap_snprintf(tmp, sizeof(tmp), "%c", ((ch >= ' ') && (ch <= '~')) ? ch : '.');
                ap_cpystrn(buf+strlen(buf), tmp, sizeof(buf)-strlen(buf));
            }
        }
        ap_cpystrn(buf+strlen(buf), " |", sizeof(buf)-strlen(buf));
        ssl_log(srvr, SSL_LOG_DEBUG|SSL_NO_TIMESTAMP|SSL_NO_LEVELID, "%s", buf);
    }
    if (trunc > 0)
        ssl_log(srvr, SSL_LOG_DEBUG|SSL_NO_TIMESTAMP|SSL_NO_LEVELID,
                "| %04x - <SPACES/NULS>", len + trunc);
    ssl_log(srvr, SSL_LOG_DEBUG|SSL_NO_TIMESTAMP|SSL_NO_LEVELID,
            "+-------------------------------------------------------------------------+");
    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 = c->server;

    if (   cmd == (BIO_CB_WRITE|BIO_CB_RETURN)
        || cmd == (BIO_CB_READ |BIO_CB_RETURN) ) {
        if (rc >= 0) {
            ssl_log(s, SSL_LOG_DEBUG,
                    "%s: %s %ld/%d bytes %s BIO#%08X [mem: %08lX] %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)" : "(Ops, no memory buffer?)"));
            if (argp != NULL)
                ssl_io_data_dump(s, argp, rc);
        }
        else {
            ssl_log(s, SSL_LOG_DEBUG,
                    "%s: I/O error, %d bytes expected to %s on BIO#%08X [mem: %08lX]",
                    SSL_LIBRARY_NAME, argi,
                    (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"),
                    bio, argp);
        }
    }
    return rc;
}

