diff --git a/lib/_tls_legacy.js b/lib/_tls_legacy.js index 2f7559d09d..be3c8aff55 100644 --- a/lib/_tls_legacy.js +++ b/lib/_tls_legacy.js @@ -314,16 +314,21 @@ CryptoStream.prototype._read = function read(size) { if (bytesRead === 0) { // EOF when cleartext has finished and we have nothing to read - if (this._opposite._finished && this._internallyPendingBytes() === 0) { + if (this._opposite._finished && this._internallyPendingBytes() === 0 || + this.pair.ssl && this.pair.ssl.receivedShutdown) { // Perform graceful shutdown this._done(); // No half-open, sorry! - if (this === this.pair.cleartext) + if (this === this.pair.cleartext) { this._opposite._done(); - // EOF - this.push(null); + // EOF + this.push(null); + } else if (!this.pair.ssl || !this.pair.ssl.receivedShutdown) { + // EOF + this.push(null); + } } else { // Bail out this.push(''); diff --git a/node.gyp b/node.gyp index a0a1774a43..f8f4a9ac8e 100644 --- a/node.gyp +++ b/node.gyp @@ -320,7 +320,7 @@ ], }], [ - 'OS=="linux"', { + 'OS=="linux" and node_shared_v8=="false"', { 'ldflags': [ '-Wl,--whole-archive <(PRODUCT_DIR)/obj.target/deps/v8/tools/gyp/libv8_base.<(target_arch).a -Wl,--no-whole-archive', ], diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 1e7bf368af..d1467527e4 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -852,9 +852,9 @@ void SSLWrap::AddMethods(Handle t) { NODE_SET_PROTOTYPE_METHOD(t, "isInitFinished", IsInitFinished); NODE_SET_PROTOTYPE_METHOD(t, "verifyError", VerifyError); NODE_SET_PROTOTYPE_METHOD(t, "getCurrentCipher", GetCurrentCipher); - NODE_SET_PROTOTYPE_METHOD(t, "receivedShutdown", ReceivedShutdown); NODE_SET_PROTOTYPE_METHOD(t, "endParser", EndParser); NODE_SET_PROTOTYPE_METHOD(t, "renegotiate", Renegotiate); + NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Shutdown); #ifdef OPENSSL_NPN_NEGOTIATED NODE_SET_PROTOTYPE_METHOD(t, "getNegotiatedProtocol", GetNegotiatedProto); @@ -1206,15 +1206,6 @@ void SSLWrap::IsSessionReused(const FunctionCallbackInfo& args) { } -template -void SSLWrap::ReceivedShutdown(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - Base* w = Unwrap(args.This()); - bool yes = SSL_get_shutdown(w->ssl_) == SSL_RECEIVED_SHUTDOWN; - args.GetReturnValue().Set(yes); -} - - template void SSLWrap::EndParser(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); @@ -1237,6 +1228,17 @@ void SSLWrap::Renegotiate(const FunctionCallbackInfo& args) { } +template +void SSLWrap::Shutdown(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + + Base* w = Unwrap(args.This()); + + int rv = SSL_shutdown(w->ssl_); + args.GetReturnValue().Set(rv); +} + + template void SSLWrap::IsInitFinished(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); @@ -1619,7 +1621,6 @@ void Connection::Initialize(Environment* env, Handle target) { NODE_SET_PROTOTYPE_METHOD(t, "clearPending", Connection::ClearPending); NODE_SET_PROTOTYPE_METHOD(t, "encPending", Connection::EncPending); NODE_SET_PROTOTYPE_METHOD(t, "start", Connection::Start); - NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Connection::Shutdown); NODE_SET_PROTOTYPE_METHOD(t, "close", Connection::Close); SSLWrap::AddMethods(t); @@ -2054,22 +2055,6 @@ void Connection::Start(const FunctionCallbackInfo& args) { } -void Connection::Shutdown(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - - Connection* conn = Unwrap(args.This()); - - if (conn->ssl_ == NULL) { - return args.GetReturnValue().Set(false); - } - - int rv = SSL_shutdown(conn->ssl_); - conn->HandleSSLError("SSL_shutdown", rv, kZeroIsNotAnError, kIgnoreSyscall); - conn->SetShutdownFlags(); - args.GetReturnValue().Set(rv); -} - - void Connection::Close(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); diff --git a/src/node_crypto.h b/src/node_crypto.h index f11f2a00ce..ed86429793 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -181,9 +181,9 @@ class SSLWrap { static void IsInitFinished(const v8::FunctionCallbackInfo& args); static void VerifyError(const v8::FunctionCallbackInfo& args); static void GetCurrentCipher(const v8::FunctionCallbackInfo& args); - static void ReceivedShutdown(const v8::FunctionCallbackInfo& args); static void EndParser(const v8::FunctionCallbackInfo& args); static void Renegotiate(const v8::FunctionCallbackInfo& args); + static void Shutdown(const v8::FunctionCallbackInfo& args); #ifdef OPENSSL_NPN_NEGOTIATED static void GetNegotiatedProto( @@ -254,7 +254,6 @@ class Connection : public SSLWrap, public AsyncWrap { static void EncPending(const v8::FunctionCallbackInfo& args); static void EncOut(const v8::FunctionCallbackInfo& args); static void ClearIn(const v8::FunctionCallbackInfo& args); - static void Shutdown(const v8::FunctionCallbackInfo& args); static void Start(const v8::FunctionCallbackInfo& args); static void Close(const v8::FunctionCallbackInfo& args); diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index b7bce94706..03ccee4b67 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -74,7 +74,8 @@ TLSCallbacks::TLSCallbacks(Environment* env, pending_write_item_(NULL), started_(false), established_(false), - shutdown_(false) { + shutdown_(false), + eof_(false) { node::Wrap(object(), this); // Initialize queue for clearIn writes @@ -374,6 +375,13 @@ void TLSCallbacks::ClearOut() { } } while (read > 0); + int flags = SSL_get_shutdown(ssl_); + if (!eof_ && flags & SSL_RECEIVED_SHUTDOWN) { + eof_ = true; + Local arg = Integer::New(UV_EOF, node_isolate); + wrap()->MakeCallback(env()->onread_string(), 1, &arg); + } + if (read == -1) { int err; Handle arg = GetSSLError(read, &err); @@ -513,6 +521,14 @@ void TLSCallbacks::DoRead(uv_stream_t* handle, if (nread < 0) { // Error should be emitted only after all data was read ClearOut(); + + // Ignore EOF if received close_notify + if (nread == UV_EOF) { + if (eof_) + return; + eof_ = true; + } + HandleScope handle_scope(env()->isolate()); Context::Scope context_scope(env()->context()); Local arg = Integer::New(nread, node_isolate); diff --git a/src/tls_wrap.h b/src/tls_wrap.h index 9e42d4bd37..2b10e09bca 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -66,7 +66,7 @@ class TLSCallbacks : public crypto::SSLWrap, int DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb); protected: - static const int kClearOutChunkSize = 16384; // 16kb + static const int kClearOutChunkSize = 1024; // Maximum number of buffers passed to uv_write() static const int kSimultaneousBufferCount = 10; @@ -137,6 +137,10 @@ class TLSCallbacks : public crypto::SSLWrap, bool established_; bool shutdown_; + // If true - delivered EOF to the js-land, either after `close_notify`, or + // after the `UV_EOF` on socket. + bool eof_; + #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB v8::Persistent sni_context_; #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB diff --git a/test/simple/test-tls-close-notify.js b/test/simple/test-tls-close-notify.js new file mode 100644 index 0000000000..3c6bf53f21 --- /dev/null +++ b/test/simple/test-tls-close-notify.js @@ -0,0 +1,58 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +if (!process.versions.openssl) { + console.error('Skipping because node compiled without OpenSSL.'); + process.exit(0); +} + +var assert = require('assert'); +var fs = require('fs'); +var net = require('net'); +var tls = require('tls'); + +var common = require('../common'); + +var ended = 0; + +var server = tls.createServer({ + key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'), + cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem') +}, function(c) { + // Send close-notify without shutting down TCP socket + if (c.ssl.shutdown() !== 1) + c.ssl.shutdown(); +}).listen(common.PORT, function() { + var c = tls.connect(common.PORT, { + rejectUnauthorized: false + }, function() { + // Ensure that we receive 'end' event anyway + c.on('end', function() { + ended++; + c.destroy(); + server.close(); + }); + }); +}); + +process.on('exit', function() { + assert.equal(ended, 1); +});