diff --git a/lib/net/imap.rb b/lib/net/imap.rb
index 50ab01d2..029ec7f3 100644
--- a/lib/net/imap.rb
+++ b/lib/net/imap.rb
@@ -704,6 +704,19 @@ class << self
alias default_imap_port default_port
alias default_imaps_port default_tls_port
alias default_ssl_port default_tls_port
+
+ # The default value for the +tls+ option of ::new, when +port+ is
+ # unspecified or non-standard.
+ #
+ # *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].
+ #
+ # Set to +true+ for the secure default without warnings. Set to
+ # +false+ to globally silence warnings and use insecure defaults.
+ attr_accessor :default_tls
+ alias default_ssl default_tls
end
# Returns the initial greeting the server, an UntaggedResponse.
@@ -746,16 +759,28 @@ class << self
# Accepts the following options:
#
# [port]
- # Port number. Defaults to 993 when +ssl+ is truthy, and 143 otherwise.
+ # Port number. Defaults to 143 when +tls+ is false, 993 when +tls+ is
+ # truthy. Based on ::default_tls when both +port+ and +tls+ are nil.
#
- # [ssl]
+ # [tls]
# If +true+, the connection will use TLS with the default params set by
# {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params].
- # If +ssl+ is a hash, it's passed to
+ # If +tls+ is a hash, it's passed to
# {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
# the keys are names of attribute assignment methods on
# SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
#
+ # When port: 993, +tls+ defaults to +true+.
+ # When port: 143, +tls+ defaults to +false+.
+ # When port is unspecified or non-standard, +tls+ defaults to
+ # ::default_tls. When ::default_tls is also +nil+, a warning is printed
+ # and the connection does _not_ use TLS.
+ #
+ # When +nil+ or unassigned a default value is assigned: the default is
+ # +true+ if port: 993, +false+ if port: 143, and
+ # ::default_tls when +port+ is unspecified or non-standard. When
+ # ::default_tls is +nil+, a back
+ #
# [open_timeout]
# Seconds to wait until a connection is opened
# [idle_response_timeout]
@@ -816,15 +841,15 @@ class << self
# [Net::IMAP::ByeResponseError]
# Connected to the host successfully, but it immediately said goodbye.
#
- def initialize(host, port: nil, ssl: nil,
+ def initialize(host, port: nil, tls: nil,
open_timeout: 30, idle_response_timeout: 5)
super()
# Config options
@host = host
- @port = port || (ssl ? SSL_PORT : PORT)
+ tls, @port = default_tls_and_port(tls, port)
@open_timeout = Integer(open_timeout)
@idle_response_timeout = Integer(idle_response_timeout)
- @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl)
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(tls)
# Basic Client State
@utf8_strings = false
@@ -939,7 +964,7 @@ def capabilities
# servers will drop all AUTH= mechanisms from #capabilities after
# the connection has authenticated.
#
- # imap = Net::IMAP.new(hostname, ssl: false)
+ # imap = Net::IMAP.new(hostname, tls: false)
# imap.capabilities # => ["IMAP4REV1", "LOGINDISABLED"]
# imap.auth_mechanisms # => []
#
@@ -2394,6 +2419,27 @@ def remove_response_handler(handler)
@@debug = false
+ def default_tls_and_port(tls, port)
+ if tls.nil? && port
+ tls = true if port == SSL_PORT || /\Aimaps\z/i === port
+ tls = false if port == PORT
+ elsif port.nil? && !tls.nil?
+ port = tls ? SSL_PORT : PORT
+ end
+ if tls.nil? && port.nil?
+ tls = self.class.default_tls.dup.freeze
+ port = tls ? SSL_PORT : PORT
+ if tls.nil?
+ warn "A future version of Net::IMAP.default_tls " \
+ "will default to 'true', for secure connections by default. " \
+ "Use 'Net::IMAP.new(host, tls: false)' or set " \
+ "Net::IMAP.default_tls = false' to silence this warning."
+ end
+ end
+ tls &&= tls.respond_to?(:to_hash) ? tls.to_hash : {}
+ [tls, port]
+ end
+
def start_imap_connection
@greeting = get_server_greeting
@capabilities = capabilities_from_resp_code @greeting
diff --git a/lib/net/imap/deprecated_client_options.rb b/lib/net/imap/deprecated_client_options.rb
index d747d68b..341df4ef 100644
--- a/lib/net/imap/deprecated_client_options.rb
+++ b/lib/net/imap/deprecated_client_options.rb
@@ -5,9 +5,12 @@ class IMAP < Protocol
# This module handles deprecated arguments to various Net::IMAP methods.
module DeprecatedClientOptions
+ UNDEF = Module.new.freeze
+ private_constant :UNDEF
# :call-seq:
# Net::IMAP.new(host, **options) # standard keyword options
+ # Net::IMAP.new(host, ssl: nil, **options) # ssl => tls
# Net::IMAP.new(host, options) # obsolete hash options
# Net::IMAP.new(host, port) # obsolete port argument
# Net::IMAP.new(host, port, usessl, certs = nil, verify = true) # deprecated SSL arguments
@@ -19,6 +22,13 @@ module DeprecatedClientOptions
# Using obsolete arguments does not a warning. Obsolete arguments will be
# deprecated by a future release.
#
+ # If +ssl+ is given, it is silently converted to the +tls+ keyword
+ # argument. Combining both +ssl+ and +tls+ raises an ArgumentError. Both
+ # of the following behave identically:
+ #
+ # Net::IMAP.new("imap.example.com", port: 993, ssl: {ca_path: "path/to/certs"})
+ # Net::IMAP.new("imap.example.com", port: 993, tls: {ca_path: "path/to/certs"})
+ #
# If a second positional argument is given and it is a hash (or is
# convertable via +#to_hash+), it is converted to keyword arguments.
#
@@ -71,6 +81,7 @@ module DeprecatedClientOptions
#
def initialize(host, port_or_options = nil, *deprecated, **options)
if port_or_options.nil? && deprecated.empty?
+ translate_ssl_to_tls(options)
super host, **options
elsif options.any?
# Net::IMAP.new(host, *__invalid__, **options)
@@ -79,15 +90,17 @@ def initialize(host, port_or_options = nil, *deprecated, **options)
# Net::IMAP.new(host, options, *__invalid__)
raise ArgumentError, "Do not use deprecated SSL params with options hash"
elsif port_or_options.respond_to?(:to_hash)
- super host, **Hash.try_convert(port_or_options)
+ options = Hash.try_convert(port_or_options)
+ translate_ssl_to_tls(options)
+ super host, **options
elsif deprecated.empty?
super host, port: port_or_options
elsif deprecated.shift
warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1
- super host, port: port_or_options, ssl: create_ssl_params(*deprecated)
+ super host, port: port_or_options, tls: create_ssl_params(*deprecated)
else
warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1
- super host, port: port_or_options, ssl: false
+ super host, port: port_or_options, tls: false
end
end
@@ -120,7 +133,17 @@ def starttls(*deprecated, **options)
private
+ def translate_ssl_to_tls(options)
+ return unless options.key?(:ssl)
+ if options.key?(:tls)
+ raise ArgumentError, "conflicting :ssl and :tls keyword arguments"
+ end
+ options.merge!(tls: options.delete(:ssl))
+ end
+
def create_ssl_params(certs = nil, verify = true)
+ certs = nil if certs == UNDEF
+ verify = true if verify == UNDEF
params = {}
if certs
if File.file?(certs)