@@ -179,13 +179,29 @@ class SMTPUnsupportedCommand < ProtocolError
179179 # # PLAIN
180180 # Net::SMTP.start('your.smtp.server', 25,
181181 # user: 'Your Account', secret: 'Your Password', authtype: :plain)
182+ # Net::SMTP.start("your.smtp.server", 25,
183+ # auth: {type: :plain,
184+ # username: "authentication identity",
185+ # password: password})
186+ #
182187 # # LOGIN
183188 # Net::SMTP.start('your.smtp.server', 25,
184189 # user: 'Your Account', secret: 'Your Password', authtype: :login)
190+ # Net::SMTP.start("your.smtp.server", 25,
191+ # auth: {type: :login,
192+ # username: "authentication identity",
193+ # password: password})
185194 #
186195 # # CRAM MD5
187196 # Net::SMTP.start('your.smtp.server', 25,
188197 # user: 'Your Account', secret: 'Your Password', authtype: :cram_md5)
198+ # Net::SMTP.start("your.smtp.server", 25,
199+ # auth: {type: :cram_md5,
200+ # username: 'Your Account',
201+ # password: 'Your Password'})
202+ #
203+ # +LOGIN+, and +CRAM-MD5+ are still available for backwards compatibility, but
204+ # are deprecated and should be avoided.
189205 #
190206 class SMTP < Protocol
191207 VERSION = "0.4.0"
@@ -452,6 +468,7 @@ def debug_output=(arg)
452468
453469 #
454470 # :call-seq:
471+ # start(address, port = nil, helo: 'localhost', auth: nil, tls: false, starttls: :auto, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... }
455472 # 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| ... }
456473 # start(address, port = nil, helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... }
457474 #
@@ -517,16 +534,17 @@ def debug_output=(arg)
517534 # * IOError
518535 #
519536 def SMTP . start ( address , port = nil , *args , helo : nil ,
520- user : nil , secret : nil , password : nil , authtype : nil ,
537+ user : nil , username : nil , secret : nil , password : nil ,
538+ authtype : nil , auth : nil ,
521539 tls : false , starttls : :auto ,
522540 tls_verify : true , tls_hostname : nil , ssl_context_params : nil ,
523541 &block )
524542 raise ArgumentError , "wrong number of arguments (given #{ args . size + 2 } , expected 1..6)" if args . size > 4
525543 helo ||= args [ 0 ] || 'localhost'
526- user ||= args [ 1 ]
544+ user ||= username || args [ 1 ]
527545 secret ||= password || args [ 2 ]
528546 authtype ||= args [ 3 ]
529- 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 )
547+ 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 , auth : auth , &block )
530548 end
531549
532550 # +true+ if the SMTP session has been started.
@@ -538,6 +556,7 @@ def started?
538556 # :call-seq:
539557 # start(helo: 'localhost', user: nil, secret: nil, authtype: nil) { |smtp| ... }
540558 # start(helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... }
559+ # start(helo = 'localhost', auth: {type: nil, **auth_kwargs}) { |smtp| ... }
541560 #
542561 # Opens a TCP connection and starts the SMTP session.
543562 #
@@ -546,11 +565,10 @@ def started?
546565 # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see
547566 # the discussion in the overview notes.
548567 #
549- # If both of +user+ and +secret+ are given, SMTP authentication
550- # will be attempted using the AUTH command. +authtype+ specifies
551- # the type of authentication to attempt; it must be one of
552- # :login, :plain, and :cram_md5. See the notes on SMTP Authentication
553- # in the overview.
568+ # If either +auth+ or +user+ are given, SMTP authentication will be
569+ # attempted using the AUTH command. +authtype+ specifies the type of
570+ # authentication to attempt; it must be one of :login, :plain, and
571+ # :cram_md5. See the notes on SMTP Authentication in the overview.
554572 #
555573 # === Block Usage
556574 #
@@ -589,12 +607,15 @@ def started?
589607 # * Net::ReadTimeout
590608 # * IOError
591609 #
592- def start ( *args , helo : nil , user : nil , secret : nil , password : nil , authtype : nil )
610+ def start ( *args , helo : nil ,
611+ user : nil , username : nil , secret : nil , password : nil ,
612+ authtype : nil , auth : nil )
593613 raise ArgumentError , "wrong number of arguments (given #{ args . size } , expected 0..4)" if args . size > 4
594614 helo ||= args [ 0 ] || 'localhost'
595- user ||= args [ 1 ]
596- secret ||= password || args [ 2 ]
597- authtype ||= args [ 3 ]
615+ auth = merge_auth_params ( user || username || args [ 1 ] ,
616+ secret || password || args [ 2 ] ,
617+ authtype || args [ 3 ] ,
618+ auth )
598619 if defined? ( OpenSSL ::VERSION )
599620 ssl_context_params = @ssl_context_params || { }
600621 unless ssl_context_params . has_key? ( :verify_mode )
@@ -609,13 +630,13 @@ def start(*args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil
609630 end
610631 if block_given?
611632 begin
612- do_start helo , user , secret , authtype
633+ do_start helo , ** auth
613634 return yield ( self )
614635 ensure
615636 do_finish
616637 end
617638 else
618- do_start helo , user , secret , authtype
639+ do_start helo , ** auth
619640 return self
620641 end
621642 end
@@ -633,12 +654,8 @@ def tcp_socket(address, port)
633654 TCPSocket . open address , port
634655 end
635656
636- def do_start ( helo_domain , user , secret , authtype )
657+ def do_start ( helo_domain , ** authopts )
637658 raise IOError , 'SMTP session already started' if @started
638- if user or secret
639- check_auth_method ( authtype || DEFAULT_AUTH_TYPE )
640- check_auth_args user , secret
641- end
642659 s = Timeout . timeout ( @open_timeout , Net ::OpenTimeout ) do
643660 tcp_socket ( @address , @port )
644661 end
@@ -655,7 +672,7 @@ def do_start(helo_domain, user, secret, authtype)
655672 # helo response may be different after STARTTLS
656673 do_helo helo_domain
657674 end
658- authenticate user , secret , ( authtype || DEFAULT_AUTH_TYPE ) if user
675+ auth ( ** authopts ) if authopts . any?
659676 @started = true
660677 ensure
661678 unless @started
@@ -833,13 +850,39 @@ def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream
833850
834851 DEFAULT_AUTH_TYPE = :plain
835852
853+ # Deprecated: use #auth instead.
836854 def authenticate ( user , secret , authtype = DEFAULT_AUTH_TYPE )
855+ # warn "DEPRECATED: use Net::SMTP#auth instead"
837856 check_auth_method authtype
838857 check_auth_args user , secret
839858 authenticator = Authenticator . auth_class ( authtype ) . new ( self )
840859 critical { authenticator . auth ( user , secret ) }
841860 end
842861
862+ # call-seq:
863+ # auth(mechanism, ...)
864+ # auth(type: mechanism, **kwargs, &block)
865+ #
866+ # All arguments besides +mechanism+ are forwarded directly to the
867+ # authenticator. Alternatively, +mechanism+ can be provided by the +type+
868+ # keyword parameter. Positional parameters cannot be used with +type+.
869+ #
870+ # Different authenticators take different options, but common options
871+ # include +authcid+ for authentication identity, +authzid+ for authorization
872+ # identity, +username+ for either "authentication identity" or
873+ # "authorization identity" depending on the +mechanism+, and +password+.
874+ def auth ( *args , **kwargs , &blk )
875+ args , kwargs = backward_compatible_auth_args ( *args , **kwargs )
876+ authtype , *args = args
877+ authenticator = Authenticator . auth_class ( authtype ) . new ( self )
878+ if kwargs . empty?
879+ # TODO: remove this, unless it is needed for 2.6/2.7/3.0 compatibility
880+ critical { authenticator . auth ( *args , &blk ) }
881+ else
882+ critical { authenticator . auth ( *args , **kwargs , &blk ) }
883+ end
884+ end
885+
843886 private
844887
845888 def check_auth_method ( type )
@@ -857,6 +900,45 @@ def check_auth_args(user, secret, authtype = DEFAULT_AUTH_TYPE)
857900 end
858901 end
859902
903+ # Convert the original +user+, +secret+, +authtype+ with +auth+, and checks
904+ # the arguments.
905+ def merge_auth_params ( user , secret , authtype , auth )
906+ auth = Hash . try_convert ( auth ) || { }
907+ if user || secret || authtype
908+ args = { type : authtype || DEFAULT_AUTH_TYPE ,
909+ username : user , secret : secret }
910+ auth = args . merge ( auth )
911+ check_auth_method ( auth [ :type ] )
912+ check_auth_args ( auth [ :authcid ] || auth [ :username ] ,
913+ auth [ :password ] || auth [ :secret ] ,
914+ auth [ :type ] )
915+ elsif auth . any?
916+ check_auth_method ( auth [ :type ] || DEFAULT_AUTH_TYPE )
917+ # check_auth_args may not be valid, depending on the authtype.
918+ end
919+ auth
920+ end
921+
922+ # Convert +type+, +username+, +secret+ (etc) kwargs to positional args, for
923+ # compatibility with existing authenticators.
924+ def backward_compatible_auth_args ( _type = nil , *args , type : nil ,
925+ username : nil , authcid : nil ,
926+ secret : nil , password : nil ,
927+ **kwargs )
928+ type && _type and
929+ raise ArgumentError , 'conflict between "type" keyword argument ' \
930+ 'and positional argument'
931+ type ||= _type || DEFAULT_AUTH_TYPE
932+ check_auth_method ( type )
933+ auth_class = Authenticator . auth_class ( type )
934+ if auth_class . is_a? ( Class ) && auth_class <= Authenticator
935+ args [ 0 ] ||= authcid || username
936+ args [ 1 ] ||= password || secret
937+ check_auth_args ( args [ 0 ] , args [ 1 ] , type )
938+ end
939+ [ [ type , *args ] , kwargs ]
940+ end
941+
860942 #
861943 # SMTP command dispatcher
862944 #
0 commit comments