diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-ssl-iostream/ostream-openssl.c | |
parent | Initial commit. (diff) | |
download | dovecot-upstream.tar.xz dovecot-upstream.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/lib-ssl-iostream/ostream-openssl.c | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/src/lib-ssl-iostream/ostream-openssl.c b/src/lib-ssl-iostream/ostream-openssl.c new file mode 100644 index 0000000..8434a9b --- /dev/null +++ b/src/lib-ssl-iostream/ostream-openssl.c @@ -0,0 +1,339 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "istream.h" +#include "ostream-private.h" +#include "iostream-openssl.h" + +struct ssl_ostream { + struct ostream_private ostream; + struct ssl_iostream *ssl_io; + buffer_t *buffer; + + bool shutdown:1; +}; + +static void +o_stream_ssl_close(struct iostream_private *stream, bool close_parent) +{ + struct ssl_ostream *sstream = (struct ssl_ostream *)stream; + + if (close_parent) + o_stream_close(sstream->ssl_io->plain_output); +} + +static void o_stream_ssl_destroy(struct iostream_private *stream) +{ + struct ssl_ostream *sstream = (struct ssl_ostream *)stream; + struct istream *ssl_input = sstream->ssl_io->ssl_input; + + openssl_iostream_shutdown(sstream->ssl_io); + sstream->ssl_io->ssl_output = NULL; + i_stream_unref(&ssl_input); + ssl_iostream_unref(&sstream->ssl_io); + buffer_free(&sstream->buffer); +} + +static size_t get_buffer_avail_size(const struct ssl_ostream *sstream) +{ + if (sstream->ostream.max_buffer_size == 0) { + if (sstream->buffer == NULL) + return 0; + /* we're requested to use whatever space is available in + the buffer */ + return buffer_get_writable_size(sstream->buffer) - sstream->buffer->used; + } else { + size_t buffer_used = (sstream->buffer == NULL ? 0 : + sstream->buffer->used); + return sstream->ostream.max_buffer_size > buffer_used ? + sstream->ostream.max_buffer_size - buffer_used : 0; + } +} + +static size_t +o_stream_ssl_buffer(struct ssl_ostream *sstream, const struct const_iovec *iov, + unsigned int iov_count, size_t bytes_sent) +{ + size_t avail, skip_left, size; + unsigned int i; + + if (sstream->buffer == NULL) + sstream->buffer = buffer_create_dynamic(default_pool, + I_MIN(IO_BLOCK_SIZE, sstream->ostream.max_buffer_size)); + + skip_left = bytes_sent; + for (i = 0; i < iov_count; i++) { + if (skip_left < iov[i].iov_len) + break; + skip_left -= iov[i].iov_len; + } + + avail = get_buffer_avail_size(sstream); + if (i < iov_count && skip_left > 0) { + size = I_MIN(iov[i].iov_len - skip_left, avail); + buffer_append(sstream->buffer, + CONST_PTR_OFFSET(iov[i].iov_base, skip_left), + size); + bytes_sent += size; + avail -= size; + if (size != iov[i].iov_len) + i = iov_count; + } + if (avail > 0) + o_stream_set_flush_pending(sstream->ssl_io->plain_output, TRUE); + + for (; i < iov_count; i++) { + size = I_MIN(iov[i].iov_len, avail); + buffer_append(sstream->buffer, iov[i].iov_base, size); + bytes_sent += size; + avail -= size; + + if (size != iov[i].iov_len) + break; + } + + sstream->ostream.ostream.offset += bytes_sent; + return bytes_sent; +} + +static int o_stream_ssl_flush_buffer(struct ssl_ostream *sstream) +{ + struct ssl_iostream *ssl_io = sstream->ssl_io; + size_t pos = 0; + int ret = 1; + + i_assert(!sstream->shutdown); + + while (pos < sstream->buffer->used) { + /* we're writing plaintext data to OpenSSL, which it encrypts + and writes to bio_int's buffer. ssl_iostream_bio_sync() + reads it from there and adds to plain_output stream. */ + ret = SSL_write(ssl_io->ssl, + CONST_PTR_OFFSET(sstream->buffer->data, pos), + sstream->buffer->used - pos); + if (ret <= 0) { + ret = openssl_iostream_handle_error( + ssl_io, ret, OPENSSL_IOSTREAM_SYNC_TYPE_WRITE, + "SSL_write"); + if (ret < 0) { + io_stream_set_error( + &sstream->ostream.iostream, + "%s", ssl_io->last_error); + sstream->ostream.ostream.stream_errno = errno; + break; + } + if (ret == 0) + break; + } else { + pos += ret; + ret = openssl_iostream_bio_sync( + ssl_io, OPENSSL_IOSTREAM_SYNC_TYPE_WRITE); + if (ret < 0) { + i_assert(ssl_io->plain_stream_errstr != NULL && + ssl_io->plain_stream_errno != 0); + io_stream_set_error( + &sstream->ostream.iostream, + "%s", ssl_io->plain_stream_errstr); + sstream->ostream.ostream.stream_errno = + ssl_io->plain_stream_errno; + break; + } + } + } + buffer_delete(sstream->buffer, 0, pos); + return ret <= 0 ? ret : 1; +} + +static int o_stream_ssl_flush(struct ostream_private *stream) +{ + struct ssl_ostream *sstream = (struct ssl_ostream *)stream; + struct ssl_iostream *ssl_io = sstream->ssl_io; + struct ostream *plain_output = ssl_io->plain_output; + int ret = 1; + + if (!ssl_io->handshaked) { + if ((ret = ssl_iostream_handshake(ssl_io)) < 0) { + /* handshake failed */ + i_assert(errno != 0); + io_stream_set_error(&stream->iostream, + "%s", ssl_io->last_error); + stream->ostream.stream_errno = errno; + return ret; + } + } + if (ret > 0 && + openssl_iostream_bio_sync( + ssl_io, OPENSSL_IOSTREAM_SYNC_TYPE_HANDSHAKE) < 0) { + i_assert(ssl_io->plain_stream_errno != 0 && + ssl_io->plain_stream_errstr != NULL); + io_stream_set_error(&stream->iostream, + "%s", ssl_io->plain_stream_errstr); + stream->ostream.stream_errno = ssl_io->plain_stream_errno; + return -1; + } + + if (ret > 0 && sstream->buffer != NULL && sstream->buffer->used > 0) { + /* we can try to send some of our buffered data */ + ret = o_stream_ssl_flush_buffer(sstream); + } + + /* Stream is finished; shutdown the SSL write direction once our buffer + is empty. */ + if (stream->finished && !sstream->shutdown && ret >= 0 && + (sstream->buffer == NULL || sstream->buffer->used == 0)) { + sstream->shutdown = TRUE; + if (SSL_shutdown(ssl_io->ssl) < 0) { + io_stream_set_error( + &sstream->ostream.iostream, "%s", + t_strdup_printf("SSL_shutdown() failed: %s", + openssl_iostream_error())); + sstream->ostream.ostream.stream_errno = EIO; + ret = -1; + } + } + + if (ret == 0 && ssl_io->want_read) { + /* we need to read more data until we can continue. */ + o_stream_set_flush_pending(plain_output, FALSE); + ssl_io->ostream_flush_waiting_input = TRUE; + ret = 1; + } + + if (ret <= 0) + return ret; + + /* return 1 only when the output buffer is empty, which is what the + caller expects. */ + return o_stream_get_buffer_used_size(plain_output) == 0 ? 1 : 0; +} + +static ssize_t +o_stream_ssl_sendv(struct ostream_private *stream, + const struct const_iovec *iov, unsigned int iov_count) +{ + struct ssl_ostream *sstream = (struct ssl_ostream *)stream; + size_t bytes_sent = 0; + + i_assert(!sstream->shutdown); + + bytes_sent = o_stream_ssl_buffer(sstream, iov, iov_count, bytes_sent); + if (sstream->ssl_io->handshaked && + sstream->buffer->used == bytes_sent) { + /* buffer was empty before calling this. try to write it + immediately. */ + if (o_stream_ssl_flush_buffer(sstream) < 0) + return -1; + } + return bytes_sent; +} + +static void o_stream_ssl_switch_ioloop_to(struct ostream_private *stream, + struct ioloop *ioloop) +{ + struct ssl_ostream *sstream = (struct ssl_ostream *)stream; + + o_stream_switch_ioloop_to(sstream->ssl_io->plain_output, ioloop); +} + +static int plain_flush_callback(struct ssl_ostream *sstream) +{ + struct ostream *ostream = &sstream->ostream.ostream; + int ret, ret2; + + /* try to actually flush the pending data */ + if ((ret = o_stream_flush(sstream->ssl_io->plain_output)) < 0) + return -1; + + /* we may be able to copy more data, try it */ + o_stream_ref(ostream); + if (sstream->ostream.callback != NULL) + ret2 = sstream->ostream.callback(sstream->ostream.context); + else + ret2 = o_stream_flush(&sstream->ostream.ostream); + if (ret2 == 0) + o_stream_set_flush_pending(sstream->ssl_io->plain_output, TRUE); + o_stream_unref(&ostream); + if (ret2 < 0) + return -1; + return ret > 0 && ret2 > 0 ? 1 : 0; +} + +static size_t +o_stream_ssl_get_buffer_used_size(const struct ostream_private *stream) +{ + const struct ssl_ostream *sstream = (const struct ssl_ostream *)stream; + BIO *bio = SSL_get_wbio(sstream->ssl_io->ssl); + size_t wbuf_avail = BIO_ctrl_get_write_guarantee(bio); + size_t wbuf_total_size = BIO_get_write_buf_size(bio, 0); + size_t buffer_used = (sstream->buffer == NULL ? 0 : + sstream->buffer->used); + i_assert(wbuf_avail <= wbuf_total_size); + return buffer_used + (wbuf_total_size - wbuf_avail) + + o_stream_get_buffer_used_size(sstream->ssl_io->plain_output); +} + +static size_t +o_stream_ssl_get_buffer_avail_size(const struct ostream_private *stream) +{ + const struct ssl_ostream *sstream = (const struct ssl_ostream *)stream; + + return get_buffer_avail_size(sstream); +} + +static void +o_stream_ssl_flush_pending(struct ostream_private *_stream, bool set) +{ + struct ssl_ostream *sstream = (struct ssl_ostream *)_stream; + + o_stream_set_flush_pending(sstream->ssl_io->plain_output, set); +} + +static void o_stream_ssl_set_max_buffer_size(struct iostream_private *_stream, + size_t max_size) +{ + struct ssl_ostream *sstream = (struct ssl_ostream *)_stream; + + sstream->ostream.max_buffer_size = max_size; + o_stream_set_max_buffer_size(sstream->ssl_io->plain_output, max_size); +} + +struct ostream *openssl_o_stream_create_ssl(struct ssl_iostream *ssl_io) +{ + struct ssl_ostream *sstream; + + ssl_io->refcount++; + + /* When ostream is destroyed, it's flushed. With iostream-ssl the + flushing requires both istream and ostream to be available. The + istream is referenced here to make sure it's not destroyed before + the ostream. */ + i_assert(ssl_io->ssl_input != NULL); + i_stream_ref(ssl_io->ssl_input); + + sstream = i_new(struct ssl_ostream, 1); + sstream->ssl_io = ssl_io; + sstream->ostream.max_buffer_size = + ssl_io->plain_output->real_stream->max_buffer_size; + sstream->ostream.iostream.close = o_stream_ssl_close; + sstream->ostream.iostream.destroy = o_stream_ssl_destroy; + sstream->ostream.sendv = o_stream_ssl_sendv; + sstream->ostream.flush = o_stream_ssl_flush; + sstream->ostream.switch_ioloop_to = o_stream_ssl_switch_ioloop_to; + + sstream->ostream.get_buffer_used_size = + o_stream_ssl_get_buffer_used_size; + sstream->ostream.get_buffer_avail_size = + o_stream_ssl_get_buffer_avail_size; + sstream->ostream.flush_pending = o_stream_ssl_flush_pending; + sstream->ostream.iostream.set_max_buffer_size = + o_stream_ssl_set_max_buffer_size; + + sstream->ostream.callback = ssl_io->plain_output->real_stream->callback; + sstream->ostream.context = ssl_io->plain_output->real_stream->context; + o_stream_set_flush_callback(ssl_io->plain_output, + plain_flush_callback, sstream); + + return o_stream_create(&sstream->ostream, NULL, + o_stream_get_fd(ssl_io->plain_output)); +} |