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)