diff --git a/lib/modules/SSL.pmod/sslfile.pike b/lib/modules/SSL.pmod/sslfile.pike index 2fc62bffa04ae3793e0f415dc5c1348f4213991b..c7de9e7ee9297c27797706d84201ad67b853fab3 100644 --- a/lib/modules/SSL.pmod/sslfile.pike +++ b/lib/modules/SSL.pmod/sslfile.pike @@ -1,6 +1,6 @@ #pike __REAL_VERSION__ -/* $Id: sslfile.pike,v 1.89 2005/01/27 14:43:38 mast Exp $ +/* $Id: sslfile.pike,v 1.90 2005/02/08 20:11:45 mast Exp $ */ #if constant(SSL.Cipher.CipherAlgorithm) @@ -28,8 +28,8 @@ //! @item //! Blocking characterstics are retained for all functions. //! @item -//! Connection init (@[create]) and close (@[close]) can do both -//! reading and writing. +//! @[is_open], connection init (@[create]) and close (@[close]) can +//! do both reading and writing. //! @item //! Abrupt remote close without the proper handshake gets the errno //! @[System.EPIPE]. @@ -60,11 +60,14 @@ static string stream_descr; #endif static Stdio.File stream; -// The stream is closed as soon as possible to avoid garbage. That -// means as soon as 1) close messages have been exchanged according to -// the close mode (see clean_close for close()), or 2) a remote abrupt -// close or stream close has been discovered (no use sending a close -// message then). +// The stream is closed by shutdown(), which is called directly or +// indirectly from destroy() or close() but not from anywhere else. +// +// Note that a close in nonblocking callback mode might not happen +// right away. In that case stream remains set after close() returns, +// suitable callbacks are installed for the close packet exchange, and +// close_state >= NORMAL_CLOSE. The stream is closed by the callbacks +// as soon the close packets are done, or if an error occurs. static SSL.connection conn; // Always set when stream is. Destructed with destroy() at shutdown @@ -92,7 +95,6 @@ static Pike.Backend local_backend; static int nonblocking_mode; static enum CloseState { - CLOSE_CB_CALLED = -1, STREAM_OPEN = 0, STREAM_UNINITIALIZED = 1, NORMAL_CLOSE = 2, // The caller has requested a normal close. @@ -100,8 +102,26 @@ static enum CloseState { } static CloseState close_state = STREAM_UNINITIALIZED; -static int conn_closing; -// This is conn->closing after shutdown when conn has been destructed. +static enum ClosePacketSendState { + CLOSE_PACKET_NOT_SCHEDULED = 0, + CLOSE_PACKET_SCHEDULED, + CLOSE_PACKET_QUEUED_OR_DONE, + CLOSE_PACKET_MAYBE_IGNORED_WRITE_ERROR, + CLOSE_PACKET_WRITE_ERROR, +} +static ClosePacketSendState close_packet_send_state; +// State for the close packet we send. The trickiness here is that if +// there's an error writing it, that error should sometimes be +// ignored: +// +// The remote end is allowed to close the connection immediately +// without waiting for a response, and we can't close without +// attempting to send a close packet first (RFC 2246, section 7.2.1). +// So if we've received a close packet we need to try to send one but +// ignore any error (typically EPIPE). +// +// Also, if there is an error we can't report it immediately since the +// remote close packet might be sitting in the input buffer. static int local_errno; // If nonzero, override the errno on the stream with this. @@ -134,12 +154,33 @@ static int got_extra_read_call_out; } \ } while (0) +// Ignore user installed callbacks if a close has been requested locally. #define CALLBACK_MODE \ - (read_callback || write_callback || close_callback || accept_callback) + ((read_callback || write_callback || close_callback || accept_callback) && \ + close_state < NORMAL_CLOSE) #define SSL_HANDSHAKING (!conn->handshake_finished) -#define SSL_READING_CLEAN_CLOSE (close_state == CLEAN_CLOSE && conn->closing == 1) -#define SSL_INTERNAL_TALK (SSL_HANDSHAKING || SSL_READING_CLEAN_CLOSE) +#define SSL_CLOSING_OR_CLOSED \ + (close_packet_send_state >= CLOSE_PACKET_SCHEDULED || \ + /* conn->closing & 2 is more accurate, but the following is quicker \ + * and only overlaps a bit with the check above. */ \ + conn->closing) + +// Always wait for input during handshaking and when we expect the +// remote end to respond to our close packet. We should also check the +// input buffer for a close packet if there was a failure to write our +// close packet. +#define SSL_INTERNAL_READING \ + (SSL_HANDSHAKING || \ + (close_state == CLEAN_CLOSE ? \ + conn->closing == 1 : \ + close_packet_send_state == CLOSE_PACKET_MAYBE_IGNORED_WRITE_ERROR)) + +// Try to write when there's data in the write buffer or when we have +// a close packet to send. The packet is queued separately by +// ssl_write_callback in the latter case. +#define SSL_INTERNAL_WRITING (sizeof (write_buffer) || \ + close_packet_send_state == CLOSE_PACKET_SCHEDULED) #ifdef DEBUG @@ -254,14 +295,12 @@ static THREAD_T op_thread; // stream is assumed to be operational on entry but might be zero // afterwards. cb_errno is assumed to be 0 on entry. -#define RUN_MAYBE_BLOCKING(COND, NONBLOCKING_MODE, ENABLE_READS, ERROR_CODE) do { \ - run_maybe_blocking: \ - if (COND) { \ +#define RUN_MAYBE_BLOCKING(REPEAT_COND, NONWAITING_MODE, \ + ENABLE_READS, ERROR_CODE) do { \ + run_local_backend: { \ if (!local_backend) local_backend = Pike.Backend(); \ stream->set_backend (local_backend); \ stream->set_id (0); \ - SSL3_DEBUG_MSG ("Starting %s local backend\n", \ - NONBLOCKING_MODE ? "nonblocking" : "blocking"); \ \ while (1) { \ float|int(0..0) action; \ @@ -277,10 +316,11 @@ static THREAD_T op_thread; if (got_extra_read_call_out > 0) \ real_backend->remove_call_out (ssl_read_callback); \ ssl_read_callback (0, 0); /* Will clear got_extra_read_call_out. */ \ + action = 0.0; \ } \ \ else { \ - stream->set_write_callback (sizeof (write_buffer) && ssl_write_callback); \ + stream->set_write_callback (SSL_INTERNAL_WRITING && ssl_write_callback); \ \ if (ENABLE_READS) { \ stream->set_read_callback (ssl_read_callback); \ @@ -293,45 +333,54 @@ static THREAD_T op_thread; stream->set_close_callback (0); \ } \ \ - SSL3_DEBUG_MORE_MSG ("Running local backend [%O %O %O]\n", \ - stream->query_read_callback(), \ - stream->query_write_callback(), \ - stream->query_close_callback()); \ + /* When we fail to write the close packet we should check if \ + * a close packet is in the input buffer before signalling \ + * the error. That means installing the read callbacks \ + * without waiting in the backend. */ \ + int zero_timeout = NONWAITING_MODE || \ + close_packet_send_state == CLOSE_PACKET_MAYBE_IGNORED_WRITE_ERROR; \ + \ + SSL3_DEBUG_MSG ("Running local backend [r:%O w:%O], %s timeout\n", \ + !!stream->query_read_callback(), \ + !!stream->query_write_callback(), \ + zero_timeout ? "zero" : "infinite"); \ + \ + action = local_backend (zero_timeout ? 0.0 : 0); \ + } \ \ - action = local_backend (NONBLOCKING_MODE ? 0.0 : 0); \ + if (!action && \ + close_packet_send_state == CLOSE_PACKET_MAYBE_IGNORED_WRITE_ERROR) { \ + SSL3_DEBUG_MSG ("Did not get a remote close - " \ + "signalling delayed error from writing close message\n"); \ + close_packet_send_state = CLOSE_PACKET_WRITE_ERROR; \ + cb_errno = System.EPIPE; \ } \ \ FIX_ERRNOS ({ \ - SSL3_DEBUG_MSG ("%s local backend ended with error\n", \ - NONBLOCKING_MODE ? "Nonblocking" : "Blocking"); \ + SSL3_DEBUG_MSG ("Local backend ended with error\n"); \ if (stream) { \ stream->set_backend (real_backend); \ stream->set_id (1); \ update_internal_state(); \ } \ {ERROR_CODE;} \ - break run_maybe_blocking; \ + break run_local_backend; \ }, 0); \ \ if (!stream) { \ - SSL3_DEBUG_MSG ("%s local backend ended after clean close\n", \ - NONBLOCKING_MODE ? "Nonblocking" : "Blocking"); \ - break run_maybe_blocking; \ + SSL3_DEBUG_MSG ("Local backend ended after close\n"); \ + break run_local_backend; \ } \ \ - if (NONBLOCKING_MODE && !action) { \ - SSL3_DEBUG_MSG ("Nonblocking local backend ended - nothing to do\n"); \ + if (NONWAITING_MODE && !action) { \ + SSL3_DEBUG_MSG ("Nonwaiting local backend ended - nothing to do\n"); \ break; \ } \ \ - if (!(COND)) { \ - SSL3_DEBUG_MSG ("%s local backend ended\n", \ - NONBLOCKING_MODE ? "Nonblocking" : "Blocking"); \ + if (!(REPEAT_COND)) { \ + SSL3_DEBUG_MSG ("Local backend ended - repeat condition false\n"); \ break; \ } \ - \ - SSL3_DEBUG_MSG ("Reentering %s backend\n", \ - NONBLOCKING_MODE ? "nonblocking" : "blocking"); \ } \ \ stream->set_backend (real_backend); \ @@ -365,12 +414,16 @@ static void create (Stdio.File stream, SSL.context ctx, #endif write_buffer = ({}); read_buffer = String.Buffer(); - callback_id = 0; real_backend = stream->query_backend(); - local_backend = 0; close_state = STREAM_OPEN; +#if 0 + // Unnecessary to init stuff to zero. + callback_id = 0; + local_backend = 0; + close_packet_send_state = CLOSE_PACKET_NOT_SCHEDULED; local_errno = cb_errno = 0; got_extra_read_call_out = 0; +#endif stream->set_read_callback (0); stream->set_write_callback (0); @@ -437,7 +490,7 @@ int close (void|string how, void|int clean_close) error ("Can only close the connection in both directions simultaneously.\n"); ENTER (0, 0) { - if (close_state > 0) { + if (close_state > STREAM_OPEN) { SSL3_DEBUG_MSG ("SSL.sslfile->close: Already closed (%d)\n", close_state); local_errno = System.EBADF; RETURN (0); @@ -445,33 +498,36 @@ int close (void|string how, void|int clean_close) close_state = clean_close ? CLEAN_CLOSE : NORMAL_CLOSE; FIX_ERRNOS ({ - // Get here if a close callback calls close after an error. + // Get here e.g. if a close callback calls close after an error. SSL3_DEBUG_MSG ("SSL.sslfile->close: Shutdown after error\n"); shutdown(); RETURN (0); }, 0); - if (!stream) - if (conn_closing == 3) { - SSL3_DEBUG_MSG ("SSL.sslfile->close: Already closed cleanly remotely\n"); - RETURN (1); - } - else { - local_errno = System.EPIPE; - // Errors are thrown from close(). - error ("Failed to close SSL connection: Got abrupt remote close.\n"); - } - - conn->send_close(); - update_internal_state(); // conn->closing changed. + if (close_packet_send_state == CLOSE_PACKET_NOT_SCHEDULED) { + close_packet_send_state = CLOSE_PACKET_SCHEDULED; + update_internal_state(); + } - if (!direct_write()) + if (!direct_write()) { + // Should be shut down after close(), even if an error occurred. + shutdown(); // Errors are thrown from close(). error ("Failed to close SSL connection: %s\n", strerror (errno())); + } + + if (stream && (stream->query_read_callback() || stream->query_write_callback())) + SSL3_DEBUG_MSG ("SSL.sslfile->close: Close underway\n"); + else { + // The local backend run by direct_write has typically already + // done this, but it might happen that it doesn't do anything in + // case close packets already have been exchanged. + shutdown(); + SSL3_DEBUG_MSG ("SSL.sslfile->close: Close done\n"); + } - SSL3_DEBUG_MSG ("SSL.sslfile->close: Close %s\n", stream ? "underway" : "done"); - RETURN (1); } LEAVE; + return 1; } Stdio.File shutdown() @@ -485,18 +541,19 @@ Stdio.File shutdown() RETURN (0); } - conn_closing = conn->closing; - if (close_state == NORMAL_CLOSE) + if (close_state != CLEAN_CLOSE) // If we didn't request a clean close then we pretend to have // received a close message. According to the standard it's ok // anyway as long as the transport isn't used for anything else. - conn_closing |= 2; + conn->closing |= 2; SSL3_DEBUG_MSG ("SSL.sslfile->shutdown(): %s close\n", - conn_closing == 3 && !sizeof (write_buffer) ? - "Clean" : "Abrupt"); + conn->closing & 2 && + close_packet_send_state == CLOSE_PACKET_QUEUED_OR_DONE && + !sizeof (write_buffer) ? + "Proper" : "Abrupt"); - if ((conn_closing & 2) && sizeof (conn->left_over || "")) { + if ((conn->closing & 2) && sizeof (conn->left_over || "")) { #ifdef DEBUG werror ("Warning: Got buffered data after close in %O: %O%s\n", this, conn->left_over[..99], sizeof (conn->left_over) > 100 ? "..." : ""); @@ -513,10 +570,12 @@ Stdio.File shutdown() write_buffer = ({}); +#if 0 accept_callback = 0; read_callback = 0; write_callback = 0; close_callback = 0; +#endif if (got_extra_read_call_out > 0) real_backend->remove_call_out (ssl_read_callback); @@ -529,16 +588,13 @@ Stdio.File shutdown() stream->set_write_callback (0); stream->set_close_callback (0); - if (close_state == NORMAL_CLOSE) { - SSL3_DEBUG_MSG ("SSL.sslfile->shutdown(): Normal close - closing stream\n"); + if (close_state != CLEAN_CLOSE) { + SSL3_DEBUG_MSG ("SSL.sslfile->shutdown(): Nonclean close - closing stream\n"); stream->close(); RETURN (0); } else { - // This also happens if we've been called due to a remote close. - // Since all references to and in the stream object has been - // removed it'll run out of refs and be closed shortly anyway. - SSL3_DEBUG_MSG ("SSL.sslfile->shutdown(): Leaving stream\n"); + SSL3_DEBUG_MSG ("SSL.sslfile->shutdown(): Clean close - leaving stream\n"); RETURN (stream); } } LEAVE; @@ -556,16 +612,19 @@ static void destroy() // happen if somebody has destructed this object explicitly // though, and in that case he can have all that's coming. ENTER (0, 0) { - if (stream && !close_state && - // Don't bother with closing nicely if there's an error from - // an earlier operation. close() will throw an error for it. - !cb_errno) { - // Have to do the close in blocking mode since this object will - // go away as soon as we return. - set_blocking(); - close(); + if (stream) { + if (close_state <= STREAM_OPEN && + // Don't bother with closing nicely if there's an error from + // an earlier operation. close() will throw an error for it. + !cb_errno) { + // Have to do the close in blocking mode since this object will + // go away as soon as we return. + set_blocking(); + close(); + } + else + shutdown(); } - shutdown(); } LEAVE; } @@ -580,8 +639,7 @@ string read (void|int length, void|int(0..1) not_all) SSL3_DEBUG_MSG ("SSL.sslfile->read (%d, %d)\n", length, not_all); ENTER (0, 0) { - // Only signal an error after an explicit close() call. - if (close_state > 0) error ("Not open.\n"); + if (close_state > STREAM_OPEN) error ("Not open.\n"); FIX_ERRNOS ({ SSL3_DEBUG_MSG ("SSL.sslfile->read: Propagating old callback error: %s\n", @@ -590,13 +648,31 @@ string read (void|int length, void|int(0..1) not_all) }, 0); if (stream) - if (not_all) - RUN_MAYBE_BLOCKING (!sizeof (read_buffer), 0, 1, - RETURN (0)); - else - RUN_MAYBE_BLOCKING (sizeof (read_buffer) < length || zero_type (length), - nonblocking_mode, 1, - RETURN (0)); + if (not_all) { + if (!sizeof (read_buffer)) + RUN_MAYBE_BLOCKING (!sizeof (read_buffer), 0, 1, + if (sizeof (read_buffer)) { + // Got data to return first. Push the + // error back so it'll get reported by + // the next call. + cb_errno = local_errno; + local_errno = 0; + } + else RETURN (0);); + } + else { + if (sizeof (read_buffer) < length || zero_type (length)) + RUN_MAYBE_BLOCKING (sizeof (read_buffer) < length || zero_type (length), + nonblocking_mode, 1, + if (sizeof (read_buffer)) { + // Got data to return first. Push the + // error back so it'll get reported by + // the next call. + cb_errno = local_errno; + local_errno = 0; + } + else RETURN (0);); + } string res = read_buffer->get(); if (!zero_type (length)) { @@ -620,7 +696,7 @@ int write (string|array(string) data, mixed... args) //! //! @note //! This function returns zero if attempts are made to write data -//! during the handshake phase. +//! during the handshake phase and the mode is nonblocking. //! //! @note //! I/O errors from both reading and writing might occur in blocking @@ -632,8 +708,7 @@ int write (string|array(string) data, mixed... args) SSL3_DEBUG_MSG ("SSL.sslfile->write (%t[%d])\n", data, sizeof (data)); ENTER (0, 0) { - // Only signal an error after an explicit close() call. - if (close_state > 0) error ("Not open.\n"); + if (close_state > STREAM_OPEN) error ("Not open.\n"); FIX_ERRNOS ({ SSL3_DEBUG_MSG ("SSL.sslfile->write: Propagating old callback error: %s\n", @@ -641,7 +716,7 @@ int write (string|array(string) data, mixed... args) RETURN (-1); }, 0); - if (SSL_HANDSHAKING) { + if (nonblocking_mode && SSL_HANDSHAKING) { SSL3_DEBUG_MSG ("SSL.sslfile->write: " "Still in handshake - cannot accept application data\n"); RETURN (0); @@ -725,8 +800,7 @@ int renegotiate() SSL3_DEBUG_MSG ("SSL.sslfile->renegotiate()\n"); ENTER (0, 0) { - // Only signal an error after an explicit close() call. - if (close_state > 0) error ("Not open.\n"); + if (close_state > STREAM_OPEN) error ("Not open.\n"); FIX_ERRNOS ({ SSL3_DEBUG_MSG ("SSL.sslfile->renegotiate: " @@ -776,8 +850,7 @@ void set_nonblocking (void|function(void|mixed,void|string:int) read, "" || describe_backtrace (backtrace())); ENTER (0, 0) { - // Only signal an error after an explicit close() call. - if (close_state > 0) error ("Not open.\n"); + if (close_state > STREAM_OPEN) error ("Not open.\n"); nonblocking_mode = 1; @@ -797,6 +870,28 @@ void set_nonblocking (void|function(void|mixed,void|string:int) read, } LEAVE; } +void set_nonblocking_keep_callbacks() +//! Set nonblocking mode like @[set_nonblocking], but don't alter any +//! callbacks. +{ + SSL3_DEBUG_MSG ("SSL.sslfile->set_nonblocking_keep_callbacks()\n"); + + ENTER (0, 0) { + if (close_state > STREAM_OPEN) error ("Not open.\n"); + + nonblocking_mode = 1; + + if (stream) { + stream->set_nonblocking_keep_callbacks(); + // Has to restore here since a backend waiting in another thread + // might be woken immediately when callbacks are registered. + RESTORE; + update_internal_state(); + return; + } + } LEAVE; +} + void set_blocking() //! Set the stream in blocking mode. All but the alert callback are //! zapped. @@ -827,14 +922,32 @@ void set_blocking() "" || describe_backtrace (backtrace())); ENTER (0, 0) { - if (!close_state) { - nonblocking_mode = 0; - accept_callback = read_callback = write_callback = close_callback = 0; + if (close_state > STREAM_OPEN) error ("Not open.\n"); - if (stream) { - update_internal_state(); - stream->set_blocking(); - } + nonblocking_mode = 0; + accept_callback = read_callback = write_callback = close_callback = 0; + + if (stream) { + update_internal_state(); + stream->set_blocking(); + } + } LEAVE; +} + +void set_blocking_keep_callbacks() +//! Set blocking mode like @[set_blocking], but don't alter any +//! callbacks. +{ + SSL3_DEBUG_MSG ("SSL.sslfile->set_blocking_keep_callbacks()\n"); + + ENTER (0, 0) { + if (close_state > STREAM_OPEN) error ("Not open.\n"); + + nonblocking_mode = 0; + + if (stream) { + update_internal_state(); + stream->set_blocking(); } } LEAVE; } @@ -874,7 +987,7 @@ void set_accept_callback (function(void|object,void|mixed:int) accept) //! Install a function that will be called when the handshake is //! finished and the connection is ready for use. //! -//! the callback function will be called with the sslfile object +//! The callback function will be called with the sslfile object //! and the additional id arguments (set with @[set_id]). //! //! @note @@ -977,8 +1090,7 @@ void set_backend (Pike.Backend backend) //! Set the backend used for the file callbacks. { ENTER (0, 0) { - // Only signal an error after an explicit close() call. - if (close_state > 0) error ("Not open.\n"); + if (close_state > STREAM_OPEN) error ("Not open.\n"); if (stream) { if (stream->query_backend() != local_backend) @@ -997,23 +1109,43 @@ void set_backend (Pike.Backend backend) Pike.Backend query_backend() //! Return the backend used for the file callbacks. { - // Only signal an error after an explicit close() call. - if (close_state > 0) error ("Not open.\n"); + if (close_state > STREAM_OPEN) error ("Not open.\n"); return real_backend; } string query_address(int|void arg) //! { - // Only signal an error after an explicit close() call. - if (close_state > 0) error ("Not open.\n"); + if (close_state > STREAM_OPEN) error ("Not open.\n"); return stream->query_address(arg); } int is_open() -//! +//! Return nonzero if the stream currently is open, zero otherwise. +//! This function does nonblocking I/O to check for a close packet in +//! the input buffer. { - return close_state <= 0 && stream && stream->is_open(); + SSL3_DEBUG_MSG ("SSL.sslfile->is_open()\n"); + ENTER (0, 0) { + if (close_state <= STREAM_OPEN && stream && stream->is_open()) { + // Have to check if there's a close packet waiting to be read. + // This is common in keep-alive situations since the remote end + // might have sent a close packet and closed the connection a + // long time ago, and in a typical blocking client no action has + // been taken causing the close packet to be read. + // + // Could avoid the whole local backend hoopla here by + // essentially doing a peek and call ssl_read_callback directly, + // but that'd lead to subtle code duplication. (Also, peek is + // currently not implemented on NT.) + if (!conn->closing) + RUN_MAYBE_BLOCKING ( + action && !conn->closing, 1, 1, + RETURN (!(<System.EPIPE, System.ECONNRESET>)[local_errno])); + RETURN (conn && !conn->closing); + } + } LEAVE; + return 0; } Stdio.File query_stream() @@ -1054,55 +1186,57 @@ static void update_internal_state() // When the local backend is used, callbacks are set explicitly // before it's started. if (stream->query_backend() != local_backend) { + mixed install_read_cbs, install_write_cb; - if (CALLBACK_MODE) { + if (nonblocking_mode && close_state >= NORMAL_CLOSE) { + // Normally we never install our own callbacks if we aren't in + // callback mode, but handling a nonblocking close is an + // exception. + install_read_cbs = SSL_INTERNAL_READING; + install_write_cb = SSL_INTERNAL_WRITING; - if (read_callback || close_callback || accept_callback || SSL_INTERNAL_TALK) { - stream->set_read_callback (ssl_read_callback); - stream->set_close_callback (ssl_close_callback); - if (got_extra_read_call_out < 0) { - // Install it even if we're in a handshake. There might - // still be old data to read if we're renegotiating. - real_backend->call_out (ssl_read_callback, 0, 1, 0); - got_extra_read_call_out = 1; - } - } - else { - stream->set_read_callback (0); - stream->set_close_callback (0); - if (got_extra_read_call_out > 0) { - real_backend->remove_call_out (ssl_read_callback); - got_extra_read_call_out = -1; - } - } + SSL3_DEBUG_MORE_MSG ("update_internal_state: After close [r:%O w:%O]\n", + !!install_read_cbs, !!install_write_cb); + } - if (write_callback || sizeof (write_buffer)) - stream->set_write_callback (ssl_write_callback); - else - stream->set_write_callback (0); + // CALLBACK_MODE but slightly optimized below. + else if (read_callback || write_callback || close_callback || accept_callback) { + install_read_cbs = (read_callback || close_callback || accept_callback || + SSL_INTERNAL_READING); + install_write_cb = (write_callback || SSL_INTERNAL_WRITING); SSL3_DEBUG_MORE_MSG ("update_internal_state: " - "After handshake, callback mode [%O %O %O %O]\n", - stream->query_read_callback(), - stream->query_write_callback(), - stream->query_close_callback(), - got_extra_read_call_out); - return; + "After handshake, callback mode [r:%O w:%O]\n", + !!install_read_cbs, !!install_write_cb); } - // Not in callback mode. Can't install callbacks even though we'd - // "need" to - have to cope with the local backend in each - // operation instead. - stream->set_read_callback (0); - stream->set_close_callback (0); - stream->set_write_callback (0); - if (got_extra_read_call_out > 0) { - real_backend->remove_call_out (ssl_read_callback); - got_extra_read_call_out = -1; + else { + // Not in callback mode. Can't install callbacks even though we'd + // "need" to - have to cope with the local backend in each + // operation instead. + SSL3_DEBUG_MORE_MSG ("update_internal_state: Not in callback mode\n"); } - SSL3_DEBUG_MORE_MSG ("update_internal_state: Not in callback mode [0 0 0 %O]\n", - got_extra_read_call_out); + if (install_read_cbs) { + stream->set_read_callback (ssl_read_callback); + stream->set_close_callback (ssl_close_callback); + if (got_extra_read_call_out < 0) { + real_backend->call_out (ssl_read_callback, 0, 1, 0); + got_extra_read_call_out = 1; + } + } + else { + stream->set_read_callback (0); + // Installing a close callback without a read callback + // currently doesn't work well in Stdio.File. + stream->set_close_callback (0); + if (got_extra_read_call_out > 0) { + real_backend->remove_call_out (ssl_read_callback); + got_extra_read_call_out = -1; + } + } + + stream->set_write_callback (install_write_cb && ssl_write_callback); } } @@ -1140,7 +1274,8 @@ static int queue_write() } static int direct_write() -// Do a write directly. Something to write is assumed to exist (either +// Do a write directly (and maybe also read if there's internal +// reading to be done). Something to write is assumed to exist (either // in write_buffer or in the packet queue). Returns zero on error (as // opposed to queue_write). { @@ -1159,30 +1294,22 @@ static int direct_write() SSL3_DEBUG_MSG ("direct_write: " "Connection closed abruptly - simulating System.EPIPE\n"); local_errno = System.EPIPE; - shutdown(); return 0; } - RUN_MAYBE_BLOCKING (sizeof (write_buffer) || SSL_INTERNAL_TALK, - nonblocking_mode, SSL_INTERNAL_TALK, - return 0;); + if (SSL_INTERNAL_WRITING || SSL_INTERNAL_READING) + RUN_MAYBE_BLOCKING (SSL_INTERNAL_WRITING || SSL_INTERNAL_READING, + nonblocking_mode, SSL_INTERNAL_READING, + SSL3_DEBUG_MORE_MSG ("direct_write: Got error\n"); + return 0;); } + SSL3_DEBUG_MORE_MSG ("direct_write: Ok\n"); return 1; } private int call_close_callback() { - // errno() should return the error in the close callback - need to - // propagate it here. - FIX_ERRNOS ( - SSL3_DEBUG_MSG ("call_close_callback: Calling close callback %O (error %d)\n", - close_callback, cb_errno), - SSL3_DEBUG_MSG ("call_close_callback: Calling close callback %O (read eof)\n", - close_callback) - ); - close_state = CLOSE_CB_CALLED; - return close_callback (callback_id); } static int ssl_read_callback (int called_from_real_backend, string input) @@ -1193,7 +1320,9 @@ static int ssl_read_callback (int called_from_real_backend, string input) input ? "string[" + sizeof (input) + "]" : "0 (queued extra call)", nonblocking_mode, !!(CALLBACK_MODE), conn && SSL_HANDSHAKING ? ", handshaking" : "", - conn && conn->closing ? ", closing (" + conn->closing + ")" : ""); + conn && SSL_CLOSING_OR_CLOSED ? + ", closing (" + conn->closing + ", " + close_state + ", " + + close_packet_send_state + ")" : ""); ENTER (1, called_from_real_backend) { int call_accept_cb; @@ -1212,7 +1341,8 @@ static int ssl_read_callback (int called_from_real_backend, string input) if (!stream) error ("Got zapped stream in callback.\n"); #endif - // got_data might have put more packets in the write queue. + // got_data might have put more packets in the write queue if + // we're handshaking. write_res = queue_write(); } @@ -1222,8 +1352,13 @@ static int ssl_read_callback (int called_from_real_backend, string input) if (!handshake_already_finished && conn->handshake_finished) { SSL3_DEBUG_MSG ("ssl_read_callback: Handshake finished\n"); update_internal_state(); - if (called_from_real_backend && accept_callback) + if (called_from_real_backend && accept_callback) { +#ifdef DEBUG + if (close_state >= NORMAL_CLOSE) + error ("Didn't expect the connection to be explicitly closed already.\n"); +#endif call_accept_cb = 1; + } } SSL3_DEBUG_MSG ("ssl_read_callback: " @@ -1237,12 +1372,17 @@ static int ssl_read_callback (int called_from_real_backend, string input) cb_errno = System.EPIPE; } -#ifdef SSL3_DEBUG // Don't use data > 0 here since we might have processed some // application data and a close in the same got_data call. - if (conn->closing & 2) - SSL3_DEBUG_MSG ("ssl_read_callback: Got close message\n"); -#endif + if (conn->closing & 2) { + if (close_packet_send_state == CLOSE_PACKET_MAYBE_IGNORED_WRITE_ERROR) { + SSL3_DEBUG_MSG ("ssl_read_callback: Got close packet - " + "ignoring failure to send one\n"); + close_packet_send_state = CLOSE_PACKET_QUEUED_OR_DONE; + } + else + SSL3_DEBUG_MSG ("ssl_read_callback: Got close packet\n"); + } } else @@ -1259,15 +1399,10 @@ static int ssl_read_callback (int called_from_real_backend, string input) // Figure out what we need to do. call_accept_cb is already set // from above. int(0..1) call_read_cb = - called_from_real_backend && read_callback && !!sizeof (read_buffer); + called_from_real_backend && read_callback && sizeof (read_buffer) && + close_state < NORMAL_CLOSE; int(0..1) do_close_stuff = - !!(conn->closing & 2); - - if (cb_errno) { - // An error changes everything.. - call_accept_cb = call_read_cb = 0; - do_close_stuff = 1; - } + !!((conn->closing & 2) || cb_errno); if (call_accept_cb + call_read_cb + do_close_stuff > 1) { // Need to do a call out to ourselves; see comment above. @@ -1318,29 +1453,51 @@ static int ssl_read_callback (int called_from_real_backend, string input) } else if (do_close_stuff) { - update_internal_state(); + if (conn->closing & 2 && + close_packet_send_state == CLOSE_PACKET_NOT_SCHEDULED) { + close_packet_send_state = CLOSE_PACKET_SCHEDULED; + // Deinstall read side cbs to avoid reading more and install + // the write cb to send the close packet. Can't use + // update_internal_state here since we should force a + // deinstall of the read side cbs even when we're still in + // callback mode, to correctly imitate the behavior in + // Stdio.File (it deinstalls read side cbs whenever the close + // cb is called, but it's possible to reinstall the close cb + // later and get another call to it). + if (stream->query_backend() != local_backend) { + stream->set_read_callback (0); + stream->set_close_callback (0); + stream->set_write_callback (ssl_write_callback); + SSL3_DEBUG_MORE_MSG ("ssl_read_callback: Setting cbs for close [r:0 w:1]\n"); + } + } - if (called_from_real_backend && close_callback && !close_state) { + if (called_from_real_backend && close_callback) { + // errno() should return the error in the close callback - need to + // propagate it here. + FIX_ERRNOS ( + SSL3_DEBUG_MSG ("ssl_read_callback: Calling close callback %O (error %s)\n", + close_callback, strerror (local_errno)), + SSL3_DEBUG_MSG ("ssl_read_callback: Calling close callback %O (read eof)\n", + close_callback) + ); RESTORE; - // Note that the callback should call close() (or free things - // so that we get destructed) - there's no need for us to - // schedule a shutdown after it. - return call_close_callback(); + return close_callback (callback_id); } - if (cb_errno) { - SSL3_DEBUG_MSG ("ssl_read_callback: Shutting down with error\n"); + if (close_state >= NORMAL_CLOSE) { + SSL3_DEBUG_MSG ("ssl_read_callback: " + "In or after local close - shutting down\n"); shutdown(); + } + + if (cb_errno) { + SSL3_DEBUG_MSG ("ssl_read_callback: Returning with error\n"); // Make sure the local backend exits after this, so that the // error isn't clobbered by later I/O. RESTORE; return -1; } - else if (!sizeof (write_buffer)) { - SSL3_DEBUG_MSG ("ssl_read_callback: " - "Close messages exchanged - shutting down\n"); - shutdown(); - } } } LEAVE; @@ -1354,7 +1511,9 @@ static int ssl_write_callback (int called_from_real_backend) called_from_real_backend, nonblocking_mode, !!(CALLBACK_MODE), conn && SSL_HANDSHAKING ? ", handshaking" : "", - conn && conn->closing ? ", closing (" + conn->closing + ")" : ""); + conn && SSL_CLOSING_OR_CLOSED ? + ", closing (" + conn->closing + ", " + close_state + ", " + + close_packet_send_state + ")" : ""); int ret = 0; @@ -1385,7 +1544,13 @@ static int ssl_write_callback (int called_from_real_backend) do { if (sizeof (write_buffer)) { string output = write_buffer[0]; - int written = stream->write (output); + int written; +#ifdef SIMULATE_CLOSE_PACKET_WRITE_FAILURE + if (close_packet_send_state == CLOSE_PACKET_QUEUED_OR_DONE) + written = -1; + else +#endif + written = stream->write (output); if (written < 0 #if 0 @@ -1395,22 +1560,68 @@ static int ssl_write_callback (int called_from_real_backend) #endif #endif ) { +#ifdef SIMULATE_CLOSE_PACKET_WRITE_FAILURE + if (close_packet_send_state == CLOSE_PACKET_QUEUED_OR_DONE) + cb_errno = System.EPIPE; + else +#endif + cb_errno = stream->errno(); SSL3_DEBUG_MSG ("ssl_write_callback: Write failed: %s\n", - strerror (stream->errno())); - cb_errno = stream->errno(); + strerror (cb_errno)); // Make sure the local backend exits after this, so that the // error isn't clobbered by later I/O. ret = -1; - if ((<System.EPIPE, System.ECONNRESET>)[cb_errno]) { - SSL3_DEBUG_MSG ("ssl_write_callback: " - "Stream closed remotely - shutting down\n"); - shutdown(); + if (close_packet_send_state == CLOSE_PACKET_QUEUED_OR_DONE && + (<System.EPIPE, System.ECONNRESET>)[cb_errno]) { + // See if it's an error from writing a close packet that + // should be ignored. + write_buffer = ({}); // No use trying to write the close again. + + if (close_state == CLEAN_CLOSE) { + // Never accept a failure if a clean close is requested. + close_packet_send_state = CLOSE_PACKET_WRITE_ERROR; + update_internal_state(); + } + + else { + cb_errno = 0; + + if (conn->closing & 2) + SSL3_DEBUG_MSG ("ssl_write_callback: Stream closed properly " + "remotely - ignoring failure to send close packet\n"); + + else { + SSL3_DEBUG_MSG ("ssl_write_callback: Stream closed remotely - " + "checking input buffer for proper remote close\n"); + // This causes the read/close callbacks to be + // installed to handle the close packet that might be + // sitting in the input buffer. + close_packet_send_state = CLOSE_PACKET_MAYBE_IGNORED_WRITE_ERROR; + update_internal_state(); + if (called_from_real_backend) { + // Shouldn't wait for a close packet that might + // arrive later on, so we start a nonblocking local + // backend to check for it. If we're already in a + // local backend, this is handled by special cases + // in RUN_MAYBE_BLOCKING. + RUN_MAYBE_BLOCKING ( + close_packet_send_state == CLOSE_PACKET_MAYBE_IGNORED_WRITE_ERROR, + 1, 1, {}); + } + else { + // Can't start a nested local backend - skip out to + // the one we're called from. + RESTORE; + return ret; + } + } + } } - // Still try to call the write callback so that we propagate - // the error through the write() call it probably will do. + // Still try to call the write (or close) callback to + // propagate the error to it. break write_to_stream; } @@ -1428,24 +1639,37 @@ static int ssl_write_callback (int called_from_real_backend) update_internal_state(); } + else + // Ensure that the close is sent separately in case we should + // ignore the error from it. + if (close_packet_send_state == CLOSE_PACKET_SCHEDULED) { + SSL3_DEBUG_MSG ("ssl_write_callback: Queuing close packet\n"); + conn->send_close(); + close_packet_send_state = CLOSE_PACKET_QUEUED_OR_DONE; + } + if (int err = queue_write()) { if (err > 0) { #ifdef DEBUG - if (!conn->closing) + if (!conn->closing || close_packet_send_state < CLOSE_PACKET_QUEUED_OR_DONE) error ("Expected a close to be sent or received\n"); #endif - if (!sizeof (write_buffer) && - (conn->closing == 3 || close_state == NORMAL_CLOSE)) { - SSL3_DEBUG_MSG ("ssl_write_callback: %s - shutting down\n", - conn->closing == 3 ? "Close messages exchanged" : - "Close message sent"); - shutdown(); + if (sizeof (write_buffer)) + SSL3_DEBUG_MSG ("ssl_write_callback: " + "Close packet queued but not yet sent\n"); + else if (!SSL_INTERNAL_READING) { + SSL3_DEBUG_MSG ("ssl_write_callback: %s\n", + conn->closing == 3 ? + "Close packets exchanged" : "Close packet sent"); + break write_to_stream; } - else + else { SSL3_DEBUG_MSG ("ssl_write_callback: " - "Waiting for close message to be %s\n", - conn->closing == 1 ? "received" : "sent"); + "Close packet sent - expecting response\n"); + // Not SSL_INTERNAL_WRITING anymore. + update_internal_state(); + } RESTORE; return ret; @@ -1457,24 +1681,52 @@ static int ssl_write_callback (int called_from_real_backend) "simulating System.EPIPE\n"); cb_errno = System.EPIPE; ret = -1; - shutdown(); break write_to_stream; } } } while (sizeof (write_buffer)); - if (called_from_real_backend && write_callback && !SSL_INTERNAL_TALK) - { - // errno() should return the error in the write callback - need - // to propagate it here. - FIX_ERRNOS ( - SSL3_DEBUG_MSG ("ssl_write_callback: Calling write callback %O (error %d)\n", - write_callback, cb_errno), - SSL3_DEBUG_MSG ("ssl_write_callback: Calling write callback %O\n", - write_callback) - ); - RESTORE; - return write_callback (callback_id); + if (called_from_real_backend) { + if (close_packet_send_state >= CLOSE_PACKET_SCHEDULED) { + if (close_callback && cb_errno && close_state < NORMAL_CLOSE) { + // Better signal errors writing the close packet to the + // close callback. + FIX_ERRNOS ( + SSL3_DEBUG_MSG ("ssl_write_callback: Calling close callback %O " + "(error %s)\n", close_callback, strerror (local_errno)), + 0 + ); + RESTORE; + return close_callback (callback_id); + } + } + + else { + if (write_callback && !sizeof (write_buffer) + && (close_state < NORMAL_CLOSE || cb_errno)) { + // errno() should return the error in the write callback - need + // to propagate it here. + FIX_ERRNOS ( + SSL3_DEBUG_MSG ("ssl_write_callback: Calling write callback %O " + "(error %s)\n", write_callback, strerror (local_errno)), + SSL3_DEBUG_MSG ("ssl_write_callback: Calling write callback %O\n", + write_callback) + ); + RESTORE; + return write_callback (callback_id); + } + } + } + + if (close_state >= NORMAL_CLOSE && + (close_packet_send_state >= CLOSE_PACKET_QUEUED_OR_DONE || cb_errno)) { +#ifdef DEBUG + if (close_packet_send_state == CLOSE_PACKET_MAYBE_IGNORED_WRITE_ERROR) + error ("Unexpected close_packet_send_state\n"); +#endif + SSL3_DEBUG_MSG ("ssl_write_callback: " + "In or after local close - shutting down\n"); + shutdown(); } } LEAVE; return ret; @@ -1487,7 +1739,9 @@ static int ssl_close_callback (int called_from_real_backend) called_from_real_backend, nonblocking_mode, !!(CALLBACK_MODE), conn && SSL_HANDSHAKING ? ", handshaking" : "", - conn && conn->closing ? ", closing (" + conn->closing + ")" : ""); + conn && SSL_CLOSING_OR_CLOSED ? + ", closing (" + conn->closing + ", " + close_state + ", " + + close_packet_send_state + ")" : ""); ENTER (1, called_from_real_backend) { #ifdef DEBUG @@ -1515,7 +1769,7 @@ static int ssl_close_callback (int called_from_real_backend) // If we've arrived here due to an error, let it override any // older errno from an earlier callback. if (int new_errno = stream->errno()) { - SSL3_DEBUG_MSG ("ssl_close_callback: Got error %d\n", new_errno); + SSL3_DEBUG_MSG ("ssl_close_callback: Got error %s\n", strerror (new_errno)); cb_errno = new_errno; } #ifdef SSL3_DEBUG @@ -1524,33 +1778,46 @@ static int ssl_close_callback (int called_from_real_backend) #endif if (!cb_errno) { - if (conn->closing & 2) { - // A proper close is handled in ssl_read_callback when we get - // the close packet, so there's nothing to do here. - SSL3_DEBUG_MSG ("ssl_close_callback: Clean close already handled\n"); - RESTORE; - return 0; - } + if (conn->closing & 2) + SSL3_DEBUG_MSG ("ssl_close_callback: After clean close\n"); - // The remote end has closed the connection without sending a - // close packet. Treat that as an error so that the caller can - // detect truncation attacks. - SSL3_DEBUG_MSG ("ssl_close_callback: Abrupt close - simulating System.EPIPE\n"); - cb_errno = System.EPIPE; + else { + // The remote end has closed the connection without sending a + // close packet. Treat that as an error so that the caller can + // detect truncation attacks. + if (close_packet_send_state == CLOSE_PACKET_MAYBE_IGNORED_WRITE_ERROR) { + SSL3_DEBUG_MSG ("ssl_close_callback: Did not get a remote close - " + "signalling delayed error from writing close message\n"); + close_packet_send_state = CLOSE_PACKET_WRITE_ERROR; + } + else + SSL3_DEBUG_MSG ("ssl_close_callback: Abrupt close - " + "simulating System.EPIPE\n"); + cb_errno = System.EPIPE; + } } - // Got an error. - - if (called_from_real_backend && close_callback && !close_state) { - // Report the error using the close callback. + if (called_from_real_backend && close_callback) { + // errno() should return the error in the close callback - need to + // propagate it here. + FIX_ERRNOS ( + SSL3_DEBUG_MSG ("ssl_close_callback: Calling close callback %O (error %s)\n", + close_callback, strerror (local_errno)), + SSL3_DEBUG_MSG ("ssl_close_callback: Calling close callback %O (read eof)\n", + close_callback) + ); RESTORE; // Note that the callback should call close() (or free things // so that we get destructed) - there's no need for us to // schedule a shutdown after it. - return call_close_callback(); + return close_callback (callback_id); } - shutdown(); + if (close_state >= NORMAL_CLOSE) { + SSL3_DEBUG_MSG ("ssl_close_callback: " + "In or after local close - shutting down\n"); + shutdown(); + } } LEAVE; // Make sure the local backend exits after this, so that the error