diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fe57307..0c6a6df 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: engine: cruby - min_version: 2.6 + min_version: 2.7 build: needs: ruby-versions @@ -26,4 +26,4 @@ jobs: - name: Install dependencies run: bundle install - name: Run test - run: rake test + run: bundle exec rake test diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb index 13e1f1f..28b15d0 100644 --- a/lib/net/smtp.rb +++ b/lib/net/smtp.rb @@ -175,20 +175,48 @@ class SMTPUnsupportedCommand < ProtocolError # # The Net::SMTP class supports the \SMTP extension for SASL Authentication # [RFC4954[https://www.rfc-editor.org/rfc/rfc4954.html]] and the following - # SASL mechanisms: +PLAIN+, +LOGIN+ _(deprecated)_, and +CRAM-MD5+ - # _(deprecated)_. + # SASL mechanisms: +ANONYMOUS+, +EXTERNAL+, +OAUTHBEARER+, +PLAIN+, + # +SCRAM-SHA-1+, +SCRAM-SHA-256+, and +XOAUTH2+. # # To use \SMTP authentication, pass extra arguments to # SMTP.start or SMTP#start. # # # PLAIN # Net::SMTP.start('your.smtp.server', 25, - # user: 'Your Account', secret: 'Your Password', authtype: :plain) + # username: 'Your Account', secret: 'Your Password', authtype: :plain) # - # Support for other SASL mechanisms-such as +EXTERNAL+, +OAUTHBEARER+, - # +SCRAM-SHA-256+, and +XOAUTH2+-will be added in a future release. + # # SCRAM-SHA-256 + # Net::SMTP.start("your.smtp.server", 25, + # user: "authentication identity", secret: password, + # authtype: :scram_sha_256) + # Net::SMTP.start("your.smtp.server", 25, + # auth: {type: :scram_sha_256, + # username: "authentication identity", + # password: password, + # authzid: "authorization identity"}) # optional # - # The +LOGIN+ and +CRAM-MD5+ mechanisms are still available for backwards + # # OAUTHBEARER + # Net::SMTP.start("your.smtp.server", 25, + # auth: {type: :oauthbearer, + # oauth2_token: oauth2_access_token, + # authzid: "authorization identity", # optional + # host: "your.smtp.server", # optional + # port: 25}) # optional + # + # # XOAUTH2 + # Net::SMTP.start("your.smtp.server", 25, + # user: "username", secret: oauth2_access_token, authtype: :xoauth2) + # Net::SMTP.start("your.smtp.server", 25, + # auth: {type: :xoauth2, + # username: "username", + # oauth2_token: oauth2_token}) + # + # # EXTERNAL + # Net::SMTP.start("your.smtp.server", 587, + # starttls: :always, ssl_context_params: ssl_ctx_params, + # authtype: "external") + # + # +DIGEST-MD5+, +LOGIN+, and +CRAM-MD5+ are still available for backwards # compatibility, but are deprecated and should be avoided. # class SMTP < Protocol @@ -459,15 +487,16 @@ def debug_output=(arg) # # :call-seq: - # start(address, port = nil, helo: 'localhost', user: nil, secret: nil, authtype: nil, tls: false, starttls: :auto, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... } - # start(address, port = nil, helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... } + # start(address, port = nil, helo: 'localhost', auth: nil, tls: false, starttls: :auto, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... } + # start(address, port = nil, helo: 'localhost', username: nil, secret: nil, authtype: nil, tls: false, starttls: :auto, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... } + # start(address, port = nil, helo = 'localhost', username = nil, secret = nil, authtype = nil) { |smtp| ... } # # Creates a new Net::SMTP object and connects to the server. # # This method is equivalent to: # # Net::SMTP.new(address, port, tls_verify: flag, tls_hostname: hostname, ssl_context_params: nil) - # .start(helo: helo_domain, user: account, secret: password, authtype: authtype) + # .start(helo: helo_domain, username: account, secret: password, authtype: authtype) # # See also: Net::SMTP.new, #start # @@ -514,13 +543,15 @@ def debug_output=(arg) # # +authtype+ is the SASL authentication mechanism. # - # +user+ is the authentication or authorization identity. + # +username+ or +user+ is the authentication or authorization identity. # # +secret+ or +password+ is your password or other authentication token. # # These will be sent to #authenticate as positional arguments-the exact # semantics are dependent on the +authtype+. # + # +auth+ is an optional hash of keyword arguments for #authenticate. + # # See the discussion of Net::SMTP@SMTP+Authentication in the overview notes. # # === Errors @@ -538,15 +569,18 @@ def debug_output=(arg) # def SMTP.start(address, port = nil, *args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil, + username: nil, + auth: nil, tls: false, starttls: :auto, tls_verify: true, tls_hostname: nil, ssl_context_params: nil, &block) raise ArgumentError, "wrong number of arguments (given #{args.size + 2}, expected 1..6)" if args.size > 4 helo ||= args[0] || 'localhost' - user ||= args[1] + username ||= user || args[1] secret ||= password || args[2] authtype ||= args[3] - new(address, port, tls: tls, starttls: starttls, tls_verify: tls_verify, tls_hostname: tls_hostname, ssl_context_params: ssl_context_params).start(helo: helo, user: user, secret: secret, authtype: authtype, &block) + new(address, port, tls: tls, starttls: starttls, tls_verify: tls_verify, tls_hostname: tls_hostname, ssl_context_params: ssl_context_params) + .start(helo: helo, username: username, secret: secret, authtype: authtype, auth: auth, &block) end # +true+ if the \SMTP session has been started. @@ -556,8 +590,9 @@ def started? # # :call-seq: - # start(helo: 'localhost', user: nil, secret: nil, authtype: nil) { |smtp| ... } - # start(helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... } + # start(helo: 'localhost', username: nil, secret: nil, authtype: nil) { |smtp| ... } + # start(helo = 'localhost', username = nil, secret = nil, authtype = nil) { |smtp| ... } + # start(helo = 'localhost', auth: {type: nil, **auth_kwargs}) { |smtp| ... } # # Opens a TCP connection and starts the SMTP session. # @@ -571,13 +606,15 @@ def started? # # +authtype+ is the SASL authentication mechanism. # - # +user+ is the authentication or authorization identity. + # +username+ or +user+ is the authentication or authorization identity. # # +secret+ or +password+ is your password or other authentication token. # # These will be sent to #authenticate as positional arguments-the exact # semantics are dependent on the +authtype+. # + # +auth+ is an optional hash of keyword arguments for #authenticate. + # # See the discussion of Net::SMTP@SMTP+Authentication in the overview notes. # # See also: Net::SMTP.start @@ -595,7 +632,7 @@ def started? # # require 'net/smtp' # smtp = Net::SMTP.new('smtp.mail.server', 25) - # smtp.start(helo: helo_domain, user: account, secret: password, authtype: authtype) do |smtp| + # smtp.start(helo: helo_domain, username: account, secret: password, authtype: authtype) do |smtp| # smtp.send_message msgstr, 'from@example.com', ['dest@example.com'] # end # @@ -619,12 +656,15 @@ def started? # * Net::ReadTimeout # * IOError # - def start(*args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil) + def start(*args, helo: nil, + user: nil, username: nil, secret: nil, password: nil, + authtype: nil, auth: nil) raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..4)" if args.size > 4 helo ||= args[0] || 'localhost' - user ||= args[1] + username ||= user || args[1] secret ||= password || args[2] authtype ||= args[3] + auth ||= {} if defined?(OpenSSL::VERSION) ssl_context_params = @ssl_context_params || {} unless ssl_context_params.has_key?(:verify_mode) @@ -639,13 +679,13 @@ def start(*args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil end if block_given? begin - do_start helo, user, secret, authtype + do_start helo, username, secret, authtype, **auth return yield(self) ensure - do_finish + quit! end else - do_start helo, user, secret, authtype + do_start helo, username, secret, authtype, **auth return self end end @@ -654,7 +694,7 @@ def start(*args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil # Raises IOError if not started. def finish raise IOError, 'not yet started' unless started? - do_finish + quit! end private @@ -663,10 +703,10 @@ def tcp_socket(address, port) TCPSocket.open address, port end - def do_start(helo_domain, user, secret, authtype) + def do_start(helo_domain, user, secret, authtype, **auth) raise IOError, 'SMTP session already started' if @started - if user || secret || authtype - check_auth_args authtype, user, secret + if user || secret || authtype || auth.any? + check_auth_args(authtype, user, secret, **auth) end s = Timeout.timeout(@open_timeout, Net::OpenTimeout) do tcp_socket(@address, @port) @@ -684,7 +724,11 @@ def do_start(helo_domain, user, secret, authtype) # helo response may be different after STARTTLS do_helo helo_domain end - authenticate user, secret, (authtype || DEFAULT_AUTH_TYPE) if user + if user or secret + authenticate(user, secret, authtype, **auth) + elsif authtype or auth.any? + authenticate(authtype, **auth) + end @started = true ensure unless @started @@ -728,15 +772,46 @@ def do_helo(helo_domain) raise end - def do_finish + public + + # Calls #quit and ensures that #disconnect is called. Returns the result + # from #quit. Returns +nil+ when the client is already disconnected or when + # a prior error prevents the client from calling #quit. Unlike #finish, + # this an exception will not be raised when the client has not started. + # + # When exception: :warn is specified, when #quit raises a + # StandardError, a warning will be printed and the exception is returned, + # not re-raised. When exception: false is specified, a warning + # will not be printed. This is useful when the connection must be dropped, + # for example in a test suite or due to security concerns. + # + # Related: #finish, #quit, #disconnect + def quit!(exception: true) quit if @socket and not @socket.closed? and not @error_occurred + rescue => ex + if exception == :warn + warn "%s during %p #%s: %s" % [ex.class, self, __method__, ex] + elsif exception + raise ex + end + ex ensure + disconnect + end + + # Disconnects the socket without checking if the connection has started yet, + # and without sending a final QUIT message to the server. + # + # Generally, either #finish or #quit! should be used instead. + def disconnect @started = false @error_occurred = false @socket.close if @socket @socket = nil end + private + def requires_smtputf8(address) if address.kind_of? Address !address.address.ascii_only? @@ -862,26 +937,56 @@ def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream DEFAULT_AUTH_TYPE = :plain + # call-seq: + # authenticate(type: DEFAULT_AUTH_TYPE, **, &) + # authenticate(type = DEFAULT_AUTH_TYPE, **, &) + # authenticate(username, secret, type: DEFAULT_AUTH_TYPE, **, &) + # authenticate(username, secret, type = DEFAULT_AUTH_TYPE, **, &) + # # Authenticates with the server, using the "AUTH" command. # - # +authtype+ is the name of a SASL authentication mechanism. + # +type+ is the name of a SASL authentication mechanism. # # All arguments-other than +authtype+-are forwarded to the authenticator. - # Different authenticators may interpret the +user+ and +secret+ + # Different authenticators may interpret the +username+ and +secret+ # arguments differently. - def authenticate(user, secret, authtype = DEFAULT_AUTH_TYPE) - check_auth_args authtype, user, secret + def authenticate(*args, **kwargs, &block) + case args.length + when 1, 3 then authtype = args.pop + when (4..) + raise ArgumentError, "wrong number of arguments " \ + "(given %d, expected 0..3)" % [args.length] + end + auth(authtype, *args, **kwargs, &block) + end + + # call-seq: + # auth(type = DEFAULT_AUTH_TYPE, ...) + # auth(type: DEFAULT_AUTH_TYPE, **kwargs, &block) + # + # All arguments besides +mechanism+ are forwarded directly to the + # authenticator. Alternatively, +mechanism+ can be provided by the +type+ + # keyword parameter. Positional parameters cannot be used with +type+. + # + # Different authenticators take different options, but common options + # include +authcid+ for authentication identity, +authzid+ for authorization + # identity, +username+ for either "authentication identity" or + # "authorization identity" depending on the +mechanism+, and +password+. + def auth(authtype = DEFAULT_AUTH_TYPE, *args, **kwargs, &block) + authtype, args, kwargs = check_auth_args authtype, *args, **kwargs authenticator = Authenticator.auth_class(authtype).new(self) - authenticator.auth(user, secret) + authenticator.auth(*args, **kwargs, &block) end private - def check_auth_args(type, *args, **kwargs) - type ||= DEFAULT_AUTH_TYPE + def check_auth_args(type_arg = nil, *args, type: nil, user: nil, **kwargs) + type ||= type_arg || DEFAULT_AUTH_TYPE + kwargs[:username] ||= user if user klass = Authenticator.auth_class(type) or raise ArgumentError, "wrong authentication type #{type}" klass.check_args(*args, **kwargs) + [type, args, kwargs] end # @@ -992,6 +1097,21 @@ def get_response(reqline) recv_response() end + # Returns a successful Response. + # + # Yields continuation data and replies to the server using the block result. + # + # Raises an exception for any non-successful, non-continuation response. + def send_command_with_continuations(*args) + server_resp = get_response args.join(" ") + while server_resp.continue? + client_resp = yield server_resp.string.strip.split(nil, 2).last + server_resp = get_response client_resp + end + server_resp.success? or raise server_resp.exception_class.new(server_resp) + server_resp + end + private def validate_line(line) diff --git a/lib/net/smtp/auth_cram_md5.rb b/lib/net/smtp/auth_cram_md5.rb deleted file mode 100644 index 0490cd6..0000000 --- a/lib/net/smtp/auth_cram_md5.rb +++ /dev/null @@ -1,48 +0,0 @@ -unless defined? OpenSSL - begin - require 'digest/md5' - rescue LoadError - end -end - -class Net::SMTP - class AuthCramMD5 < Net::SMTP::Authenticator - auth_type :cram_md5 - - def auth(user, secret) - challenge = continue('AUTH CRAM-MD5') - crammed = cram_md5_response(secret, challenge.unpack1('m')) - finish(base64_encode("#{user} #{crammed}")) - end - - IMASK = 0x36 - OMASK = 0x5c - - # CRAM-MD5: [RFC2195] - def cram_md5_response(secret, challenge) - tmp = digest_class::MD5.digest(cram_secret(secret, IMASK) + challenge) - digest_class::MD5.hexdigest(cram_secret(secret, OMASK) + tmp) - end - - CRAM_BUFSIZE = 64 - - def cram_secret(secret, mask) - secret = digest_class::MD5.digest(secret) if secret.size > CRAM_BUFSIZE - buf = secret.ljust(CRAM_BUFSIZE, "\0") - 0.upto(buf.size - 1) do |i| - buf[i] = (buf[i].ord ^ mask).chr - end - buf - end - - def digest_class - @digest_class ||= if defined?(OpenSSL::Digest) - OpenSSL::Digest - elsif defined?(::Digest) - ::Digest - else - raise '"openssl" or "digest" library is required' - end - end - end -end diff --git a/lib/net/smtp/auth_login.rb b/lib/net/smtp/auth_login.rb deleted file mode 100644 index 545c1f9..0000000 --- a/lib/net/smtp/auth_login.rb +++ /dev/null @@ -1,11 +0,0 @@ -class Net::SMTP - class AuthLogin < Net::SMTP::Authenticator - auth_type :login - - def auth(user, secret) - continue('AUTH LOGIN') - continue(base64_encode(user)) - finish(base64_encode(secret)) - end - end -end diff --git a/lib/net/smtp/auth_plain.rb b/lib/net/smtp/auth_plain.rb deleted file mode 100644 index 7fa1198..0000000 --- a/lib/net/smtp/auth_plain.rb +++ /dev/null @@ -1,9 +0,0 @@ -class Net::SMTP - class AuthPlain < Net::SMTP::Authenticator - auth_type :plain - - def auth(user, secret) - finish('AUTH PLAIN ' + base64_encode("\0#{user}\0#{secret}")) - end - end -end diff --git a/lib/net/smtp/auth_sasl_client_adapter.rb b/lib/net/smtp/auth_sasl_client_adapter.rb new file mode 100644 index 0000000..a65b157 --- /dev/null +++ b/lib/net/smtp/auth_sasl_client_adapter.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "net/imap" + +module Net + class SMTP + SASL = Net::IMAP::SASL + + # Experimental + # + # Initialize with a block that runs a command, yielding for continuations. + class SASLClientAdapter < SASL::ClientAdapter + include SASL::ProtocolAdapters::SMTP + + RESPONSE_ERRORS = [ + SMTPAuthenticationError, + SMTPServerBusy, + SMTPSyntaxError, + SMTPFatalError, + ].freeze + + def initialize(...) + super + @command_proc ||= client.method(:send_command_with_continuations) + end + + def authenticate(...) + super + rescue SMTPServerBusy, SMTPSyntaxError, SMTPFatalError => error + raise SMTPAuthenticationError.new(error.response) + rescue SASL::AuthenticationIncomplete => error + raise error.response.exception_class.new(error.response) + end + + def host; client.address end + def response_errors; RESPONSE_ERRORS end + def sasl_ir_capable?; true end + def drop_connection; client.quit!(exception: :warn) end + def drop_connection!; client.disconnect end + end + end +end diff --git a/lib/net/smtp/auth_sasl_compatibility_adapter.rb b/lib/net/smtp/auth_sasl_compatibility_adapter.rb new file mode 100644 index 0000000..e6b5642 --- /dev/null +++ b/lib/net/smtp/auth_sasl_compatibility_adapter.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Net + class SMTP + + # Curries arguments to SASLAdapter.authenticate. + class AuthSASLCompatibilityAdapter + def initialize(mechanism) @mechanism = mechanism end + def check_args(...) SASL.authenticator(@mechanism, ...) end + def new(smtp) @sasl_adapter = SASLClientAdapter.new(smtp); self end + def auth(...) @sasl_adapter.authenticate(@mechanism, ...) end + end + + Authenticator.auth_classes.default_proc = ->hash, mechanism { + hash[mechanism] = AuthSASLCompatibilityAdapter.new(mechanism) + } + + end +end diff --git a/lib/net/smtp/auth_xoauth2.rb b/lib/net/smtp/auth_xoauth2.rb deleted file mode 100644 index ece2e11..0000000 --- a/lib/net/smtp/auth_xoauth2.rb +++ /dev/null @@ -1,17 +0,0 @@ -class Net::SMTP - class AuthXoauth2 < Net::SMTP::Authenticator - auth_type :xoauth2 - - def auth(user, secret) - token = xoauth2_string(user, secret) - - finish("AUTH XOAUTH2 #{base64_encode(token)}") - end - - private - - def xoauth2_string(user, secret) - "user=#{user}\1auth=Bearer #{secret}\1\1" - end - end -end diff --git a/lib/net/smtp/authenticator.rb b/lib/net/smtp/authenticator.rb index 4e91228..6e381af 100644 --- a/lib/net/smtp/authenticator.rb +++ b/lib/net/smtp/authenticator.rb @@ -15,11 +15,14 @@ def self.auth_class(type) Authenticator.auth_classes[type] end - def self.check_args(user_arg = nil, secret_arg = nil, *, **) - unless user_arg + def self.check_args(user_arg = nil, secret_arg = nil, *, + authcid: nil, username: nil, user: nil, + secret: nil, password: nil, + **) + unless authcid || username || user || user_arg raise ArgumentError, 'SMTP-AUTH requested but missing user name' end - unless secret_arg + unless password || secret || secret_arg raise ArgumentError, 'SMTP-AUTH requested but missing secret phrase' end end @@ -52,6 +55,12 @@ def base64_encode(str) # expects "str" may not become too long [str].pack('m0') end + + def req_param(*args, name) + args.compact.first or + raise ArgumentError, "SMTP-AUTH requested but missing #{name}" + end + end end end diff --git a/net-smtp.gemspec b/net-smtp.gemspec index 07d9703..eca091d 100644 --- a/net-smtp.gemspec +++ b/net-smtp.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |spec| spec.description = %q{Simple Mail Transfer Protocol client library for Ruby.} spec.homepage = "https://github.com/ruby/net-smtp" spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.required_ruby_version = ">= 2.6.0" + spec.required_ruby_version = ">= 2.7.3" spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage @@ -26,4 +26,6 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency "net-protocol" + + spec.add_dependency "net-imap", ">= 0.4.2" # experimental SASL support end diff --git a/test/net/smtp/test_smtp.rb b/test/net/smtp/test_smtp.rb index 320e75d..3f9d50c 100644 --- a/test/net/smtp/test_smtp.rb +++ b/test/net/smtp/test_smtp.rb @@ -110,6 +110,21 @@ def test_auth_plain smtp = Net::SMTP.start 'localhost', server.port assert smtp.authenticate("account", "password", :plain).success? assert_equal "AUTH PLAIN AGFjY291bnQAcGFzc3dvcmQ=\r\n", server.commands.last + + server = FakeServer.start(auth: 'plain') + smtp = Net::SMTP.start 'localhost', server.port + assert smtp.auth("PLAIN", "account", "password").success? + assert_equal "AUTH PLAIN AGFjY291bnQAcGFzc3dvcmQ=\r\n", server.commands.last + + server = FakeServer.start(auth: 'plain') + smtp = Net::SMTP.start 'localhost', server.port + assert smtp.auth(type: "PLAIN", username: "account", secret: "password").success? + assert_equal "AUTH PLAIN AGFjY291bnQAcGFzc3dvcmQ=\r\n", server.commands.last + + server = FakeServer.start(auth: 'plain') + smtp = Net::SMTP.start 'localhost', server.port + assert smtp.auth("PLAIN", username: "account", password: "password").success? + assert_equal "AUTH PLAIN AGFjY291bnQAcGFzc3dvcmQ=\r\n", server.commands.last end def test_unsucessful_auth_plain @@ -120,16 +135,31 @@ def test_unsucessful_auth_plain assert_equal "535", err.response.status end + def test_auth_cram_md5 + server = FakeServer.start(auth: 'CRAM-MD5') + smtp = Net::SMTP.start 'localhost', server.port + assert smtp.auth(:cram_md5, "account", password: "password", + warn_deprecation: false).success? + end + def test_auth_login server = FakeServer.start(auth: 'login') smtp = Net::SMTP.start 'localhost', server.port - assert smtp.authenticate("account", "password", :login).success? + assert smtp.auth(:login, "account", "password", + warn_deprecation: false).success? + + server = FakeServer.start(auth: 'login') + smtp = Net::SMTP.start 'localhost', server.port + assert smtp.auth(username: "account", secret: "password", + type: :login, warn_deprecation: false).success? end def test_unsucessful_auth_login server = FakeServer.start(auth: 'login') smtp = Net::SMTP.start 'localhost', server.port - err = assert_raise(Net::SMTPAuthenticationError) { smtp.authenticate("foo", "bar", :login) } + err = assert_raise(Net::SMTPAuthenticationError) { + smtp.auth(:login, "foo", "bar", warn_deprecation: false) + } assert_equal "535 5.7.8 Error: authentication failed: authentication failure\n", err.message assert_equal "535", err.response.status end @@ -142,7 +172,9 @@ def server.auth(*) @sock.puts "235 2.7.0 Authentication successful\r\n" end smtp = Net::SMTP.start 'localhost', server.port - err = assert_raise(Net::SMTPUnknownError) { smtp.authenticate("account", "password", :login) } + err = assert_raise(Net::SMTPUnknownError) { + smtp.auth(:login, "account", "password", warn_deprecation: false) + } assert_equal "235 2.7.0 Authentication successful\n", err.message assert_equal "235", err.response.status end @@ -470,6 +502,15 @@ def test_start_auth_plain port = fake_server_start(auth: 'plain') Net::SMTP.start('localhost', port, user: 'account', password: 'password', authtype: :plain){} + port = fake_server_start(auth: 'plain') + Net::SMTP.start('localhost', port, authtype: "PLAIN", + auth: {username: 'account', password: 'password'}){} + + port = fake_server_start(auth: 'plain') + Net::SMTP.start('localhost', port, auth: {username: 'account', + password: 'password', + type: :plain}){} + port = fake_server_start(auth: 'plain') assert_raise Net::SMTPAuthenticationError do Net::SMTP.start('localhost', port, user: 'account', password: 'invalid', authtype: :plain){} @@ -483,16 +524,19 @@ def test_start_auth_plain def test_start_auth_login port = fake_server_start(auth: 'LOGIN') - Net::SMTP.start('localhost', port, user: 'account', password: 'password', authtype: :login){} + Net::SMTP.start('localhost', port, user: 'account', password: 'password', + authtype: :login, auth: {warn_deprecation: false}){} port = fake_server_start(auth: 'LOGIN') assert_raise Net::SMTPAuthenticationError do - Net::SMTP.start('localhost', port, user: 'account', password: 'invalid', authtype: :login){} + Net::SMTP.start('localhost', port, user: 'account', password: 'invalid', + authtype: :login, auth: {warn_deprecation: false}){} end port = fake_server_start(auth: 'PLAIN') assert_raise Net::SMTPAuthenticationError do - Net::SMTP.start('localhost', port, user: 'account', password: 'password', authtype: :login){} + Net::SMTP.start('localhost', port, user: 'account', password: 'password', + authtype: :login, auth: {warn_deprecation: false}){} end end @@ -500,27 +544,20 @@ def test_start_auth_cram_md5 omit "openssl or digest library not loaded" unless defined? OpenSSL or defined? Digest port = fake_server_start(auth: 'CRAM-MD5') - Net::SMTP.start('localhost', port, user: 'account', password: 'password', authtype: "CRAM-MD5"){} + Net::SMTP.start('localhost', port, user: 'account', password: 'password', + authtype: "CRAM-MD5", auth: {warn_deprecation: false}){} port = fake_server_start(auth: 'CRAM-MD5') assert_raise Net::SMTPAuthenticationError do - Net::SMTP.start('localhost', port, user: 'account', password: 'invalid', authtype: :cram_md5){} + Net::SMTP.start('localhost', port, user: 'account', password: 'invalid', + authtype: :cram_md5, auth: {warn_deprecation: false}){} end port = fake_server_start(auth: 'PLAIN') assert_raise Net::SMTPAuthenticationError do - Net::SMTP.start('localhost', port, user: 'account', password: 'password', authtype: :cram_md5){} - end - - port = fake_server_start(auth: 'CRAM-MD5') - smtp = Net::SMTP.new('localhost', port) - auth_cram_md5 = Net::SMTP::AuthCramMD5.new(smtp) - auth_cram_md5.define_singleton_method(:digest_class) { raise '"openssl" or "digest" library is required' } - Net::SMTP::AuthCramMD5.define_singleton_method(:new) { |_| auth_cram_md5 } - e = assert_raise RuntimeError do - smtp.start(user: 'account', password: 'password', authtype: :cram_md5){} + Net::SMTP.start('localhost', port, user: 'account', password: 'password', + authtype: :cram_md5, auth: {warn_deprecation: false}){} end - assert_equal('"openssl" or "digest" library is required', e.message) end def test_start_instance