ssl_engine_io.c revision 3fde4c273ea649d1320ec9c51e7d096cd9340a94
/* _ _
** _ __ ___ ___ __| | ___ ___| | 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-2001 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);
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);
if (!(b->blen || b->length)) {
return APR_SUCCESS;
}
if (b->blen) {
apr_bucket *bucket =
apr_bucket_transient_create(b->buffer,
b->blen);
/* we filled this buffer first so add it to the
* head of the brigade
*/
APR_BRIGADE_INSERT_HEAD(b->bb, bucket);
b->blen = 0;
}
b->length = 0;
APR_BRIGADE_INSERT_TAIL(b->bb, apr_bucket_flush_create());
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->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);
int len = 0;
apr_off_t readbytes = inl;
/* 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) {
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;
}
}
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, &readbytes);
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;
}
else if (ssl_err == SSL_ERROR_SSL) {
/*
* Log SSL errors
*/
conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
ssl_log(c->base_server, SSL_LOG_ERROR|SSL_ADD_SSLERR,
"SSL error on reading data");
}
/*
* XXX - Just trying to reflect the behaviour in
* openssl_state_machine.c [mod_tls]. TBD
*/
rc = -1;
}
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);
ssl_log(c->base_server, SSL_LOG_ERROR|SSL_ADD_SSLERR,
"SSL error on writing data");
}
/*
* 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";
}
ssl_log(c->base_server, SSL_LOG_ERROR,
"failed to write %d of %d bytes (%s)",
n > 0 ? len - n : len, len, reason);
return APR_EINVAL;
}
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;
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)) {
SSLFilterRec *ctx = f->ctx;
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 ((*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;
}
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;
apr_status_t status;
status = ssl_io_input_read(ctx, buf, len);
if (status != APR_SUCCESS) {
return status;
}
if ((pos = memchr(buf, APR_ASCII_LF, *len))) {
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;
}
#define HTTP_ON_HTTPS_PORT \
"GET /mod_ssl:error:HTTP-request HTTP/1.0\r\n\r\n"
#define HTTP_ON_HTTPS_PORT_BUCKET() \
apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, \
sizeof(HTTP_ON_HTTPS_PORT) - 1)
static apr_status_t ssl_io_filter_error(ap_filter_t *f,
apr_bucket_brigade *bb,
apr_status_t status)
{
apr_bucket *bucket;
switch (status) {
case HTTP_BAD_REQUEST:
/* log the situation */
ssl_log(f->c->base_server, SSL_LOG_ERROR|SSL_ADD_SSLERR,
"SSL handshake failed: HTTP spoken on HTTPS port; "
"trying to send HTML error page");
/* fake the request line */
bucket = HTTP_ON_HTTPS_PORT_BUCKET();
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);
apr_off_t bytes = *readbytes;
int is_init = (mode == AP_MODE_INIT);
/* XXX: we don't currently support peek or readbytes == -1 */
if (mode == AP_MODE_EATCRLF || *readbytes == -1) {
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) {
/* Protected from truncation, bytes < MAX_SIZE_T */
if (bytes < len) {
len = (apr_size_t)bytes;
}
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);
APR_BRIGADE_INSERT_TAIL(bb, bucket);
}
*readbytes = len;
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 = &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);
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)
{
SSLSrvConfigRec *sc = mySrvConfig(c->base_server);
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 = 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 (sc->nLogLevel >= SSL_LOG_DEBUG) {
BIO_set_callback(SSL_get_rbio(ssl), ssl_io_data_cb);
BIO_set_callback_arg(SSL_get_rbio(ssl), ssl);
}
return;
}
void ssl_io_filter_register(apr_pool_t *p)
{
ap_register_input_filter (ssl_io_filter, ssl_io_filter_Input, AP_FTYPE_CONNECTION + 5);
ap_register_output_filter (ssl_io_filter, ssl_io_filter_Output, AP_FTYPE_CONNECTION + 5);
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++;
ssl_log(srvr, SSL_LOG_DEBUG|SSL_NO_TIMESTAMP|SSL_NO_LEVELID,
"+-------------------------------------------------------------------------+");
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));
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->base_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;
}