/* _ _ ** _ __ ___ ___ __| | ___ ___| | 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); ssl_log(c->base_server, SSL_LOG_ERROR|SSL_ADD_SSLERR, "SSL error on reading data"); } } 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; 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 == -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; } #define HTTP_ON_HTTPS_PORT \ "GET /mod_ssl:error:HTTP-request HTTP/1.0\r\n\r\n" #define HTTP_ON_HTTPS_PORT_BUCKET(alloc) \ apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, \ sizeof(HTTP_ON_HTTPS_PORT) - 1, \ alloc) 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(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); /* 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) { 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 = (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 (sc->log_level >= SSL_LOG_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, 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, 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++; 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 - ", len + trunc); ssl_log(srvr, SSL_LOG_DEBUG|SSL_NO_TIMESTAMP|SSL_NO_LEVELID, "+-------------------------------------------------------------------------+"); 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) { 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)" : "(Oops, 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; }