/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein * * 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 #include #include "serf.h" #include "serf_bucket_util.h" #include #include #include /*#define SSL_VERBOSE*/ /* * Here's an overview of the SSL bucket's relationship to OpenSSL and serf. * * HTTP request: SSLENCRYPT(REQUEST) * [context.c reads from SSLENCRYPT and writes out to the socket] * HTTP response: RESPONSE(SSLDECRYPT(SOCKET)) * [handler function reads from RESPONSE which in turn reads from SSLDECRYPT] * * HTTP request read call path: * * write_to_connection * |- serf_bucket_read on SSLENCRYPT * |- serf_ssl_read * |- serf_databuf_read * |- common_databuf_prep * |- ssl_encrypt * |- 1. Try to read pending encrypted data; If available, return. * |- 2. Try to read from ctx->stream [REQUEST bucket] * |- 3. Call SSL_write with read data * |- ... * |- bio_bucket_write with encrypted data * |- store in sink * |- 4. If successful, read pending encrypted data and return. * |- 5. If fails, place read data back in ctx->stream * * HTTP response read call path: * * read_from_connection * |- acceptor * |- handler * |- ... * |- serf_bucket_read(SSLDECRYPT) * |- serf_ssl_read * |- serf_databuf_read * |- ssl_decrypt * |- 1. SSL_read() for pending decrypted data; if any, return. * |- 2. Try to read from ctx->stream [SOCKET bucket] * |- 3. Append data to ssl_ctx->source * |- 4. Call SSL_read() * |- ... * |- bio_bucket_read * |- read data from ssl_ctx->source * |- If data read, return it. * |- If an error, set the STATUS value and return. * */ typedef struct bucket_list { serf_bucket_t *bucket; struct bucket_list *next; } bucket_list_t; typedef struct { /* Helper to read data. Wraps stream. */ serf_databuf_t databuf; /* Our source for more data. */ serf_bucket_t *stream; /* The next set of buckets */ bucket_list_t *stream_next; /* The status of the last thing we read. */ apr_status_t status; /* Data we've read but not processed. */ serf_bucket_t *pending; } serf_ssl_stream_t; struct serf_ssl_context_t { /* How many open buckets refer to this context. */ int refcount; /* The pool that this context uses. */ apr_pool_t *pool; /* The allocator associated with the above pool. */ serf_bucket_alloc_t *allocator; /* Internal OpenSSL parameters */ SSL_CTX *ctx; SSL *ssl; BIO *bio; serf_ssl_stream_t encrypt; serf_ssl_stream_t decrypt; }; typedef struct { /* The bucket-independent ssl context that this bucket is associated with */ serf_ssl_context_t *ssl_ctx; /* Pointer to the 'right' databuf. */ serf_databuf_t *databuf; /* Pointer to our stream, so we can find it later. */ serf_bucket_t **our_stream; } ssl_context_t; /* Returns the amount read. */ static int bio_bucket_read(BIO *bio, char *in, int inlen) { serf_ssl_context_t *ctx = bio->ptr; const char *data; apr_status_t status; apr_size_t len; #ifdef SSL_VERBOSE printf("bio_bucket_read called for %d bytes\n", inlen); #endif BIO_clear_retry_flags(bio); status = serf_bucket_read(ctx->decrypt.pending, inlen, &data, &len); ctx->decrypt.status = status; #ifdef SSL_VERBOSE printf("bio_bucket_read received %d bytes (%d)\n", len, status); #endif if (!SERF_BUCKET_READ_ERROR(status)) { /* Oh suck. */ if (len) { memcpy(in, data, len); return len; } if (APR_STATUS_IS_EOF(status)) { BIO_set_retry_read(bio); return -1; } } return -1; } /* Returns the amount written. */ static int bio_bucket_write(BIO *bio, const char *in, int inl) { serf_ssl_context_t *ctx = bio->ptr; serf_bucket_t *tmp; #ifdef SSL_VERBOSE printf("bio_bucket_write called for %d bytes\n", inl); #endif BIO_clear_retry_flags(bio); tmp = serf_bucket_simple_copy_create(in, inl, ctx->encrypt.pending->allocator); serf_bucket_aggregate_append(ctx->encrypt.pending, tmp); return inl; } static int bio_bucket_create(BIO *bio) { bio->shutdown = 1; bio->init = 1; bio->num = -1; bio->ptr = NULL; return 1; } static int bio_bucket_destroy(BIO *bio) { /* Did we already free this? */ if (bio == NULL) { return 0; } return 1; } static long bio_bucket_ctrl(BIO *bio, int cmd, long num, void *ptr) { long ret = 1; switch (cmd) { default: abort(); break; case BIO_CTRL_FLUSH: /* At this point we can't force a flush. */ break; case BIO_CTRL_PUSH: case BIO_CTRL_POP: ret = 0; break; } return ret; } static BIO_METHOD bio_bucket_method = { BIO_TYPE_MEM, "Serf SSL encryption and decryption buckets", bio_bucket_write, bio_bucket_read, NULL, /* Is this called? */ NULL, /* Is this called? */ bio_bucket_ctrl, bio_bucket_create, bio_bucket_destroy, #ifdef OPENSSL_VERSION_NUMBER NULL /* sslc does not have the callback_ctrl field */ #endif }; /* This function reads an encrypted stream and returns the decrypted stream. */ static apr_status_t ssl_decrypt(void *baton, apr_size_t bufsize, char *buf, apr_size_t *len) { serf_ssl_context_t *ctx = baton; apr_size_t priv_len; apr_status_t status; const char *data; int ssl_len; /* Is there some data waiting to be read? */ ssl_len = SSL_read(ctx->ssl, buf, bufsize); if (ssl_len > 0) { #ifdef SSL_VERBOSE printf("ssl_decrypt: %d bytes (%d); status: %d; flags: %d\n", ssl_len, bufsize, ctx->decrypt.status, BIO_get_retry_flags(ctx->bio)); #endif *len = ssl_len; return APR_SUCCESS; } status = serf_bucket_read(ctx->decrypt.stream, bufsize, &data, &priv_len); if (!SERF_BUCKET_READ_ERROR(status) && priv_len) { serf_bucket_t *tmp; #ifdef SSL_VERBOSE printf("ssl_decrypt: read %d bytes (%d); status: %d\n", priv_len, bufsize, status); #endif tmp = serf_bucket_simple_copy_create(data, priv_len, ctx->decrypt.pending->allocator); serf_bucket_aggregate_append(ctx->decrypt.pending, tmp); ssl_len = SSL_read(ctx->ssl, buf, bufsize); if (ssl_len == -1) { int ssl_err; ssl_err = SSL_get_error(ctx->ssl, ssl_len); switch (ssl_err) { case SSL_ERROR_SYSCALL: *len = 0; status = ctx->decrypt.status; break; case SSL_ERROR_WANT_READ: *len = 0; status = APR_EAGAIN; break; default: abort(); } } else { *len = ssl_len; } } else { *len = 0; } #ifdef SSL_VERBOSE printf("ssl_decrypt: %d %d %d\n", status, *len, BIO_get_retry_flags(ctx->bio)); #endif return status; } /* This function reads a decrypted stream and returns an encrypted stream. */ static apr_status_t ssl_encrypt(void *baton, apr_size_t bufsize, char *buf, apr_size_t *len) { const char *data; serf_ssl_context_t *ctx = baton; apr_status_t status; /* Try to read unread data first. */ status = serf_bucket_read(ctx->encrypt.pending, bufsize, &data, len); if (SERF_BUCKET_READ_ERROR(status)) { return status; } /* Aha, we read something. Return that now. */ if (*len) { memcpy(buf, data, *len); if (APR_STATUS_IS_EOF(status)) { status = APR_SUCCESS; } #ifdef SSL_VERBOSE printf("ssl_encrypt: %d %d %d (quick read)\n", status, *len, BIO_get_retry_flags(ctx->bio)); #endif return status; } if (BIO_should_retry(ctx->bio) && BIO_should_write(ctx->bio)) { #ifdef SSL_VERBOSE printf("ssl_encrypt: %d %d %d (should write exit)\n", status, *len, BIO_get_retry_flags(ctx->bio)); #endif return APR_EAGAIN; } /* Oh well, read from our stream now. */ if (!APR_STATUS_IS_EOF(ctx->encrypt.status)) { status = serf_bucket_read(ctx->encrypt.stream, bufsize, &data, len); } else { *len = 0; status = APR_EOF; } if (!SERF_BUCKET_READ_ERROR(status) && *len) { int ssl_len; #ifdef SSL_VERBOSE printf("ssl_encrypt: bucket read %d bytes; status %d\n", *len, status); #endif ctx->encrypt.status = status; ssl_len = SSL_write(ctx->ssl, data, *len); #ifdef SSL_VERBOSE printf("ssl_encrypt: SSL write: %d\n", ssl_len); #endif if (ssl_len == -1) { int ssl_err; serf_bucket_t *tmp; /* Ah, bugger. We need to put that data back. */ if (!SERF_BUCKET_IS_AGGREGATE(ctx->encrypt.stream)) { tmp = serf_bucket_aggregate_create(ctx->encrypt.stream->allocator); serf_bucket_aggregate_append(tmp, ctx->encrypt.stream); ctx->encrypt.stream = tmp; } tmp = serf_bucket_simple_copy_create(data, *len, ctx->encrypt.stream->allocator); serf_bucket_aggregate_prepend(ctx->encrypt.stream, tmp); ssl_err = SSL_get_error(ctx->ssl, ssl_len); if (ssl_err == SSL_ERROR_SYSCALL) { status = ctx->encrypt.status; if (SERF_BUCKET_READ_ERROR(status)) { return status; } } else { /* Oh, no. */ if (ssl_err == SSL_ERROR_WANT_READ) { status = APR_EAGAIN; } else { abort(); } } *len = 0; } else { apr_status_t agg_status; /* We read something! */ agg_status = serf_bucket_read(ctx->encrypt.pending, bufsize, &data, len); memcpy(buf, data, *len); if (APR_STATUS_IS_EOF(status) && !APR_STATUS_IS_EOF(agg_status)) { status = agg_status; } } } #ifdef SSL_VERBOSE printf("ssl_encrypt finished: %d %d %d\n", status, *len, BIO_get_retry_flags(ctx->bio)); #endif return status; } static int have_init_ssl = 0; static void init_ssl_libraries(void) { if (!have_init_ssl) { CRYPTO_malloc_init(); ERR_load_crypto_strings(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); have_init_ssl = 1; } } static serf_ssl_context_t *ssl_init_context(void) { serf_ssl_context_t *ssl_ctx; apr_pool_t *pool; serf_bucket_alloc_t *allocator; init_ssl_libraries(); apr_pool_create(&pool, NULL); allocator = serf_bucket_allocator_create(pool, NULL, NULL); ssl_ctx = serf_bucket_mem_alloc(allocator, sizeof(*ssl_ctx)); ssl_ctx->refcount = 0; ssl_ctx->pool = pool; ssl_ctx->allocator = allocator; /* This is wrong-ish. */ ssl_ctx->ctx = SSL_CTX_new(SSLv23_client_method()); SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_ALL); ssl_ctx->ssl = SSL_new(ssl_ctx->ctx); ssl_ctx->bio = BIO_new(&bio_bucket_method); ssl_ctx->bio->ptr = ssl_ctx; SSL_set_bio(ssl_ctx->ssl, ssl_ctx->bio, ssl_ctx->bio); SSL_set_connect_state(ssl_ctx->ssl); ssl_ctx->encrypt.stream = NULL; ssl_ctx->encrypt.stream_next = NULL; ssl_ctx->encrypt.pending = NULL; ssl_ctx->encrypt.status = APR_SUCCESS; serf_databuf_init(&ssl_ctx->encrypt.databuf); ssl_ctx->encrypt.databuf.read = ssl_encrypt; ssl_ctx->encrypt.databuf.read_baton = ssl_ctx; ssl_ctx->decrypt.stream = NULL; ssl_ctx->decrypt.pending = NULL; ssl_ctx->decrypt.status = APR_SUCCESS; serf_databuf_init(&ssl_ctx->decrypt.databuf); ssl_ctx->decrypt.databuf.read = ssl_decrypt; ssl_ctx->decrypt.databuf.read_baton = ssl_ctx; return ssl_ctx; } static apr_status_t ssl_free_context( serf_ssl_context_t *ssl_ctx) { apr_pool_t *p; /* If never had the pending buckets, don't try to free them. */ if (ssl_ctx->decrypt.pending != NULL) { serf_bucket_destroy(ssl_ctx->decrypt.pending); } if (ssl_ctx->encrypt.pending != NULL) { serf_bucket_destroy(ssl_ctx->encrypt.pending); } /* SSL_free implicitly frees the underlying BIO. */ SSL_free(ssl_ctx->ssl); SSL_CTX_free(ssl_ctx->ctx); p = ssl_ctx->pool; serf_bucket_mem_free(ssl_ctx->allocator, ssl_ctx); apr_pool_destroy(p); return APR_SUCCESS; } static serf_bucket_t * serf_bucket_ssl_create( serf_ssl_context_t *ssl_ctx, serf_bucket_alloc_t *allocator, const serf_bucket_type_t *type) { ssl_context_t *ctx; ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx)); if (!ssl_ctx) { ctx->ssl_ctx = ssl_init_context(); } else { ctx->ssl_ctx = ssl_ctx; } ctx->ssl_ctx->refcount++; return serf_bucket_create(type, allocator, ctx); } SERF_DECLARE(serf_bucket_t *) serf_bucket_ssl_decrypt_create( serf_bucket_t *stream, serf_ssl_context_t *ssl_ctx, serf_bucket_alloc_t *allocator) { serf_bucket_t *bkt; ssl_context_t *ctx; bkt = serf_bucket_ssl_create(ssl_ctx, allocator, &serf_bucket_type_ssl_decrypt); ctx = bkt->data; ctx->databuf = &ctx->ssl_ctx->decrypt.databuf; if (ctx->ssl_ctx->decrypt.stream != NULL) { abort(); } ctx->ssl_ctx->decrypt.stream = stream; ctx->our_stream = &ctx->ssl_ctx->decrypt.stream; ctx->ssl_ctx->decrypt.pending = serf_bucket_aggregate_create(allocator); return bkt; } SERF_DECLARE(serf_ssl_context_t *) serf_bucket_ssl_decrypt_context_get( serf_bucket_t *bucket) { ssl_context_t *ctx = bucket->data; return ctx->ssl_ctx; } SERF_DECLARE(serf_bucket_t *) serf_bucket_ssl_encrypt_create( serf_bucket_t *stream, serf_ssl_context_t *ssl_ctx, serf_bucket_alloc_t *allocator) { serf_bucket_t *bkt; ssl_context_t *ctx; bkt = serf_bucket_ssl_create(ssl_ctx, allocator, &serf_bucket_type_ssl_encrypt); ctx = bkt->data; ctx->databuf = &ctx->ssl_ctx->encrypt.databuf; ctx->our_stream = &ctx->ssl_ctx->encrypt.stream; if (ctx->ssl_ctx->encrypt.stream == NULL) { ctx->ssl_ctx->encrypt.stream = stream; ctx->ssl_ctx->encrypt.pending = serf_bucket_aggregate_create(allocator); } else { bucket_list_t *new_list; new_list = serf_bucket_mem_alloc(ctx->ssl_ctx->allocator, sizeof(*new_list)); new_list->bucket = stream; new_list->next = NULL; if (ctx->ssl_ctx->encrypt.stream_next == NULL) { ctx->ssl_ctx->encrypt.stream_next = new_list; } else { bucket_list_t *scan = ctx->ssl_ctx->encrypt.stream_next; while (scan->next != NULL) scan = scan->next; scan->next = new_list; } } return bkt; } SERF_DECLARE(serf_ssl_context_t *) serf_bucket_ssl_encrypt_context_get( serf_bucket_t *bucket) { ssl_context_t *ctx = bucket->data; return ctx->ssl_ctx; } static void serf_ssl_destroy_and_data(serf_bucket_t *bucket) { ssl_context_t *ctx = bucket->data; if (!--ctx->ssl_ctx->refcount) { ssl_free_context(ctx->ssl_ctx); } serf_default_destroy_and_data(bucket); } static void serf_ssl_decrypt_destroy_and_data(serf_bucket_t *bucket) { ssl_context_t *ctx = bucket->data; serf_bucket_destroy(*ctx->our_stream); serf_ssl_destroy_and_data(bucket); } static void serf_ssl_encrypt_destroy_and_data(serf_bucket_t *bucket) { ssl_context_t *ctx = bucket->data; serf_ssl_context_t *ssl_ctx = ctx->ssl_ctx; if (ssl_ctx->encrypt.stream == *ctx->our_stream) { serf_bucket_destroy(*ctx->our_stream); serf_bucket_destroy(ssl_ctx->encrypt.pending); /* Reset our encrypted status and databuf. */ ssl_ctx->encrypt.status = APR_SUCCESS; ssl_ctx->encrypt.databuf.status = APR_SUCCESS; /* Advance to the next stream - if we have one. */ if (ssl_ctx->encrypt.stream_next == NULL) { ssl_ctx->encrypt.stream = NULL; ssl_ctx->encrypt.pending = NULL; } else { bucket_list_t *cur; cur = ssl_ctx->encrypt.stream_next; ssl_ctx->encrypt.stream = cur->bucket; ssl_ctx->encrypt.pending = serf_bucket_aggregate_create(cur->bucket->allocator); ssl_ctx->encrypt.stream_next = cur->next; serf_bucket_mem_free(ssl_ctx->allocator, cur); } } else { /* Ah, darn. We haven't sent this one along yet. */ abort(); } serf_ssl_destroy_and_data(bucket); } static apr_status_t serf_ssl_read(serf_bucket_t *bucket, apr_size_t requested, const char **data, apr_size_t *len) { ssl_context_t *ctx = bucket->data; return serf_databuf_read(ctx->databuf, requested, data, len); } static apr_status_t serf_ssl_readline(serf_bucket_t *bucket, int acceptable, int *found, const char **data, apr_size_t *len) { ssl_context_t *ctx = bucket->data; return serf_databuf_readline(ctx->databuf, acceptable, found, data, len); } static apr_status_t serf_ssl_peek(serf_bucket_t *bucket, const char **data, apr_size_t *len) { ssl_context_t *ctx = bucket->data; return serf_databuf_peek(ctx->databuf, data, len); } SERF_DECLARE_DATA const serf_bucket_type_t serf_bucket_type_ssl_encrypt = { "SSLENCRYPT", serf_ssl_read, serf_ssl_readline, serf_default_read_iovec, serf_default_read_for_sendfile, serf_default_read_bucket, serf_ssl_peek, serf_ssl_encrypt_destroy_and_data, }; SERF_DECLARE_DATA const serf_bucket_type_t serf_bucket_type_ssl_decrypt = { "SSLDECRYPT", serf_ssl_read, serf_ssl_readline, serf_default_read_iovec, serf_default_read_for_sendfile, serf_default_read_bucket, serf_ssl_peek, serf_ssl_decrypt_destroy_and_data, };