return -1;
}
+ /* BIO pairs use default buffer sizes (17 kB in OpenSSL 0.9.8e).
+ Each of the BIOs have one "write buffer". BIO_write() copies data
+ to them, while BIO_read() reads from the other BIO's write buffer
+ into the given buffer. The bio_int is used by OpenSSL and bio_ext
+ is used by this library. */
if (BIO_new_bio_pair(&bio_int, 0, &bio_ext, 0) != 1) {
i_error("BIO_new_bio_pair() failed: %s", ssl_iostream_error());
SSL_free(ssl);
ssl_io->plain_input = *input;
ssl_io->plain_output = *output;
ssl_io->source = i_strdup(source);
+ /* bio_int will be freed by SSL_free() */
SSL_set_bio(ssl_io->ssl, bio_int, bio_int);
SSL_set_ex_data(ssl_io->ssl, dovecot_ssl_extdata_index, ssl_io);
{
size_t bytes, max_bytes;
ssize_t sent;
- unsigned char buffer[1024];
+ unsigned char buffer[IO_BLOCK_SIZE];
bool bytes_sent = FALSE;
int ret;
+ o_stream_cork(ssl_io->plain_output);
while ((bytes = BIO_ctrl_pending(ssl_io->bio_ext)) > 0) {
+ /* bytes contains how many SSL encrypted bytes we should be
+ sending out */
max_bytes = o_stream_get_buffer_avail_size(ssl_io->plain_output);
if (bytes > max_bytes) {
if (max_bytes == 0) {
if (bytes > sizeof(buffer))
bytes = sizeof(buffer);
+ /* BIO_read() is guaranteed to return all the bytes that
+ BIO_ctrl_pending() returned */
ret = BIO_read(ssl_io->bio_ext, buffer, bytes);
i_assert(ret == (int)bytes);
+ /* we limited number of read bytes to plain_output's
+ available size. this send() is guaranteed to either
+ fully succeed or completely fail due to some error. */
sent = o_stream_send(ssl_io->plain_output, buffer, bytes);
if (sent < 0) {
i_assert(ssl_io->plain_output->stream_errno != 0);
i_assert(sent == (ssize_t)bytes);
bytes_sent = TRUE;
}
+ o_stream_uncork(ssl_io->plain_output);
return bytes_sent;
}
static bool ssl_iostream_bio_input(struct ssl_iostream *ssl_io)
{
const unsigned char *data;
- size_t size;
+ size_t bytes, size;
bool bytes_read = FALSE;
int ret;
- while (BIO_ctrl_get_read_request(ssl_io->bio_ext) > 0) {
+ while ((bytes = BIO_ctrl_get_write_guarantee(ssl_io->bio_ext)) > 0) {
+ /* bytes contains how many bytes we can write to bio_ext */
(void)i_stream_read_data(ssl_io->plain_input, &data, &size, 0);
if (size == 0) {
/* wait for more input */
break;
}
+ if (size > bytes)
+ size = bytes;
+
ret = BIO_write(ssl_io->bio_ext, data, size);
i_assert(ret == (ssize_t)size);
bool ret;
ret = ssl_iostream_bio_output(ssl_io);
- if (ssl_iostream_bio_input(ssl_io))
+ if (ssl_iostream_bio_input(ssl_io)) {
+ if (ssl_io->ostream_flush_waiting_input) {
+ ssl_io->ostream_flush_waiting_input = FALSE;
+ o_stream_set_flush_pending(ssl_io->plain_output, TRUE);
+ }
+ ssl_io->want_read = FALSE;
ret = TRUE;
+ }
return ret;
}
+int ssl_iostream_more(struct ssl_iostream *ssl_io)
+{
+ int ret;
+
+ if (!ssl_io->handshaked) {
+ if ((ret = ssl_iostream_handshake(ssl_io)) <= 0)
+ return ret;
+ }
+ (void)ssl_iostream_bio_sync(ssl_io);
+ return 1;
+}
+
int ssl_iostream_handle_error(struct ssl_iostream *ssl_io, int ret,
const char *func_name)
{
err = SSL_get_error(ssl_io->ssl, ret);
switch (err) {
case SSL_ERROR_WANT_WRITE:
- if (!ssl_iostream_bio_sync(ssl_io))
+ if (!ssl_iostream_bio_sync(ssl_io)) {
+ i_panic("SSL ostream buffer size not unlimited");
return 0;
+ }
return 1;
case SSL_ERROR_WANT_READ:
- if (!ssl_iostream_bio_sync(ssl_io))
+ if (!ssl_iostream_bio_sync(ssl_io)) {
+ ssl_io->want_read = TRUE;
return 0;
+ }
return 1;
case SSL_ERROR_SYSCALL:
/* eat up the error queue */
return ret;
}
}
+ /* handshake finished */
(void)ssl_iostream_bio_sync(ssl_io);
i_free_and_null(ssl_io->last_error);
unsigned int handshaked:1;
unsigned int cert_received:1;
unsigned int cert_broken:1;
+ unsigned int want_read:1;
+ unsigned int ostream_flush_waiting_input:1;
};
extern int dovecot_ssl_extdata_index;
const char *key_source, EVP_PKEY **pkey_r);
const char *ssl_iostream_get_use_certificate_error(const char *cert);
+/* Sync plain_input/plain_output streams with BIOs. Returns TRUE if at least
+ one byte was read/written. */
bool ssl_iostream_bio_sync(struct ssl_iostream *ssl_io);
+/* Call when there's more data available in plain_input/plain_output.
+ Returns 1 if it's ok to continue with SSL_read/SSL_write, 0 if not
+ (still handshaking), -1 if error occurred. */
+int ssl_iostream_more(struct ssl_iostream *ssl_io);
+
+/* Returns 1 if the operation should be retried (we read/wrote more data),
+ 0 if the operation should retried later once more data has been
+ read/written, -1 if a fatal error occurred (errno is set). */
int ssl_iostream_handle_error(struct ssl_iostream *ssl_io, int ret,
const char *func_name);
stream->istream.eof = TRUE;
return -1;
}
- if (!sstream->ssl_io->handshaked) {
- if ((ret = ssl_iostream_handshake(sstream->ssl_io)) <= 0) {
- if (ret < 0)
- stream->istream.stream_errno = errno;
- return ret;
+ ret = ssl_iostream_more(sstream->ssl_io);
+ if (ret <= 0) {
+ if (ret < 0) {
+ /* handshake failed */
+ stream->istream.stream_errno = errno;
}
+ return ret;
}
if (!i_stream_get_buffer_space(stream, 1, &size))
while ((ret = SSL_read(sstream->ssl_io->ssl,
stream->w_buffer + stream->pos, size)) <= 0) {
+ /* failed to read anything */
ret = ssl_iostream_handle_error(sstream->ssl_io, ret,
"SSL_read");
if (ret <= 0) {
}
return ret;
}
- (void)ssl_iostream_bio_sync(sstream->ssl_io);
+ /* we did some BIO I/O, try reading again */
}
stream->pos += ret;
return ret;
static int o_stream_ssl_flush_buffer(struct ssl_ostream *sstream)
{
size_t pos = 0;
- int ret;
+ int ret = 1;
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(sstream->ssl_io->ssl,
CONST_PTR_OFFSET(sstream->buffer->data, pos),
sstream->buffer->used - pos);
if (ret <= 0) {
ret = ssl_iostream_handle_error(sstream->ssl_io, ret,
"SSL_write");
- if (ret <= 0) {
- if (ret < 0) {
- sstream->ostream.ostream.stream_errno =
- errno;
- }
- buffer_delete(sstream->buffer, 0, pos);
- return ret;
+ if (ret < 0) {
+ sstream->ostream.ostream.stream_errno = errno;
+ break;
+ }
+ if (ret == 0) {
+ /* bio_int's buffer is full */
+ break;
}
} else {
pos += ret;
}
}
buffer_delete(sstream->buffer, 0, pos);
- return 1;
+ return ret <= 0 ? ret : 1;
}
static int o_stream_ssl_flush(struct ostream_private *stream)
struct ssl_ostream *sstream = (struct ssl_ostream *)stream;
int ret;
- if (!sstream->ssl_io->handshaked) {
- if ((ret = ssl_iostream_handshake(sstream->ssl_io)) <= 0) {
- if (ret < 0)
- stream->ostream.stream_errno = errno;
- return ret;
- }
+ if ((ret = ssl_iostream_more(sstream->ssl_io)) < 0) {
+ /* handshake failed */
+ stream->ostream.stream_errno = errno;
+ } else 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);
}
- if (sstream->buffer != NULL && sstream->buffer->used > 0) {
- if ((ret = o_stream_ssl_flush_buffer(sstream)) <= 0)
- return ret;
+ if (ret == 0 && sstream->ssl_io->want_read) {
+ /* we need to read more data until we can continue. */
+ sstream->ssl_io->ostream_flush_waiting_input = TRUE;
+ ret = 1;
}
- return 1;
+ return ret;
}
static ssize_t
-o_stream_ssl_sendv(struct ostream_private *stream,
- const struct const_iovec *iov, unsigned int iov_count)
+o_stream_ssl_sendv_try(struct ssl_ostream *sstream,
+ const struct const_iovec *iov, unsigned int iov_count,
+ size_t *bytes_sent_r)
{
- struct ssl_ostream *sstream = (struct ssl_ostream *)stream;
unsigned int i;
- size_t bytes_sent = 0;
size_t pos;
- int ret = 0;
-
- if (o_stream_flush(&stream->ostream) <= 0)
- return o_stream_ssl_buffer(sstream, iov, iov_count, 0);
+ ssize_t ret;
+ *bytes_sent_r = 0;
for (i = 0, pos = 0; i < iov_count; ) {
ret = SSL_write(sstream->ssl_io->ssl,
CONST_PTR_OFFSET(iov[i].iov_base, pos),
if (ret <= 0) {
ret = ssl_iostream_handle_error(sstream->ssl_io, ret,
"SSL_write");
- if (ret <= 0) {
- if (ret < 0)
- stream->ostream.stream_errno = errno;
+ if (ret < 0) {
+ sstream->ostream.ostream.stream_errno = errno;
break;
}
+ if (ret == 0)
+ break;
} else {
- bytes_sent += ret;
+ *bytes_sent_r += ret;
if ((size_t)ret < iov[i].iov_len)
pos += ret;
else {
(void)ssl_iostream_bio_sync(sstream->ssl_io);
}
}
+ return ret <= 0 ? ret : 1;
+}
+
+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;
+ int ret;
+
+ if (!sstream->ssl_io->handshaked)
+ ret = 0;
+ else {
+ ret = o_stream_ssl_sendv_try(sstream, iov, iov_count,
+ &bytes_sent);
+ }
+
bytes_sent = o_stream_ssl_buffer(sstream, iov, iov_count, bytes_sent);
return bytes_sent != 0 ? (ssize_t)bytes_sent : ret;
}
static int plain_flush_callback(struct ssl_ostream *sstream)
{
- int ret;
+ int ret, ret2;
+ /* try to actually flush the pending data */
if ((ret = o_stream_flush(sstream->ssl_io->plain_output)) < 0)
- return 1;
+ return -1;
- if (ret > 0)
- return o_stream_flush(&sstream->ostream.ostream);
- return 1;
+ /* we may be able to copy more data, try it */
+ ret2 = o_stream_flush(&sstream->ostream.ostream);
+ if (ret2 < 0)
+ return -1;
+ return ret > 0 && ret2 > 0 ? 1 : 0;
}
struct ostream *o_stream_create_ssl(struct ssl_iostream *ssl_io)