diff options
author | rcritten <> | 2005-05-17 14:50:16 +0000 |
---|---|---|
committer | rcritten <> | 2005-05-17 14:50:16 +0000 |
commit | f6ecd9df97f9bb1be9b69ae97a78eb54b6599463 (patch) | |
tree | c4c8e184503e99362268f59dfa8d3ce114c18013 /nss_engine_io.c | |
download | mod_nss-f6ecd9df97f9bb1be9b69ae97a78eb54b6599463.tar.gz mod_nss-f6ecd9df97f9bb1be9b69ae97a78eb54b6599463.tar.xz mod_nss-f6ecd9df97f9bb1be9b69ae97a78eb54b6599463.zip |
Initial import of mod_nss
Diffstat (limited to 'nss_engine_io.c')
-rw-r--r-- | nss_engine_io.c | 1194 |
1 files changed, 1194 insertions, 0 deletions
diff --git a/nss_engine_io.c b/nss_engine_io.c new file mode 100644 index 0000000..27b5671 --- /dev/null +++ b/nss_engine_io.c @@ -0,0 +1,1194 @@ +/* Copyright 2001-2004 The Apache Software Foundation + * + * Licensed 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. + */ + +#include "mod_nss.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) + * - nspr_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. + * + */ + +/* Private structures */ +typedef struct nspr_filter_in_ctx_t nspr_filter_in_ctx_t; +typedef struct nspr_filter_out_ctx_t nspr_filter_out_ctx_t; + +typedef struct { + PRFileDesc *pssl; + conn_rec *c; + ap_filter_t *pInputFilter; + ap_filter_t *pOutputFilter; + nspr_filter_in_ctx_t *inctx; + nspr_filter_out_ctx_t *outctx; + int nobuffer; /* non-zero to prevent buffering */ +} ssl_filter_ctx_t; + +typedef struct { + int length; + char *value; +} char_buffer_t; + +struct nspr_filter_out_ctx_t { + ssl_filter_ctx_t *filter_ctx; + apr_bucket_brigade *bb; + apr_size_t length; + char buffer[AP_IOBUFSIZE]; + apr_size_t blen; + apr_status_t rc; +}; + +struct nspr_filter_in_ctx_t { + 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; +}; + +/* Global variables for the NSPR I/O layer */ +static PRDescIdentity gIdentity = PR_INVALID_IO_LAYER; +static PRIOMethods gMethods; + +/* + * 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 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 PR_Read() + */ +static PRInt32 PR_CALLBACK +nspr_filter_in_read(PRFileDesc *fd, void *in, PRInt32 inlen) +{ + apr_size_t inl = inlen; + ssl_filter_ctx_t *filter_ctx = (ssl_filter_ctx_t *)(fd->secret); + nspr_filter_in_ctx_t *inctx = filter_ctx->inctx; + apr_read_type_e block = inctx->block; + + inctx->rc = APR_SUCCESS; + + /* mod_ssl catches this case, so should we. */ + if (!in) + return 0; + + 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); + + /* Not a problem, there was simply no data ready yet. + */ + if (APR_STATUS_IS_EAGAIN(inctx->rc) || APR_STATUS_IS_EINTR(inctx->rc) + || (inctx->rc == APR_SUCCESS && APR_BRIGADE_EMPTY(inctx->bb))) { + return 0; + } + + 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)) { + 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 apr_status_t ssl_io_input_read(nspr_filter_in_ctx_t *inctx, + char *buf, + apr_size_t *len) +{ + apr_size_t wanted = *len; + apr_size_t bytes = 0; + int rc; + conn_rec *c = inctx->filter_ctx->c; + + *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; + } + + rc = PR_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 = PR_GetError(); + + if (ssl_err == PR_WOULD_BLOCK_ERROR) { + /* + * If NSPR 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 + * renegotation which is handled implicitly by NSS.) + */ + 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 != 0) { + 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_error(APLOG_MARK, APLOG_INFO, inctx->rc, c->base_server, + "SSL input filter read failed."); + if (inctx->rc == 0) + ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, c->base_server); + } + } + if (inctx->rc == APR_SUCCESS) { + inctx->rc = APR_EGENERAL; + } + break; + } + } + return inctx->rc; +} + +static apr_status_t ssl_io_input_getline(nspr_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) { + 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; + nspr_filter_out_ctx_t *outctx; + int res; + + /* write SSL */ + if (filter_ctx->pssl == NULL) { + return APR_EGENERAL; + } + + outctx = filter_ctx->outctx; + + res = PR_Write(filter_ctx->pssl, (char *)data, len); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, + "Sent returned %d", res); + + if (res < 0) { + int ssl_err = PR_GetError(); + + if (ssl_err == PR_WOULD_BLOCK_ERROR) { + /* + * If NSS 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 + * renegotation which is handled implicitly by OpenSSL.) + */ + outctx->rc = APR_EAGAIN; + } + else { + conn_rec *c = f->c; + /* + * Log SSL errors + */ + ap_log_error(APLOG_MARK, APLOG_INFO, outctx->rc, c->base_server, + "SSL library error %d writing data", ssl_err); + ssl_log_ssl_error(APLOG_MARK, APLOG_INFO, c->base_server); + } + 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"; + + ap_log_error(APLOG_MARK, APLOG_INFO, outctx->rc, c->base_server, + "failed to write %d of %d 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. + * + * To avoid calling back for more data from the socket, use an HTTP/0.9 + * request, and tack on an EOS bucket. + */ +#define HTTP_ON_HTTPS_PORT \ + "GET /" CRLF + +#define HTTP_ON_HTTPS_PORT_BUCKET(alloc) \ + apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, \ + sizeof(HTTP_ON_HTTPS_PORT) - 1, \ + alloc) + + +static void ssl_io_filter_disable(SSLConnRec *sslconn, ap_filter_t *f) +{ + nspr_filter_in_ctx_t *inctx = f->ctx; + sslconn->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) +{ + SSLConnRec *sslconn = myConnConfig(f->c); + apr_bucket *bucket; + + switch (status) { + case HTTP_BAD_REQUEST: + /* log the situation */ + ap_log_error(APLOG_MARK, APLOG_INFO, 0, + f->c->base_server, + "SSL handshake failed: HTTP spoken on HTTPS port; " + "trying to send HTML error page"); + + sslconn->non_ssl_request = 1; + ssl_io_filter_disable(sslconn, f); + + /* fake the request line */ + bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc); + break; + + default: + return status; + } + + APR_BRIGADE_INSERT_TAIL(bb, bucket); + bucket = apr_bucket_eos_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, bucket); + + return APR_SUCCESS; +} + +static const char ssl_io_filter[] = "NSS SSL/TLS Filter"; + +static apr_status_t ssl_filter_io_shutdown(ssl_filter_ctx_t *filter_ctx, + conn_rec *c, + int abortive) +{ + PRFileDesc *ssl = filter_ctx->pssl; + SSLConnRec *sslconn = myConnConfig(c); + + if (!ssl) { + return APR_SUCCESS; + } + + PR_Shutdown(ssl, PR_SHUTDOWN_SEND); + PR_Close(ssl); + + /* log the fact that we've closed the connection */ + if (c->base_server->loglevel >= APLOG_INFO) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, c->base_server, + "Connection to child %ld closed " + "(server %s, client %s)", + c->id, + ssl_util_vhostid(c->pool, c->base_server), + c->remote_ip ? c->remote_ip : "unknown"); + } + + /* deallocate the SSL connection */ + if (sslconn->client_cert) { + CERT_DestroyCertificate(sslconn->client_cert); + sslconn->client_cert = NULL; + } + + sslconn->ssl = NULL; + filter_ctx->pssl = NULL; /* so filters know we've been shutdown */ + + if (abortive) { + /* prevent any further I/O */ + c->aborted = 1; + } + + return APR_SUCCESS; +} + +static apr_status_t ssl_io_filter_cleanup(void *data) +{ + ssl_filter_ctx_t *filter_ctx = data; + + if (filter_ctx->pssl) { + conn_rec *c = filter_ctx->c; + SSLConnRec *sslconn = myConnConfig(c); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, + "SSL connection destroyed without being closed"); + + PR_Close(sslconn->ssl); + sslconn->ssl = filter_ctx->pssl = NULL; + } + + 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; + nspr_filter_in_ctx_t *inctx = f->ctx; + + apr_size_t len = sizeof(inctx->buffer); + 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->filter_ctx->pssl) { + 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; + + 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) { + status = ssl_io_input_getline(inctx, inctx->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); + } + + /* Create a transient bucket out of the decrypted data. */ + if (len > 0) { + apr_bucket *bucket = + apr_bucket_transient_create(inctx->buffer, len, f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, bucket); + } + + return APR_SUCCESS; +} + +static int nspr_filter_out_flush(nspr_filter_out_ctx_t *outctx) +{ + apr_bucket *e; + + if (!(outctx->blen || outctx->length)) { + outctx->rc = APR_SUCCESS; + return 1; + } + + if (outctx->blen) { + e = apr_bucket_transient_create(outctx->buffer, outctx->blen, + outctx->bb->bucket_alloc); + /* we filled this buffer first so add it to the + * head of the brigade + */ + APR_BRIGADE_INSERT_HEAD(outctx->bb, e); + outctx->blen = 0; + } + + outctx->length = 0; + e = apr_bucket_flush_create(outctx->bb->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(outctx->bb, e); + + outctx->rc = ap_pass_brigade(outctx->filter_ctx->pOutputFilter->next, + outctx->bb); + if (outctx->rc == APR_SUCCESS && outctx->filter_ctx->c->aborted) { + outctx->rc = APR_ECONNRESET; + } + return (outctx->rc == APR_SUCCESS) ? 1 : -1; +} + +static PRInt32 PR_CALLBACK +nspr_filter_out_write(PRFileDesc *fd, const void *in, PRInt32 inl) +{ + ssl_filter_ctx_t *filter_ctx = (ssl_filter_ctx_t *)(fd->secret); + nspr_filter_out_ctx_t *outctx = filter_ctx->outctx; + + /* 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, + outctx->bb->bucket_alloc); + + outctx->length += inl; + APR_BRIGADE_INSERT_TAIL(outctx->bb, bucket); + + if (nspr_filter_out_flush(outctx) < 0) { + return -1; + } + + return inl; +} + +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; + nspr_filter_in_ctx_t *inctx; + nspr_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 = filter_ctx->inctx; + outctx = filter_ctx->outctx; + + /* 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; + + 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)) { + if (nspr_filter_out_flush(filter_ctx->outctx) < 0) { + status = outctx->rc; + break; + } + + 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 { + /* nspr_filter_out_flush() already passed down a flush bucket + * if there was any data to be flushed. + */ + apr_bucket_delete(bucket); + } + } + else if (AP_BUCKET_IS_EOC(bucket)) { + /* The special "EOC" bucket means a shutdown is needed; + * - turn off buffering in nspr_filter_out_write + * - issue the SSL_shutdown + */ + filter_ctx->nobuffer = 1; + status = ssl_filter_io_shutdown(filter_ctx, f->c, 0); + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_INFO, status, NULL, + "SSL filter error shutting down I/O"); + } + if ((status = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) { + return status; + } + break; + } + else { + /* filter output */ + 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 (nspr_filter_out_flush(filter_ctx->outctx) < 0) { + status = outctx->rc; + break; + } + rblock = APR_BLOCK_READ; + continue; /* and try again with a blocking read. */ + } + + 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); + + if (status != APR_SUCCESS) { + break; + } + } + } + return status; +} + +static void ssl_io_output_create(ssl_filter_ctx_t *filter_ctx, conn_rec *c) +{ + nspr_filter_out_ctx_t *outctx = apr_palloc(c->pool, sizeof(*outctx)); + + outctx->filter_ctx = filter_ctx; + outctx->bb = apr_brigade_create(c->pool, c->bucket_alloc); + outctx->blen = 0; + outctx->length = 0; + + filter_ctx->outctx = outctx; + + return; +} + +static void ssl_io_input_add_filter(ssl_filter_ctx_t *filter_ctx, conn_rec *c, + PRFileDesc *ssl) +{ + nspr_filter_in_ctx_t *inctx; + + inctx = apr_palloc(c->pool, sizeof(*inctx)); + + filter_ctx->pInputFilter = ap_add_input_filter(ssl_io_filter, inctx, NULL, c); + + 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; + + filter_ctx->inctx = inctx; +} + +void ssl_io_filter_init(conn_rec *c, PRFileDesc *ssl) +{ + ssl_filter_ctx_t *filter_ctx; + + filter_ctx = apr_palloc(c->pool, sizeof(ssl_filter_ctx_t)); + filter_ctx->pOutputFilter = ap_add_output_filter(ssl_io_filter, + filter_ctx, NULL, c); + + ssl_io_input_add_filter(filter_ctx, c, ssl); + ssl_io_output_create(filter_ctx, c); + + filter_ctx->pssl = ssl; + filter_ctx->c = c; + ssl->lower->secret = (PRFilePrivate *)filter_ctx; + + apr_pool_cleanup_register(c->pool, (void*)filter_ctx, + ssl_io_filter_cleanup, apr_pool_cleanup_null); + + return; +} + +void ssl_io_filter_register(apr_pool_t *p) +{ + ap_register_input_filter (ssl_io_filter, ssl_io_filter_input, NULL, AP_FTYPE_CONNECTION + 5); + ap_register_output_filter (ssl_io_filter, ssl_io_filter_output, NULL, AP_FTYPE_CONNECTION + 5); + return; +} + +PRFileDesc * ssl_io_new_fd() { + PRFileDesc *ssl = PR_CreateIOLayerStub(gIdentity, &gMethods); + + return ssl; +} + +static PRStatus PR_CALLBACK nspr_filter_getpeername(PRFileDesc *fd, PRNetAddr *addr) { + ssl_filter_ctx_t *filter_ctx; + conn_rec *c; + + /* This can occur when doing SSL_ImportFD(NULL, something); */ + if (fd->secret == NULL) + return PR_FAILURE; + + filter_ctx = (ssl_filter_ctx_t *)(fd->secret); + c = filter_ctx->c; + + return PR_StringToNetAddr(c->remote_ip, addr); +} + +/* + * Translate NSPR PR_GetSocketOption() calls into apr_socket_opt_get() calls. + */ +static PRStatus PR_CALLBACK nspr_filter_getsocketoption(PRFileDesc *fd, PRSocketOptionData *data) { + ssl_filter_ctx_t *filter_ctx = (ssl_filter_ctx_t *)(fd->secret); + conn_rec *c = filter_ctx->c; + SSLConnRec *sslconn = myConnConfig(c); /* for the Apache socket */ + apr_int32_t on; + PRStatus rv = PR_FAILURE; + + switch(data->option) { + case PR_SockOpt_Nonblocking: + if (apr_socket_opt_get(sslconn->client_socket, APR_SO_NONBLOCK, &on) == APR_SUCCESS) { + data->value.non_blocking = (on == 1) ? PR_TRUE : PR_FALSE; + rv = PR_SUCCESS; + } + break; + case PR_SockOpt_Linger: + if (apr_socket_opt_get(sslconn->client_socket, APR_SO_LINGER, &on) == APR_SUCCESS) { + data->value.linger.polarity = (on == 1) ? PR_TRUE : PR_FALSE; + data->value.linger.linger = APR_MAX_SECS_TO_LINGER; + rv = PR_SUCCESS; + } + break; + case PR_SockOpt_NoDelay: + if (apr_socket_opt_get(sslconn->client_socket, APR_TCP_NODELAY, &on) == APR_SUCCESS) { + data->value.no_delay = (on == 1) ? PR_TRUE : PR_FALSE; + rv = PR_SUCCESS; + } + case PR_SockOpt_Reuseaddr: + if (apr_socket_opt_get(sslconn->client_socket, APR_SO_REUSEADDR, &on) == APR_SUCCESS) { + data->value.reuse_addr = (on == 1) ? PR_TRUE : PR_FALSE; + rv = PR_SUCCESS; + } + break; + case PR_SockOpt_Keepalive: /* has separate #define in Apache, use it */ + if (apr_socket_opt_get(sslconn->client_socket, APR_SO_KEEPALIVE, &on) == APR_SUCCESS) { + data->value.keep_alive = (on == 1) ? PR_TRUE : PR_FALSE; + rv = PR_SUCCESS; + } + break; + case PR_SockOpt_RecvBufferSize: + case PR_SockOpt_SendBufferSize: + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, c->base_server, "For sendbuffersize and recvbuffersize we can only see if they are on, not the value."); + break; + case PR_SockOpt_McastLoopback: + case PR_SockOpt_MaxSegment: + case PR_SockOpt_IpTimeToLive: + case PR_SockOpt_IpTypeOfService: + case PR_SockOpt_McastTimeToLive: + case PR_SockOpt_McastInterface: + ap_log_error(APLOG_MARK, APLOG_INFO, 0, c->base_server, "Unsupported or socket option."); + break; + default: + ap_log_error(APLOG_MARK, APLOG_INFO, 0, c->base_server, "Unknown socket option."); + break; + } + + return rv; +} + +/* + * Translate NSPR PR_SetSocketOption() calls into apr_socket_opt_set() calls. + */ +static PRStatus PR_CALLBACK nspr_filter_setsocketOption(PRFileDesc *fd, const PRSocketOptionData *data) { + ssl_filter_ctx_t *filter_ctx = (ssl_filter_ctx_t *)(fd->secret); + conn_rec *c = filter_ctx->c; + SSLConnRec *sslconn = myConnConfig(c); /* for the Apache socket */ + PRStatus rv = PR_FAILURE; + + switch(data->option) { + case PR_SockOpt_Nonblocking: + if (apr_socket_opt_set(sslconn->client_socket, APR_SO_NONBLOCK, data->value.non_blocking) == APR_SUCCESS) { + rv = PR_SUCCESS; + } + break; + case PR_SockOpt_Linger: + if (apr_socket_opt_set(sslconn->client_socket, APR_SO_LINGER, data->value.linger.polarity) == APR_SUCCESS) { + rv = PR_SUCCESS; + } + break; + case PR_SockOpt_NoDelay: + if (apr_socket_opt_set(sslconn->client_socket, APR_TCP_NODELAY, data->value.no_delay) == APR_SUCCESS) { + rv = PR_SUCCESS; + } + case PR_SockOpt_Reuseaddr: + if (apr_socket_opt_set(sslconn->client_socket, APR_SO_REUSEADDR, data->value.reuse_addr) == APR_SUCCESS) { + rv = PR_SUCCESS; + } + break; + case PR_SockOpt_Keepalive: /* has separate #define in Apache, use it */ + if (apr_socket_opt_set(sslconn->client_socket, APR_SO_KEEPALIVE, data->value.keep_alive) == APR_SUCCESS) { + rv = PR_SUCCESS; + } + break; + case PR_SockOpt_RecvBufferSize: + if (apr_socket_opt_set(sslconn->client_socket, APR_SO_RCVBUF, data->value.recv_buffer_size) == APR_SUCCESS) { + rv = PR_SUCCESS; + } + break; + case PR_SockOpt_SendBufferSize: + if (apr_socket_opt_set(sslconn->client_socket, APR_SO_SNDBUF, data->value.send_buffer_size) == APR_SUCCESS) { + rv = PR_SUCCESS; + } + break; + case PR_SockOpt_McastLoopback: + case PR_SockOpt_MaxSegment: + case PR_SockOpt_IpTimeToLive: + case PR_SockOpt_IpTypeOfService: + case PR_SockOpt_McastTimeToLive: + case PR_SockOpt_McastInterface: + ap_log_error(APLOG_MARK, APLOG_INFO, 0, c->base_server, "Unsupported or socket option."); + break; + default: + ap_log_error(APLOG_MARK, APLOG_INFO, 0, c->base_server, "Unknown socket option."); + break; + } + + return rv; +} + +static PRStatus PR_CALLBACK +nspr_filter_shutdown(PRFileDesc *fd, PRIntn how) +{ + return PR_SUCCESS; +} + +static PRStatus PR_CALLBACK +nspr_filter_close(PRFileDesc *fd) +{ + return PR_SUCCESS; +} + +/* Wrapper that ignores flags and timeouts */ +static PRInt32 PR_CALLBACK nspr_filter_recv(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout) { + return nspr_filter_in_read(fd, buf, amount); +} + +/* Wrapper that ignores flags and timeouts */ +static PRInt32 PR_CALLBACK nspr_filter_send(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout) { + return nspr_filter_out_write(fd, buf, amount); +} + +/* + * Called once to initialize the NSPR layer that we push for each + * request. + */ +int ssl_io_layer_init() +{ + const PRIOMethods *defaultMethods; + int rc = 1; + + if (gIdentity != PR_INVALID_IO_LAYER) { + // already initialized + return PR_FAILURE; + } + + gIdentity = PR_GetUniqueIdentity("ApacheNSSLayer"); + + if (gIdentity == PR_INVALID_IO_LAYER) + return PR_FAILURE; + + defaultMethods = PR_GetDefaultIOMethods(); + + if (defaultMethods == NULL) + return PR_FAILURE; + + gMethods = *defaultMethods; + + gMethods.close = nspr_filter_close; + gMethods.read = nspr_filter_in_read; + gMethods.write = nspr_filter_out_write; + gMethods.recv = nspr_filter_recv; + gMethods.send = nspr_filter_send;; + gMethods.getpeername = nspr_filter_getpeername; + gMethods.shutdown = nspr_filter_shutdown; + gMethods.getsocketoption = nspr_filter_getsocketoption; + gMethods.setsocketoption = nspr_filter_setsocketOption; + + return rc; +} + +SECStatus +ssl_AuthCertificate(void *arg, PRFileDesc *socket, + PRBool checksig, PRBool isServer) +{ + SECStatus status; + ssl_filter_ctx_t *filter_ctx; + + if (!arg || !socket) { + return SECFailure; + } + + filter_ctx = (ssl_filter_ctx_t *)(socket->lower->secret); + + status = SSL_AuthCertificate(arg, socket, checksig, isServer); + + if (status == SECSuccess) { + conn_rec *c = filter_ctx->c; + SSLConnRec *sslconn = myConnConfig(c); + + sslconn->client_cert = SSL_PeerCertificate(socket); + sslconn->client_dn = NULL; + } + + return status; +} |