/* _ _ ** _ __ ___ ___ __| | ___ ___| | mod_ssl ** | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL ** | | | | | | (_) | (_| | \__ \__ \ | www.modssl.org ** |_| |_| |_|\___/ \__,_|___|___/___/_| ftp.modssl.org ** |_____| ** ssl_engine_io.c ** I/O Functions */ /* ==================================================================== * Copyright (c) 1998-2003 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 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 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 */ ap_hard_timeout("SSL I/O request trailing data pre-sucking", r); while (ap_bpeekc(r->connection->client) != EOF) { c = ap_bgetc(r->connection->client); ssl_io_suck_record(r, &c, 1); sucked++; } ap_kill_timeout(r); 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 ** _________________________________________________________________ */ #include #include static int ssl_io_hook_read(BUFF *fb, char *buf, int len); static int ssl_io_hook_write(BUFF *fb, char *buf, int len); static int ssl_io_hook_writev(BUFF *fb, const struct iovec *iov, int iovcnt); 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); ap_hook_register("ap::buff::writev", ssl_io_hook_writev, AP_HOOK_NOCTX); 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); ap_hook_unregister("ap::buff::writev", ssl_io_hook_writev); 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 * renegotiation 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; } /* 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; } /* _________________________________________________________________ ** ** Special Functions for OpenSSL ** _________________________________________________________________ */ /* * 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. */ 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; } /* _________________________________________________________________ ** ** 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 - ", 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; }