1
0
mirror of https://github.com/apache/httpd.git synced 2025-08-08 15:02:10 +03:00
Files
apache/modules/ssl/ssl_engine_io.c
Doug MacEachern d79cea3d70 enable ssl Translate, UserCheck, Access and Auth hooks
add support for renegotiation during the Access hook
this requires hooking into the read and write SSL BIOs in order to
flush data to the client and read from the filter chain

this also requires that the ssl filters become "aware" that
renegotitation is in progress so that the BIOs are left alone for
SSL_renegotiate/SSL_do_handshake in ssl_hook_Access to deal with

PR:
Obtained from:
Submitted by:
Reviewed by:


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@90185 13f79535-47bb-0310-9956-ffa450edef68
2001-08-16 03:58:16 +00:00

544 lines
17 KiB
C

/* _ _
** _ __ ___ ___ __| | ___ ___| | 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";
void ssl_io_register(void)
{
#if 0 /* XXX */
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);
#endif /* XXX */
return;
}
void ssl_io_unregister(void)
{
#if 0 /* XXX */
ap_hook_unregister("ap::buff::read", ssl_io_hook_read);
ap_hook_unregister("ap::buff::write", ssl_io_hook_write);
#endif /* XXX */
return;
}
static int ssl_io_hook_read(SSL *ssl, unsigned char *buf, int len)
{
conn_rec *c;
int rc;
if (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->base_server, SSL_LOG_ERROR|SSL_ADD_SSLERR,
"SSL error on reading data");
}
/*
* read(2) returns only the generic error number -1
*/
if (rc < 0)
/*
* XXX - Just trying to reflect the behaviour in
* openssl_state_machine.c [mod_tls]. TBD
*/
rc = -1;
}
else
rc = -1;
return rc;
}
static int ssl_io_hook_write(SSL *ssl, unsigned char *buf, int len)
{
conn_rec *c;
int rc;
if (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->base_server, SSL_LOG_ERROR|SSL_ADD_SSLERR,
"SSL error on writing data");
}
/*
* write(2) returns only the generic error number -1
*/
if (rc < 0)
/*
* XXX - Just trying to reflect the behaviour in
* openssl_state_machine.c [mod_tls]. TBD
*/
rc = 0;
}
else
rc = -1;
return rc;
}
static apr_status_t churn_output(SSLFilterRec *pRec)
{
apr_bucket_brigade *pbbOutput=NULL;
int done;
do {
char buf[1024];
int n;
apr_bucket *pbkt;
done=0;
if (BIO_pending(pRec->pbioWrite)) {
n = BIO_read(pRec->pbioWrite,buf,sizeof buf);
if(n > 0) {
char *pbuf;
if(!pbbOutput)
pbbOutput=apr_brigade_create(pRec->pOutputFilter->c->pool);
pbuf=apr_pmemdup(pRec->pOutputFilter->c->pool,buf,n);
pbkt=apr_bucket_pool_create(pbuf,n,
pRec->pOutputFilter->c->pool);
APR_BRIGADE_INSERT_TAIL(pbbOutput,pbkt);
done=1;
/* } else if(n == 0) {
apr_bucket *pbktEOS=apr_bucket_create_eos();
APR_BRIGADE_INSERT_TAIL(pbbOutput,pbktEOS);*/
}
assert (n > 0); /* XXX => Check if required */
#if 0 /* XXX */
else if (n == 0)
done = 1;
else
assert (n > 0);
#endif
}
#if 0 /* XXX */
else
{
done = 1;
}
#endif
} while(done);
/* XXX: check for errors */
if(pbbOutput) {
apr_bucket *pbkt;
/* XXX: it may be possible to not always flush */
pbkt=apr_bucket_flush_create();
APR_BRIGADE_INSERT_TAIL(pbbOutput,pbkt);
ap_pass_brigade(pRec->pOutputFilter->next,pbbOutput);
}
return APR_SUCCESS;
}
#define bio_is_renegotiating(bio) \
(((int)BIO_get_callback_arg(bio)) == SSL_ST_RENEGOTIATE)
static apr_status_t churn (SSLFilterRec *pRec,
apr_read_type_e eReadType, apr_off_t *readbytes)
{
apr_bucket *pbktIn;
ap_input_mode_t eMode = (eReadType == APR_BLOCK_READ)
? AP_MODE_BLOCKING : AP_MODE_NONBLOCKING;
if(APR_BRIGADE_EMPTY(pRec->pbbInput)) {
ap_get_brigade(pRec->pInputFilter->next,pRec->pbbInput,eMode,readbytes);
if(APR_BRIGADE_EMPTY(pRec->pbbInput))
return APR_EOF;
}
APR_BRIGADE_FOREACH(pbktIn,pRec->pbbInput) {
const char *data;
apr_size_t len;
int n;
char buf[1024];
apr_status_t ret;
if(APR_BUCKET_IS_EOS(pbktIn)) {
break;
}
/* read filter */
ret=apr_bucket_read(pbktIn,&data,&len,eReadType);
if (!(eReadType == APR_NONBLOCK_READ && APR_STATUS_IS_EAGAIN(ret))) {
/* allow retry */
APR_BUCKET_REMOVE(pbktIn);
}
if(ret == APR_SUCCESS && len == 0 && eReadType == APR_BLOCK_READ)
ret=APR_EOF;
if(len == 0) {
/* Lazy frickin browsers just reset instead of shutting down. */
if(ret == APR_EOF || APR_STATUS_IS_ECONNRESET(ret)) {
if(APR_BRIGADE_EMPTY(pRec->pbbPendingInput))
return APR_EOF;
else
/* Next time around, the incoming brigade will be empty,
* so we'll return EOF then
*/
return APR_SUCCESS;
}
if(eReadType != APR_NONBLOCK_READ)
ap_log_error(APLOG_MARK,APLOG_ERR,ret,NULL,
"Read failed in tls_in_filter");
assert(eReadType == APR_NONBLOCK_READ);
assert(ret == APR_SUCCESS || APR_STATUS_IS_EAGAIN(ret));
/* In this case, we have data in the output bucket, or we were
* non-blocking, so returning nothing is fine.
*/
return APR_SUCCESS;
}
n = BIO_write (pRec->pbioRead, data, len);
assert(n >= 0 && (apr_size_t)n == len);
if (bio_is_renegotiating(pRec->pbioRead)) {
/* we're doing renegotiation in the access phase */
if (len >= *readbytes) {
/* trick ap_http_filter into leaving us alone */
*readbytes = 0;
break; /* SSL_renegotiate will take it from here */
}
}
if ((ret = ssl_hook_process_connection(pRec)) != APR_SUCCESS) {
/* if this is the case, ssl connection has been shutdown
* and pRec->pssl has been freed
*/
return ret;
}
/* pass along all of the current BIO */
while ((n = ssl_io_hook_read(pRec->pssl,
(unsigned char *)buf, sizeof(buf))) > 0)
{
apr_bucket *pbktOut;
char *pbuf;
pbuf=apr_pmemdup(pRec->pInputFilter->c->pool,buf,n);
/* XXX: should we use a heap bucket instead? Or a transient (in
* which case we need a separate brigade for each bucket)?
*/
pbktOut=apr_bucket_pool_create(pbuf,n,pRec->pInputFilter->c->pool);
APR_BRIGADE_INSERT_TAIL(pRec->pbbPendingInput,pbktOut);
/* Once we've read something, we can move to non-blocking mode (if
* we weren't already).
*/
eReadType = APR_NONBLOCK_READ;
}
ret=churn_output(pRec);
if(ret != APR_SUCCESS)
return ret;
}
return churn_output(pRec);
}
apr_status_t ssl_io_filter_Output(ap_filter_t *f,apr_bucket_brigade *pbbIn)
{
SSLFilterRec *pRec=f->ctx;
apr_bucket *pbktIn;
APR_BRIGADE_FOREACH(pbktIn,pbbIn) {
const char *data;
apr_size_t len, n;
apr_status_t ret;
if(APR_BUCKET_IS_EOS(pbktIn)) {
if ((ret = churn_output(pRec)) != APR_SUCCESS)
{
ap_log_error(
APLOG_MARK,APLOG_ERR,ret,NULL, "Error in churn_output");
return ret;
}
break;
}
if (!APR_BUCKET_IS_FLUSH(pbktIn)) {
/* read filter */
apr_bucket_read(pbktIn,&data,&len,APR_BLOCK_READ);
/* write SSL */
n = ssl_io_hook_write(pRec->pssl, (unsigned char *)data, len);
if (n != len) {
conn_rec *c = (conn_rec *)SSL_get_app_data(pRec->pssl);
char *reason = "reason unknown";
/* XXX: probably a better way to determine this */
if (SSL_total_renegotiations(pRec->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;
}
}
/* else fallthrough to flush the current wbio
* likely triggered by renegotiation in ssl_hook_Access
*/
/* churn the state machine */
ret=churn_output(pRec);
if(ret != APR_SUCCESS)
return ret;
}
return APR_SUCCESS;
}
apr_status_t ssl_io_filter_Input(ap_filter_t *f,apr_bucket_brigade *pbbOut,
ap_input_mode_t eMode, apr_off_t *readbytes)
{
apr_status_t ret;
SSLFilterRec *pRec = f->ctx;
apr_read_type_e eReadType =
(eMode == AP_MODE_BLOCKING) ? APR_BLOCK_READ : APR_NONBLOCK_READ;
/* XXX: we don't currently support peek */
assert(eMode != AP_MODE_PEEK);
/* churn the state machine */
ret = churn(pRec,eReadType,readbytes);
if(ret != APR_SUCCESS)
return ret;
/* XXX: shame that APR_BRIGADE_FOREACH doesn't work here */
while(!APR_BRIGADE_EMPTY(pRec->pbbPendingInput)) {
apr_bucket *pbktIn=APR_BRIGADE_FIRST(pRec->pbbPendingInput);
APR_BUCKET_REMOVE(pbktIn);
APR_BRIGADE_INSERT_TAIL(pbbOut,pbktIn);
}
return APR_SUCCESS;
}
apr_status_t ssl_io_filter_cleanup (void *data)
{
apr_status_t ret;
SSLFilterRec *pRec = (SSLFilterRec *)data;
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)
{
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->pbbInput = apr_brigade_create(c->pool);
filter->pbbPendingInput = 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);
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 - <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;
}