diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 54753b5a..3ee77208 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -404,6 +404,12 @@ module Net # - #connection_state: Returns the connection state. # - #disconnected?: True if the connection has been closed. # - #tls_verified?: Returns whether TLS is used and #host has been verified. + # - #tls_connected?: Returns +true+ after TLS negotiation has completed. + # + # *NOTE:* This does _NOT_ indicate a secure TLS connection. + # - #tls_socket?: Returns +true+ after TLS negotiation has started. + # + # *NOTE:* This does _NOT_ indicate a secure TLS connection. # - #ssl_ctx: Returns the {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html] # after attempting to start TLS. # @@ -1035,8 +1041,10 @@ def max_response_size=(val) config.max_response_size = val end # Returns the # {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html] - # used by the SSLSocket when TLS is attempted, even when the TLS handshake - # is unsuccessful. The context object will be frozen. + # used by the + # {OpenSSL::SSL::SSLSocket}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLSocket.html]. + # when TLS is attempted, even when the TLS handshake is unsuccessful. The + # context object will be frozen. # # Returns +nil+ for a plaintext connection. # @@ -1299,12 +1307,11 @@ def inspect end private def inspect_tls_state - if tls_verified? - "TLS" - elsif ssl_ctx && @sock.kind_of?(OpenSSL::SSL::SSLSocket) - "TLS (#{@tls_connected ? "NOT VERIFIED" : "NOT ESTABLISHED"})" - else - "PLAINTEXT#{" (TLS NOT STARTED)" if ssl_ctx}" + if tls_verified? then "TLS" + elsif tls_connected? then "TLS (NOT VERIFIED)" + elsif tls_socket? then "TLS (NOT ESTABLISHED)" + elsif ssl_ctx then "PLAINTEXT (TLS NOT STARTED)" + else "PLAINTEXT" end end @@ -1313,6 +1320,30 @@ def inspect # but peer verification was disabled. def tls_verified?; @tls_verified end + # Returns +true+ after + # {OpenSSL::SSL::SSLSocket#connect}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLSocket.html#method-i-connect] + # completes successfully. + # + # *NOTE:* This does _NOT_ indicate that the remote hostname has been + # verified. + # + # This does _not_ indicate current connection state. It will continue to + # return +true+ even after a successful connection has disconnected. + # + # See #tls_verified? + def tls_connected?; @tls_connected end + + # Returns +true+ when the connection is a + # {OpenSSL::SSL::SSLSocket}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLSocket.html] + # + # *NOTE:* This does _NOT_ indicate that a TLS session has been + # established or that remote hostname has been verified. + # + # This only indicates that TLS negotiation has started. + # + # See #tls_verified? + def tls_socket?; @sock.kind_of?(OpenSSL::SSL::SSLSocket) end + # Disconnects from the server. # # Waits for receiver thread to close before returning, except when called diff --git a/test/net/imap/test_imap_tls.rb b/test/net/imap/test_imap_tls.rb index b5873299..12ad1476 100644 --- a/test/net/imap/test_imap_tls.rb +++ b/test/net/imap/test_imap_tls.rb @@ -35,6 +35,8 @@ def assert_tls_incomplete(imap) = assert_tls_stage imap, :incomplete def assert_tls_unverified(imap) = assert_tls_stage imap, :unverified def assert_tls_verified(imap) = assert_tls_stage imap, :verified + STAGES = %i[unstarted incomplete unverified verified].freeze + INSPECT_INCLUDES = { unstarted: " PLAINTEXT (TLS NOT STARTED) disconnected", incomplete: " TLS (NOT ESTABLISHED) disconnected", @@ -43,12 +45,16 @@ def assert_tls_verified(imap) = assert_tls_stage imap, :verified } def assert_tls_stage(imap, stage) - verified = stage == :verified + socket, connected, verified = Array.new(3) { _1 < STAGES.index(stage) } assert imap.ssl_ctx_params.frozen?, "#ssl_ctx_params should be frozen" assert_kind_of Hash, imap.ssl_ctx_params, "#ssl_ctx_params should be a Hash" assert_kind_of OpenSSL::SSL::SSLContext, imap.ssl_ctx, "#ssl_ctx should be an OpenSSL::SSL::SSLContext" + assert_equal socket, imap.tls_socket?, + "#tls_socket? should be #{socket}" + assert_equal connected, imap.tls_connected?, + "#tls_connected? should be #{connected}" assert_equal verified, imap.tls_verified?, "#tls_verified? should be #{verified}" assert_include imap.inspect, INSPECT_INCLUDES.fetch(stage)