/* _ _ ** _ __ ___ ___ __| | ___ ___| | 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 */ 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; } #define BIO_mem(b) ((BUF_MEM *)b->ptr) static apr_status_t churn_output(SSLFilterRec *ctx) { ap_filter_t *f = ctx->pOutputFilter; apr_pool_t *p = f->c->pool; if (!ctx->pssl) { /* we've been shutdown */ return APR_EOF; } if (BIO_pending(ctx->pbioWrite)) { BUF_MEM *bm = BIO_mem(ctx->pbioWrite); apr_bucket_brigade *bb = apr_brigade_create(p); apr_bucket *bucket; /* * use the BIO memory buffer that has already been allocated, * rather than making another copy of it. * use bm directly here is *much* faster than calling BIO_read() * look at crypto/bio/bss_mem.c:mem_read and you'll see why */ bucket = apr_bucket_transient_create((const char *)bm->data, bm->length); bm->length = 0; /* reset */ APR_BRIGADE_INSERT_TAIL(bb, bucket); /* XXX: it may be possible to not always flush */ bucket = apr_bucket_flush_create(); APR_BRIGADE_INSERT_TAIL(bb, bucket); return ap_pass_brigade(f->next, bb); } return APR_SUCCESS; } #define bio_is_renegotiating(bio) \ (((int)BIO_get_callback_arg(bio)) == SSL_ST_RENEGOTIATE) #define HTTP_ON_HTTPS_PORT "GET /mod_ssl:error:HTTP-request HTTP/1.0\r\n" static apr_status_t churn_input(SSLFilterRec *pRec, ap_input_mode_t eMode, apr_off_t *readbytes) { ap_filter_t *f = pRec->pInputFilter; SSLFilterRec *ctx = pRec; conn_rec *c = f->c; apr_pool_t *p = c->pool; apr_bucket *e; int found_eos = 0, n; char buf[1024]; apr_status_t rv; /* Flush the output buffers. */ churn_output(pRec); /* We have something in the processed brigade. Use that first. */ if (!APR_BRIGADE_EMPTY(ctx->b)) { return APR_SUCCESS; } /* If we have nothing in the raw brigade, get some more. */ if (APR_BRIGADE_EMPTY(ctx->rawb)) { rv = ap_get_brigade(f->next, ctx->rawb, eMode, readbytes); if (rv != APR_SUCCESS) return rv; /* Can't make any progress here. */ if (*readbytes == 0) { /* This means that we have nothing else to read ever. */ if (eMode == AP_MODE_BLOCKING) { APR_BRIGADE_INSERT_TAIL(ctx->b, apr_bucket_eos_create()); } return APR_SUCCESS; } } /* Process anything we have that we haven't done so already. */ while (!APR_BRIGADE_EMPTY(ctx->rawb)) { const char *data; apr_size_t len; e = APR_BRIGADE_FIRST(ctx->rawb); if (APR_BUCKET_IS_EOS(e)) { apr_bucket_delete(e); found_eos = 1; break; } /* read from the bucket */ rv = apr_bucket_read(e, &data, &len, eMode); if (rv != APR_SUCCESS) return rv; /* Write it to our BIO */ n = BIO_write(pRec->pbioRead, data, len); if ((apr_size_t)n != len) { /* this should never really happen, since we're just writing * into a memory buffer, unless, of course, we run out of * memory */ ssl_log(c->base_server, SSL_LOG_ERROR|SSL_ADD_SSLERR, "attempting to write %d bytes to rbio, only wrote %d", len, n); return APR_ENOMEM; } /* If we reached here, we read the bucket successfully, so toss * it from the raw brigade. */ apr_bucket_delete(e); } /* Flush the output buffers. */ churn_output(pRec); /* Before we actually read any unencrypted data, go ahead and * let ssl_hook_process_connection have a shot at it. */ rv = ssl_hook_process_connection(pRec); /* Flush again. */ churn_output(pRec); if (rv != APR_SUCCESS) { /* if process connection says HTTP_BAD_REQUEST, we've seen a * HTTP on HTTPS error. * * The case where OpenSSL has recognized a HTTP request: * This means the client speaks plain HTTP on our HTTPS port. * Hmmmm... At least for this error we can be more friendly * and try to provide him with a HTML error page. We have only * one problem:OpenSSL has already read some bytes from the HTTP * request. So we have to skip the request line manually and * instead provide a faked one in order to continue the internal * Apache processing. * */ if (rv == HTTP_BAD_REQUEST) { /* log the situation */ ssl_log(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 */ e = apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, sizeof(HTTP_ON_HTTPS_PORT) - 1); APR_BRIGADE_INSERT_TAIL(ctx->b, e); e = apr_bucket_immortal_create(CRLF, sizeof(CRLF) - 1); APR_BRIGADE_INSERT_TAIL(ctx->b, e); return APR_SUCCESS; } if (rv == SSL_ERROR_WANT_READ) { apr_off_t tempread = 1024; return churn_input(pRec, eMode, &tempread); } return rv; } /* try to pass along all of the current BIO to ctx->b */ /* FIXME: If there's an error and there was EOS, we may not really * reach EOS. */ while ((n = ssl_io_hook_read(pRec->pssl, buf, sizeof(buf))) > 0) { char *pbuf; pbuf = apr_pmemdup(p, buf, n); e = apr_bucket_pool_create(pbuf, n, p); APR_BRIGADE_INSERT_TAIL(ctx->b, e); /* Flush the output buffers. */ churn_output(pRec); } if (n < 0 && errno == EINTR && APR_BRIGADE_EMPTY(ctx->b)) { apr_off_t tempread = 1024; return churn_input(pRec, eMode, &tempread); } if (found_eos) { APR_BRIGADE_INSERT_TAIL(ctx->b, apr_bucket_eos_create()); } return churn_output(pRec); } static apr_status_t ssl_io_filter_Output(ap_filter_t *f, apr_bucket_brigade *bb) { SSLFilterRec *ctx = f->ctx; apr_bucket *bucket; apr_status_t ret = APR_SUCCESS; while (!APR_BRIGADE_EMPTY(bb)) { const char *data; apr_size_t len, n; 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)) { apr_bucket_brigade *outbb; if ((ret = churn_output(ctx)) != APR_SUCCESS) { return ret; } outbb = apr_brigade_create(f->c->pool); APR_BUCKET_REMOVE(bucket); APR_BRIGADE_INSERT_TAIL(outbb, bucket); ret = ap_pass_brigade(f->next, outbb); if (ret != APR_SUCCESS) { return ret; } /* By definition, nothing can come after EOS. */ if (APR_BUCKET_IS_EOS(bucket)) { break; } } else { /* read filter */ apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); /* 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); ret = APR_EINVAL; break; } /* churn the state machine */ if ((ret = churn_output(ctx)) != APR_SUCCESS) { break; } } apr_bucket_delete(bucket); } apr_brigade_destroy(bb); return ret; } static apr_status_t ssl_io_filter_Input(ap_filter_t *f, apr_bucket_brigade *pbbOut, ap_input_mode_t mode, apr_off_t *readbytes) { apr_status_t ret; SSLFilterRec *ctx = f->ctx; apr_status_t rv; apr_bucket *e; apr_off_t tempread; /* XXX: we don't currently support peek or readbytes == -1 */ if (mode == AP_MODE_PEEK || *readbytes == -1) { return APR_ENOTIMPL; } /* Return the requested amount or less. */ if (*readbytes) { apr_bucket_brigade *newbb; /* churn the state machine */ ret = churn_input(ctx, mode, readbytes); if (ret != APR_SUCCESS) return ret; /* ### This is bad. */ APR_BRIGADE_NORMALIZE(ctx->b); apr_brigade_length(ctx->b, 0, &tempread); if (*readbytes < tempread) { tempread = *readbytes; } else { *readbytes = tempread; } apr_brigade_partition(ctx->b, tempread, &e); newbb = apr_brigade_split(ctx->b, e); APR_BRIGADE_CONCAT(pbbOut, ctx->b); APR_BRIGADE_CONCAT(ctx->b, newbb); return APR_SUCCESS; } /* Readbytes == 0 implies we only want a LF line. * 1024 seems like a good number for now. */ if (APR_BRIGADE_EMPTY(ctx->b)) { tempread = 1024; rv = churn_input(ctx, mode, &tempread); if (rv != APR_SUCCESS) return rv; /* We have already blocked. */ mode = AP_MODE_NONBLOCKING; } while (!APR_BRIGADE_EMPTY(ctx->b)) { const char *pos, *str; apr_size_t len; e = APR_BRIGADE_FIRST(ctx->b); /* Sure, we'll call this is a line. Whatever. */ if (APR_BUCKET_IS_EOS(e)) { APR_BUCKET_REMOVE(e); APR_BRIGADE_INSERT_TAIL(pbbOut, e); break; } if ((rv = apr_bucket_read(e, &str, &len, AP_MODE_NONBLOCKING)) != APR_SUCCESS) { return rv; } pos = memchr(str, APR_ASCII_LF, len); /* We found a match. */ if (pos != NULL) { apr_bucket_split(e, pos - str + 1); APR_BUCKET_REMOVE(e); APR_BRIGADE_INSERT_TAIL(pbbOut, e); *readbytes += pos - str; return APR_SUCCESS; } APR_BUCKET_REMOVE(e); APR_BRIGADE_INSERT_TAIL(pbbOut, e); *readbytes += len; /* Hey, we're about to be starved - go fetch more data. */ if (APR_BRIGADE_EMPTY(ctx->b)) { tempread = 1024; ret = churn_input(ctx, mode, &tempread); if (ret != APR_SUCCESS) return ret; mode = AP_MODE_NONBLOCKING; } } return APR_SUCCESS; } 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_pcalloc(c->pool, sizeof(SSLFilterRec)); filter->pInputFilter = ap_add_input_filter(ssl_io_filter, filter, NULL, c); filter->pOutputFilter = ap_add_output_filter(ssl_io_filter, filter, NULL, c); filter->b = apr_brigade_create(c->pool); filter->rawb = apr_brigade_create(c->pool); filter->pbioRead = BIO_new(BIO_s_mem()); filter->pbioWrite = BIO_new(BIO_s_mem()); 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) { /* XXX: this will currently get wiped out if renegotiation * happens in ssl_hook_Access */ 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_NETWORK); ap_register_output_filter (ssl_io_filter, ssl_io_filter_Output, AP_FTYPE_NETWORK); 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 - ", 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; }