diff --git a/lib/modules/Protocols.pmod/HTTP.pmod/Query.pike b/lib/modules/Protocols.pmod/HTTP.pmod/Query.pike index 6e0302eb8b6a1f5772c08b742e90c140401f330c..93f1f79961ce928bc1e5743de47a9198ff53dda1 100644 --- a/lib/modules/Protocols.pmod/HTTP.pmod/Query.pike +++ b/lib/modules/Protocols.pmod/HTTP.pmod/Query.pike @@ -194,7 +194,7 @@ void start_tls(int|void blocking, int|void async) object write_callback=con->query_write_callback(); object close_callback=con->query_close_callback(); - SSL.sslfile ssl = SSL.sslfile(con, context); + SSL.File ssl = SSL.File(con, context); if (blocking) { ssl->set_blocking(); } diff --git a/lib/modules/Protocols.pmod/HTTP.pmod/Server.pmod/SSLPort.pike b/lib/modules/Protocols.pmod/HTTP.pmod/Server.pmod/SSLPort.pike index 2bff51b29d6867547547d8efcec41169de7dd02a..cb4209ec18bd0ba8616d76488d9858cb31b17c9b 100644 --- a/lib/modules/Protocols.pmod/HTTP.pmod/Server.pmod/SSLPort.pike +++ b/lib/modules/Protocols.pmod/HTTP.pmod/Server.pmod/SSLPort.pike @@ -63,7 +63,7 @@ void destroy() { close(); } //! The port accept callback protected void new_connection() { - SSL.sslfile fd=port->accept(); + SSL.File fd=port->accept(); Request r=request_program(); r->attach_fd(fd,this,callback); } @@ -72,7 +72,7 @@ protected void new_connection() class MySSLPort { - inherit SSL.sslport; + inherit SSL.Port; //! void set_default_keycert() diff --git a/lib/modules/Protocols.pmod/HTTP.pmod/Session.pike b/lib/modules/Protocols.pmod/HTTP.pmod/Session.pike index c326f0ee258aab7374e25aa08112c55f6a4941b1..1a8d7dc84a5283370703dea752adea8563cfdaeb 100644 --- a/lib/modules/Protocols.pmod/HTTP.pmod/Session.pike +++ b/lib/modules/Protocols.pmod/HTTP.pmod/Session.pike @@ -74,7 +74,7 @@ class Request url=Standards.URI(url); url_requested=url; -#if constant(SSL.sslfile) +#if constant(SSL.File) if(url->scheme!="http" && url->scheme!="https") error("Protocols.HTTP can't handle %O or any other " "protocols than HTTP or HTTPS\n", @@ -87,7 +87,7 @@ class Request error("Protocols.HTTP can't handle %O or any other " "protocol than HTTP\n", url->scheme); -#endif +#endif /* constant(SSL.File) */ mapping request_headers = copy_value(default_headers); if (url->referer) request_headers->referer=(string)url->referer; diff --git a/lib/modules/Protocols.pmod/HTTP.pmod/module.pmod b/lib/modules/Protocols.pmod/HTTP.pmod/module.pmod index 2a41875c923174a91e8695502737bddd5892ec3a..a540e6bba6378db2a26a70d6bed29bafa4ff3922 100644 --- a/lib/modules/Protocols.pmod/HTTP.pmod/module.pmod +++ b/lib/modules/Protocols.pmod/HTTP.pmod/module.pmod @@ -195,7 +195,7 @@ constant response_codes = url->port = proxy->port; query_variables = url->query = 0; url->path = web_url; -#if constant(SSL.sslfile) +#if constant(SSL.File) } else if (url->scheme == "https") { #ifdef HTTP_QUERY_DEBUG werror("Proxied SSL request.\n"); @@ -220,7 +220,7 @@ constant response_codes = con->start_tls(1); } proxy_headers = request_headers; -#endif +#endif /* constant(SSL.File) */ } else { error("Can't handle proxying of %O.\n", url->scheme); } @@ -267,7 +267,7 @@ constant response_codes = if(!con) con = .Query(); -#if constant(SSL.sslfile) +#if constant(SSL.File) if(url->scheme!="http" && url->scheme!="https") error("Can't handle %O or any other protocols than HTTP or HTTPS.\n", url->scheme); @@ -278,7 +278,7 @@ constant response_codes = error("Can't handle %O or any other protocol than HTTP " "(HTTPS requires Nettle support).\n", url->scheme); -#endif +#endif /* constant(SSL.File) */ mapping default_headers = ([ "user-agent" : "Mozilla/5.0 (compatible; MSIE 6.0; Pike HTTP client)" @@ -403,7 +403,7 @@ void do_async_method(string method, error("Asynchronous httpu or httpmu not yet supported.\n"); } -#if constant(SSL.sslfile) +#if constant(SSL.File) if(url->scheme!="http" && url->scheme!="https") error("Can't handle %O or any other protocols than HTTP or HTTPS.\n", url->scheme); @@ -413,7 +413,7 @@ void do_async_method(string method, if(url->scheme!="http") error("Can't handle %O or any other protocol than HTTP.\n", url->scheme); -#endif +#endif /* constant(SSL.File) */ if(!request_headers) request_headers = ([]); @@ -567,7 +567,7 @@ void do_async_proxied_method(string|Standards.URI proxy, url->port = proxy->port; query_variables = url->query = 0; url->path = web_url; -#if constant(SSL.sslfile) +#if constant(SSL.File) } else if(url->scheme == "https") { #ifdef HTTP_QUERY_DEBUG werror("Proxied SSL request.\n"); diff --git a/lib/modules/Protocols.pmod/IRC.pmod/Client.pike b/lib/modules/Protocols.pmod/IRC.pmod/Client.pike index 09366cced2abc75807f78a57b0e05bbc5d7aa3fd..16fe3d66021168329aea70649442b885883dbb6d 100644 --- a/lib/modules/Protocols.pmod/IRC.pmod/Client.pike +++ b/lib/modules/Protocols.pmod/IRC.pmod/Client.pike @@ -15,7 +15,7 @@ mapping channels=([]); //! @param server //! The IRC server to connect to. //! If server is an object, it is assumed to be a newly established -//! connection to the IRC server to be used. Pass @[SSL.sslfile] +//! connection to the IRC server to be used. Pass @[SSL.File] //! connections here to connect to SSL secured IRC networks. //! @param options //! An optional mapping with additional IRC client options. diff --git a/lib/modules/Protocols.pmod/LDAP.pmod/client.pike b/lib/modules/Protocols.pmod/LDAP.pmod/client.pike index 92f824945f23bf6588a2d4de0f60407787ed35d7..e803dba1a5fb60ab83bbb42c10bfbe9dd686be53 100644 --- a/lib/modules/Protocols.pmod/LDAP.pmod/client.pike +++ b/lib/modules/Protocols.pmod/LDAP.pmod/client.pike @@ -705,7 +705,7 @@ typedef mapping(string:ResultAttributeValue) ResultEntry; #if constant(SSL.Cipher) if(lauth->scheme == "ldaps") { - SSL.sslfile ssl_fd = SSL.sslfile(low_fd, context); + SSL.File ssl_fd = SSL.File(low_fd, context); if (!ssl_fd->connect()) { ERROR("Failed to connect to LDAPS server.\n"); } @@ -786,7 +786,7 @@ void reset_options() context = SSL.Context(); } object _f = ldapfd; - ldapfd = SSL.sslfile(_f, context); + ldapfd = SSL.File(_f, context); return ldapfd->connect(); #endif return 0; diff --git a/lib/modules/Protocols.pmod/LDAP.pmod/protocol.pike b/lib/modules/Protocols.pmod/LDAP.pmod/protocol.pike index 9d2a3b2dc41a08814c179fd9ee651b7f651d3854..ea6c1540625708ce2ecb6d39993eb71c2b6ebbe6 100644 --- a/lib/modules/Protocols.pmod/LDAP.pmod/protocol.pike +++ b/lib/modules/Protocols.pmod/LDAP.pmod/protocol.pike @@ -40,7 +40,7 @@ import Protocols.LDAP; int connected = 0; #if constant(SSL.Cipher) - Stdio.Stream|SSL.sslfile ldapfd; // helper fd + Stdio.Stream|SSL.File ldapfd; // helper fd #else Stdio.Stream ldapfd; // helper fd #endif diff --git a/lib/modules/SSL.pmod/Connection.pike b/lib/modules/SSL.pmod/Connection.pike index c5ec435bc37ad61ee1e007ac319ad323248f5720..4b6866edb7430147b1a3d0c9dd514963ca89b282 100644 --- a/lib/modules/SSL.pmod/Connection.pike +++ b/lib/modules/SSL.pmod/Connection.pike @@ -21,11 +21,11 @@ //! classes that inherits it should be used (ie either //! @[ClientConnection] or @[ServerConnection]) depending on whether //! this is to be a client-side or server-side connection. These in -//! turn are typically created by @[sslfile()->create()]. +//! turn are typically created by @[File()->create()]. //! //! @seealso //! @[ClientConnection], @[ServerConnection], @[Context], -//! @[Session], @[sslfile], @[state] +//! @[Session], @[File], @[State] //#define SSL3_PROFILING diff --git a/lib/modules/SSL.pmod/Constants.pmod b/lib/modules/SSL.pmod/Constants.pmod index 1aa7727ad09d6629216c9886b8fd1488b83a5d6c..af956720b9d809fd2b9e93ce761011666389d3a4 100644 --- a/lib/modules/SSL.pmod/Constants.pmod +++ b/lib/modules/SSL.pmod/Constants.pmod @@ -70,7 +70,7 @@ //! Constants for specifying the versions of SSL to use. //! //! @seealso -//! @[SSL.sslfile()->create()], @[SSL.handshake()->create()] +//! @[Context] enum ProtocolVersion { PROTOCOL_SSL_3_0 = 0x300, //! SSL 3.0 - The original SSL3 draft version. PROTOCOL_SSL_3_1 = 0x301, //! SSL 3.1 - The RFC 2246 version of SSL. diff --git a/lib/modules/SSL.pmod/Context.pike b/lib/modules/SSL.pmod/Context.pike index a8f96be62aff18d725bfcadf28ea5b6057184629..bf882869160552425be505aa59049035b073a922 100644 --- a/lib/modules/SSL.pmod/Context.pike +++ b/lib/modules/SSL.pmod/Context.pike @@ -27,11 +27,11 @@ //! satisfactory. //! @endul //! -//! The initialized @[context] object is then passed to -//! @[sslfile()->create()] or used as is embedded in @[sslport]. +//! The initialized @[Context] object is then passed to +//! @[File()->create()] or used as is embedded in @[Port]. //! //! @seealso -//! @[sslfile], @[sslport], @[Standards.X509] +//! @[File], @[Port], @[Standards.X509] #ifdef SSL3_DEBUG #define SSL3_DEBUG_MSG(X ...) werror(X) @@ -216,8 +216,7 @@ array(int) ecc_curves = reverse(sort(indices(ECC_CURVES))); array(string(8bit)) advertised_protocols; //! The maximum amount of data that is sent in each SSL packet by -//! @[sslfile]. A value between 1 and -//! @[SSL.Constants.PACKET_MAX_SIZE]. +//! @[File]. A value between 1 and @[Constants.PACKET_MAX_SIZE]. int packet_max_size = PACKET_MAX_SIZE; // The signature algorithms to use. According to RFC 5246 7.4.2 all diff --git a/lib/modules/SSL.pmod/File.pike b/lib/modules/SSL.pmod/File.pike new file mode 100644 index 0000000000000000000000000000000000000000..eabae5b7903d31276a6a4324a103dbb520cc78fd --- /dev/null +++ b/lib/modules/SSL.pmod/File.pike @@ -0,0 +1,2324 @@ +#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|array(string)|void dest_addr) +{ + if (conn) error("A connection is already configured!\n"); + + ENTER (0, 0) { + if (stringp(dest_addr)) { + dest_addr = ({ dest_addr }); + } + 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_names() +{ + if (!conn) error("No active conection.\n"); + return conn->session->server_names; +} + +//! @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] +{ + if (sizeof (args)) + data = sprintf (arrayp (data) ? data * "" : data, @args); + + SSL3_DEBUG_MSG ("SSL.File->write (%t[%d])\n", data, sizeof (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 written = 0; + + if (arrayp (data)) { + int idx = 0, pos = 0; + + while (idx < sizeof (data) && !sizeof (write_buffer) && + // 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. + (!nonblocking_mode || written < Stdio.DATA_CHUNK_SIZE)) { + int size = sizeof (data[idx]) - pos; + if (size > fragment_max_size) { + // send_streaming_data will pick the first fragment_max_size + // bytes of the string, so do that right away in the same + // range operation. + int n = conn->send_streaming_data ( + data[idx][pos..pos + fragment_max_size - 1]); + SSL3_DEBUG_MSG ("SSL.File->write: Queued data[%d][%d..%d]\n", + idx, pos, pos + n - 1); + written += n; + pos += n; + } + + else { + // Try to fill a packet. + int end; + for (end = idx + 1; end < sizeof (data); end++) { + int newsize = size + sizeof (data[end]); + if (newsize > fragment_max_size) break; + size = newsize; + } + + if (conn->send_streaming_data ( + `+ (data[idx][pos..], @data[idx+1..end-1])) < size) + error ("Unexpected fragment_max_size discrepancy wrt send_streaming_data.\n"); + + SSL3_DEBUG_MSG ("SSL.File->write: " + "Queued data[%d][%d..%d] + data[%d..%d]\n", + idx, pos, sizeof (data[idx]) - 1, idx + 1, end - 1); + written += size; + idx = end; + pos = 0; + } + + if (!direct_write()) RETURN (written); + } + } + + else // data is a string. + while (written < sizeof (data) && !sizeof (write_buffer) && + // Limit the amount written in a single call, for the + // same reason as above. + (!nonblocking_mode || written < Stdio.DATA_CHUNK_SIZE)) { + int n = conn->send_streaming_data ( + data[written..written + fragment_max_size - 1]); + SSL3_DEBUG_MSG ("SSL.File->write: Queued data[%d..%d]\n", + written, written + n - 1); + 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 next protocol chosen by the client during application layer +//! protocol negotiation (ALPN) or next protocol negotiation (NPN). +string `->next_protocol() { + return conn->next_protocol; +} + +//! Return the currently active cipher suite. +int query_suite() +{ + return conn?->session?->cipher_suite; +} diff --git a/lib/modules/SSL.pmod/Port.pike b/lib/modules/SSL.pmod/Port.pike new file mode 100644 index 0000000000000000000000000000000000000000..f51b2c60d83f2c784e1966cf246cc09f24300904 --- /dev/null +++ b/lib/modules/SSL.pmod/Port.pike @@ -0,0 +1,172 @@ +#pike __REAL_VERSION__ +#require constant(SSL.Cipher) + +#define Context .Context + +//! Interface similar to @[Stdio.Port]. + +//! +inherit Stdio.Port : socket; + +//! Context to use for the connections. +Context ctx; + +protected ADT.Queue accept_queue = ADT.Queue(); + +//! +function(object, mixed|void:void) accept_callback; + +//! @decl void finished_callback(SSL.File f, mixed|void id) +//! +//! SSL connection accept callback. +//! +//! @param f +//! The @[File] that just finished negotiation. +//! +//! This function is installed as the @[File] accept callback by +//! @[ssl_callback()], and enqueues the newly negotiated @[File] on +//! the accept queue. +//! +//! If there has been an @[accept_callback] installed by @[bind()] or +//! @[listen_fd()], it will be called with all pending @[File]s on the +//! accept queue. +//! +//! If there's no @[accept_callback], then the @[File] will have to be +//! retrieved from the queue by calling @[accept()]. +void finished_callback(object f, mixed|void id) +{ + accept_queue->put(f); + while (accept_callback && sizeof(accept_queue)) + { + accept_callback(f, id); + } +} + +//! Connection accept callback. +//! +//! This function is installed as the @[Stdio.Port] callback, and +//! accepts the connection and creates a corresponding @[File] with +//! @[finished_callback()] as the accept callback. +//! +//! @seealso +//! @[bind()], @[finished_callback()] +void ssl_callback(mixed id) +{ + object f = socket_accept(); + if (f) + { + object ssl_fd = .File(f, ctx); + ssl_fd->accept(); + ssl_fd->set_accept_callback(finished_callback); + } +} + +#if 0 +void set_id(mixed id) +{ + error( "Not supported\n" ); +} + +mixed query_id() +{ + error( "Not supported\n" ); +} +#endif + +//! @decl int bind(int port, @ +//! function(SSL.File|void, mixed|void: int) callback, @ +//! string|void ip) +//! +//! Bind an SSL port. +//! +//! @param port +//! Port number to bind. +//! +//! @param callback +//! Callback to call when the SSL connection has been negotiated. +//! +//! The callback is called with an @[File] as the first argument, +//! and the id for the @[File] as the second. +//! +//! If the @[callback] is @expr{0@} (zero), then negotiated @[File]s +//! will be enqueued for later retrieval with @[accept()]. +//! +//! @param ip +//! Optional IP-number to bind. +//! +//! @returns +//! Returns @expr{1@} if binding of the port succeeded, +//! and @expr{0@} (zero) on failure. +//! +//! @seealso +//! @[Stdio.Port()->bind()], @[File()->set_accept_callback()], +//! @[listen_fd()] +int bind(int port, function callback, string|void ip) +{ + accept_callback = callback; + return socket::bind(port, ssl_callback, ip); +} + +//! @decl int listen_fd(int fd, @ +//! function(File|void, mixed|void: int) callback) +//! +//! Set up listening for SSL connections on an already opened fd. +//! +//! @param fd +//! File descriptor to listen on. +//! +//! @param callback +//! Callback to call when the SSL connection has been negotiated. +//! +//! The callback is called with an @[File] as the first argument, +//! and the id for the @[File] as the second. +//! +//! If the @[callback] is @expr{0@} (zero), then negotiated @[File]s +//! will be enqueued for later retrieval with @[accept()]. +//! +//! @returns +//! Returns @expr{1@} if listening on the fd succeeded, +//! and @expr{0@} (zero) on failure. +//! +//! @seealso +//! @[Stdio.Port()->listen_fd()], @[File()->set_accept_callback()], +//! @[bind()] +int listen_fd(int fd, function callback) +{ + accept_callback = callback; + return socket::listen_fd(fd, ssl_callback); +} + +//! Low-level accept. +//! +//! @seealso +//! @[Stdio.Port()->accept()] +Stdio.File socket_accept() +{ + return socket::accept(); +} + +//! @decl File accept() +//! +//! Get the next pending @[File] from the @[accept_queue]. +//! +//! @returns +//! Returns the next pending @[File] if any, and @expr{0@} (zero) if +//! there are none. +object accept() +{ + return accept_queue->get(); +} + +//! Create a new port for accepting SSL connections. +//! +//! @seealso +//! @[bind()], @[listen_fd()] +void create(Context|void ctx) +{ +#ifdef SSL3_DEBUG + werror("SSL.Port->create\n"); +#endif + if (!ctx) ctx = Context(); + this_program::ctx = ctx; +} diff --git a/lib/modules/SSL.pmod/https.pike b/lib/modules/SSL.pmod/https.pike index 14181a719c2ce84481657a08b61ce0fb3e7b0c5d..73f71f49cdd3d27e9ca7a2a5d8f5864cded629df 100644 --- a/lib/modules/SSL.pmod/https.pike +++ b/lib/modules/SSL.pmod/https.pike @@ -52,7 +52,7 @@ class MyContext } #ifndef HTTPS_CLIENT -SSL.sslport port; +SSL.Port port; void my_accept_callback(object f) { @@ -111,7 +111,7 @@ class Client "Host: " HOST ":" + PORT + "\r\n" "\r\n"; - SSL.sslfile ssl; + SSL.File ssl; int sent; void write_cb() @@ -143,7 +143,7 @@ class Client // Make sure all cipher suites are available. ctx->preferred_suites = ctx->get_suites(-1, 2); werror("Starting\n"); - ssl = SSL.sslfile(con, ctx); + ssl = SSL.File(con, ctx); ssl->connect(); ssl->set_nonblocking(got_data, write_cb, con_closed); } @@ -220,7 +220,7 @@ int main() SSL3_DEBUG_MSG("Certs:\n%O\n", ctx->cert_pairs); - port = SSL.sslport(ctx); + port = SSL.Port(ctx); werror("Starting\n"); if (!port->bind(PORT, my_accept_callback)) diff --git a/lib/modules/SSL.pmod/module.pmod b/lib/modules/SSL.pmod/module.pmod index cd3576251f9f7b4c316b7836a1ca41e23b167888..85679964a9cd9bd53b01f902719e435c587cfb4b 100644 --- a/lib/modules/SSL.pmod/module.pmod +++ b/lib/modules/SSL.pmod/module.pmod @@ -9,17 +9,17 @@ //! //! The classes that typical users need to use are //! @dl -//! @item @[sslfile] +//! @item @[File] //! This is an object that attempts to behave as a @[Stdio.File] //! as much as possible. //! -//! @item @[sslport] +//! @item @[Port] //! This is an object that attempts to behave as a @[Stdio.Port] -//! as much as possible, with @[sslport()->accept()] returning -//! @[sslfile] objects. +//! as much as possible, with @[Port()->accept()] returning +//! @[File] objects. //! -//! @item @[context] -//! The configurated context for the @[sslfile]. +//! @item @[Context] +//! The configurated context for the @[File]. //! //! @item @[Constants.CertificatePair] //! A class for keeping track of certificate chains and their @@ -31,5 +31,5 @@ //! the constants for output. //! //! @seealso -//! @[sslfile], @[sslport], @[context], @[Constants.CertificatePair], +//! @[File], @[Port], @[Context], @[Constants.CertificatePair], //! @[Constants] diff --git a/lib/modules/SSL.pmod/sslfile.pike b/lib/modules/SSL.pmod/sslfile.pike index d6c08f705e187ece3dc85144d12ed6ea2813a0f5..40b40315054f3a06fc89201a1b01b7664afc602c 100644 --- a/lib/modules/SSL.pmod/sslfile.pike +++ b/lib/modules/SSL.pmod/sslfile.pike @@ -1,2324 +1,4 @@ -#pike __REAL_VERSION__ +#pike 7.8 #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.sslfile->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|array(string)|void dest_addr) -{ - if (conn) error("A connection is already configured!\n"); - - ENTER (0, 0) { - if (stringp(dest_addr)) { - dest_addr = ({ dest_addr }); - } - 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_names() -{ - if (!conn) error("No active conection.\n"); - return conn->session->server_names; -} - -//! @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.sslfile->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.sslfile->close: Already closed (%d)\n", close_state); - RETURN (1); - } - close_state = clean_close ? CLEAN_CLOSE : NORMAL_CLOSE; - - FIX_ERRNOS ({ - SSL3_DEBUG_MSG ("SSL.sslfile->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.sslfile->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.sslfile->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.sslfile->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.sslfile->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.sslfile->shutdown(): Clean close - " - "leaving stream\n"); - local_errno = 0; - RETURN (stream); - } - else { - SSL3_DEBUG_MSG ("SSL.sslfile->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.sslfile->shutdown(): Not closed - leaving stream\n"); - local_errno = 0; - RETURN (stream); - default: - SSL3_DEBUG_MSG ("SSL.sslfile->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.sslfile->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.sslfile->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.sslfile->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.sslfile->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] -{ - if (sizeof (args)) - data = sprintf (arrayp (data) ? data * "" : data, @args); - - SSL3_DEBUG_MSG ("SSL.sslfile->write (%t[%d])\n", data, sizeof (data)); - - ENTER (0, 0) { - if (close_state > STREAM_OPEN) error ("Not open.\n"); - - FIX_ERRNOS ({ - SSL3_DEBUG_MSG ("SSL.sslfile->write: Propagating old callback error: %s\n", - strerror (local_errno)); - RETURN (-1); - }, 0); - - if (nonblocking_mode && SSL_HANDSHAKING) { - SSL3_DEBUG_MSG ("SSL.sslfile->write: " - "Still in handshake - cannot accept application data\n"); - RETURN (0); - } - - // Take care of any old data first. - if (!direct_write()) RETURN (-1); - - int written = 0; - - if (arrayp (data)) { - int idx = 0, pos = 0; - - while (idx < sizeof (data) && !sizeof (write_buffer) && - // 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. - (!nonblocking_mode || written < Stdio.DATA_CHUNK_SIZE)) { - int size = sizeof (data[idx]) - pos; - if (size > fragment_max_size) { - // send_streaming_data will pick the first fragment_max_size - // bytes of the string, so do that right away in the same - // range operation. - int n = conn->send_streaming_data ( - data[idx][pos..pos + fragment_max_size - 1]); - SSL3_DEBUG_MSG ("SSL.sslfile->write: Queued data[%d][%d..%d]\n", - idx, pos, pos + n - 1); - written += n; - pos += n; - } - - else { - // Try to fill a packet. - int end; - for (end = idx + 1; end < sizeof (data); end++) { - int newsize = size + sizeof (data[end]); - if (newsize > fragment_max_size) break; - size = newsize; - } - - if (conn->send_streaming_data ( - `+ (data[idx][pos..], @data[idx+1..end-1])) < size) - error ("Unexpected fragment_max_size discrepancy wrt send_streaming_data.\n"); - - SSL3_DEBUG_MSG ("SSL.sslfile->write: " - "Queued data[%d][%d..%d] + data[%d..%d]\n", - idx, pos, sizeof (data[idx]) - 1, idx + 1, end - 1); - written += size; - idx = end; - pos = 0; - } - - if (!direct_write()) RETURN (written); - } - } - - else // data is a string. - while (written < sizeof (data) && !sizeof (write_buffer) && - // Limit the amount written in a single call, for the - // same reason as above. - (!nonblocking_mode || written < Stdio.DATA_CHUNK_SIZE)) { - int n = conn->send_streaming_data ( - data[written..written + fragment_max_size - 1]); - SSL3_DEBUG_MSG ("SSL.sslfile->write: Queued data[%d..%d]\n", - written, written + n - 1); - written += n; - if (!direct_write()) RETURN (written); - } - - SSL3_DEBUG_MSG ("SSL.sslfile->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.sslfile->renegotiate()\n"); - - ENTER (0, 0) { - if (close_state > STREAM_OPEN) error ("Not open.\n"); - - FIX_ERRNOS ({ - SSL3_DEBUG_MSG ("SSL.sslfile->renegotiate: " - "Propagating old callback error: %s\n", - strerror (local_errno)); - RETURN (0); - }, 0); - - if (!stream) { - SSL3_DEBUG_MSG ("SSL.sslfile->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.sslfile->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.sslfile->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.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. -//! -//! @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.sslfile->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.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; -} - -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.sslfile->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 sslfile 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.sslfile->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.sslfile->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.sslfile->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.sslfile->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.sslfile->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.sslfile->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(sslfile_obj->query_stream())@}. That almost -//! certainly creates more problems than it solves. -//! -//! You probably want to use @[shutdown]. -//! -//! @seealso -//! @[shutdown] -{ - SSL3_DEBUG_MSG ("SSL.sslfile->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.sslfile->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.sslfile(%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 next protocol chosen by the client during application layer -//! protocol negotiation (ALPN) or next protocol negotiation (NPN). -string `->next_protocol() { - return conn->next_protocol; -} - -//! Return the currently active cipher suite. -int query_suite() -{ - return conn?->session?->cipher_suite; -} +inherit .File; diff --git a/lib/modules/SSL.pmod/sslport.pike b/lib/modules/SSL.pmod/sslport.pike index 2322f460105934851190052e21507255d8a7d20d..36c1369dcb49d43130681fdbf29e81c195445c53 100644 --- a/lib/modules/SSL.pmod/sslport.pike +++ b/lib/modules/SSL.pmod/sslport.pike @@ -1,175 +1,4 @@ -#pike __REAL_VERSION__ +#pike 7.8 #require constant(SSL.Cipher) -#define Context .Context - -//! Interface similar to @[Stdio.Port]. - -//! -inherit Stdio.Port : socket; - -//! Context to use for the connections. -Context ctx; - -protected ADT.Queue accept_queue = ADT.Queue(); - -//! -function(object, mixed|void:void) accept_callback; - -//! @decl void finished_callback(SSL.sslfile f, mixed|void id) -//! -//! SSL connection accept callback. -//! -//! @param f -//! The @[SSL.sslfile] that just finished negotiation. -//! -//! This function is installed as the @[SSL.sslfile] accept -//! callback by @[ssl_callback()], and enqueues the newly -//! negotiated @[SSL.sslfile] on the accept queue. -//! -//! If there has been an @[accept_callback] installed by -//! @[bind()] or @[listen_fd()], it will be called with -//! all pending @[SSL.sslfile]s on the accept queue. -//! -//! If there's no @[accept_callback], then the @[SSL.sslfile] -//! will have to be retrieved from the queue by calling @[accept()]. -void finished_callback(object f, mixed|void id) -{ - accept_queue->put(f); - while (accept_callback && sizeof(accept_queue)) - { - accept_callback(f, id); - } -} - -//! Connection accept callback. -//! -//! This function is installed as the @[Stdio.Port] callback, -//! and accepts the connection and creates a corresponding -//! @[SSL.sslfile] with @[finished_callback()] as the accept -//! callback. -//! -//! @seealso -//! @[bind()], @[finished_callback()] -void ssl_callback(mixed id) -{ - object f = socket_accept(); - if (f) - { - object ssl_fd = .sslfile(f, ctx); - ssl_fd->accept(); - ssl_fd->set_accept_callback(finished_callback); - } -} - -#if 0 -void set_id(mixed id) -{ - error( "Not supported\n" ); -} - -mixed query_id() -{ - error( "Not supported\n" ); -} -#endif - -//! @decl int bind(int port, @ -//! function(SSL.sslfile|void, mixed|void: int) callback, @ -//! string|void ip) -//! -//! Bind an SSL port. -//! -//! @param port -//! Port number to bind. -//! -//! @param callback -//! Callback to call when the SSL connection has been negotiated. -//! -//! The callback is called with an @[SSL.sslfile] as the first argument, -//! and the id for the @[SSL.sslfile] as the second. -//! -//! If the @[callback] is @expr{0@} (zero), then negotiated -//! @[SSL.sslfile]s will be enqueued for later retrieval with -//! @[accept()]. -//! -//! @param ip -//! Optional IP-number to bind. -//! -//! @returns -//! Returns @expr{1@} if binding of the port succeeded, -//! and @expr{0@} (zero) on failure. -//! -//! @seealso -//! @[Stdio.Port()->bind()], @[SSL.sslfile()->set_accept_callback()], -//! @[listen_fd()] -int bind(int port, function callback, string|void ip) -{ - accept_callback = callback; - return socket::bind(port, ssl_callback, ip); -} - -//! @decl int listen_fd(int fd, @ -//! function(SSL.sslfile|void, mixed|void: int) callback) -//! -//! Set up listening for SSL connections on an already opened fd. -//! -//! @param fd -//! File descriptor to listen on. -//! -//! @param callback -//! Callback to call when the SSL connection has been negotiated. -//! -//! The callback is called with an @[SSL.sslfile] as the first argument, -//! and the id for the @[SSL.sslfile] as the second. -//! -//! If the @[callback] is @expr{0@} (zero), then negotiated -//! @[SSL.sslfile]s will be enqueued for later retrieval with -//! @[accept()]. -//! -//! @returns -//! Returns @expr{1@} if listening on the fd succeeded, -//! and @expr{0@} (zero) on failure. -//! -//! @seealso -//! @[Stdio.Port()->listen_fd()], @[SSL.sslfile()->set_accept_callback()], -//! @[bind()] -int listen_fd(int fd, function callback) -{ - accept_callback = callback; - return socket::listen_fd(fd, ssl_callback); -} - -//! Low-level accept. -//! -//! @seealso -//! @[Stdio.Port()->accept()] -Stdio.File socket_accept() -{ - return socket::accept(); -} - -//! @decl SSL.sslfile accept() -//! -//! Get the next pending @[SSL.sslfile] from the @[accept_queue]. -//! -//! @returns -//! Returns the next pending @[SSL.sslfile] if any, -//! and @expr{0@} (zero) if there are none. -object accept() -{ - return accept_queue->get(); -} - -//! Create a new port for accepting SSL connections. -//! -//! @seealso -//! @[bind()], @[listen_fd()] -void create(Context|void ctx) -{ -#ifdef SSL3_DEBUG - werror("SSL.sslport->create\n"); -#endif - if (!ctx) ctx = Context(); - this_program::ctx = ctx; -} +inherit .Port; diff --git a/lib/modules/SSL.pmod/testsuite.in b/lib/modules/SSL.pmod/testsuite.in index f83184e1d040d44574dc25563ddd64eb47dde195..b23b6d6a638cc0e170f302d2f4935f0f7ae43a55 100644 --- a/lib/modules/SSL.pmod/testsuite.in +++ b/lib/modules/SSL.pmod/testsuite.in @@ -238,7 +238,7 @@ define(run_sub_test, [[ ]]) ]]) -dnl Displaced tests for SSL.sslfile. +dnl Displaced tests for SSL.File. run_sub_test(({"SRCDIR../../../src/modules/_Stdio/async_tls_close_test.pike", "0", "0"})) run_sub_test(({"SRCDIR../../../src/modules/_Stdio/async_tls_close_test.pike", "0", "1"})) run_sub_test(({"SRCDIR../../../src/modules/_Stdio/async_tls_close_test.pike", "1", "0"})) @@ -440,9 +440,9 @@ test_do([[ Stdio.File server_con = client_con->pipe(Stdio.PROP_NONBLOCK | Stdio.PROP_BIDIRECTIONAL); - SSL.sslfile server = SSL.sslfile(server_con, server_ctx); + SSL.File server = SSL.File(server_con, server_ctx); - SSL.sslfile client = SSL.sslfile(client_con, client_ctx); + SSL.File client = SSL.File(client_con, client_ctx); if (!client->connect()) return 0; if (!server->accept()) return 0; diff --git a/lib/modules/Sql.pmod/pgsql.pike b/lib/modules/Sql.pmod/pgsql.pike index db0850eb8bb63539883af7dc192a70ed66e6b751..276a9ac5e0fe612fe63ad11e12052431e29e0c60 100644 --- a/lib/modules/Sql.pmod/pgsql.pike +++ b/lib/modules/Sql.pmod/pgsql.pike @@ -333,7 +333,7 @@ final private object getsocket(void|int nossl) return UNDEFINED; object fcon; -#if constant(SSL.sslfile) +#if constant(SSL.File) if(!nossl && (options->use_ssl || options->force_ssl)) { PD("SSLRequest\n"); { object c=.pgsql_util.PGassist(); diff --git a/lib/modules/Sql.pmod/pgsql_util.pmod b/lib/modules/Sql.pmod/pgsql_util.pmod index dcbba5fd0a4c886c9114c747878678e51bad0769..a338346bda88c34848669fa61e0009ae436227c8 100644 --- a/lib/modules/Sql.pmod/pgsql_util.pmod +++ b/lib/modules/Sql.pmod/pgsql_util.pmod @@ -189,16 +189,16 @@ class PGconn } } -#if constant(SSL.sslfile) +#if constant(SSL.File) class PGconnS -{ inherit SSL.sslfile:std; +{ inherit SSL.File:std; inherit PGassist:pg; Stdio.File rawstream; inline int(-1..1) peek(int timeout) { return rawstream.peek(timeout); // This is a kludge - } // Actually SSL.sslfile should provide a peek() method + } // Actually SSL.File should provide a peek() method inline string read(int len,void|int(0..1) not_all) { return std::read(len,not_all); diff --git a/src/modules/_Stdio/async_tls_close_test.pike b/src/modules/_Stdio/async_tls_close_test.pike index 04702a0285b2083cab77eb739ab29095ce03b22b..1f4b5c7021e9fc9d44de2f628ab50da1e087aa7d 100644 --- a/src/modules/_Stdio/async_tls_close_test.pike +++ b/src/modules/_Stdio/async_tls_close_test.pike @@ -21,12 +21,12 @@ int main (int argc, array(string) argv) }); Stdio.File con = Stdio.File(); - SSL.sslfile tlscon; + SSL.File tlscon; con->async_connect ( "127.0.0.1", 36565, lambda (int success) { if (success) { - tlscon = SSL.sslfile (con, SSL.Context()); + tlscon = SSL.File (con, SSL.Context()); tlscon->connect("localhost"); tlscon->set_write_callback (lambda () { fail = "Handshake should not succeed.\n";