From edd46cd7be61d51ebab8bdf4df8a0f47d6df17a7 Mon Sep 17 00:00:00 2001 From: Sharon Rosner Date: Mon, 19 Jan 2026 09:15:41 +0100 Subject: [PATCH] Add `sync_close` kwarg to `SSLSocket.new` (fixes #955) --- ext/openssl/ossl_ssl.c | 26 ++++++++++++++++++++++++-- test/openssl/test_ssl.rb | 16 ++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 630d46e43..fc8dec08c 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -47,7 +47,7 @@ static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, id_i_session_remove_cb, id_i_npn_select_cb, id_i_npn_protocols, id_i_alpn_select_cb, id_i_alpn_protocols, id_i_servername_cb, id_i_verify_hostname, id_i_keylog_cb, id_i_tmp_dh_callback; -static ID id_i_io, id_i_context, id_i_hostname; +static ID id_i_io, id_i_context, id_i_hostname, id_i_sync_close; static int ossl_ssl_ex_ptr_idx; static int ossl_sslctx_ex_ptr_idx; @@ -1616,6 +1616,7 @@ peeraddr_ip_str(VALUE self) * call-seq: * SSLSocket.new(io) => aSSLSocket * SSLSocket.new(io, ctx) => aSSLSocket + * SSLSocket.new(io, ctx, sync_close:) => aSSLSocket * * Creates a new SSL socket from _io_ which must be a real IO object (not an * IO-like object that responds to read/write). @@ -1623,6 +1624,10 @@ peeraddr_ip_str(VALUE self) * If _ctx_ is provided the SSL Sockets initial params will be taken from * the context. * + * The optional _sync_close_ keyword parameter sets the _sync_close_ instance + * variable. Setting this to +true+ will cause the underlying socket to be + * closed when the SSL/TLS connection is shut down. + * * The OpenSSL::Buffering module provides additional IO methods. * * This method will freeze the SSLContext if one is provided; @@ -1631,6 +1636,10 @@ peeraddr_ip_str(VALUE self) static VALUE ossl_ssl_initialize(int argc, VALUE *argv, VALUE self) { + static ID kw_ids[1]; + VALUE kw_args[1]; + VALUE opts; + VALUE io, v_ctx; SSL *ssl; SSL_CTX *ctx; @@ -1639,13 +1648,25 @@ ossl_ssl_initialize(int argc, VALUE *argv, VALUE self) if (ssl) ossl_raise(eSSLError, "SSL already initialized"); - if (rb_scan_args(argc, argv, "11", &io, &v_ctx) == 1) + if (rb_scan_args(argc, argv, "11:", &io, &v_ctx, &opts) == 1) v_ctx = rb_funcall(cSSLContext, rb_intern("new"), 0); GetSSLCTX(v_ctx, ctx); rb_ivar_set(self, id_i_context, v_ctx); ossl_sslctx_setup(v_ctx); + if (!NIL_P(opts)) { + if (!kw_ids[0]) { + CONST_ID(kw_ids[0], "sync_close"); + } + + rb_get_kwargs(opts, kw_ids, 0, 1, kw_args); + if (kw_args[0] != Qundef) { + rb_ivar_set(self, id_i_sync_close, kw_args[0]); + } + } + + if (rb_respond_to(io, rb_intern("nonblock="))) rb_funcall(io, rb_intern("nonblock="), 1, Qtrue); Check_Type(io, T_FILE); @@ -3300,5 +3321,6 @@ Init_ossl_ssl(void) DefIVarID(io); DefIVarID(context); DefIVarID(hostname); + DefIVarID(sync_close); #endif /* !defined(OPENSSL_NO_SOCK) */ } diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 5d20ccd1f..627280658 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -355,6 +355,22 @@ def test_sync_close end end + def test_sync_close_initialize_opt + start_server do |port| + begin + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock, sync_close: true) + assert_equal true, ssl.sync_close + ssl.connect + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + ssl.close + assert_predicate sock, :closed? + ensure + sock&.close + end + end + end + def test_copy_stream start_server do |port| server_connect(port) do |ssl|