diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 6e7a5829..cfcee6e6 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -816,8 +816,21 @@ def idle_response_timeout; config.idle_response_timeout end # Returns +false+ for a plaintext connection. attr_reader :ssl_ctx_params - # Creates a new Net::IMAP object and connects it to the specified - # +host+. + # Creates a new Net::IMAP object and connects it to the specified +host+. + # + # ==== Default port and SSL + # + # When both both +port+ and +ssl+ are unspecified or +nil+, + # +ssl+ is determined by {config.default_ssl}[rdoc-ref:Config#default_ssl] + # and +port+ is based on that implicit value for +ssl+. + # + # When only one of the two is specified: + # * When +ssl+ is truthy, +port+ defaults to +993+. + # * When +ssl+ is +false+, +port+ defaults to +143+. + # * When +port+ is +993+, +ssl+ defaults to +true+. + # * When +port+ is +143+, +ssl+ defaults to +false+. + # * When +port+ is nonstandard, the default for +ssl+ is determined + # by {config.default_ssl}[rdoc-ref:Config#default_ssl]. # # ==== Options # @@ -845,7 +858,9 @@ def idle_response_timeout; config.idle_response_timeout end # SSL session verification mode. Valid modes include # +OpenSSL::SSL::VERIFY_PEER+ and +OpenSSL::SSL::VERIFY_NONE+. # - # See {OpenSSL::SSL::SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html] for other valid SSL context params. + # See + # {OpenSSL::SSL::SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html] + # for other valid SSL context params. # # See DeprecatedClientOptions.new for deprecated SSL arguments. # @@ -926,7 +941,7 @@ def initialize(host, port: nil, ssl: nil, # Config options @host = host @config = Config.new(config, **config_options) - @port = port || (ssl ? SSL_PORT : PORT) + ssl, @port = default_ssl_and_port(ssl, port) @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl) # Basic Client State @@ -3100,6 +3115,76 @@ def remove_response_handler(handler) PORT = 143 # :nodoc: SSL_PORT = 993 # :nodoc: + def default_ssl_and_port(tls, port) + case [tls && true, classify_port(port)] + in true, nil then return tls, SSL_PORT + in false, nil then return tls, PORT + in nil, :tls then return true, port + in nil, :plain then return false, port + in nil, nil then return use_default_ssl + in true, :tls | :other then return tls, port + in false, :plain | :other then return tls, port + in true, :plain then return warn_mismatched_port tls, port + in false, :tls then return warn_mismatched_port tls, port + in nil, :other then return warn_nonstandard_port port + end + # TODO: move this wherever is appropriate + tls &&= tls.respond_to?(:to_hash) ? tls.to_hash : {} + end + + # classify_port(port) -> :tls | :plain | :other | nil + def classify_port(port) + case port + in (SSL_PORT | /\Aimaps\z/i) then :tls + in (PORT | /\Aimap\z/i) then :plain + in (Integer | String) then :other + in nil then nil + end + end + + def warn_mismatched_port(tls, port) + if tls + warn "Using TLS on plaintext IMAP port" + else + warn "Using plaintext on TLS IMAP port" + end + [tls, port] + end + + def warn_nonstandard_port(port) + tls = !!config.default_ssl + if config.warn_nonstandard_port_without_ssl + warn "Using #{tls ? "TLS" : "plaintext"} on port #{port}. " \ + "Set ssl explicitly for non-standard IMAP ports." + end + # TODO: print default_ssl warning + [tls, port] + end + + TLS_DEFAULT_WARNING = + "Net::IMAP.config.default_ssl will default to true in the future. " \ + "To silence this warning, " \ + "set Net::IMAP.config.default_ssl = (true | false)' or " \ + "use 'Net::IMAP.new(host, ssl: (true | false))'." + private_constant :TLS_DEFAULT_WARNING + + def use_default_ssl + case config.default_ssl + when true then [true, SSL_PORT] + when false then [false, PORT] + when :warn + warn TLS_DEFAULT_WARNING unless port + port ||= SSL_PORT + warn "Using TLS on port #{port}." + [true, port] + when nil + warn TLS_DEFAULT_WARNING unless port + port ||= PORT + warn "Using plain-text on port #{port}." + [false, port] + end + end + def start_imap_connection @greeting = get_server_greeting @capabilities = capabilities_from_resp_code @greeting diff --git a/lib/net/imap/config.rb b/lib/net/imap/config.rb index 7532737c..9a1a0949 100644 --- a/lib/net/imap/config.rb +++ b/lib/net/imap/config.rb @@ -207,6 +207,51 @@ def self.[](config) # The default value is +5+ seconds. attr_accessor :idle_response_timeout, type: Integer + # The default value for the +ssl+ option of Net::IMAP.new, when +port+ is + # unspecified or non-standard and +ssl+ is unspecified. default_ssl is + # ignored when Net::IMAP.new is called with any value for +ssl+ besides + # +nil+, + # + # *Note*: A future release of Net::IMAP will set the default to +true+, as + # per RFC7525[https://tools.ietf.org/html/rfc7525], + # RFC7817[https://tools.ietf.org/html/rfc7817], and + # RFC8314[https://tools.ietf.org/html/rfc8314]. + # + # (The default_ssl config attribute was added in +v0.5.?+.) + # + # ==== Valid options + # + # [+false+ (original behavior)] + # Plaintext by default, with no warnings. + # [+nil+ (planned default for +v0.6+)] + # Plaintext by default, and prints a warning. + # + # This option will be removed in +v0.7+, when +:warn+ becomes the + # default. + # [+:warn+ (planned default for +v0.7+)] + # A warning will be printed, and behave as if the default were +true+. + # [+true+ (planned future default)] + # Use TLS by default, with the default SSL context params set by calling + # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params] + # with no params. + attr_accessor :default_ssl, type: [false, nil, :warn, true] + + # Whether to warn for using default_ssl when the port is non-standard. + # + # Although default_ssl is used for non-standard ports, this warning is + # different replaces the warning when default_ssl is +nil+ or +:warn+. + # When this option is false but default_ssl is +nil+ or +:warn+, that + # warning will be printed instead. + # + # ==== Valid options + # + # [+false+ (original behavior)] + # Don't print a special warning for nonstandard ports without explicit + # +ssl+. + # [+true+ (eventual future default)] + # Print a special warning for nonstandard ports without explicit +ssl+. + attr_accessor :warn_nonstandard_port_without_ssl, type: :boolean + # Whether to use the +SASL-IR+ extension when the server and \SASL # mechanism both support it. Can be overridden by the +sasl_ir+ keyword # parameter to Net::IMAP#authenticate. @@ -362,6 +407,8 @@ def defaults_hash debug: false, open_timeout: 30, idle_response_timeout: 5, + default_ssl: false, + warn_nonstandard_port_without_ssl: false, sasl_ir: true, enforce_logindisabled: true, responses_without_block: :warn, @@ -390,9 +437,18 @@ def defaults_hash version_defaults[0.6] = Config[0.5].dup.update( responses_without_block: :frozen_dup, + default_ssl: nil, ).freeze version_defaults[:next] = Config[0.6] - version_defaults[:future] = Config[:next] + + version_defaults[0.7] = Config[0.6].dup.update( + default_ssl: :warn, + ).freeze + + version_defaults[:future] = Config[0.7].dup.update( + default_ssl: true, + warn_nonstandard_port_without_ssl: true, + ).freeze version_defaults.freeze end