mirror of
https://github.com/apache/httpd.git
synced 2025-08-13 14:40:20 +00:00

Obtained from: Submitted by: Reviewed by: fix bug in ssl_io_input_getline(): 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 now try to read until LF is seen. bug seen with netscape client (running both on linux and win32) and server running on win32. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@93931 13f79535-47bb-0310-9956-ffa450edef68
995 lines
28 KiB
C
995 lines
28 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-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);
|
|
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;
|
|
|
|
/* 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;
|
|
}
|
|
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;
|
|
}
|
|
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 (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);
|
|
}
|
|
}
|
|
|
|
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() \
|
|
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);
|
|
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);
|
|
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 = &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;
|
|
}
|