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