| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* _ _ |
| * _ __ ___ ___ __| | ___ ___| | mod_ssl |
| * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL |
| * | | | | | | (_) | (_| | \__ \__ \ | |
| * |_| |_| |_|\___/ \__,_|___|___/___/_| |
| * |_____| |
| * ssl_engine_io.c |
| * I/O Functions |
| */ |
| /* ``MY HACK: This universe. |
| Just one little problem: |
| core keeps dumping.'' |
| -- Unknown */ |
| #include "ssl_private.h" |
| #include "mod_ssl.h" |
| #include "apr_date.h" |
| |
| /* _________________________________________________________________ |
| ** |
| ** I/O Hooks |
| ** _________________________________________________________________ |
| */ |
| |
| /* This file is designed to be the bridge between OpenSSL and httpd. |
| * However, we really don't expect anyone (let alone ourselves) to |
| * remember what is in this file. So, first, a quick overview. |
| * |
| * In this file, you will find: |
| * - ssl_io_filter_input (Apache input filter) |
| * - ssl_io_filter_output (Apache output filter) |
| * |
| * - bio_filter_in_* (OpenSSL input filter) |
| * - bio_filter_out_* (OpenSSL output filter) |
| * |
| * The input chain is roughly: |
| * |
| * ssl_io_filter_input->ssl_io_input_read->SSL_read->... |
| * ...->bio_filter_in_read->ap_get_brigade/next-httpd-filter |
| * |
| * In mortal terminology, we do the following: |
| * - Receive a request for data to the SSL input filter |
| * - Call a helper function once we know we should perform a read |
| * - Call OpenSSL's SSL_read() |
| * - SSL_read() will then call bio_filter_in_read |
| * - bio_filter_in_read will then try to fetch data from the next httpd filter |
| * - bio_filter_in_read will flatten that data and return it to SSL_read |
| * - SSL_read will then decrypt the data |
| * - ssl_io_input_read will then receive decrypted data as a char* and |
| * ensure that there were no read errors |
| * - The char* is placed in a brigade and returned |
| * |
| * Since connection-level input filters in httpd need to be able to |
| * handle AP_MODE_GETLINE calls (namely identifying LF-terminated strings), |
| * ssl_io_input_getline which will handle this special case. |
| * |
| * Due to AP_MODE_GETLINE and AP_MODE_SPECULATIVE, we may sometimes have |
| * 'leftover' decoded data which must be setaside for the next read. That |
| * is currently handled by the char_buffer_{read|write} functions. So, |
| * ssl_io_input_read may be able to fulfill reads without invoking |
| * SSL_read(). |
| * |
| * Note that the filter context of ssl_io_filter_input and bio_filter_in_* |
| * are shared as bio_filter_in_ctx_t. |
| * |
| * Note that the filter is by choice limited to reading at most |
| * AP_IOBUFSIZE (8192 bytes) per call. |
| * |
| */ |
| |
| /* 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_filter_out_ctrl() -> bio_filter_out_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 { |
| SSL *pssl; |
| BIO *pbioRead; |
| BIO *pbioWrite; |
| ap_filter_t *pInputFilter; |
| ap_filter_t *pOutputFilter; |
| SSLConnRec *config; |
| } ssl_filter_ctx_t; |
| |
| typedef struct { |
| ssl_filter_ctx_t *filter_ctx; |
| conn_rec *c; |
| apr_bucket_brigade *bb; /* Brigade used as a buffer. */ |
| apr_status_t rc; |
| } bio_filter_out_ctx_t; |
| |
| static bio_filter_out_ctx_t *bio_filter_out_ctx_new(ssl_filter_ctx_t *filter_ctx, |
| conn_rec *c) |
| { |
| bio_filter_out_ctx_t *outctx = apr_palloc(c->pool, sizeof(*outctx)); |
| |
| outctx->filter_ctx = filter_ctx; |
| outctx->c = c; |
| outctx->bb = apr_brigade_create(c->pool, c->bucket_alloc); |
| |
| return outctx; |
| } |
| |
| /* Pass an output brigade down the filter stack; returns 1 on success |
| * or -1 on failure. */ |
| static int bio_filter_out_pass(bio_filter_out_ctx_t *outctx) |
| { |
| AP_DEBUG_ASSERT(!APR_BRIGADE_EMPTY(outctx->bb)); |
| |
| outctx->rc = ap_pass_brigade(outctx->filter_ctx->pOutputFilter->next, |
| outctx->bb); |
| /* Fail if the connection was reset: */ |
| if (outctx->rc == APR_SUCCESS && outctx->c->aborted) { |
| outctx->rc = APR_ECONNRESET; |
| } |
| return (outctx->rc == APR_SUCCESS) ? 1 : -1; |
| } |
| |
| /* Send a FLUSH bucket down the output filter stack; returns 1 on |
| * success, -1 on failure. */ |
| static int bio_filter_out_flush(BIO *bio) |
| { |
| bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)(bio->ptr); |
| apr_bucket *e; |
| |
| AP_DEBUG_ASSERT(APR_BRIGADE_EMPTY(outctx->bb)); |
| |
| e = apr_bucket_flush_create(outctx->bb->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(outctx->bb, e); |
| |
| return bio_filter_out_pass(outctx); |
| } |
| |
| static int bio_filter_create(BIO *bio) |
| { |
| bio->shutdown = 1; |
| bio->init = 1; |
| bio->num = -1; |
| bio->ptr = NULL; |
| |
| return 1; |
| } |
| |
| static int bio_filter_destroy(BIO *bio) |
| { |
| if (bio == NULL) { |
| return 0; |
| } |
| |
| /* nothing to free here. |
| * apache will destroy the bucket brigade for us |
| */ |
| return 1; |
| } |
| |
| static int bio_filter_out_read(BIO *bio, char *out, int outl) |
| { |
| /* this is never called */ |
| return -1; |
| } |
| |
| static int bio_filter_out_write(BIO *bio, const char *in, int inl) |
| { |
| bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)(bio->ptr); |
| apr_bucket *e; |
| int need_flush; |
| |
| /* Abort early if the client has initiated a renegotiation. */ |
| if (outctx->filter_ctx->config->reneg_state == RENEG_ABORT) { |
| outctx->rc = APR_ECONNABORTED; |
| return -1; |
| } |
| |
| /* 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); |
| |
| /* Use a transient bucket for the output data - any downstream |
| * filter must setaside if necessary. */ |
| e = apr_bucket_transient_create(in, inl, outctx->bb->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(outctx->bb, e); |
| |
| /* In theory, OpenSSL should flush as necessary, but it is known |
| * not to do so correctly in some cases (< 0.9.8m; see PR 46952), |
| * or on the proxy/client side (after ssl23_client_hello(), e.g. |
| * ssl/proxy.t test suite). |
| * |
| * Historically, this flush call was performed only for an SSLv2 |
| * connection or for a proxy connection. Calling _out_flush can |
| * be expensive in cases where requests/reponses are pipelined, |
| * so limit the performance impact to handshake time. |
| */ |
| #if OPENSSL_VERSION_NUMBER < 0x0009080df |
| need_flush = !SSL_is_init_finished(outctx->filter_ctx->pssl) |
| #else |
| need_flush = SSL_in_connect_init(outctx->filter_ctx->pssl); |
| #endif |
| if (need_flush) { |
| e = apr_bucket_flush_create(outctx->bb->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(outctx->bb, e); |
| } |
| |
| if (bio_filter_out_pass(outctx) < 0) { |
| return -1; |
| } |
| |
| return inl; |
| } |
| |
| static long bio_filter_out_ctrl(BIO *bio, int cmd, long num, void *ptr) |
| { |
| long ret = 1; |
| bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)(bio->ptr); |
| |
| switch (cmd) { |
| case BIO_CTRL_RESET: |
| case BIO_CTRL_EOF: |
| case BIO_C_SET_BUF_MEM_EOF_RETURN: |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, outctx->c, |
| "output bio: unhandled control %d", cmd); |
| ret = 0; |
| break; |
| case BIO_CTRL_WPENDING: |
| case BIO_CTRL_PENDING: |
| case BIO_CTRL_INFO: |
| ret = 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_FLUSH: |
| ret = bio_filter_out_flush(bio); |
| 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_filter_out_gets(BIO *bio, char *buf, int size) |
| { |
| /* this is never called */ |
| return -1; |
| } |
| |
| static int bio_filter_out_puts(BIO *bio, const char *str) |
| { |
| /* this is never called */ |
| return -1; |
| } |
| |
| static BIO_METHOD bio_filter_out_method = { |
| BIO_TYPE_MEM, |
| "APR output filter", |
| bio_filter_out_write, |
| bio_filter_out_read, /* read is never called */ |
| bio_filter_out_puts, /* puts is never called */ |
| bio_filter_out_gets, /* gets is never called */ |
| bio_filter_out_ctrl, |
| bio_filter_create, |
| bio_filter_destroy, |
| NULL |
| }; |
| |
| typedef struct { |
| int length; |
| char *value; |
| } char_buffer_t; |
| |
| typedef struct { |
| SSL *ssl; |
| BIO *bio_out; |
| ap_filter_t *f; |
| apr_status_t rc; |
| ap_input_mode_t mode; |
| apr_read_type_e block; |
| apr_bucket_brigade *bb; |
| char_buffer_t cbuf; |
| apr_pool_t *pool; |
| char buffer[AP_IOBUFSIZE]; |
| ssl_filter_ctx_t *filter_ctx; |
| } bio_filter_in_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. |
| */ |
| |
| /* Copy up to INL bytes from the char_buffer BUFFER into IN. Note |
| * that due to the strange way this API is designed/used, the |
| * char_buffer object is used to cache a segment of inctx->buffer, and |
| * then this function called to copy (part of) that segment to the |
| * beginning of inctx->buffer. So the segments to copy cannot be |
| * presumed to be non-overlapping, and memmove must be used. */ |
| static int char_buffer_read(char_buffer_t *buffer, char *in, int inl) |
| { |
| if (!buffer->length) { |
| return 0; |
| } |
| |
| if (buffer->length > inl) { |
| /* we have enough to fill the caller's buffer */ |
| memmove(in, buffer->value, inl); |
| buffer->value += inl; |
| buffer->length -= inl; |
| } |
| else { |
| /* swallow remainder of the buffer */ |
| memmove(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 function will read from a brigade and discard the read buckets as it |
| * proceeds. It will read at most *len bytes. |
| */ |
| static apr_status_t brigade_consume(apr_bucket_brigade *bb, |
| apr_read_type_e block, |
| char *c, apr_size_t *len) |
| { |
| apr_size_t actual = 0; |
| apr_status_t status = APR_SUCCESS; |
| |
| while (!APR_BRIGADE_EMPTY(bb)) { |
| apr_bucket *b = APR_BRIGADE_FIRST(bb); |
| const char *str; |
| apr_size_t str_len; |
| apr_size_t consume; |
| |
| /* Justin points out this is an http-ism that might |
| * not fit if brigade_consume is added to APR. Perhaps |
| * apr_bucket_read(eos_bucket) should return APR_EOF? |
| * Then this becomes mainline instead of a one-off. |
| */ |
| if (APR_BUCKET_IS_EOS(b)) { |
| status = APR_EOF; |
| break; |
| } |
| |
| /* The reason I'm not offering brigade_consume yet |
| * across to apr-util is that the following call |
| * illustrates how borked that API really is. For |
| * this sort of case (caller provided buffer) it |
| * would be much more trivial for apr_bucket_consume |
| * to do all the work that follows, based on the |
| * particular characteristics of the bucket we are |
| * consuming here. |
| */ |
| status = apr_bucket_read(b, &str, &str_len, block); |
| |
| if (status != APR_SUCCESS) { |
| if (APR_STATUS_IS_EOF(status)) { |
| /* This stream bucket was consumed */ |
| apr_bucket_delete(b); |
| continue; |
| } |
| break; |
| } |
| |
| if (str_len > 0) { |
| /* Do not block once some data has been consumed */ |
| block = APR_NONBLOCK_READ; |
| |
| /* Assure we don't overflow. */ |
| consume = (str_len + actual > *len) ? *len - actual : str_len; |
| |
| memcpy(c, str, consume); |
| |
| c += consume; |
| actual += consume; |
| |
| if (consume >= b->length) { |
| /* This physical bucket was consumed */ |
| apr_bucket_delete(b); |
| } |
| else { |
| /* Only part of this physical bucket was consumed */ |
| b->start += consume; |
| b->length -= consume; |
| } |
| } |
| else if (b->length == 0) { |
| apr_bucket_delete(b); |
| } |
| |
| /* This could probably be actual == *len, but be safe from stray |
| * photons. */ |
| if (actual >= *len) { |
| break; |
| } |
| } |
| |
| *len = actual; |
| return status; |
| } |
| |
| /* |
| * this is the function called by SSL_read() |
| */ |
| static int bio_filter_in_read(BIO *bio, char *in, int inlen) |
| { |
| apr_size_t inl = inlen; |
| bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)(bio->ptr); |
| apr_read_type_e block = inctx->block; |
| |
| inctx->rc = APR_SUCCESS; |
| |
| /* OpenSSL catches this case, so should we. */ |
| if (!in) |
| return 0; |
| |
| /* Abort early if the client has initiated a renegotiation. */ |
| if (inctx->filter_ctx->config->reneg_state == RENEG_ABORT) { |
| inctx->rc = APR_ECONNABORTED; |
| return -1; |
| } |
| |
| BIO_clear_retry_flags(bio); |
| |
| if (!inctx->bb) { |
| inctx->rc = APR_EOF; |
| return -1; |
| } |
| |
| if (APR_BRIGADE_EMPTY(inctx->bb)) { |
| |
| inctx->rc = ap_get_brigade(inctx->f->next, inctx->bb, |
| AP_MODE_READBYTES, block, |
| inl); |
| |
| /* If the read returns EAGAIN or success with an empty |
| * brigade, return an error after setting the retry flag; |
| * SSL_read() will then return -1, and SSL_get_error() will |
| * indicate SSL_ERROR_WANT_READ. */ |
| if (APR_STATUS_IS_EAGAIN(inctx->rc) || APR_STATUS_IS_EINTR(inctx->rc) |
| || (inctx->rc == APR_SUCCESS && APR_BRIGADE_EMPTY(inctx->bb))) { |
| BIO_set_retry_read(bio); |
| return -1; |
| } |
| |
| if (inctx->rc != APR_SUCCESS) { |
| /* Unexpected errors discard the brigade */ |
| apr_brigade_cleanup(inctx->bb); |
| inctx->bb = NULL; |
| return -1; |
| } |
| } |
| |
| inctx->rc = brigade_consume(inctx->bb, block, in, &inl); |
| |
| if (inctx->rc == APR_SUCCESS) { |
| return (int)inl; |
| } |
| |
| if (APR_STATUS_IS_EAGAIN(inctx->rc) |
| || APR_STATUS_IS_EINTR(inctx->rc)) { |
| BIO_set_retry_read(bio); |
| return (int)inl; |
| } |
| |
| /* Unexpected errors and APR_EOF clean out the brigade. |
| * Subsequent calls will return APR_EOF. |
| */ |
| apr_brigade_cleanup(inctx->bb); |
| inctx->bb = NULL; |
| |
| if (APR_STATUS_IS_EOF(inctx->rc) && inl) { |
| /* Provide the results of this read pass, |
| * without resetting the BIO retry_read flag |
| */ |
| return (int)inl; |
| } |
| |
| return -1; |
| } |
| |
| |
| static BIO_METHOD bio_filter_in_method = { |
| BIO_TYPE_MEM, |
| "APR input filter", |
| NULL, /* write is never called */ |
| bio_filter_in_read, |
| NULL, /* puts is never called */ |
| NULL, /* gets is never called */ |
| NULL, /* ctrl is never called */ |
| bio_filter_create, |
| bio_filter_destroy, |
| NULL |
| }; |
| |
| |
| static apr_status_t ssl_io_input_read(bio_filter_in_ctx_t *inctx, |
| char *buf, |
| apr_size_t *len) |
| { |
| apr_size_t wanted = *len; |
| apr_size_t bytes = 0; |
| int rc; |
| |
| *len = 0; |
| |
| /* If we have something leftover from last time, try that first. */ |
| if ((bytes = char_buffer_read(&inctx->cbuf, buf, wanted))) { |
| *len = bytes; |
| if (inctx->mode == AP_MODE_SPECULATIVE) { |
| /* We want to rollback this read. */ |
| if (inctx->cbuf.length > 0) { |
| inctx->cbuf.value -= bytes; |
| inctx->cbuf.length += bytes; |
| } else { |
| char_buffer_write(&inctx->cbuf, buf, (int)bytes); |
| } |
| return APR_SUCCESS; |
| } |
| /* This could probably be *len == wanted, but be safe from stray |
| * photons. |
| */ |
| if (*len >= wanted) { |
| return APR_SUCCESS; |
| } |
| if (inctx->mode == AP_MODE_GETLINE) { |
| if (memchr(buf, APR_ASCII_LF, *len)) { |
| return APR_SUCCESS; |
| } |
| } |
| else { |
| /* Down to a nonblock pattern as we have some data already |
| */ |
| inctx->block = APR_NONBLOCK_READ; |
| } |
| } |
| |
| while (1) { |
| |
| if (!inctx->filter_ctx->pssl) { |
| /* Ensure a non-zero error code is returned */ |
| if (inctx->rc == APR_SUCCESS) { |
| inctx->rc = APR_EGENERAL; |
| } |
| break; |
| } |
| |
| /* SSL_read may not read because we haven't taken enough data |
| * from the stack. This is where we want to consider all of |
| * the blocking and SPECULATIVE semantics |
| */ |
| rc = SSL_read(inctx->filter_ctx->pssl, buf + bytes, wanted - bytes); |
| |
| if (rc > 0) { |
| *len += rc; |
| if (inctx->mode == AP_MODE_SPECULATIVE) { |
| /* We want to rollback this read. */ |
| char_buffer_write(&inctx->cbuf, buf, rc); |
| } |
| return inctx->rc; |
| } |
| else if (rc == 0) { |
| /* If EAGAIN, we will loop given a blocking read, |
| * otherwise consider ourselves at EOF. |
| */ |
| if (APR_STATUS_IS_EAGAIN(inctx->rc) |
| || APR_STATUS_IS_EINTR(inctx->rc)) { |
| /* Already read something, return APR_SUCCESS instead. |
| * On win32 in particular, but perhaps on other kernels, |
| * a blocking call isn't 'always' blocking. |
| */ |
| if (*len > 0) { |
| inctx->rc = APR_SUCCESS; |
| break; |
| } |
| if (inctx->block == APR_NONBLOCK_READ) { |
| break; |
| } |
| } |
| else { |
| if (*len > 0) { |
| inctx->rc = APR_SUCCESS; |
| } |
| else { |
| inctx->rc = APR_EOF; |
| } |
| break; |
| } |
| } |
| else /* (rc < 0) */ { |
| int ssl_err = SSL_get_error(inctx->filter_ctx->pssl, rc); |
| conn_rec *c = (conn_rec*)SSL_get_app_data(inctx->filter_ctx->pssl); |
| |
| if (ssl_err == SSL_ERROR_WANT_READ) { |
| /* |
| * If OpenSSL wants to read more, and we were nonblocking, |
| * report as an EAGAIN. Otherwise loop, pulling more |
| * data from network filter. |
| * |
| * (This is usually the case when the client forces an SSL |
| * renegotiation which is handled implicitly by OpenSSL.) |
| */ |
| inctx->rc = APR_EAGAIN; |
| |
| if (*len > 0) { |
| inctx->rc = APR_SUCCESS; |
| break; |
| } |
| if (inctx->block == APR_NONBLOCK_READ) { |
| break; |
| } |
| continue; /* Blocking and nothing yet? Try again. */ |
| } |
| else if (ssl_err == SSL_ERROR_SYSCALL) { |
| if (APR_STATUS_IS_EAGAIN(inctx->rc) |
| || APR_STATUS_IS_EINTR(inctx->rc)) { |
| /* Already read something, return APR_SUCCESS instead. */ |
| if (*len > 0) { |
| inctx->rc = APR_SUCCESS; |
| break; |
| } |
| if (inctx->block == APR_NONBLOCK_READ) { |
| break; |
| } |
| continue; /* Blocking and nothing yet? Try again. */ |
| } |
| else { |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, inctx->rc, c, APLOGNO(01991) |
| "SSL input filter read failed."); |
| } |
| } |
| else /* if (ssl_err == SSL_ERROR_SSL) */ { |
| /* |
| * Log SSL errors and any unexpected conditions. |
| */ |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, inctx->rc, c, APLOGNO(01992) |
| "SSL library error %d reading data", ssl_err); |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, mySrvFromConn(c)); |
| |
| } |
| if (inctx->rc == APR_SUCCESS) { |
| inctx->rc = APR_EGENERAL; |
| } |
| break; |
| } |
| } |
| return inctx->rc; |
| } |
| |
| /* Read a line of input from the SSL input layer into buffer BUF of |
| * length *LEN; updating *len to reflect the length of the line |
| * including the LF character. */ |
| static apr_status_t ssl_io_input_getline(bio_filter_in_ctx_t *inctx, |
| 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(inctx, buf + offset, &tmplen); |
| |
| if (status != APR_SUCCESS) { |
| if (APR_STATUS_IS_EAGAIN(status) && (*len > 0)) { |
| /* Save the part of the line we already got */ |
| char_buffer_write(&inctx->cbuf, buf, *len); |
| } |
| 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(&inctx->cbuf, value, length); |
| |
| *len = bytes; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| |
| static apr_status_t ssl_filter_write(ap_filter_t *f, |
| const char *data, |
| apr_size_t len) |
| { |
| ssl_filter_ctx_t *filter_ctx = f->ctx; |
| bio_filter_out_ctx_t *outctx; |
| int res; |
| |
| /* write SSL */ |
| if (filter_ctx->pssl == NULL) { |
| return APR_EGENERAL; |
| } |
| |
| outctx = (bio_filter_out_ctx_t *)filter_ctx->pbioWrite->ptr; |
| res = SSL_write(filter_ctx->pssl, (unsigned char *)data, len); |
| |
| if (res < 0) { |
| int ssl_err = SSL_get_error(filter_ctx->pssl, res); |
| conn_rec *c = (conn_rec*)SSL_get_app_data(outctx->filter_ctx->pssl); |
| |
| if (ssl_err == SSL_ERROR_WANT_WRITE) { |
| /* |
| * If OpenSSL wants to write more, and we were nonblocking, |
| * report as an EAGAIN. Otherwise loop, pushing more |
| * data at the network filter. |
| * |
| * (This is usually the case when the client forces an SSL |
| * renegotiation which is handled implicitly by OpenSSL.) |
| */ |
| outctx->rc = APR_EAGAIN; |
| } |
| else if (ssl_err == SSL_ERROR_WANT_READ) { |
| /* |
| * If OpenSSL wants to read during write, and we were |
| * nonblocking, set the sense explicitly to read and |
| * report as an EAGAIN. |
| * |
| * (This is usually the case when the client forces an SSL |
| * renegotiation which is handled implicitly by OpenSSL.) |
| */ |
| outctx->c->cs->sense = CONN_SENSE_WANT_READ; |
| outctx->rc = APR_EAGAIN; |
| } |
| else if (ssl_err == SSL_ERROR_SYSCALL) { |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, outctx->rc, c, APLOGNO(01993) |
| "SSL output filter write failed."); |
| } |
| else /* if (ssl_err == SSL_ERROR_SSL) */ { |
| /* |
| * Log SSL errors |
| */ |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, outctx->rc, c, APLOGNO(01994) |
| "SSL library error %d writing data", ssl_err); |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, mySrvFromConn(c)); |
| } |
| if (outctx->rc == APR_SUCCESS) { |
| outctx->rc = APR_EGENERAL; |
| } |
| } |
| else if ((apr_size_t)res != len) { |
| conn_rec *c = f->c; |
| char *reason = "reason unknown"; |
| |
| /* XXX: probably a better way to determine this */ |
| if (SSL_total_renegotiations(filter_ctx->pssl)) { |
| reason = "likely due to failed renegotiation"; |
| } |
| |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, outctx->rc, c, APLOGNO(01995) |
| "failed to write %" APR_SSIZE_T_FMT |
| " of %" APR_SIZE_T_FMT " bytes (%s)", |
| len - (apr_size_t)res, len, reason); |
| |
| outctx->rc = APR_EGENERAL; |
| } |
| return outctx->rc; |
| } |
| |
| /* Just use a simple request. Any request will work for this, because |
| * we use a flag in the conn_rec->conn_vector now. The fake request just |
| * gets the request back to the Apache core so that a response can be sent. |
| * Since we use an HTTP/1.x request, we also have to inject the empty line |
| * that terminates the headers, or the core will read more data from the |
| * socket. |
| */ |
| #define HTTP_ON_HTTPS_PORT \ |
| "GET / HTTP/1.0" CRLF |
| |
| #define HTTP_ON_HTTPS_PORT_BUCKET(alloc) \ |
| apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, \ |
| sizeof(HTTP_ON_HTTPS_PORT) - 1, \ |
| alloc) |
| |
| /* Custom apr_status_t error code, used when a plain HTTP request is |
| * recevied on an SSL port. */ |
| #define MODSSL_ERROR_HTTP_ON_HTTPS (APR_OS_START_USERERR + 0) |
| |
| /* Custom apr_status_t error code, used when the proxy cannot |
| * establish an outgoing SSL connection. */ |
| #define MODSSL_ERROR_BAD_GATEWAY (APR_OS_START_USERERR + 1) |
| |
| static void ssl_io_filter_disable(SSLConnRec *sslconn, ap_filter_t *f) |
| { |
| bio_filter_in_ctx_t *inctx = f->ctx; |
| SSL_free(inctx->ssl); |
| sslconn->ssl = NULL; |
| inctx->ssl = NULL; |
| inctx->filter_ctx->pssl = NULL; |
| } |
| |
| static apr_status_t ssl_io_filter_error(ap_filter_t *f, |
| apr_bucket_brigade *bb, |
| apr_status_t status, |
| int is_init) |
| { |
| SSLConnRec *sslconn = myConnConfig(f->c); |
| apr_bucket *bucket; |
| int send_eos = 1; |
| |
| switch (status) { |
| case MODSSL_ERROR_HTTP_ON_HTTPS: |
| /* log the situation */ |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c, APLOGNO(01996) |
| "SSL handshake failed: HTTP spoken on HTTPS port; " |
| "trying to send HTML error page"); |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, sslconn->server); |
| |
| ssl_io_filter_disable(sslconn, f); |
| f->c->keepalive = AP_CONN_CLOSE; |
| if (is_init) { |
| sslconn->non_ssl_request = NON_SSL_SEND_REQLINE; |
| return APR_EGENERAL; |
| } |
| sslconn->non_ssl_request = NON_SSL_SEND_HDR_SEP; |
| |
| /* fake the request line */ |
| bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc); |
| send_eos = 0; |
| break; |
| |
| case MODSSL_ERROR_BAD_GATEWAY: |
| bucket = ap_bucket_error_create(HTTP_BAD_REQUEST, NULL, |
| f->c->pool, |
| f->c->bucket_alloc); |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c, APLOGNO(01997) |
| "SSL handshake failed: sending 502"); |
| break; |
| |
| default: |
| return status; |
| } |
| |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); |
| if (send_eos) { |
| bucket = apr_bucket_eos_create(f->c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); |
| } |
| return APR_SUCCESS; |
| } |
| |
| static const char ssl_io_filter[] = "SSL/TLS Filter"; |
| static const char ssl_io_buffer[] = "SSL/TLS Buffer"; |
| static const char ssl_io_coalesce[] = "SSL/TLS Coalescing Filter"; |
| |
| /* |
| * Close the SSL part of the socket connection |
| * (called immediately _before_ the socket is closed) |
| * or called with |
| */ |
| static void ssl_filter_io_shutdown(ssl_filter_ctx_t *filter_ctx, |
| conn_rec *c, int abortive) |
| { |
| SSL *ssl = filter_ctx->pssl; |
| const char *type = ""; |
| SSLConnRec *sslconn = myConnConfig(c); |
| int shutdown_type; |
| int loglevel = APLOG_DEBUG; |
| const char *logno; |
| |
| if (!ssl) { |
| return; |
| } |
| |
| /* |
| * Now close the SSL layer of the connection. We've to take |
| * the TLSv1 standard into account here: |
| * |
| * | 7.2.1. Closure alerts |
| * | |
| * | The client and the server must share knowledge that the connection is |
| * | ending in order to avoid a truncation attack. Either party may |
| * | initiate the exchange of closing messages. |
| * | |
| * | close_notify |
| * | This message notifies the recipient that the sender will not send |
| * | any more messages on this connection. The session becomes |
| * | unresumable if any connection is terminated without proper |
| * | close_notify messages with level equal to warning. |
| * | |
| * | Either party may initiate a close by sending a close_notify alert. |
| * | Any data received after a closure alert is ignored. |
| * | |
| * | Each party is required to send a close_notify alert before closing |
| * | the write side of the connection. It is required that the other party |
| * | respond with a close_notify alert of its own and close down the |
| * | connection immediately, discarding any pending writes. It is not |
| * | required for the initiator of the close to wait for the responding |
| * | close_notify alert before closing the read side of the connection. |
| * |
| * This means we've to send a close notify message, but haven't to wait |
| * for the close notify of the client. Actually we cannot wait for the |
| * close notify of the client because some clients (including Netscape |
| * 4.x) don't send one, so we would hang. |
| */ |
| |
| /* |
| * exchange close notify messages, but allow the user |
| * to force the type of handshake via SetEnvIf directive |
| */ |
| if (abortive) { |
| shutdown_type = SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN; |
| type = "abortive"; |
| logno = APLOGNO(01998); |
| loglevel = APLOG_INFO; |
| } |
| else switch (sslconn->shutdown_type) { |
| case SSL_SHUTDOWN_TYPE_UNCLEAN: |
| /* perform no close notify handshake at all |
| (violates the SSL/TLS standard!) */ |
| shutdown_type = SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN; |
| type = "unclean"; |
| logno = APLOGNO(01999); |
| break; |
| case SSL_SHUTDOWN_TYPE_ACCURATE: |
| /* send close notify and wait for clients close notify |
| (standard compliant, but usually causes connection hangs) */ |
| shutdown_type = 0; |
| type = "accurate"; |
| logno = APLOGNO(02000); |
| break; |
| default: |
| /* |
| * case SSL_SHUTDOWN_TYPE_UNSET: |
| * case SSL_SHUTDOWN_TYPE_STANDARD: |
| */ |
| /* send close notify, but don't wait for clients close notify |
| (standard compliant and safe, so it's the DEFAULT!) */ |
| shutdown_type = SSL_RECEIVED_SHUTDOWN; |
| type = "standard"; |
| logno = APLOGNO(02001); |
| break; |
| } |
| |
| SSL_set_shutdown(ssl, shutdown_type); |
| modssl_smart_shutdown(ssl); |
| |
| /* and finally log the fact that we've closed the connection */ |
| if (APLOG_CS_IS_LEVEL(c, mySrvFromConn(c), loglevel)) { |
| ap_log_cserror(APLOG_MARK, loglevel, 0, c, mySrvFromConn(c), |
| "%sConnection closed to child %ld with %s shutdown " |
| "(server %s)", |
| logno, c->id, type, |
| ssl_util_vhostid(c->pool, mySrvFromConn(c))); |
| } |
| |
| /* deallocate the SSL connection */ |
| if (sslconn->client_cert) { |
| X509_free(sslconn->client_cert); |
| sslconn->client_cert = NULL; |
| } |
| SSL_free(ssl); |
| sslconn->ssl = NULL; |
| filter_ctx->pssl = NULL; /* so filters know we've been shutdown */ |
| |
| if (abortive) { |
| /* prevent any further I/O */ |
| c->aborted = 1; |
| } |
| } |
| |
| static apr_status_t ssl_io_filter_cleanup(void *data) |
| { |
| ssl_filter_ctx_t *filter_ctx = data; |
| |
| if (filter_ctx->pssl) { |
| conn_rec *c = (conn_rec *)SSL_get_app_data(filter_ctx->pssl); |
| SSLConnRec *sslconn = myConnConfig(c); |
| |
| SSL_free(filter_ctx->pssl); |
| sslconn->ssl = filter_ctx->pssl = NULL; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| /* |
| * The hook is NOT registered with ap_hook_process_connection. Instead, it is |
| * called manually from the churn () before it tries to read any data. |
| * There is some problem if I accept conn_rec *. Still investigating.. |
| * Adv. if conn_rec * can be accepted is we can hook this function using the |
| * ap_hook_process_connection hook. |
| */ |
| |
| /* Perform the SSL handshake (whether in client or server mode), if |
| * necessary, for the given connection. */ |
| static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx) |
| { |
| conn_rec *c = (conn_rec *)SSL_get_app_data(filter_ctx->pssl); |
| SSLConnRec *sslconn = myConnConfig(c); |
| SSLSrvConfigRec *sc; |
| X509 *cert; |
| int n; |
| int ssl_err; |
| long verify_result; |
| server_rec *server; |
| |
| if (SSL_is_init_finished(filter_ctx->pssl)) { |
| return APR_SUCCESS; |
| } |
| |
| server = sslconn->server; |
| if (sslconn->is_proxy) { |
| #ifdef HAVE_TLSEXT |
| apr_ipsubnet_t *ip; |
| #endif |
| const char *hostname_note = apr_table_get(c->notes, |
| "proxy-request-hostname"); |
| BOOL proxy_ssl_check_peer_ok = TRUE; |
| sc = mySrvConfig(server); |
| |
| #ifdef HAVE_TLSEXT |
| /* |
| * Enable SNI for backend requests. Make sure we don't do it for |
| * pure SSLv3 connections, and also prevent IP addresses |
| * from being included in the SNI extension. (OpenSSL would simply |
| * pass them on, but RFC 6066 is quite clear on this: "Literal |
| * IPv4 and IPv6 addresses are not permitted".) |
| */ |
| if (hostname_note && |
| #ifndef OPENSSL_NO_SSL3 |
| sc->proxy->protocol != SSL_PROTOCOL_SSLV3 && |
| #endif |
| apr_ipsubnet_create(&ip, hostname_note, NULL, |
| c->pool) != APR_SUCCESS) { |
| if (SSL_set_tlsext_host_name(filter_ctx->pssl, hostname_note)) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c, |
| "SNI extension for SSL Proxy request set to '%s'", |
| hostname_note); |
| } else { |
| ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(02002) |
| "Failed to set SNI extension for SSL Proxy " |
| "request to '%s'", hostname_note); |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_WARNING, server); |
| } |
| } |
| #endif |
| |
| if ((n = SSL_connect(filter_ctx->pssl)) <= 0) { |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02003) |
| "SSL Proxy connect failed"); |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server); |
| /* ensure that the SSL structures etc are freed, etc: */ |
| ssl_filter_io_shutdown(filter_ctx, c, 1); |
| apr_table_setn(c->notes, "SSL_connect_rv", "err"); |
| return MODSSL_ERROR_BAD_GATEWAY; |
| } |
| |
| cert = SSL_get_peer_certificate(filter_ctx->pssl); |
| |
| if (sc->proxy_ssl_check_peer_expire != SSL_ENABLED_FALSE) { |
| if (!cert |
| || (X509_cmp_current_time( |
| X509_get_notBefore(cert)) >= 0) |
| || (X509_cmp_current_time( |
| X509_get_notAfter(cert)) <= 0)) { |
| proxy_ssl_check_peer_ok = FALSE; |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02004) |
| "SSL Proxy: Peer certificate is expired"); |
| } |
| } |
| if ((sc->proxy_ssl_check_peer_name != SSL_ENABLED_FALSE) && |
| hostname_note) { |
| apr_table_unset(c->notes, "proxy-request-hostname"); |
| if (!cert |
| || modssl_X509_match_name(c->pool, cert, hostname_note, |
| TRUE, server) == FALSE) { |
| proxy_ssl_check_peer_ok = FALSE; |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02411) |
| "SSL Proxy: Peer certificate does not match " |
| "for hostname %s", hostname_note); |
| } |
| } |
| else if ((sc->proxy_ssl_check_peer_cn != SSL_ENABLED_FALSE) && |
| hostname_note) { |
| const char *hostname; |
| int match = 0; |
| |
| hostname = ssl_var_lookup(NULL, server, c, NULL, |
| "SSL_CLIENT_S_DN_CN"); |
| apr_table_unset(c->notes, "proxy-request-hostname"); |
| |
| /* Do string match or simplest wildcard match if that |
| * fails. */ |
| match = strcasecmp(hostname, hostname_note) == 0; |
| if (!match && strncmp(hostname, "*.", 2) == 0) { |
| const char *p = ap_strchr_c(hostname_note, '.'); |
| |
| match = p && strcasecmp(p, hostname + 1) == 0; |
| } |
| |
| if (!match) { |
| proxy_ssl_check_peer_ok = FALSE; |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02005) |
| "SSL Proxy: Peer certificate CN mismatch:" |
| " Certificate CN: %s Requested hostname: %s", |
| hostname, hostname_note); |
| } |
| } |
| |
| if (cert) { |
| X509_free(cert); |
| } |
| |
| if (proxy_ssl_check_peer_ok != TRUE) { |
| /* ensure that the SSL structures etc are freed, etc: */ |
| ssl_filter_io_shutdown(filter_ctx, c, 1); |
| apr_table_setn(c->notes, "SSL_connect_rv", "err"); |
| return HTTP_BAD_GATEWAY; |
| } |
| |
| apr_table_setn(c->notes, "SSL_connect_rv", "ok"); |
| return APR_SUCCESS; |
| } |
| |
| if ((n = SSL_accept(filter_ctx->pssl)) <= 0) { |
| bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *) |
| (filter_ctx->pbioRead->ptr); |
| bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *) |
| (filter_ctx->pbioWrite->ptr); |
| apr_status_t rc = inctx->rc ? inctx->rc : outctx->rc ; |
| ssl_err = SSL_get_error(filter_ctx->pssl, n); |
| |
| if (ssl_err == SSL_ERROR_ZERO_RETURN) { |
| /* |
| * The case where the connection was closed before any data |
| * was transferred. That's not a real error and can occur |
| * sporadically with some clients. |
| */ |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, rc, c, APLOGNO(02006) |
| "SSL handshake stopped: connection was closed"); |
| } |
| else if (ssl_err == SSL_ERROR_WANT_READ) { |
| /* |
| * This is in addition to what was present earlier. It is |
| * borrowed from openssl_state_machine.c [mod_tls]. |
| * TBD. |
| */ |
| outctx->rc = APR_EAGAIN; |
| return APR_EAGAIN; |
| } |
| else if (ERR_GET_LIB(ERR_peek_error()) == ERR_LIB_SSL && |
| ERR_GET_REASON(ERR_peek_error()) == SSL_R_HTTP_REQUEST) { |
| /* |
| * The case where OpenSSL has recognized a HTTP request: |
| * This means the client speaks plain HTTP on our HTTPS port. |
| * ssl_io_filter_error will disable the ssl filters when it |
| * sees this status code. |
| */ |
| return MODSSL_ERROR_HTTP_ON_HTTPS; |
| } |
| else if (ssl_err == SSL_ERROR_SYSCALL) { |
| ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rc, c, APLOGNO(02007) |
| "SSL handshake interrupted by system " |
| "[Hint: Stop button pressed in browser?!]"); |
| } |
| else /* if (ssl_err == SSL_ERROR_SSL) */ { |
| /* |
| * Log SSL errors and any unexpected conditions. |
| */ |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, rc, c, APLOGNO(02008) |
| "SSL library error %d in handshake " |
| "(server %s)", ssl_err, |
| ssl_util_vhostid(c->pool, server)); |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server); |
| |
| } |
| if (inctx->rc == APR_SUCCESS) { |
| inctx->rc = APR_EGENERAL; |
| } |
| |
| ssl_filter_io_shutdown(filter_ctx, c, 1); |
| return inctx->rc; |
| } |
| sc = mySrvConfig(sslconn->server); |
| |
| /* |
| * Check for failed client authentication |
| */ |
| verify_result = SSL_get_verify_result(filter_ctx->pssl); |
| |
| if ((verify_result != X509_V_OK) || |
| sslconn->verify_error) |
| { |
| if (ssl_verify_error_is_optional(verify_result) && |
| (sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA)) |
| { |
| /* leaving this log message as an error for the moment, |
| * according to the mod_ssl docs: |
| * "level optional_no_ca is actually against the idea |
| * of authentication (but can be used to establish |
| * SSL test pages, etc.)" |
| * optional_no_ca doesn't appear to work as advertised |
| * in 1.x |
| */ |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02009) |
| "SSL client authentication failed, " |
| "accepting certificate based on " |
| "\"SSLVerifyClient optional_no_ca\" " |
| "configuration"); |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server); |
| } |
| else { |
| const char *error = sslconn->verify_error ? |
| sslconn->verify_error : |
| X509_verify_cert_error_string(verify_result); |
| |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02010) |
| "SSL client authentication failed: %s", |
| error ? error : "unknown"); |
| ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server); |
| |
| ssl_filter_io_shutdown(filter_ctx, c, 1); |
| return APR_ECONNABORTED; |
| } |
| } |
| |
| /* |
| * Remember the peer certificate's DN |
| */ |
| if ((cert = SSL_get_peer_certificate(filter_ctx->pssl))) { |
| if (sslconn->client_cert) { |
| X509_free(sslconn->client_cert); |
| } |
| sslconn->client_cert = cert; |
| sslconn->client_dn = NULL; |
| } |
| |
| /* |
| * Make really sure that when a peer certificate |
| * is required we really got one... (be paranoid) |
| */ |
| if ((sc->server->auth.verify_mode == SSL_CVERIFY_REQUIRE) && |
| !sslconn->client_cert) |
| { |
| ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02011) |
| "No acceptable peer certificate available"); |
| |
| ssl_filter_io_shutdown(filter_ctx, c, 1); |
| return APR_ECONNABORTED; |
| } |
| |
| 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; |
| bio_filter_in_ctx_t *inctx = f->ctx; |
| const char *start = inctx->buffer; /* start of block to return */ |
| apr_size_t len = sizeof(inctx->buffer); /* length of block to return */ |
| int is_init = (mode == AP_MODE_INIT); |
| |
| if (f->c->aborted) { |
| /* XXX: Ok, if we aborted, we ARE at the EOS. We also have |
| * aborted. This 'double protection' is probably redundant, |
| * but also effective against just about anything. |
| */ |
| apr_bucket *bucket = apr_bucket_eos_create(f->c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); |
| return APR_ECONNABORTED; |
| } |
| |
| if (!inctx->ssl) { |
| apr_bucket *bucket; |
| SSLConnRec *sslconn = myConnConfig(f->c); |
| if (sslconn->non_ssl_request == NON_SSL_SEND_REQLINE) { |
| bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); |
| if (mode != AP_MODE_SPECULATIVE) { |
| sslconn->non_ssl_request = NON_SSL_SEND_HDR_SEP; |
| } |
| return APR_SUCCESS; |
| } |
| if (sslconn->non_ssl_request == NON_SSL_SEND_HDR_SEP) { |
| bucket = apr_bucket_immortal_create(CRLF, 2, f->c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); |
| if (mode != AP_MODE_SPECULATIVE) { |
| sslconn->non_ssl_request = NON_SSL_SET_ERROR_MSG; |
| } |
| return APR_SUCCESS; |
| } |
| return ap_get_brigade(f->next, bb, mode, block, readbytes); |
| } |
| |
| /* 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; |
| } |
| |
| inctx->mode = mode; |
| inctx->block = block; |
| |
| /* XXX: we could actually move ssl_io_filter_handshake 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. |
| */ |
| if ((status = ssl_io_filter_handshake(inctx->filter_ctx)) != APR_SUCCESS) { |
| return ssl_io_filter_error(f, bb, status, is_init); |
| } |
| |
| if (is_init) { |
| /* protocol module needs to handshake before sending |
| * data to client (e.g. NNTP or FTP) |
| */ |
| return APR_SUCCESS; |
| } |
| |
| if (inctx->mode == AP_MODE_READBYTES || |
| inctx->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(inctx, inctx->buffer, &len); |
| } |
| else if (inctx->mode == AP_MODE_GETLINE) { |
| const char *pos; |
| |
| /* Satisfy the read directly out of the buffer if possible; |
| * invoking ssl_io_input_getline will mean the entire buffer |
| * is copied once (unnecessarily) for each GETLINE call. */ |
| if (inctx->cbuf.length |
| && (pos = memchr(inctx->cbuf.value, APR_ASCII_LF, |
| inctx->cbuf.length)) != NULL) { |
| start = inctx->cbuf.value; |
| len = 1 + pos - start; /* +1 to include LF */ |
| /* Buffer contents now consumed. */ |
| inctx->cbuf.value += len; |
| inctx->cbuf.length -= len; |
| status = APR_SUCCESS; |
| } |
| else { |
| /* Otherwise fall back to the hard way. */ |
| status = ssl_io_input_getline(inctx, inctx->buffer, &len); |
| } |
| } |
| else { |
| /* We have no idea what you are talking about, so return an error. */ |
| status = APR_ENOTIMPL; |
| } |
| |
| /* It is possible for mod_ssl's BIO to be used outside of the |
| * direct control of mod_ssl's input or output filter -- notably, |
| * when mod_ssl initiates a renegotiation. Switching the BIO mode |
| * back to "blocking" here ensures such operations don't fail with |
| * SSL_ERROR_WANT_READ. */ |
| inctx->block = APR_BLOCK_READ; |
| |
| /* Handle custom errors. */ |
| if (status != APR_SUCCESS) { |
| return ssl_io_filter_error(f, bb, status, 0); |
| } |
| |
| /* Create a transient bucket out of the decrypted data. */ |
| if (len > 0) { |
| apr_bucket *bucket = |
| apr_bucket_transient_create(start, len, f->c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| |
| /* ssl_io_filter_output() produces one SSL/TLS message per bucket |
| * passed down the output filter stack. This results in a high |
| * overhead (network packets) for any output comprising many small |
| * buckets. SSI page applied through the HTTP chunk filter, for |
| * example, may produce many brigades containing small buckets - |
| * [chunk-size CRLF] [chunk-data] [CRLF]. |
| * |
| * The coalescing filter merges many small buckets into larger buckets |
| * where possible, allowing the SSL I/O output filter to handle them |
| * more efficiently. */ |
| |
| #define COALESCE_BYTES (2048) |
| |
| struct coalesce_ctx { |
| char buffer[COALESCE_BYTES]; |
| apr_size_t bytes; /* number of bytes of buffer used. */ |
| }; |
| |
| static apr_status_t ssl_io_filter_coalesce(ap_filter_t *f, |
| apr_bucket_brigade *bb) |
| { |
| apr_bucket *e, *last = NULL; |
| apr_size_t bytes = 0; |
| struct coalesce_ctx *ctx = f->ctx; |
| unsigned count = 0; |
| |
| /* The brigade consists of zero-or-more small data buckets which |
| * can be coalesced (the prefix), followed by the remainder of the |
| * brigade. |
| * |
| * Find the last bucket - if any - of that prefix. count gives |
| * the number of buckets in the prefix. The "prefix" must contain |
| * only data buckets with known length, and must be of a total |
| * size which fits into the buffer. |
| * |
| * N.B.: The process here could be repeated throughout the brigade |
| * (coalesce any run of consecutive data buckets) but this would |
| * add significant complexity, particularly to memory |
| * management. */ |
| for (e = APR_BRIGADE_FIRST(bb); |
| e != APR_BRIGADE_SENTINEL(bb) |
| && !APR_BUCKET_IS_METADATA(e) |
| && e->length != (apr_size_t)-1 |
| && e->length < COALESCE_BYTES |
| && (bytes + e->length) < COALESCE_BYTES |
| && (ctx == NULL |
| || bytes + ctx->bytes + e->length < COALESCE_BYTES); |
| e = APR_BUCKET_NEXT(e)) { |
| last = e; |
| if (e->length) count++; /* don't count zero-length buckets */ |
| bytes += e->length; |
| } |
| |
| /* Coalesce the prefix, if: |
| * a) more than one bucket is found to coalesce, or |
| * b) the brigade contains only a single data bucket, or |
| * c) |
| */ |
| if (bytes > 0 |
| && (count > 1 |
| || (count == 1 && APR_BUCKET_NEXT(last) == APR_BRIGADE_SENTINEL(bb)))) { |
| /* If coalescing some bytes, ensure a context has been |
| * created. */ |
| if (!ctx) { |
| f->ctx = ctx = apr_palloc(f->c->pool, sizeof *ctx); |
| ctx->bytes = 0; |
| } |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, |
| "coalesce: have %" APR_SIZE_T_FMT " bytes, " |
| "adding %" APR_SIZE_T_FMT " more", ctx->bytes, bytes); |
| |
| /* Iterate through the prefix segment. For non-fatal errors |
| * in this loop it is safe to break out and fall back to the |
| * normal path of sending the buffer + remaining buckets in |
| * brigade. */ |
| e = APR_BRIGADE_FIRST(bb); |
| while (e != last) { |
| apr_size_t len; |
| const char *data; |
| apr_bucket *next; |
| |
| if (APR_BUCKET_IS_METADATA(e) |
| || e->length == (apr_size_t)-1) { |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(02012) |
| "unexpected bucket type during coalesce"); |
| break; /* non-fatal error; break out */ |
| } |
| |
| if (e->length) { |
| apr_status_t rv; |
| |
| /* A blocking read should be fine here for a |
| * known-length data bucket, rather than the usual |
| * non-block/flush/block. */ |
| rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ); |
| if (rv) { |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(02013) |
| "coalesce failed to read from data bucket"); |
| return AP_FILTER_ERROR; |
| } |
| |
| /* Be paranoid. */ |
| if (len > sizeof ctx->buffer |
| || (len + ctx->bytes > sizeof ctx->buffer)) { |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(02014) |
| "unexpected coalesced bucket data length"); |
| break; /* non-fatal error; break out */ |
| } |
| |
| memcpy(ctx->buffer + ctx->bytes, data, len); |
| ctx->bytes += len; |
| } |
| |
| next = APR_BUCKET_NEXT(e); |
| apr_bucket_delete(e); |
| e = next; |
| } |
| } |
| |
| if (APR_BRIGADE_EMPTY(bb)) { |
| /* If the brigade is now empty, our work here is done. */ |
| return APR_SUCCESS; |
| } |
| |
| /* If anything remains in the brigade, it must now be passed down |
| * the filter stack, first prepending anything that has been |
| * coalesced. */ |
| if (ctx && ctx->bytes) { |
| apr_bucket *e; |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, |
| "coalesce: passing on %" APR_SIZE_T_FMT " bytes", ctx->bytes); |
| |
| e = apr_bucket_transient_create(ctx->buffer, ctx->bytes, bb->bucket_alloc); |
| APR_BRIGADE_INSERT_HEAD(bb, e); |
| ctx->bytes = 0; /* buffer now emptied. */ |
| } |
| |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| static apr_status_t ssl_io_filter_output(ap_filter_t *f, |
| apr_bucket_brigade *bb) |
| { |
| apr_status_t status = APR_SUCCESS; |
| ssl_filter_ctx_t *filter_ctx = f->ctx; |
| bio_filter_in_ctx_t *inctx; |
| bio_filter_out_ctx_t *outctx; |
| apr_read_type_e rblock = APR_NONBLOCK_READ; |
| |
| if (f->c->aborted) { |
| apr_brigade_cleanup(bb); |
| return APR_ECONNABORTED; |
| } |
| |
| if (!filter_ctx->pssl) { |
| /* ssl_filter_io_shutdown was called */ |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| inctx = (bio_filter_in_ctx_t *)filter_ctx->pbioRead->ptr; |
| outctx = (bio_filter_out_ctx_t *)filter_ctx->pbioWrite->ptr; |
| |
| /* When we are the writer, we must initialize the inctx |
| * mode so that we block for any required ssl input, because |
| * output filtering is always nonblocking. |
| */ |
| inctx->mode = AP_MODE_READBYTES; |
| inctx->block = APR_BLOCK_READ; |
| |
| if ((status = ssl_io_filter_handshake(filter_ctx)) != APR_SUCCESS) { |
| return ssl_io_filter_error(f, bb, status, 0); |
| } |
| |
| while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) { |
| apr_bucket *bucket = APR_BRIGADE_FIRST(bb); |
| |
| if (APR_BUCKET_IS_METADATA(bucket)) { |
| /* Pass through metadata buckets untouched. EOC is |
| * special; terminate the SSL layer first. */ |
| if (AP_BUCKET_IS_EOC(bucket)) { |
| ssl_filter_io_shutdown(filter_ctx, f->c, 0); |
| } |
| AP_DEBUG_ASSERT(APR_BRIGADE_EMPTY(outctx->bb)); |
| |
| /* Metadata buckets are passed one per brigade; it might |
| * be more efficient (but also more complex) to use |
| * outctx->bb as a true buffer and interleave these with |
| * data buckets. */ |
| APR_BUCKET_REMOVE(bucket); |
| APR_BRIGADE_INSERT_HEAD(outctx->bb, bucket); |
| status = ap_pass_brigade(f->next, outctx->bb); |
| if (status == APR_SUCCESS && f->c->aborted) |
| status = APR_ECONNRESET; |
| apr_brigade_cleanup(outctx->bb); |
| } |
| else { |
| /* Filter a data bucket. */ |
| const char *data; |
| apr_size_t len; |
| |
| status = apr_bucket_read(bucket, &data, &len, rblock); |
| |
| if (APR_STATUS_IS_EAGAIN(status)) { |
| /* No data available: flush... */ |
| if (bio_filter_out_flush(filter_ctx->pbioWrite) < 0) { |
| status = outctx->rc; |
| break; |
| } |
| rblock = APR_BLOCK_READ; |
| /* and try again with a blocking read. */ |
| status = APR_SUCCESS; |
| continue; |
| } |
| |
| rblock = APR_NONBLOCK_READ; |
| |
| if (!APR_STATUS_IS_EOF(status) && (status != APR_SUCCESS)) { |
| break; |
| } |
| |
| status = ssl_filter_write(f, data, len); |
| apr_bucket_delete(bucket); |
| } |
| |
| } |
| |
| return status; |
| } |
| |
| struct modssl_buffer_ctx { |
| apr_bucket_brigade *bb; |
| }; |
| |
| int ssl_io_buffer_fill(request_rec *r, apr_size_t maxlen) |
| { |
| conn_rec *c = r->connection; |
| struct modssl_buffer_ctx *ctx; |
| apr_bucket_brigade *tempb; |
| apr_off_t total = 0; /* total length buffered */ |
| int eos = 0; /* non-zero once EOS is seen */ |
| |
| /* Create the context which will be passed to the input filter; |
| * containing a setaside pool and a brigade which constrain the |
| * lifetime of the buffered data. */ |
| ctx = apr_palloc(r->pool, sizeof *ctx); |
| ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc); |
| |
| /* ... and a temporary brigade. */ |
| tempb = apr_brigade_create(r->pool, c->bucket_alloc); |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c, "filling buffer, max size " |
| "%" APR_SIZE_T_FMT " bytes", maxlen); |
| |
| do { |
| apr_status_t rv; |
| apr_bucket *e, *next; |
| |
| /* The request body is read from the protocol-level input |
| * filters; the buffering filter will reinject it from that |
| * level, allowing content/resource filters to run later, if |
| * necessary. */ |
| |
| rv = ap_get_brigade(r->proto_input_filters, tempb, AP_MODE_READBYTES, |
| APR_BLOCK_READ, 8192); |
| if (rv) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02015) |
| "could not read request body for SSL buffer"); |
| return ap_map_http_request_error(rv, HTTP_INTERNAL_SERVER_ERROR); |
| } |
| |
| /* Iterate through the returned brigade: setaside each bucket |
| * into the context's pool and move it into the brigade. */ |
| for (e = APR_BRIGADE_FIRST(tempb); |
| e != APR_BRIGADE_SENTINEL(tempb) && !eos; e = next) { |
| const char *data; |
| apr_size_t len; |
| |
| next = APR_BUCKET_NEXT(e); |
| |
| if (APR_BUCKET_IS_EOS(e)) { |
| eos = 1; |
| } else if (!APR_BUCKET_IS_METADATA(e)) { |
| rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ); |
| if (rv != APR_SUCCESS) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02016) |
| "could not read bucket for SSL buffer"); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| total += len; |
| } |
| |
| rv = apr_bucket_setaside(e, r->pool); |
| if (rv != APR_SUCCESS) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02017) |
| "could not setaside bucket for SSL buffer"); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| APR_BUCKET_REMOVE(e); |
| APR_BRIGADE_INSERT_TAIL(ctx->bb, e); |
| } |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c, |
| "total of %" APR_OFF_T_FMT " bytes in buffer, eos=%d", |
| total, eos); |
| |
| /* Fail if this exceeds the maximum buffer size. */ |
| if (total > maxlen) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02018) |
| "request body exceeds maximum size (%" APR_SIZE_T_FMT |
| ") for SSL buffer", maxlen); |
| return HTTP_REQUEST_ENTITY_TOO_LARGE; |
| } |
| |
| } while (!eos); |
| |
| apr_brigade_destroy(tempb); |
| |
| /* After consuming all protocol-level input, remove all protocol-level |
| * filters. It should strictly only be necessary to remove filters |
| * at exactly ftype == AP_FTYPE_PROTOCOL, since this filter will |
| * precede all > AP_FTYPE_PROTOCOL anyway. */ |
| while (r->proto_input_filters->frec->ftype < AP_FTYPE_CONNECTION) { |
| ap_remove_input_filter(r->proto_input_filters); |
| } |
| |
| /* Insert the filter which will supply the buffered content. */ |
| ap_add_input_filter(ssl_io_buffer, ctx, r, c); |
| |
| return 0; |
| } |
| |
| /* This input filter supplies the buffered request body to the caller |
| * from the brigade stored in f->ctx. Note that the placement of this |
| * filter in the filter stack is important; it must be the first |
| * r->proto_input_filter; lower-typed filters will not be preserved |
| * across internal redirects (see PR 43738). */ |
| static apr_status_t ssl_io_filter_buffer(ap_filter_t *f, |
| apr_bucket_brigade *bb, |
| ap_input_mode_t mode, |
| apr_read_type_e block, |
| apr_off_t bytes) |
| { |
| struct modssl_buffer_ctx *ctx = f->ctx; |
| apr_status_t rv; |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, |
| "read from buffered SSL brigade, mode %d, " |
| "%" APR_OFF_T_FMT " bytes", |
| mode, bytes); |
| |
| if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) { |
| return APR_ENOTIMPL; |
| } |
| |
| if (APR_BRIGADE_EMPTY(ctx->bb)) { |
| /* Suprisingly (and perhaps, wrongly), the request body can be |
| * pulled from the input filter stack more than once; a |
| * handler may read it, and ap_discard_request_body() will |
| * attempt to do so again after *every* request. So input |
| * filters must be prepared to give up an EOS if invoked after |
| * initially reading the request. The HTTP_IN filter does this |
| * with its ->eos_sent flag. */ |
| |
| APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(f->c->bucket_alloc)); |
| return APR_SUCCESS; |
| } |
| |
| if (mode == AP_MODE_READBYTES) { |
| apr_bucket *e; |
| |
| /* Partition the buffered brigade. */ |
| rv = apr_brigade_partition(ctx->bb, bytes, &e); |
| if (rv && rv != APR_INCOMPLETE) { |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(02019) |
| "could not partition buffered SSL brigade"); |
| ap_remove_input_filter(f); |
| return rv; |
| } |
| |
| /* If the buffered brigade contains less then the requested |
| * length, just pass it all back. */ |
| if (rv == APR_INCOMPLETE) { |
| APR_BRIGADE_CONCAT(bb, ctx->bb); |
| } else { |
| apr_bucket *d = APR_BRIGADE_FIRST(ctx->bb); |
| |
| e = APR_BUCKET_PREV(e); |
| |
| /* Unsplice the partitioned segment and move it into the |
| * passed-in brigade; no convenient way to do this with |
| * the APR_BRIGADE_* macros. */ |
| APR_RING_UNSPLICE(d, e, link); |
| APR_RING_SPLICE_HEAD(&bb->list, d, e, apr_bucket, link); |
| |
| APR_BRIGADE_CHECK_CONSISTENCY(bb); |
| APR_BRIGADE_CHECK_CONSISTENCY(ctx->bb); |
| } |
| } |
| else { |
| /* Split a line into the passed-in brigade. */ |
| rv = apr_brigade_split_line(bb, ctx->bb, block, bytes); |
| |
| if (rv) { |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(02020) |
| "could not split line from buffered SSL brigade"); |
| ap_remove_input_filter(f); |
| return rv; |
| } |
| } |
| |
| if (APR_BRIGADE_EMPTY(ctx->bb)) { |
| apr_bucket *e = APR_BRIGADE_LAST(bb); |
| |
| /* Ensure that the brigade is terminated by an EOS if the |
| * buffered request body has been entirely consumed. */ |
| if (e == APR_BRIGADE_SENTINEL(bb) || !APR_BUCKET_IS_EOS(e)) { |
| e = apr_bucket_eos_create(f->c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, e); |
| } |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, |
| "buffered SSL brigade exhausted"); |
| /* Note that the filter must *not* be removed here; it may be |
| * invoked again, see comment above. */ |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| /* The request_rec pointer is passed in here only to ensure that the |
| * filter chain is modified correctly when doing a TLS upgrade. It |
| * must *not* be used otherwise. */ |
| static void ssl_io_input_add_filter(ssl_filter_ctx_t *filter_ctx, conn_rec *c, |
| request_rec *r, SSL *ssl) |
| { |
| bio_filter_in_ctx_t *inctx; |
| |
| inctx = apr_palloc(c->pool, sizeof(*inctx)); |
| |
| filter_ctx->pInputFilter = ap_add_input_filter(ssl_io_filter, inctx, r, c); |
| |
| filter_ctx->pbioRead = BIO_new(&bio_filter_in_method); |
| filter_ctx->pbioRead->ptr = (void *)inctx; |
| |
| inctx->ssl = ssl; |
| inctx->bio_out = filter_ctx->pbioWrite; |
| inctx->f = filter_ctx->pInputFilter; |
| inctx->rc = APR_SUCCESS; |
| inctx->mode = AP_MODE_READBYTES; |
| inctx->cbuf.length = 0; |
| inctx->bb = apr_brigade_create(c->pool, c->bucket_alloc); |
| inctx->block = APR_BLOCK_READ; |
| inctx->pool = c->pool; |
| inctx->filter_ctx = filter_ctx; |
| } |
| |
| /* The request_rec pointer is passed in here only to ensure that the |
| * filter chain is modified correctly when doing a TLS upgrade. It |
| * must *not* be used otherwise. */ |
| void ssl_io_filter_init(conn_rec *c, request_rec *r, SSL *ssl) |
| { |
| ssl_filter_ctx_t *filter_ctx; |
| |
| filter_ctx = apr_palloc(c->pool, sizeof(ssl_filter_ctx_t)); |
| |
| filter_ctx->config = myConnConfig(c); |
| |
| ap_add_output_filter(ssl_io_coalesce, NULL, r, c); |
| |
| filter_ctx->pOutputFilter = ap_add_output_filter(ssl_io_filter, |
| filter_ctx, r, c); |
| |
| filter_ctx->pbioWrite = BIO_new(&bio_filter_out_method); |
| filter_ctx->pbioWrite->ptr = (void *)bio_filter_out_ctx_new(filter_ctx, c); |
| |
| /* write is non blocking for the benefit of async mpm */ |
| if (c->cs) { |
| BIO_set_nbio(filter_ctx->pbioWrite, 1); |
| } |
| |
| ssl_io_input_add_filter(filter_ctx, c, r, ssl); |
| |
| SSL_set_bio(ssl, filter_ctx->pbioRead, filter_ctx->pbioWrite); |
| filter_ctx->pssl = ssl; |
| |
| apr_pool_cleanup_register(c->pool, (void*)filter_ctx, |
| ssl_io_filter_cleanup, apr_pool_cleanup_null); |
| |
| if (APLOG_CS_IS_LEVEL(c, mySrvFromConn(c), APLOG_TRACE4)) { |
| BIO *rbio = SSL_get_rbio(ssl), |
| *wbio = SSL_get_wbio(ssl); |
| BIO_set_callback(rbio, ssl_io_data_cb); |
| BIO_set_callback_arg(rbio, (void *)ssl); |
| if (wbio && wbio != rbio) { |
| BIO_set_callback(wbio, ssl_io_data_cb); |
| BIO_set_callback_arg(wbio, (void *)ssl); |
| } |
| } |
| |
| return; |
| } |
| |
| void ssl_io_filter_register(apr_pool_t *p) |
| { |
| ap_register_input_filter (ssl_io_filter, ssl_io_filter_input, NULL, AP_FTYPE_CONNECTION + 5); |
| ap_register_output_filter (ssl_io_coalesce, ssl_io_filter_coalesce, NULL, AP_FTYPE_CONNECTION + 4); |
| ap_register_output_filter (ssl_io_filter, ssl_io_filter_output, NULL, AP_FTYPE_CONNECTION + 5); |
| |
| ap_register_input_filter (ssl_io_buffer, ssl_io_filter_buffer, NULL, AP_FTYPE_PROTOCOL); |
| |
| return; |
| } |
| |
| /* _________________________________________________________________ |
| ** |
| ** I/O Data Debugging |
| ** _________________________________________________________________ |
| */ |
| |
| #define DUMP_WIDTH 16 |
| |
| static void ssl_io_data_dump(server_rec *s, |
| const char *b, |
| long len) |
| { |
| char buf[256]; |
| char tmp[64]; |
| int i, j, rows, trunc; |
| unsigned char ch; |
| |
| trunc = 0; |
| for(; (len > 0) && ((b[len-1] == ' ') || (b[len-1] == '\0')); len--) |
| trunc++; |
| rows = (len / DUMP_WIDTH); |
| if ((rows * DUMP_WIDTH) < len) |
| rows++; |
| ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, s, |
| "+-------------------------------------------------------------------------+"); |
| for(i = 0 ; i< rows; i++) { |
| #if APR_CHARSET_EBCDIC |
| char ebcdic_text[DUMP_WIDTH]; |
| j = DUMP_WIDTH; |
| if ((i * DUMP_WIDTH + j) > len) |
| j = len % DUMP_WIDTH; |
| if (j == 0) |
| j = DUMP_WIDTH; |
| memcpy(ebcdic_text,(char *)(b) + i * DUMP_WIDTH, j); |
| ap_xlate_proto_from_ascii(ebcdic_text, j); |
| #endif /* APR_CHARSET_EBCDIC */ |
| 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 *)(b) + 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 *)(b) + i * DUMP_WIDTH + j)) & 0xff; |
| #if APR_CHARSET_EBCDIC |
| apr_snprintf(tmp, sizeof(tmp), "%c", (ch >= 0x20 && ch <= 0x7F) ? ebcdic_text[j] : '.'); |
| #else /* APR_CHARSET_EBCDIC */ |
| apr_snprintf(tmp, sizeof(tmp), "%c", ((ch >= ' ') && (ch <= '~')) ? ch : '.'); |
| #endif /* APR_CHARSET_EBCDIC */ |
| apr_cpystrn(buf+strlen(buf), tmp, sizeof(buf)-strlen(buf)); |
| } |
| } |
| apr_cpystrn(buf+strlen(buf), " |", sizeof(buf)-strlen(buf)); |
| ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, s, "%s", buf); |
| } |
| if (trunc > 0) |
| ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, s, |
| "| %04ld - <SPACES/NULS>", len + trunc); |
| ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, s, |
| "+-------------------------------------------------------------------------+"); |
| 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 = mySrvFromConn(c); |
| |
| if ( cmd == (BIO_CB_WRITE|BIO_CB_RETURN) |
| || cmd == (BIO_CB_READ |BIO_CB_RETURN) ) { |
| if (rc >= 0) { |
| ap_log_cserror(APLOG_MARK, APLOG_TRACE4, 0, c, s, |
| "%s: %s %ld/%d bytes %s BIO#%pp [mem: %pp] %s", |
| MODSSL_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) && APLOG_CS_IS_LEVEL(c, s, APLOG_TRACE7)) |
| ssl_io_data_dump(s, argp, rc); |
| } |
| else { |
| ap_log_cserror(APLOG_MARK, APLOG_TRACE4, 0, c, s, |
| "%s: I/O error, %d bytes expected to %s on BIO#%pp [mem: %pp]", |
| MODSSL_LIBRARY_NAME, argi, |
| (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"), |
| bio, argp); |
| } |
| } |
| return rc; |
| } |