static void tlsp_strategy(TLSP_STATE *state) { TLS_SESS_STATE *tls_context = state->tls_context; NBBIO *plaintext_buf; int ssl_stat; int ssl_read_err; int ssl_write_err; int handshake_err; /* * Be sure to complete the TLS handshake before enabling plain-text I/O. * In case of an unrecoverable error, this automagically cleans up all * pending read/write and timeout event requests. */ if (state->flags & TLSP_FLAG_DO_HANDSHAKE) { ssl_stat = SSL_accept(tls_context->con); if (ssl_stat != 1) { handshake_err = SSL_get_error(tls_context->con, ssl_stat); tlsp_eval_tls_error(state, handshake_err); /* At this point, state could be a dangling pointer. */ return; } if ((state->tls_context = tls_server_post_accept(tls_context)) == 0) { tlsp_state_free(state); return; } if ((state->req_flags & TLS_PROXY_FLAG_SEND_CONTEXT) != 0 && (attr_print(state->plaintext_stream, ATTR_FLAG_NONE, SEND_ATTR_FUNC(tls_proxy_context_print, (void *) state->tls_context), ATTR_TYPE_END) != 0 || vstream_fflush(state->plaintext_stream) != 0)) { msg_warn("cannot send TLS context: %m"); tlsp_state_free(state); return; } state->flags &= ~TLSP_FLAG_DO_HANDSHAKE; } /* * Shutdown and self-destruct after NBBIO error. This automagically * cleans up all pending read/write and timeout event requests. Before * shutting down TLS, we stop all plain-text I/O events but keep the * NBBIO error flags. */ plaintext_buf = state->plaintext_buf; if (NBBIO_ERROR_FLAGS(plaintext_buf)) { if (NBBIO_ACTIVE_FLAGS(plaintext_buf)) nbbio_disable_readwrite(state->plaintext_buf); ssl_stat = SSL_shutdown(tls_context->con); /* XXX Wait for return value 1 if sessions are to be reused? */ if (ssl_stat < 0) { handshake_err = SSL_get_error(tls_context->con, ssl_stat); tlsp_eval_tls_error(state, handshake_err); /* At this point, state could be a dangling pointer. */ return; } tlsp_state_free(state); return; } /* * Try to move data from the plaintext input buffer to the TLS engine. * * XXX We're supposed to repeat the exact same SSL_write() call arguments * after an SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE result. Rumor has * it that this is because each SSL_write() call reads from the buffer * incrementally, and returns > 0 only after the final byte is processed. * Rumor also has it that setting SSL_MODE_ENABLE_PARTIAL_WRITE and * SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER voids this requirement, and that * repeating the request with an increased request size is OK. * Unfortunately all this is not or poorly documented, and one has to * rely on statements from OpenSSL developers in public mailing archives. */ ssl_write_err = SSL_ERROR_NONE; while (NBBIO_READ_PEND(plaintext_buf) > 0) { ssl_stat = SSL_write(tls_context->con, NBBIO_READ_BUF(plaintext_buf), NBBIO_READ_PEND(plaintext_buf)); ssl_write_err = SSL_get_error(tls_context->con, ssl_stat); if (ssl_write_err != SSL_ERROR_NONE) break; /* Allow the plaintext pseudothread to read more data. */ NBBIO_READ_PEND(plaintext_buf) -= ssl_stat; if (NBBIO_READ_PEND(plaintext_buf) > 0) memmove(NBBIO_READ_BUF(plaintext_buf), NBBIO_READ_BUF(plaintext_buf) + ssl_stat, NBBIO_READ_PEND(plaintext_buf)); } /* * Try to move data from the TLS engine to the plaintext output buffer. * Note: data may arrive as a side effect of calling SSL_write(), * therefore we call SSL_read() after calling SSL_write(). * * XXX We're supposed to repeat the exact same SSL_read() call arguments * after an SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE result. This * supposedly means that our plaintext writer must not memmove() the * plaintext output buffer until after the SSL_read() call succeeds. For * now I'll ignore this, because 1) SSL_read() is documented to return * the bytes available, instead of returning > 0 only after the entire * buffer is processed like SSL_write() does; and 2) there is no "read" * equivalent of the SSL_R_BAD_WRITE_RETRY, SSL_MODE_ENABLE_PARTIAL_WRITE * or SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER features. */ ssl_read_err = SSL_ERROR_NONE; while (NBBIO_WRITE_PEND(state->plaintext_buf) < NBBIO_BUFSIZE(plaintext_buf)) { ssl_stat = SSL_read(tls_context->con, NBBIO_WRITE_BUF(plaintext_buf) + NBBIO_WRITE_PEND(state->plaintext_buf), NBBIO_BUFSIZE(plaintext_buf) - NBBIO_WRITE_PEND(state->plaintext_buf)); ssl_read_err = SSL_get_error(tls_context->con, ssl_stat); if (ssl_read_err != SSL_ERROR_NONE) break; NBBIO_WRITE_PEND(plaintext_buf) += ssl_stat; } /* * Try to enable/disable ciphertext read/write events. If SSL_write() was * satisfied, see if SSL_read() wants to do some work. In case of an * unrecoverable error, this automagically destroys the session state * after cleaning up all pending read/write and timeout event requests. */ if (tlsp_eval_tls_error(state, ssl_write_err != SSL_ERROR_NONE ? ssl_write_err : ssl_read_err) < 0) return; /* * Try to enable/disable plaintext read/write events. Basically, if we * have nothing to write to the postscreen(8) server, see if there is * something to read. If the write buffer is empty and the read buffer is * full, suspend plaintext I/O until conditions change (but keep the * timer active, as a safety mechanism in case ciphertext I/O gets * stuck). * * XXX In theory, if the client keeps writing fast enough then we would * never read from postscreen(8), and cause postscreen(8) to block. In * practice, postscreen(8) limits the number of client commands, and thus * postscreen(8)'s output will fit in a kernel buffer. This may not be * true in other scenarios where the tlsproxy(8) server could be used. */ if (NBBIO_WRITE_PEND(plaintext_buf) > 0) { if (NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_READ) nbbio_disable_readwrite(plaintext_buf); if ((NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_WRITE) == 0) nbbio_enable_write(plaintext_buf, state->timeout); } else if (NBBIO_READ_PEND(plaintext_buf) < NBBIO_BUFSIZE(plaintext_buf)) { if (NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_WRITE) nbbio_disable_readwrite(plaintext_buf); if ((NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_READ) == 0) nbbio_enable_read(plaintext_buf, state->timeout); } else { if (NBBIO_ACTIVE_FLAGS(plaintext_buf)) nbbio_slumber(plaintext_buf, state->timeout); } }
/* * This is the actual startup routine for a new connection. We expect that * the SMTP buffers are flushed and the "220 Ready to start TLS" was sent to * the client, so that we can immediately start the TLS handshake process. */ TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props) { int sts; TLS_SESS_STATE *TLScontext; const char *cipher_list; TLS_APPL_STATE *app_ctx = props->ctx; if (props->log_level >= 1) msg_info("setting up TLS connection from %s", props->namaddr); cipher_list = tls_set_ciphers(app_ctx, "TLS", props->cipher_grade, props->cipher_exclusions); if (cipher_list == 0) { msg_warn("%s: %s: aborting TLS session", props->namaddr, vstring_str(app_ctx->why)); return (0); } if (props->log_level >= 2) msg_info("%s: TLS cipher list \"%s\"", props->namaddr, cipher_list); /* * Allocate a new TLScontext for the new connection and get an SSL * structure. Add the location of TLScontext to the SSL to later retrieve * the information inside the tls_verify_certificate_callback(). */ TLScontext = tls_alloc_sess_context(props->log_level, props->namaddr); TLScontext->cache_type = app_ctx->cache_type; TLScontext->serverid = mystrdup(props->serverid); TLScontext->am_server = 1; TLScontext->fpt_dgst = mystrdup(props->fpt_dgst); TLScontext->stream = props->stream; ERR_clear_error(); if ((TLScontext->con = (SSL *) SSL_new(app_ctx->ssl_ctx)) == 0) { msg_warn("Could not allocate 'TLScontext->con' with SSL_new()"); tls_print_errors(); tls_free_context(TLScontext); return (0); } if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) { msg_warn("Could not set application data for 'TLScontext->con'"); tls_print_errors(); tls_free_context(TLScontext); return (0); } /* * Before really starting anything, try to seed the PRNG a little bit * more. */ tls_int_seed(); (void) tls_ext_seed(var_tls_daemon_rand_bytes); /* * Initialize the SSL connection to accept state. This should not be * necessary anymore since 0.9.3, but the call is still in the library * and maintaining compatibility never hurts. */ SSL_set_accept_state(TLScontext->con); /* * Connect the SSL connection with the network socket. */ if (SSL_set_fd(TLScontext->con, props->stream == 0 ? props->fd : vstream_fileno(props->stream)) != 1) { msg_info("SSL_set_fd error to %s", props->namaddr); tls_print_errors(); uncache_session(app_ctx->ssl_ctx, TLScontext); tls_free_context(TLScontext); return (0); } /* * If the debug level selected is high enough, all of the data is dumped: * 3 will dump the SSL negotiation, 4 will dump everything. * * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called? * Well there is a BIO below the SSL routines that is automatically * created for us, so we can use it for debugging purposes. */ if (props->log_level >= 3) BIO_set_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb); /* * If we don't trigger the handshake in the library, leave control over * SSL_accept/read/write/etc with the application. */ if (props->stream == 0) return (TLScontext); /* * Turn on non-blocking I/O so that we can enforce timeouts on network * I/O. */ non_blocking(vstream_fileno(props->stream), NON_BLOCKING); /* * Start TLS negotiations. This process is a black box that invokes our * call-backs for session caching and certificate verification. * * Error handling: If the SSL handhake fails, we print out an error message * and remove all TLS state concerning this session. */ sts = tls_bio_accept(vstream_fileno(props->stream), props->timeout, TLScontext); if (sts <= 0) { msg_info("SSL_accept error from %s: %d", props->namaddr, sts); tls_print_errors(); tls_free_context(TLScontext); return (0); } return (tls_server_post_accept(TLScontext)); }