Skip to content
Snippets Groups Projects
Select Git revision
  • b00bef2f83c63be464e2c9b6a232f97b24262a24
  • master default protected
  • 9.0
  • 8.0
  • 7.8
  • 7.6
  • 7.4
  • 7.2
  • 7.0
  • 0.6
  • rosuav/latex-markdown-renderer
  • rxnpatch/rxnpatch
  • marcus/gobject-introspection
  • rxnpatch/8.0
  • rosuav/pre-listening-ports
  • nt-tools
  • rosuav/async-annotations
  • rosuav/pgsql-ssl
  • rxnpatch/rxnpatch-broken/2023-10-06T094250
  • grubba/fdlib
  • grubba/wip/sakura/8.0
  • v8.0.2000
  • v8.0.1998
  • v8.0.1996
  • v8.0.1994
  • v8.0.1992
  • v8.0.1990
  • v8.0.1988
  • v8.0.1986
  • rxnpatch/clusters/8.0/2025-04-29T124414
  • rxnpatch/2025-04-29T124414
  • v8.0.1984
  • v8.0.1982
  • v8.0.1980
  • v8.0.1978
  • v8.0.1976
  • v8.0.1974
  • v8.0.1972
  • v8.0.1970
  • v8.0.1968
  • v8.0.1966
41 results

File.pike

Blame
  • File.pike 70.40 KiB
    #pike __REAL_VERSION__
    #require constant(SSL.Cipher)
    
    //! Interface similar to @[Stdio.File].
    //!
    //! @ul
    //! @item
    //!   Handles blocking and nonblocking mode.
    //! @item
    //!   Handles callback mode in an arbitrary backend (also in blocking
    //!   mode).
    //! @item
    //!   Read and write operations might each do both reading and
    //!   writing. In callback mode that means that installing either a
    //!   read or a write callback might install both internally. It also
    //!   means that reading in one thread while writing in another
    //!   doesn't work.
    //! @item
    //!   Callback changing operations like @[set_blocking] and
    //!   @[set_nonblocking] aren't atomic.
    //! @item
    //!   Apart from the above, thread safety/atomicity characteristics
    //!   are retained.
    //! @item
    //!   Blocking characterstics are retained for all functions.
    //! @item
    //!   @[is_open], connection init (@[create]) and close (@[close]) can
    //!   do both reading and writing.
    //! @item
    //!   @[destroy] attempts to close the stream properly by sending the
    //!   close packet, but since it can't do blocking I/O it's not
    //!   certain that it will succeed. The stream should therefore always
    //!   be closed with an explicit @[close] call.
    //! @item
    //!   Abrupt remote close without the proper handshake gets the errno
    //!   @[System.EPIPE].
    //! @item
    //!   Objects do not contain cyclic references, so they are closed and
    //!   destructed timely when dropped.
    //! @endul
    
    // #define SSLFILE_DEBUG
    // #define SSL3_DEBUG
    // #define SSL3_DEBUG_MORE
    // #define SSL3_DEBUG_TRANSPORT
    
    #ifdef SSL3_DEBUG
    protected string stream_descr;
    #define SSL3_DEBUG_MSG(X...)						\
      werror ("[thr:" + this_thread()->id_number() +			\
    	  "," + (stream ? "" : "ex ") + stream_descr + "] " + X)
    #ifdef SSL3_DEBUG_MORE
    #define SSL3_DEBUG_MORE_MSG(X...) SSL3_DEBUG_MSG (X)
    #endif
    #else
    #define SSL3_DEBUG_MSG(X...) 0
    #endif
    
    #ifndef SSL3_DEBUG_MORE_MSG
    #define SSL3_DEBUG_MORE_MSG(X...) 0
    #endif
    
    protected Stdio.File stream;
    // 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.
    
    protected int(-1..65535) linger_time = -1;
    // The linger behaviour set by linger().
    
    protected .Context context;
    // The context to use.
    
    protected .Connection conn;
    // Always set when stream is. Destructed with destroy() at shutdown
    // since it contains cyclic references. Noone else gets to it, though.
    
    protected array(string) write_buffer; // Encrypted data to be written.
    protected String.Buffer read_buffer; // Decrypted data that has been read.
    
    protected mixed callback_id;
    protected function(void|object,void|mixed:int) accept_callback;
    protected function(void|mixed,void|string:int) read_callback;
    protected function(void|mixed:int) write_callback;
    protected function(void|mixed:int) close_callback;
    
    protected Pike.Backend real_backend;
    // The real backend for the stream.
    
    protected Pike.Backend local_backend;
    // Internally all I/O is done using callbacks. When the real backend
    // can't be used, either because we aren't in callback mode (i.e. the
    // user hasn't registered any callbacks), or because we're to do a
    // blocking operation, this local one takes its place. It's
    // created on demand.
    
    protected int nonblocking_mode;
    
    protected int fragment_max_size;
    //! The max amount of data to send in each packet.
    //! Initialized from the context when the object is created.
    
    import .Constants;
    
    protected enum CloseState {
      ABRUPT_CLOSE = -1,
      STREAM_OPEN = 0,
      STREAM_UNINITIALIZED = 1,
      NORMAL_CLOSE = 2,		// The caller has requested a normal close.
      CLEAN_CLOSE = 3,		// The caller has requested a clean close.
    }
    protected CloseState close_state = STREAM_UNINITIALIZED;
    // ABRUPT_CLOSE is set if there's a remote close without close packet.
    // The stream is still considered open locally, but reading or writing
    // to it trigs System.EPIPE.
    
    protected int local_errno;
    // If nonzero, override the errno on the stream with this.
    
    protected int cb_errno;
    // Stores the errno from failed I/O in a callback so that the next
    // visible I/O operation can report it properly.
    
    protected int got_extra_read_call_out;
    // 1 when we have a call out to ssl_read_callback. We get this when we
    // need to call read_callback or close_callback but can't do that
    // right away from ssl_read_callback. See comments in that function
    // for more details. -1 if we've switched to non-callback mode and
    // therefore has removed the call out temporarily but need to restore
    // it when switching back. 0 otherwise.
    //
    // -1 is also set before a call to update_internal_state when we want
    // to schedule an extra read call out; update_internal_state will then
    // do the actual call out installation if possible.
    
    protected int alert_cb_called;
    // Need to know if the alert callback has been called in
    // ssl_read_callback since it can't continue in that case. This is
    // only set temporarily while ssl_read_callback runs.
    
    protected constant epipe_errnos = (<
      System.EPIPE,
      System.ECONNRESET,
    #if constant(System.WSAECONNRESET)
      // The following is returned by winsock on windows.
      // Pike ought to map it to System.ECONNRESET.
      System.WSAECONNRESET,
    #endif
    >);
    // Multiset containing the errno codes that can occur if the remote
    // end has closed the connection.
    
    // This macro is used in all user called functions that can report I/O
    // errors, both at the beginning and after
    // ssl_(read|write|close)_callback calls.
    #define FIX_ERRNOS(ERROR, NO_ERROR) do {				\
        if (cb_errno) {							\
          /* Got a stored error from a previous callback that has failed. */ \
          local_errno = cb_errno;						\
          cb_errno = 0;							\
          {ERROR;}								\
        }									\
        else {								\
          local_errno = 0;							\
          {NO_ERROR;}							\
        }									\
      } while (0)
    
    // Ignore user installed callbacks if a close has been requested locally.
    #define CALLBACK_MODE							\
      ((read_callback || write_callback || close_callback || accept_callback) && \
       close_state < NORMAL_CLOSE)
    
    #define SSL_HANDSHAKING (!conn || ((conn->state & CONNECTION_handshaking) && \
    				   close_state != ABRUPT_CLOSE))
    #define SSL_CLOSING_OR_CLOSED						\
      (conn->state & CONNECTION_local_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						\
      (conn && (SSL_HANDSHAKING ||						\
    	    ((conn->state & CONNECTION_closed) == CONNECTION_local_closed)))
    
    // 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 (conn &&					\
    			      (sizeof (write_buffer) ||			\
    			       ((conn->state & CONNECTION_local_down) == \
    				CONNECTION_local_closing)))
    
    #ifdef SSLFILE_DEBUG
    
    #if constant (Thread.thread_create)
    
    #define THREAD_T Thread.Thread
    #define THIS_THREAD() this_thread()
    
    
    protected void thread_error (string msg, THREAD_T other_thread)
    {
    #if 0 && constant (_locate_references)
      werror ("%s\n%O got %d refs", msg, this, _refs (this));
      _locate_references (this);
    #endif
      error ("%s"
    	 "%s\n"
    	 "User callbacks: a=%O r=%O w=%O c=%O\n"
    	 "Internal callbacks: r=%O w=%O c=%O\n"
    	 "Backend: %O  This thread: %O  Other thread: %O\n"
    	 "%s",
    	 msg,
    	 !stream ? "Got no stream" :
    	 stream->is_open() ? "Stream is open" :
    	 "Stream is closed",
    	 accept_callback, read_callback, write_callback, close_callback,
    	 stream && stream->query_read_callback(),
    	 stream && stream->query_write_callback(),
    	 stream && stream->query_close_callback(),
    	 stream && stream->query_backend(),
    	 this_thread(), other_thread,
    	 other_thread ? ("Other thread backtrace:\n" +
    			 describe_backtrace (other_thread->backtrace()) +
    			 "----------\n") : "");
    }
    
    #else  // !constant (Thread.thread_create)
    
    #define THREAD_T int
    #define THIS_THREAD() 1
    
    protected void thread_error (string msg, THREAD_T other_thread)
    {
      error ("%s"
    	 "%s\n"
    	 "User callbacks: a=%O r=%O w=%O c=%O\n"
    	 "Internal callbacks: r=%O w=%O c=%O\n"
    	 "Backend: %O\n",
    	 msg,
    	 !stream ? "Got no stream" :
    	 stream->is_open() ? "Stream is open" :
    	 "Stream is closed",
    	 accept_callback, read_callback, write_callback, close_callback,
    	 stream && stream->query_read_callback(),
    	 stream && stream->query_write_callback(),
    	 stream && stream->query_close_callback(),
    	 stream && stream->query_backend());
    }
    
    #endif	// !constant (Thread.thread_create)
    
    protected THREAD_T op_thread;
    
    // FIXME: Looks like the following check can give false alarms since
    // an fd object can lose all refs even if some callbacks still are
    // registered.
    #define CHECK_CB_MODE(CUR_THREAD) do {					\
        if (Pike.Backend backend = stream && stream->query_backend()) {	\
          THREAD_T backend_thread = backend->executing_thread();		\
          if (backend_thread && backend_thread != CUR_THREAD &&		\
    	  (stream->query_read_callback() ||				\
    	   stream->query_write_callback() ||				\
    	   stream->query_close_callback()))				\
    	/* NB: The other thread backtrace might not be relevant at	\
    	 * all here. */							\
    	thread_error ("In callback mode in a different backend.\n",	\
    		      backend_thread);					\
        }									\
      } while (0)
    
    #define LOW_CHECK(OP_THREAD, CUR_THREAD,				\
    		  IN_CALLBACK, CALLED_FROM_REAL_BACKEND) do {		\
        if (IN_CALLBACK) {							\
          if (CALLED_FROM_REAL_BACKEND) {					\
    	if (OP_THREAD)							\
    	  thread_error ("Called from real backend while doing an operation.\n",	\
    			OP_THREAD);					\
          }									\
          else								\
    	if (!OP_THREAD)							\
    	  error ("Called from local backend outside an operation.\n");	\
        }									\
    									\
        if (OP_THREAD && OP_THREAD != CUR_THREAD)				\
          thread_error ("Doing operation in another thread.\n", OP_THREAD);	\
    									\
        CHECK_CB_MODE (CUR_THREAD);						\
      } while (0)
    
    #define CHECK(IN_CALLBACK, CALLED_FROM_REAL_BACKEND) do {		\
        THREAD_T cur_thread = THIS_THREAD();				\
        LOW_CHECK (op_thread, cur_thread, IN_CALLBACK, CALLED_FROM_REAL_BACKEND); \
      } while (0)
    
    #define ENTER(IN_CALLBACK, CALLED_FROM_REAL_BACKEND) do {		\
        THREAD_T old_op_thread;						\
        {									\
          THREAD_T cur_thread = THIS_THREAD();				\
          old_op_thread = op_thread;					\
          /* Relying on the interpreter lock here. */			\
          op_thread = cur_thread;						\
        }									\
        LOW_CHECK (old_op_thread, op_thread, IN_CALLBACK, CALLED_FROM_REAL_BACKEND); \
        mixed _op_err = catch
    
    #define RESTORE do {op_thread = old_op_thread;} while (0)
    
    #define RETURN(RET_VAL) do {RESTORE; return (RET_VAL);} while (0)
    
    #define LEAVE								\
      ;									\
      RESTORE;								\
      if (_op_err) throw (_op_err);						\
      } while (0)
    
    #else  // !SSLFILE_DEBUG
    
    #define CHECK_CB_MODE(CUR_THREAD) do {} while (0)
    #define CHECK(IN_CALLBACK, CALLED_FROM_REAL_BACKEND) do {} while (0)
    #define ENTER(IN_CALLBACK, CALLED_FROM_REAL_BACKEND) do
    #define RESTORE do {} while (0)
    #define RETURN(RET_VAL) return (RET_VAL)
    #define LEAVE while (0)
    
    #endif	// !SSLFILE_DEBUG
    
    // 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(REPEAT_COND, NONWAITING_MODE,		\
    			   ENABLE_READS, ERROR_CODE) do {		\
      run_local_backend: {							\
          CHECK_CB_MODE (THIS_THREAD());					\
          if (!local_backend) local_backend = Pike.SmallBackend();		\
          stream->set_backend (local_backend);				\
          stream->set_id (0);						\
    									\
          while (1) {							\
    	float|int(0..0) action;						\
    									\
    	if (got_extra_read_call_out) {					\
    	  /* Do whatever ssl_read_callback needs to do before we	\
    	   * continue. Since the first arg is zero here it won't call	\
    	   * any user callbacks, so they are superseded as they should	\
    	   * be if we're doing an explicit read (or a write or close,	\
    	   * which legitimately might cause reading to be done). Don't	\
    	   * need to bother with the return value from ssl_read_callback \
    	   * since we'll propagate the error, if any, just below. */	\
    	  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 (SSL_INTERNAL_WRITING && ssl_write_callback); \
    									\
    	  if (ENABLE_READS) {						\
    	    stream->set_read_callback (ssl_read_callback);		\
    	    stream->set_close_callback (ssl_close_callback);		\
    	  }								\
    	  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);				\
    	  }								\
    									\
    	  /* 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;				\
    									\
    	  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);		\
    	}								\
    									\
    	if (NONWAITING_MODE && !action) {				\
    	  SSL3_DEBUG_MSG ("Nonwaiting local backend ended - nothing to do\n"); \
    	  break;							\
    	}								\
    									\
    	if (!action && (conn->state & CONNECTION_local_closing)) {	\
    	  SSL3_DEBUG_MSG ("Did not get a remote close - "		\
    			  "signalling delayed error from writing close message\n"); \
    	  cleanup_on_error();						\
    	  cb_errno = System.EPIPE;					\
    	  if (close_state != CLEAN_CLOSE)				\
    	    close_state = ABRUPT_CLOSE;					\
    	}								\
    									\
    	FIX_ERRNOS ({							\
    	    SSL3_DEBUG_MSG ("Local backend ended with error\n");	\
    	    if (stream) {						\
    	      stream->set_id (1);					\
    	      update_internal_state (1);				\
    	      /* Switch backend after updating the installed callbacks. */ \
    	      stream->set_backend (real_backend);			\
    	      CHECK_CB_MODE (THIS_THREAD());				\
    	    }								\
    	    {ERROR_CODE;}						\
    	    break run_local_backend;					\
    	  }, 0);							\
    									\
    	if (!stream) {							\
    	  SSL3_DEBUG_MSG ("Local backend ended after close.\n");	\
    	  break run_local_backend;					\
    	}								\
    									\
    	if (!(REPEAT_COND)) {						\
    	  SSL3_DEBUG_MSG ("Local backend ended - repeat condition false\n"); \
    	  break;							\
    	}								\
          }									\
    									\
          stream->set_id (1);						\
          update_internal_state (1);					\
          /* Switch backend after updating the installed callbacks. */	\
          stream->set_backend (real_backend);				\
          CHECK_CB_MODE (THIS_THREAD());					\
        }									\
      } while (0)
    
    protected void create (Stdio.File stream, SSL.Context ctx)
    //! Create an SSL connection over an open @[stream].
    //!
    //! @param stream
    //!   Open socket or pipe to create the connection over.
    //!
    //! @param ctx
    //!   The SSL context.
    //!
    //! The backend used by @[stream] is taken over and restored after the
    //! connection is closed (see @[close] and @[shutdown]). The callbacks
    //! and id in @[stream] are overwritten.
    //!
    //! @note
    //!   The operation mode defaults to nonblocking mode.
    {
      SSL3_DEBUG_MSG ("SSL.File->create (%O, %O)\n", stream, ctx);
    
      ENTER (0, 0) {
        global::stream = stream;
        global::context = ctx;
    
    #ifdef SSL3_DEBUG
        if (stream->query_fd)
          stream_descr = "fd:" + stream->query_fd();
        else
          stream_descr = replace (sprintf ("%O", stream), "%", "%%");
    #endif
        write_buffer = ({});
        read_buffer = String.Buffer();
        real_backend = stream->query_backend();
        close_state = STREAM_OPEN;
    
        stream->set_read_callback (0);
        stream->set_write_callback (0);
        stream->set_close_callback (0);
        stream->set_id (1);
    
        fragment_max_size =
          limit(1, ctx->packet_max_size, PACKET_MAX_SIZE);
    
        set_nonblocking();
      } LEAVE;
    }
    
    //! Configure as client and set up the connection.
    //!
    //! @param dest_addr
    //!   Optional name of the server that we are connected to.
    //!
    //! @returns
    //!   Returns @expr{0@} on handshaking failure in blocking mode,
    //!   and otherwise @expr{1@}.
    int(1bit) connect(string|void dest_addr)
    {
      if (conn) error("A connection is already configured!\n");
    
      ENTER (0, 0) {
    
        conn = .ClientConnection(context, dest_addr);
    
        // Wait for the handshake to finish in blocking mode.
        if (!nonblocking_mode) {
          if (!direct_write()) {
    	local_errno = errno();
    	if (stream) {
    	  stream->set_callbacks(0, 0, 0, 0, 0);
    	}
    	conn = UNDEFINED;
    	return 0;
          }
        } else {
          queue_write();
        }
      } LEAVE;
    
      return 1;
    }
    
    //! Configure as server and set up the connection.
    //!
    //! @param pending_data
    //!   Any data that has already been read from the stream.
    //!   This is typically used with protocols that use
    //!   START TLS or similar, where there's a risk that
    //!   "too much" data (ie part of the TLS ClientHello) has
    //!   been read from the stream before deciding that the
    //!   connection is to enter TLS-mode.
    //!
    //! @returns
    //!   Returns @expr{0@} on handshaking failure in blocking mode,
    //!   and otherwise @expr{1@}.
    int(1bit) accept(string|void pending_data)
    {
      if (conn) error("A connection is already configured!\n");
    
      ENTER (0, 0) {
        conn = .ServerConnection(context);
    
        if (sizeof(pending_data || "")) {
          if (intp(conn->got_data(pending_data))) {
    	local_errno = errno();
    	if (stream) {
    	  stream->set_callbacks(0, 0, 0, 0, 0);
    	}
    	conn = UNDEFINED;
    	return 0;
          }
        }
    
        // Wait for the handshake to finish in blocking mode.
        if (!nonblocking_mode) {
          if (!direct_write()) {
    	local_errno = errno();
    	if (stream) {
    	  stream->set_callbacks(0, 0, 0, 0, 0);
    	}
    	conn = UNDEFINED;
    	return 0;
          }
        }
      } LEAVE;
    
      return 1;
    }
    
    mixed get_server_name()
    {
      if (!conn) error("No active conection.\n");
      return conn->session->server_name;
    }
    
    //! @returns
    //!   Returns peer certificate information, if any.
    mapping get_peer_certificate_info()
    {
      if (!conn) error("No active conection.\n");
      return conn->session->cert_data;
    }
    
    //! @returns
    //!   Returns the peer certificate chain, if any.
    array get_peer_certificates()
    {
      if (!conn) error("No active conection.\n");
      return conn->session->peer_certificate_chain;
    }
    
    //! Set the linger time on @[close()].
    int(0..1) linger(int(-1..65535)|void seconds)
    {
      if (!stream) return 0;
      if (zero_type(seconds)) seconds = -1;
      if (seconds == linger_time) {
        // Noop.
        return 1;
      }
      if (stream->linger && !stream->linger(seconds)) return 0;
      linger_time = seconds;
      return 1;
    }
    
    int close (void|string how, void|int clean_close, void|int dont_throw)
    //! Close the connection. Both the read and write ends are always
    //! closed
    //!
    //! @param how
    //!   This argument is only for @[Stdio.File] compatibility
    //!   and must be either @expr{"rw"@} or @expr{0@}.
    //!
    //! @param clean_close
    //!   If set then close messages are exchanged to shut down
    //!   the SSL connection but not the underlying stream. It may then
    //!   continue to be used for other communication afterwards. The
    //!   default is to send a close message and then close the stream
    //!   without waiting for a response.
    //!
    //! @param dont_throw
    //!   I/O errors are normally thrown, but that can be turned off with
    //!   @[dont_throw]. In that case @[errno] is set instead and @expr{0@} is
    //!   returned. @expr{1@} is always returned otherwise. It's not an error to
    //!   close an already closed connection.
    //!
    //! @note
    //! If a clean close is requested in nonblocking mode then the stream
    //! is most likely not closed right away, and the backend is then
    //! still needed for a while afterwards to exchange the close packets.
    //! @[is_open] returns 2 in that time window.
    //!
    //! @note
    //! I/O errors from both reading and writing might occur in blocking
    //! mode.
    //!
    //! @note
    //! If a clean close is requested and data following the close message
    //! is received at the same time, then this object will read it and
    //! has no way to undo that. That data can be retrieved with @[read]
    //! afterwards.
    //!
    //! @seealso
    //!   @[shutdown]
    {
      SSL3_DEBUG_MSG ("SSL.File->close (%O, %O, %O)\n",
    		  how, clean_close, dont_throw);
    
      if (how && how != "rw")
        error ("Can only close the connection in both directions simultaneously.\n");
    
      ENTER (0, 0) {
        if (!conn || (conn->state & CONNECTION_local_down)) {
          SSL3_DEBUG_MSG ("SSL.File->close: Already closed (%d)\n", close_state);
          RETURN (1);
        }
        close_state = clean_close ? CLEAN_CLOSE : NORMAL_CLOSE;
    
        FIX_ERRNOS ({
    	SSL3_DEBUG_MSG ("SSL.File->close: Shutdown after error\n");
    	int err = errno();
    	shutdown();
    	// Get here e.g. if a close callback calls close after an
    	// error, so never throw. (I'm somewhat suspicious to this,
    	// but I guess I did it with good reason.. :P /mast)
    	local_errno = err;
    	RETURN (0);
          }, 0);
    
        SSL3_DEBUG_MSG ("ssl_write_callback: Queuing close packet\n");
        conn->send_close();
        if (!linger_time) {
          SSL3_DEBUG_MSG ("ssl_write_callback: Don't care about it being sent.\n");
          conn->state = [int(0..0)|ConnectionState]
    	(conn->state | CONNECTION_local_closed);
        }
    
        // Even in nonblocking mode we call direct_write here to try to
        // put the close packet in the send buffer before we return. That
        // way it has a fair chance to get sent even when we're called
        // from destroy() (in which case it won't work to just install the
        // write callback as usual and wait for the backend to call it).
    
        if (!direct_write()) {
          // Should be shut down after close(), even if an error occurred.
          int err = errno();
          shutdown();
          if (dont_throw) {
    	local_errno = err;
    	RETURN (0);
          }
          else if( err != System.EPIPE )
    	// Errors are normally thrown from close().
            error ("Failed to close SSL connection: %s\n", strerror (err));
        }
    
        if (stream && (stream->query_read_callback() || stream->query_write_callback())) {
          SSL3_DEBUG_MSG ("SSL.File->close: Close underway\n");
          RESTORE;
          update_internal_state();
          return 1;
        }
        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.File->close: Close done\n");
        }
      } LEAVE;
      return 1;
    }
    
    protected void cleanup_on_error()
    // Called when any error occurs on the stream. (Doesn't handle errno
    // reporting since it might involve either local_errno and/or
    // cb_errno.)
    {
      // The session should be purged when an error has occurred.
      if (conn && conn->session)
        // Check conn->session since it doesn't exist before the
        // handshake.
        conn->context->purge_session (conn->session);
    }
    
    Stdio.File shutdown()
    //! Shut down the SSL connection without sending any more packets.
    //!
    //! If the connection is open then the underlying (still open) stream
    //! is returned.
    //!
    //! If a nonclean (i.e. normal) close has been requested then the
    //! underlying stream is closed now if it wasn't closed already, and
    //! zero is returned.
    //!
    //! If a clean close has been requested (see the second argument to
    //! @[close]) then the behavior depends on the state of the close
    //! packet exchange: The first @[shutdown] call after a successful
    //! exchange returns the (still open) underlying stream, and later
    //! calls return zero and clears @[errno]. If the exchange hasn't
    //! finished then the stream is closed, zero is returned, and @[errno]
    //! will return @[System.EPIPE].
    //!
    //! @seealso
    //!   @[close], @[set_alert_callback]
    {
      ENTER (0, 0) {
        if (!stream || !conn) {
          SSL3_DEBUG_MSG ("SSL.File->shutdown(): Already shut down\n");
          RETURN (0);
        }
    
        if (close_state == STREAM_OPEN || close_state == NORMAL_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->state |= CONNECTION_peer_closed;
    
        SSL3_DEBUG_MSG ("SSL.File->shutdown(): %s %s\n",
    		    close_state != ABRUPT_CLOSE &&
    		    (conn->state &
    		     (CONNECTION_peer_closed | CONNECTION_local_closing)) ==
    		    (CONNECTION_peer_closed | CONNECTION_local_closing) &&
    		    !sizeof (write_buffer) ?
    		    "Proper close" :
    		    close_state == STREAM_OPEN ?
    		    "Not closed" :
    		    "Abrupt close",
    		    conn->describe_state());
    
        if ((conn->state & CONNECTION_peer_closed) &&
    	sizeof (conn->left_over || "")) {
    #ifdef SSLFILE_DEBUG
          werror ("Warning: Got buffered data after close in %O: %O%s\n", this,
    	      conn->left_over[..99], sizeof (conn->left_over) > 100 ? "..." : "");
    #endif
          read_buffer = String.Buffer (sizeof (conn->left_over));
          read_buffer->add (conn->left_over);
          close_state = STREAM_OPEN;
        }
    
        .Constants.ConnectionState conn_state = conn->state;
        destruct (conn);		// Necessary to avoid garbage.
    
        write_buffer = ({});
    
        if (got_extra_read_call_out > 0)
          real_backend->remove_call_out (ssl_read_callback);
        got_extra_read_call_out = 0;
    
        Stdio.File stream = global::stream;
        global::stream = 0;
    
        stream->set_read_callback (0);
        stream->set_write_callback (0);
        stream->set_close_callback (0);
    
        switch (close_state) {
          case CLEAN_CLOSE:
    	if ((conn_state & CONNECTION_closed) == CONNECTION_closed) {
    	  SSL3_DEBUG_MSG ("SSL.File->shutdown(): Clean close - "
    			  "leaving stream\n");
    	  local_errno = 0;
    	  RETURN (stream);
    	}
    	else {
    	  SSL3_DEBUG_MSG ("SSL.File->shutdown(): Close packets not fully "
    			  "exchanged after clean close (%d) - closing stream\n",
    			  conn_state);
    	  stream->close();
    	  local_errno = System.EPIPE;
    	  RETURN (0);
    	}
          case STREAM_OPEN:
    	close_state = STREAM_UNINITIALIZED;
    	SSL3_DEBUG_MSG ("SSL.File->shutdown(): Not closed - leaving stream\n");
    	local_errno = 0;
    	RETURN (stream);
          default:
    	SSL3_DEBUG_MSG ("SSL.File->shutdown(): Nonclean close - closing stream\n");
    	// if (stream->linger) stream->linger(0);
    	stream->close();
    	local_errno = stream->errno() || local_errno;
    	RETURN (0);
        }
      } LEAVE;
    }
    
    protected void destroy()
    //! Try to close down the connection properly since it's customary to
    //! close files just by dropping them. No guarantee can be made that
    //! the close packet gets sent successfully though, because we can't
    //! risk blocking I/O here. You should call @[close] explicitly.
    //!
    //! @seealso
    //!   @[close]
    {
      SSL3_DEBUG_MSG ("SSL.File->destroy()\n");
    
      // We don't know which thread this will be called in if the refcount
      // garb or the gc got here. That's not a race problem since it won't
      // be registered in a backend in that case.
      if (stream) {
        // Make sure not to fail in ENTER below due to bad backend thread.
        // [bug 6958].
        stream->set_callbacks(0, 0, 0);
      }
      ENTER (0, 0) {
        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) {
    	// We can't use our set_nonblocking() et al here, since we
    	// might be associated with a backend in a different thread,
    	// and update_internal_state() will install callbacks, which
    	// in turn might trigger the tests in CHECK_CB_MODE().
    	stream->set_nonblocking();	// Make sure not to to block.
    	nonblocking_mode = 0;	// Make sure not to install any callbacks.
    	close (0, 0, 1);
          }
          else
    	shutdown();
        }
      } LEAVE;
    }
    
    string read (void|int length, void|int(0..1) not_all)
    //! Read some (decrypted) data from the connection. Works like
    //! @[Stdio.File.read].
    //!
    //! @note
    //! I/O errors from both reading and writing might occur in blocking
    //! mode.
    //!
    //! @seealso
    //!   @[write]
    {
      SSL3_DEBUG_MSG ("SSL.File->read (%d, %d)\n", length, not_all);
    
      ENTER (0, 0) {
        if (close_state > STREAM_OPEN) error ("Not open.\n");
    
        FIX_ERRNOS ({
    	SSL3_DEBUG_MSG ("SSL.File->read: Propagating old callback error: %s\n",
    			strerror (local_errno));
    	RETURN (0);
          }, 0);
    
        if (stream)
          if (not_all) {
    	if (!sizeof (read_buffer))
    	  RUN_MAYBE_BLOCKING (!sizeof (read_buffer) &&
    			      !(conn->state & CONNECTION_peer_closed), 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)) &&
    			      !(conn->state & CONNECTION_peer_closed),
    			      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)) {
          read_buffer->add (res[length..]);
          res = res[..length-1];
        }
    
        SSL3_DEBUG_MSG ("SSL.File->read: Read done, returning %d bytes "
    		    "(%d still in buffer)\n",
    		    sizeof (res), sizeof (read_buffer));
        RETURN (res);
      } LEAVE;
    }
    
    int write (string|array(string) data, mixed... args)
    //! Write some (unencrypted) data to the connection. Works like
    //! @[Stdio.File.write] except that this function often buffers some data
    //! internally, so there's no guarantee that all the consumed data has
    //! been successfully written to the stream in nonblocking mode. It
    //! keeps the internal buffering to a minimum, however.
    //!
    //! @note
    //! This function returns zero if attempts are made to write data
    //! during the handshake phase and the mode is nonblocking.
    //!
    //! @note
    //! I/O errors from both reading and writing might occur in blocking
    //! mode.
    //!
    //! @seealso
    //!   @[read]
    {
      SSL3_DEBUG_MSG ("SSL.File->write (%t[%d]%{, %t%})\n",
    		  data, sizeof (data), args);
    
      if (sizeof (args)) {
        data = ({ sprintf (arrayp (data) ? data * "" : data, @args) });
      } else if (stringp(data)) {
        data = ({ data });
      }
    
      ENTER (0, 0) {
        if (close_state > STREAM_OPEN) error ("Not open.\n");
    
        FIX_ERRNOS ({
    	SSL3_DEBUG_MSG ("SSL.File->write: Propagating old callback error: %s\n",
    			strerror (local_errno));
    	RETURN (-1);
          }, 0);
    
        if (nonblocking_mode && SSL_HANDSHAKING) {
          SSL3_DEBUG_MSG ("SSL.File->write: "
    		      "Still in handshake - cannot accept application data\n");
          RETURN (0);
        }
    
        // Take care of any old data first.
        if (!direct_write()) RETURN (-1);
    
        int write_limit = 0x7fffffff;
        if (nonblocking_mode) {
          // Always stop after writing DATA_CHUNK_SIZE in
          // nonblocking mode, so that we don't loop here
          // arbitrarily long if the write is very large and the
          // bottleneck is in the encryption.
          write_limit = Stdio.DATA_CHUNK_SIZE;
        }
    
        int written = 0;
    
        int idx = 0, pos = 0;
        while (idx < sizeof (data) && !sizeof (write_buffer) &&
    	   written < write_limit) {
    #ifdef SSL3_DEBUG
          int oidx = idx;
          int opos = pos;
    #endif
    
          // send_streaming_data will pick the first fragment_max_size
          // bytes of the string, so do that right away in the same
          // range operation.
          string frag = data[idx][pos..pos + fragment_max_size -1];
          pos += fragment_max_size;
    
          while (sizeof(frag) < fragment_max_size) {
    	// Try to fill a packet.
    	if (++idx >= sizeof(data)) break;
    	pos = fragment_max_size - sizeof(frag);
    	frag += data[idx][..pos -1];
          }
    
          int n = conn->send_streaming_data (frag);
          if (n != sizeof(frag)) {
    	error ("Unexpected fragment_max_size discrepancy wrt send_streaming_data.\n");
          }
    #ifdef SSL3_DEBUG
          if (oidx == idx) {
    	SSL3_DEBUG_MSG("SSL.File->write: Queued data[%d][%d..%d]\n",
    		       idx, opos, pos - 1);
          } else {
    	SSL3_DEBUG_MSG("SSL.File->write: Queued data[%d..%d][%d..%d]\n",
    		       oidx, idx, opos, pos - 1);
          }
    #endif
          written += n;
    
          if (!direct_write()) RETURN (written);
        }
    
        SSL3_DEBUG_MSG ("SSL.File->write: Write %t done, accepted %d bytes\n",
    		    data, written);
        RETURN (written);
      } LEAVE;
    }
    
    int renegotiate()
    //! Renegotiate the connection by starting a new handshake. Note that
    //! the accept callback will be called again when the handshake is
    //! finished.
    //!
    //! Returns zero if there are any I/O errors. @[errno()] will give the
    //! details.
    //!
    //! @note
    //! The read buffer is not cleared - a @expr{read()@} afterwards will
    //! return data from both before and after the renegotiation.
    //!
    //! @bugs
    //! Data in the write queue in nonblocking mode is not properly
    //! written before resetting the connection. Do a blocking
    //! @expr{write("")@} first to avoid problems with that.
    {
      SSL3_DEBUG_MSG ("SSL.File->renegotiate()\n");
    
      ENTER (0, 0) {
        if (close_state > STREAM_OPEN) error ("Not open.\n");
    
        FIX_ERRNOS ({
    	SSL3_DEBUG_MSG ("SSL.File->renegotiate: "
    			"Propagating old callback error: %s\n",
    			strerror (local_errno));
    	RETURN (0);
          }, 0);
    
        if (!stream) {
          SSL3_DEBUG_MSG ("SSL.File->renegotiate: "
    		      "Connection closed - simulating System.EPIPE\n");
          cleanup_on_error();
          local_errno = System.EPIPE;
          RETURN (0);
        }
    
        // FIXME: Change this state with a packet instead so that things
        // currently in the queue aren't affect by it.
        conn->expect_change_cipher = 0;
        conn->certificate_state = 0;
        conn->state |= CONNECTION_handshaking;
        update_internal_state();
    
        conn->send_packet(conn->hello_request());
    
        RETURN (direct_write());
      } LEAVE;
    }
    
    void set_callbacks (void|function(mixed, string:int) read,
    		    void|function(mixed:int) write,
    		    void|function(mixed:int) close,
    		    void|function(mixed, string:int) read_oob,
    		    void|function(mixed:int) write_oob,
    		    void|function(void|mixed:int) accept)
    //! Installs all the specified callbacks at once. Use @[UNDEFINED]
    //! to keep the current setting for a callback.
    //!
    //! Like @[set_nonblocking], the callbacks are installed atomically.
    //! As opposed to @[set_nonblocking], this function does not do
    //! anything with the stream, and it doesn't even have to be open.
    //!
    //! @bugs
    //! @[read_oob] and @[write_oob] are currently ignored.
    //!
    //! @seealso
    //! @[set_read_callback], @[set_write_callback],
    //! @[set_close_callback], @[set_accept_callback], @[query_callbacks]
    {
      SSL3_DEBUG_MSG ("SSL.File->set_callbacks (%O, %O, %O, %O, %O, %O)\n%s",
    		  read, write, close, read_oob, write_oob, accept,
    		  "" || describe_backtrace (backtrace()));
    
      ENTER(0, 0) {
    
        // Bypass the ::set_xxx_callback functions; we instead enable all
        // the event bits at once through the _enable_callbacks call at the end.
    
        if (!zero_type(read))
          read_callback = read;
    
        if (!zero_type(write))
          write_callback = write;
    
        if (!zero_type(close))
          close_callback = close;
    
        if (!zero_type(accept))
          accept_callback = accept;
    
        if (stream) update_internal_state();
      } LEAVE;
    }
    
    //! @returns
    //!   Returns the currently set callbacks in the same order
    //!   as the arguments to @[set_callbacks].
    //!
    //! @seealso
    //!   @[set_callbacks], @[set_nonblocking]
    array(function(mixed,void|string:int)) query_callbacks()
    {
      return ({
        read_callback,
        write_callback,
        close_callback,
        UNDEFINED,
        UNDEFINED,
        accept_callback,
      });
    }
    
    void set_nonblocking (void|function(void|mixed,void|string:int) read,
    		      void|function(void|mixed:int) write,
    		      void|function(void|mixed:int) close,
    		      void|function(void|mixed:int) read_oob,
    		      void|function(void|mixed:int) write_oob,
    		      void|function(void|mixed:int) accept)
    //! Set the stream in nonblocking mode, installing the specified
    //! callbacks. The alert callback isn't touched.
    //!
    //! @note
    //! Prior to version 7.5.12, this function didn't set the accept
    //! callback.
    //!
    //! @bugs
    //! @[read_oob] and @[write_oob] are currently ignored.
    //!
    //! @seealso
    //!   @[set_callbacks], @[query_callbacks], @[set_nonblocking_keep_callbacks],
    //!   @[set_blocking]
    {
      SSL3_DEBUG_MSG ("SSL.File->set_nonblocking (%O, %O, %O, %O, %O, %O)\n%s",
    		  read, write, close, read_oob, write_oob, accept,
    		  "" || describe_backtrace (backtrace()));
    
      ENTER (0, 0) {
        if (close_state > STREAM_OPEN) error ("Not open.\n");
    
        nonblocking_mode = 1;
    
        accept_callback = accept;
        read_callback = read;
        write_callback = write;
        close_callback = close;
    
        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_nonblocking_keep_callbacks()
    //! Set nonblocking mode like @[set_nonblocking], but don't alter any
    //! callbacks.
    //!
    //! @seealso
    //!   @[set_nonblocking], @[set_blocking], @[set_blocking_keep_callbacks]
    {
      SSL3_DEBUG_MSG ("SSL.File->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.
    //!
    //! @note
    //! There might be some data still waiting to be written to the
    //! stream. That will be written in the next blocking call, regardless
    //! what it is.
    //!
    //! @note
    //! This function doesn't solve the case when the connection is used
    //! nonblocking in some backend thread and another thread switches it
    //! to blocking and starts using it. To solve that, put a call out in
    //! the backend from the other thread that switches it to blocking,
    //! and then wait until that call out has run.
    //!
    //! @note
    //! Prior to version 7.5.12, this function didn't clear the accept
    //! callback.
    //!
    //! @seealso
    //!   @[set_nonblocking], @[set_blocking_keep_callbacks],
    //!   @[set_nonblocking_keep_callbacks]
    {
      // Previously this function wrote the remaining write buffer to the
      // stream directly. But that can only be done safely if we implement
      // a lock here to wait for any nonblocking operations to complete,
      // and that could introduce a deadlock since there might be other
      // lock dependencies between the threads.
    
      SSL3_DEBUG_MSG ("SSL.File->set_blocking()\n%s",
    		  "" || describe_backtrace (backtrace()));
    
      ENTER (0, 0) {
        if (close_state > STREAM_OPEN) error ("Not open.\n");
    
        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.
    //!
    //! @seealso
    //!   @[set_blocking], @[set_nonblocking]
    {
      SSL3_DEBUG_MSG ("SSL.File->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;
    }
    
    int errno()
    //! @returns
    //!   Returns the current error number for the connection.
    //!   Notable values are:
    //!   @int
    //!     @value 0
    //!       No error
    //!     @value System.EPIPE
    //!       Connection closed by other end.
    //!   @endint
    {
      // We don't check threads for most other query functions, but
      // looking at errno while doing I/O in another thread can't be done
      // safely.
      CHECK (0, 0);
      return local_errno ? local_errno : stream && stream->errno();
    }
    
    void set_alert_callback (function(object,int|object,string:void) alert)
    //! Install a function that will be called when an alert packet is about
    //! to be sent. It doesn't affect the callback mode - it's called both
    //! from backends and from within normal function calls like @[read]
    //! and @[write].
    //!
    //! This callback can be used to implement fallback to other protocols
    //! when used on the server side together with @[shutdown()].
    //!
    //! @note
    //! This object is part of a cyclic reference whenever this is set,
    //! just like setting any other callback.
    //!
    //! @note
    //!   This callback is not cleared by @[set_blocking], or settable
    //!   by @[set_callbacks] or @[set_nonblocking]. It is also not
    //!   part of the set returned by @[query_callbacks].
    //!
    //! @seealso
    //!   @[query_alert_callback]
    {
      SSL3_DEBUG_MSG ("SSL.File->set_alert_callback (%O)\n", alert);
      CHECK (0, 0);
    #ifdef SSLFILE_DEBUG
      if (close_state == STREAM_UNINITIALIZED || !conn)
        error ("Doesn't have any connection.\n");
    #endif
      conn->set_alert_callback (
        alert &&
        lambda (object packet, int|object seq_num, string alert_context) {
          SSL3_DEBUG_MSG ("Calling alert callback %O\n", alert);
          alert (packet, seq_num, alert_context);
          alert_cb_called = 1;
        });
    }
    
    function(object,int|object,string:void) query_alert_callback()
    //! @returns
    //!   Returns the current alert callback.
    //!
    //! @seealso
    //!   @[set_alert_callback]
    {
      return conn && conn->alert_callback;
    }
    
    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 File object and the
    //! additional id arguments (set with @[set_id]).
    //! 
    //! @note
    //! Like the read, write and close callbacks, installing this callback
    //! implies callback mode, even after the handshake is done.
    //!
    //! @seealso
    //!   @[set_nonblocking], @[set_callbacks],
    //!   @[query_accept_callback], @[query_callbacks]
    {
      SSL3_DEBUG_MSG ("SSL.File->set_accept_callback (%O)\n", accept);
      ENTER (0, 0) {
    #ifdef SSLFILE_DEBUG
        if (close_state == STREAM_UNINITIALIZED)
          error ("Doesn't have any connection.\n");
    #endif
        accept_callback = accept;
        if (stream) update_internal_state();
      } LEAVE;
    }
    
    function(void|object,void|mixed:int) query_accept_callback()
    //! @returns
    //!   Returns the current accept callback.
    //!
    //! @seealso
    //!   @[set_accept_callback]
    {
      return accept_callback;
    }
    
    void set_read_callback (function(void|mixed,void|string:int) read)
    //! Install a function to be called when data is available.
    //!
    //! @seealso
    //!   @[query_read_callback], @[set_nonblocking], @[query_callbacks]
    {
      SSL3_DEBUG_MSG ("SSL.File->set_read_callback (%O)\n", read);
      ENTER (0, 0) {
    #ifdef SSLFILE_DEBUG
        if (close_state == STREAM_UNINITIALIZED)
          error ("Doesn't have any connection.\n");
    #endif
        read_callback = read;
        if (stream) update_internal_state();
      } LEAVE;
    }
    
    function(void|mixed,void|string:int) query_read_callback()
    //! @returns
    //!   Returns the current read callback.
    //!
    //! @seealso
    //!   @[set_read_callback], @[set_nonblocking], @[query_callbacks]
    {
      return read_callback;
    }
    
    void set_write_callback (function(void|mixed:int) write)
    //! Install a function to be called when data can be written.
    //!
    //! @seealso
    //!   @[query_write_callback], @[set_nonblocking], @[query_callbacks]
    {
      SSL3_DEBUG_MSG ("SSL.File->set_write_callback (%O)\n", write);
      ENTER (0, 0) {
    #ifdef SSLFILE_DEBUG
        if (close_state == STREAM_UNINITIALIZED)
          error ("Doesn't have any connection.\n");
    #endif
        write_callback = write;
        if (stream) update_internal_state();
      } LEAVE;
    }
    
    function(void|mixed:int) query_write_callback()
    //! @returns
    //!   Returns the current write callback.
    //!
    //! @seealso
    //!   @[set_write_callback], @[set_nonblocking], @[query_callbacks]
    {
      return write_callback;
    }
    
    void set_close_callback (function(void|mixed:int) close)
    //! Install a function to be called when the connection is closed,
    //! either normally or due to an error (use @[errno] to retrieve it).
    //!
    //! @seealso
    //!   @[query_close_callback], @[set_nonblocking], @[query_callbacks]
    {
      SSL3_DEBUG_MSG ("SSL.File->set_close_callback (%O)\n", close);
      ENTER (0, 0) {
    #ifdef SSLFILE_DEBUG
        if (close_state == STREAM_UNINITIALIZED)
          error ("Doesn't have any connection.\n");
    #endif
        close_callback = close;
        if (stream) update_internal_state();
      } LEAVE;
    }
    
    function(void|mixed:int) query_close_callback()
    //! @returns
    //!   Returns the current close callback.
    //!
    //! @seealso
    //!   @[set_close_callback], @[set_nonblocking], @[query_callbacks]
    {
      return close_callback;
    }
    
    void set_id (mixed id)
    //! Set the value to be sent as the first argument to the
    //! callbacks installed by @[set_callbacks].
    //!
    //! @seealso
    //!   @[query_id]
    {
      SSL3_DEBUG_MSG ("SSL.File->set_id (%O)\n", id);
      CHECK (0, 0);
      callback_id = id;
    }
    
    mixed query_id()
    //! @returns
    //!   Returns the currently set id.
    //!
    //! @seealso
    //!   @[set_id]
    {
      return callback_id;
    }
    
    void set_backend (Pike.Backend backend)
    //! Set the backend used for the file callbacks.
    //!
    //! @seealso
    //!   @[query_backend]
    {
      ENTER (0, 0) {
        if (close_state > STREAM_OPEN) error ("Not open.\n");
    
        if (stream) {
          if (stream->query_backend() != local_backend)
    	stream->set_backend (backend);
    
          if (got_extra_read_call_out > 0) {
    	real_backend->remove_call_out (ssl_read_callback);
    	backend->call_out (ssl_read_callback, 0, 1, 0);
          }
        }
    
        real_backend = backend;
      } LEAVE;
    }
    
    Pike.Backend query_backend()
    //! Return the backend used for the file callbacks.
    //!
    //! @seealso
    //!   @[set_backend]
    {
      if (close_state > STREAM_OPEN) error ("Not open.\n");
      return real_backend;
    }
    
    string query_address(int|void arg)
    //! @returns
    //!   Returns the address and port of the connection.
    //!
    //!   See @[Stdio.File.query_address] for details.
    //!
    //! @seealso
    //!   @[Stdio.File.query_address]
    {
      if (close_state > STREAM_OPEN) error ("Not open.\n");
      return stream->query_address(arg);
    }
    
    int is_open()
    //! @returns
    //! Returns 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.
    //!
    //! If a clean close has been requested in nonblocking mode, then 2 is
    //! returned until the close packet exchanged has been completed.
    //!
    //! @note
    //! In Pike 7.8 and earlier, this function returned zero in the case
    //! above where it now returns 2.
    {
      SSL3_DEBUG_MSG ("SSL.File->is_open()\n");
      ENTER (0, 0) {
        if ((close_state == STREAM_OPEN || close_state == CLEAN_CLOSE) &&
    	stream && stream->is_open()) {
          // When close_state == STREAM_OPEN, we 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.
          //
          // If close_state == CLEAN_CLOSE, we should return true as long
          // as close packets haven't been exchanged in both directions.
          //
          // 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.)
          ConnectionState closed = conn->state & CONNECTION_closed;
          if ((close_state == CLEAN_CLOSE ?
    	   closed != CONNECTION_closed : !closed))
    	RUN_MAYBE_BLOCKING (
    	  action && (close_state == CLEAN_CLOSE ?
    		     (conn->state & CONNECTION_closed) != CONNECTION_closed :
    		     !(conn->state & CONNECTION_closed)),
    	  1, 1,
    	  RETURN (!epipe_errnos[local_errno]));
          closed = conn->state & CONNECTION_closed;
          RETURN (close_state == CLEAN_CLOSE ?
    	      ((closed != CONNECTION_closed) && 2) : !closed);
        }
      } LEAVE;
      return 0;
    }
    
    Stdio.File query_stream()
    //! Return the underlying stream.
    //!
    //! @note
    //! Avoid any temptation to do
    //! @expr{destruct(file_obj->query_stream())@}. That almost certainly
    //! creates more problems than it solves.
    //!
    //! You probably want to use @[shutdown].
    //!
    //! @seealso
    //!   @[shutdown]
    {
      SSL3_DEBUG_MSG ("SSL.File->query_stream(): Called from %s:%d\n",
    		  backtrace()[-2][0], backtrace()[-2][1]);
      return stream;
    }
    
    .Connection query_connection()
    //! Return the SSL connection object.
    //!
    //! This returns the low-level @[SSL.connection] object.
    {
      SSL3_DEBUG_MSG ("SSL.File->query_connection(): Called from %s:%d\n",
    		  backtrace()[-2][0], backtrace()[-2][1]);
      return conn;
    }
    
    SSL.Context query_context()
    //! Return the SSL context object.
    {
      return conn && conn->context;
    }
    
    string _sprintf(int t) {
      return t=='O' && sprintf("SSL.File(%O)", stream && stream->_fd);
    }
    
    
    protected void update_internal_state (void|int assume_real_backend)
    // Update the internal callbacks according to the current state. Does
    // nothing if the local backend is active, unless assume_real_backend
    // is set, in which case we're installing callbacks for the real
    // backend anyway (necessary to avoid races when we're about to switch
    // from the local to the real backend).
    {
      // When the local backend is used, callbacks are set explicitly
      // before it's started.
      if (assume_real_backend || stream->query_backend() != local_backend) {
        mixed install_read_cbs, install_write_cb;
    
        if (nonblocking_mode &&
    	(SSL_HANDSHAKING || 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;
    
          SSL3_DEBUG_MORE_MSG ("update_internal_state: "
    			   "%s [r:%O w:%O rcb:%O]\n",
    			   SSL_HANDSHAKING ? "Handshaking" : "After close",
    			   !!install_read_cbs, !!install_write_cb,
    			   got_extra_read_call_out);
        }
    
        // 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: "
    			   "Callback mode [r:%O w:%O rcb:%O]\n",
    			   !!install_read_cbs, !!install_write_cb,
    			   got_extra_read_call_out);
        }
    
        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 [rcb:%O]\n",
    			   got_extra_read_call_out);
        }
    
        if (!got_extra_read_call_out && sizeof (read_buffer) && read_callback)
          // Got buffered read data and there's someone ready to receive it,
          // so schedule a call to ssl_read_callback to handle it.
          got_extra_read_call_out = -1;
    
        // If got_extra_read_call_out is set here then we wait with all
        // other callbacks so that the extra ssl_read_callback call is
        // carried out before anything else.
    
        if (install_read_cbs) {
          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 (ssl_read_callback);
    	stream->set_close_callback (ssl_close_callback);
          }
        }
        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 && !got_extra_read_call_out &&
    				ssl_write_callback);
    
    #ifdef SSLFILE_DEBUG
        if (!assume_real_backend && op_thread)
          // Check that we haven't installed callbacks that might start
          // executing in parallell in another thread. That's legitimate
          // in some cases (e.g. set_nonblocking) but in those cases we're
          // called after RESTORE or LEAVE, which has reset op_thread, so
          // we skip this check if op_thread is zero.
          CHECK_CB_MODE (THIS_THREAD());
    #endif
      }
    
      else
        SSL3_DEBUG_MORE_MSG ("update_internal_state: "
    			 "In local backend - nothing done [rcb:%O]\n",
    			 got_extra_read_call_out);
    }
    
    protected int queue_write()
    // Return 0 if the connection is still alive, 1 if it was closed
    // politely, and -1 if it died unexpectedly (specifically, our side
    // has sent a fatal alert packet (not close notify) for some reason
    // and therefore nullified the connection).
    {
      if (!conn) return -1;
    
      // Estimate how much data there is in the write_buffer.
      int got = sizeof(write_buffer) &&
        (sizeof(write_buffer[-1]) * sizeof(write_buffer));
    
      int buffer_limit = 16384;
      if (conn->state & CONNECTION_closing) buffer_limit = 1;
    
      while (got < buffer_limit) {
        int|string res = conn->to_write();
    
    #ifdef SSL3_DEBUG_TRANSPORT
        werror ("queue_write: To write: %O\n", res);
    #endif
    
        if (!stringp(res)) {
          SSL3_DEBUG_MSG ("queue_write: Connection closed %s\n",
    		      res == 1 ? "normally" : "abruptly");
          return res;
        }
    
        if (res == "") {
          SSL3_DEBUG_MSG ("queue_write: Got nothing to write (%d strings buffered)\n",
    		      sizeof (write_buffer));
          break;
        }
    
        int was_empty = !sizeof (write_buffer);
        write_buffer += ({ res });
        got += sizeof(res);
    
        SSL3_DEBUG_MSG ("queue_write: Got %d bytes to write (%d strings buffered)\n",
    		    sizeof (res), sizeof (write_buffer));
        if (was_empty && stream)
          update_internal_state();
      }
    
      SSL3_DEBUG_MSG ("queue_write: Returning 0 (%d strings buffered)\n",
    		  sizeof(write_buffer));
    
      return 0;
    }
    
    protected int direct_write()
    // 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).
    {
      if (!stream) {
        SSL3_DEBUG_MSG ("direct_write: "
    		    "Connection already closed - simulating System.EPIPE\n");
        // If it was closed explicitly locally then close_state would be
        // set and we'd never get here, so we can report it as a remote
        // close.
        cleanup_on_error();
        local_errno = System.EPIPE;
        return 0;
      }
    
      else {
        if (queue_write() < 0 || close_state == ABRUPT_CLOSE) {
          SSL3_DEBUG_MSG ("direct_write: "
    		      "Connection closed abruptly - simulating System.EPIPE\n");
          cleanup_on_error();
          local_errno = System.EPIPE;
          // Don't set close_state = ABRUPT_CLOSE here. It's either set
          // already, or there's an abrupt close due to an ALERT_fatal,
          // which we shouldn't mix up with ABRUPT_CLOSE.
          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;
    }
    
    protected int ssl_read_callback (int called_from_real_backend, string input)
    {
      SSL3_DEBUG_MSG ("ssl_read_callback (%O, %s): "
    		  "nonblocking mode=%d, callback mode=%d %s%s\n",
    		  called_from_real_backend,
    		  input ? "string[" + sizeof (input) + "]" : "0 (queued extra call)",
    		  nonblocking_mode, !!(CALLBACK_MODE),
    		  conn ? conn->describe_state() : "not connected",
    		  conn && SSL_CLOSING_OR_CLOSED ?
    		  ", closing (" + close_state + ")" : "");
    
      ENTER (1, called_from_real_backend) {
        int call_accept_cb;
    
        if (input) {
          int handshake_already_finished = !(conn->state & CONNECTION_handshaking);
          string|int data =
    	close_state == ABRUPT_CLOSE ? -1 : conn->got_data (input);
    
    #ifdef SSLFILE_DEBUG
          if (got_extra_read_call_out)
    	error ("Got to real read callback with queued extra read call out.\n");
    #endif
    
    #ifdef SSL3_DEBUG_TRANSPORT
          werror ("ssl_read_callback: Got data: %O\n", data);
    #endif
    
          if (alert_cb_called)
    	SSL3_DEBUG_MSG ("ssl_read_callback: After alert cb call\n");
    
          else {
    	int write_res;
    	if (stringp(data) || (data > 0) ||
    	    ((data < 0) && (conn->state & CONNECTION_handshaking))) {
    #ifdef SSLFILE_DEBUG
    	  if (!stream)
    	    error ("Got zapped stream in callback.\n");
    #endif
    	  // got_data might have put more packets in the write queue if
    	  // we're handshaking.
    	  write_res = queue_write();
    	}
    
    	cb_errno = 0;
    
    	if (stringp (data)) {
    	  if (!handshake_already_finished &&
    	      !(conn->state & CONNECTION_handshaking)) {
    	    SSL3_DEBUG_MSG ("ssl_read_callback: Handshake finished\n");
    	    update_internal_state();
    	    if (called_from_real_backend && accept_callback) {
    #ifdef SSLFILE_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: "
    			  "Got %d bytes of application data\n", sizeof (data));
    	  read_buffer->add (data);
    	}
    
    	else if (data < 0 || write_res < 0) {
    	  SSL3_DEBUG_MSG ("ssl_read_callback: "
    			  "Got abrupt remote close - simulating System.EPIPE\n");
    	  cleanup_on_error();
    	  cb_errno = System.EPIPE;
    	}
    
    	// 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->state & CONNECTION_peer_closed) {
    	  SSL3_DEBUG_MSG ("ssl_read_callback: Got close packet\n");
    	}
          }
        }
    
        else {
          // This is another call that has been queued below through a
          // call out. That is necessary whenever we need to call several
          // user callbacks from the same invocation: We can't do anything
          // after calling a user callback, since the user is free to e.g.
          // remove the callbacks and "hand over" us to another thread and
          // do more I/O there while this one returns. We solve this
          // problem by queuing a call out to ourselves (with zero as
          // input) to continue.
          got_extra_read_call_out = 0;
          update_internal_state();
        }
    
        // Figure out what we need to do. call_accept_cb is already set
        // from above.
        int(0..1) call_read_cb;
        int(0..1) do_close_stuff;
        if (alert_cb_called) {
          if (!conn) {
    	SSL3_DEBUG_MSG ("ssl_read_callback: Shut down from alert callback\n");
    	RESTORE;
    	return 0;
          }
          // Make sure the alert is queued for writing.
          queue_write();
        }
        else {
          call_read_cb =
    	called_from_real_backend && read_callback && sizeof (read_buffer) &&
    	// Shouldn't get here when close_state == ABRUPT_CLOSE.
    	close_state < NORMAL_CLOSE;
          do_close_stuff =
    	!!((conn->state & CONNECTION_peer_closed) || cb_errno);
        }
    
        if (alert_cb_called || call_accept_cb + call_read_cb + do_close_stuff > 1) {
          // Need to do a call out to ourselves; see comment above.
    #ifdef SSLFILE_DEBUG
          if (!alert_cb_called && !called_from_real_backend)
    	error ("Internal confusion.\n");
          if (called_from_real_backend && got_extra_read_call_out < 0)
    	error ("Ended up in ssl_read_callback from real backend "
    	       "when no callbacks are supposed to be installed.\n");
    #endif
          if (!got_extra_read_call_out) {
    	got_extra_read_call_out = -1;
    	SSL3_DEBUG_MSG ("ssl_read_callback: Too much to do (%O, %O, %O, %O) - "
    			"scheduled another call\n", alert_cb_called, call_accept_cb,
    			call_read_cb, do_close_stuff);
    	update_internal_state();
          }
          else
    	SSL3_DEBUG_MSG ("ssl_read_callback: Too much to do (%O, %O, %O, %O) - "
    			"another call already queued\n",
    			alert_cb_called, call_accept_cb, call_read_cb, do_close_stuff);
          alert_cb_called = 0;
        }
    
        else
          if (got_extra_read_call_out) {
    	// Don't know if this actually can happen, but it's symmetric.
    	if (got_extra_read_call_out > 0)
    	  real_backend->remove_call_out (ssl_read_callback);
    	got_extra_read_call_out = 0;
    	update_internal_state();
          }
    
        // Now actually do (a bit of) what should be done.
    
        if (call_accept_cb) {
          SSL3_DEBUG_MSG ("ssl_read_callback: Calling accept callback %O\n",
    		      accept_callback);
          RESTORE;
          return accept_callback (this, callback_id);
        }
    
        else if (call_read_cb) {
          string received = read_buffer->get();
          SSL3_DEBUG_MSG ("call_read_callback: Calling read callback %O "
    		      "with %d bytes\n", read_callback, sizeof (received));
          // Never called if there's an error - no need to propagate cb_errno.
          RESTORE;
          return read_callback (callback_id, received);
        }
    
        else if (do_close_stuff) {
    #ifdef SSLFILE_DEBUG
          if (got_extra_read_call_out)
    	error ("Shouldn't have more to do after close stuff.\n");
    #endif
    
          if ((conn->state & (CONNECTION_peer_closed | CONNECTION_local_closing)) ==
    	  CONNECTION_peer_closed) {
    	// 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) {
    	// 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;
    	return close_callback (callback_id);
          }
    
          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;
          }
        }
    
      } LEAVE;
      return 0;
    }
    
    protected int ssl_write_callback (int called_from_real_backend)
    {
      SSL3_DEBUG_MSG ("ssl_write_callback (%O): "
    		  "nonblocking mode=%d, callback mode=%d %s%s\n",
    		  called_from_real_backend,
    		  nonblocking_mode, !!(CALLBACK_MODE),
    		  conn ? conn->describe_state() : "",
    		  conn && SSL_CLOSING_OR_CLOSED ?
    		  ", closing (" + close_state + ")" : "");
    
      int ret = 0;
    
      ENTER (1, called_from_real_backend) {
    #ifdef SSLFILE_DEBUG
        if (!stream)
          error ("Got zapped stream in callback.\n");
        if (got_extra_read_call_out)
          error ("Got to write callback with queued extra read call out.\n");
    #endif
    
      write_to_stream:
        do {
          if (sizeof (write_buffer)) {
    	int written;
    #ifdef SIMULATE_CLOSE_PACKET_WRITE_FAILURE
    	if (conn->state & CONNECTION_local_closing)
    	  written = -1;
    	else
    #endif
    	  written = stream->write (write_buffer);
    
    	if (written < 0 ) {
    #ifdef SIMULATE_CLOSE_PACKET_WRITE_FAILURE
    	  if (conn->state & CONNECTION_local_closing)
    	    cb_errno = System.EPIPE;
    	  else
    #endif
    	    cb_errno = stream->errno();
    	  cleanup_on_error();
    	  SSL3_DEBUG_MSG ("ssl_write_callback: Write failed: %s\n",
    			  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 (conn->state & CONNECTION_local_closing &&
    	      epipe_errnos[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.
    	      if (!(conn->state & CONNECTION_failing)) {
    		// Make sure that the connection is failing.
    		conn->send_packet(conn->alert(ALERT_fatal, ALERT_close_notify));
    	      }
    	      update_internal_state();
    	    }
    
    	    else {
    	      cb_errno = 0;
    
    	      if (conn->state & CONNECTION_peer_closed)
    		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");
    		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 (
    		    (!(conn->state & CONNECTION_peer_closed)),
    		    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 (or close) callback to
    	  // propagate the error to it.
    	  break write_to_stream;
    	}
    
    	for (int bytes = written; bytes > 0;) {
    	  if (bytes >= sizeof(write_buffer[0])) {
    	    bytes -= sizeof(write_buffer[0]);
    	    write_buffer = write_buffer[1..];
    	  } else {
    	    write_buffer[0] = write_buffer[0][bytes..];
    	    bytes = 0;
    	    break;
    	  }
    	}
    
    	SSL3_DEBUG_MSG ("ssl_write_callback: Wrote %d bytes (%d strings left)\n",
    			written, sizeof (write_buffer));
    	if (sizeof (write_buffer)) {
    	  RESTORE;
    	  return ret;
    	}
    	update_internal_state();
          }
    
          if (int err = queue_write()) {
    	if (err > 0) {
    #ifdef SSLFILE_DEBUG
    	  if (!(conn->state & CONNECTION_closed))
    	    error ("Expected a close to be sent or received\n");
    #endif
    
    	  if (sizeof (write_buffer))
    	    SSL3_DEBUG_MSG ("ssl_write_callback: "
    			    "Close packet queued but not yet sent\n");
    	  else {
    #ifdef SSLFILE_DEBUG
    	    if (!(conn->state & CONNECTION_local_closed))
    	      error ("Expected a close packet to be queued or sent.\n");
    #endif
    	    if (conn->state & CONNECTION_peer_closed ||
    		close_state != CLEAN_CLOSE) {
    	      SSL3_DEBUG_MSG ("ssl_write_callback: %s\n",
    			      conn->state & CONNECTION_peer_closed ?
    			      "Close packets exchanged" : "Close packet sent");
    	      break write_to_stream;
    	    }
    	    else {
    	      SSL3_DEBUG_MSG ("ssl_write_callback: "
    			      "Close packet sent - expecting response\n");
    	      // Not SSL_INTERNAL_WRITING anymore.
    	      update_internal_state();
    	    }
    	  }
    
    	  RESTORE;
    	  return ret;
    	}
    
    	else {
    	  SSL3_DEBUG_MSG ("ssl_write_callback: "
    			  "Connection closed abruptly remotely - "
    			  "simulating System.EPIPE\n");
    	  cleanup_on_error();
    	  cb_errno = System.EPIPE;
    	  ret = -1;
    	  break write_to_stream;
    	}
          }
        } while (sizeof (write_buffer));
    
        if (called_from_real_backend) {
          if (conn->state & CONNECTION_local_closed) {
    	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);
    	}
          }
    
          if (write_callback && !SSL_HANDSHAKING
    	  && (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 &&
    	 (conn->state & CONNECTION_local_closing)) || cb_errno) {
          SSL3_DEBUG_MSG ("ssl_write_callback: "
    		      "In or after local close - shutting down\n");
          shutdown();
        }
      } LEAVE;
      return ret;
    }
    
    protected int ssl_close_callback (int called_from_real_backend)
    {
      SSL3_DEBUG_MSG ("ssl_close_callback (%O): "
    		  "nonblocking mode=%d, callback mode=%d %s%s\n",
    		  called_from_real_backend,
    		  nonblocking_mode, !!(CALLBACK_MODE),
    		  conn ? conn->describe_state() : "",
    		  conn && SSL_CLOSING_OR_CLOSED ?
    		  ", closing (" + close_state + ")" : "");
    
      ENTER (1, called_from_real_backend) {
    #ifdef SSLFILE_DEBUG
        if (!stream)
          error ("Got zapped stream in callback.\n");
        if (got_extra_read_call_out)
          error ("Got to close callback with queued extra read call out.\n");
    #endif
    
        // 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 %s\n", strerror (new_errno));
          cleanup_on_error();
          cb_errno = new_errno;
        }
    #ifdef SSL3_DEBUG
        else if (cb_errno)
          SSL3_DEBUG_MSG ("ssl_close_callback: Propagating errno from another callback\n");
    #endif
    
        if (!cb_errno) {
          if (!conn || conn->state & CONNECTION_peer_closed)
    	SSL3_DEBUG_MSG ("ssl_close_callback: After clean close\n");
    
          else {
    	// The remote end has closed the connection without sending a
    	// close packet.
    	//
    	// This became legal by popular demand in TLS 1.1.
    	SSL3_DEBUG_MSG ("ssl_close_callback: Did not get a remote close.\n");
    	conn->state = [int(0..0)|ConnectionState]
    	  (conn->state | CONNECTION_peer_closed);
    	SSL3_DEBUG_MSG ("ssl_close_callback: Abrupt close - "
    			"simulating System.EPIPE\n");
    	cleanup_on_error();
    	cb_errno = System.EPIPE;
    	close_state = ABRUPT_CLOSE;
          }
        }
    
        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 close_callback (callback_id);
        }
    
        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
      // isn't clobbered by later I/O.
      return -1;
    }
    
    //! The application protocol chosen by the client during application layer
    //! protocol negotiation (ALPN).
    string `->application_protocol() {
        return conn->application_protocol;
    }
    
    //! Return the currently active cipher suite.
    int query_suite()
    {
      return conn?->session?->cipher_suite;
    }