@@ -171,8 +171,11 @@ class SMTPUnsupportedCommand < ProtocolError
171171 #
172172 # === SMTP Authentication
173173 #
174- # The Net::SMTP class supports three authentication schemes;
175- # PLAIN, LOGIN and CRAM MD5. (SMTP Authentication: [RFC2554])
174+ # The Net::SMTP class supports several authentication schemes;
175+ # ({SMTP Authentication: [RFC4956]}[https://www.rfc-editor.org/rfc/rfc4954.html])
176+ # +ANONYMOUS+, +EXTERNAL+, +OAUTHBEARER+, +PLAIN+, +SCRAM-SHA-1+,
177+ # +SCRAM-SHA-256+, and +XOAUTH2+.
178+ #
176179 # To use SMTP authentication, pass extra arguments to
177180 # SMTP.start or SMTP#start.
178181 #
@@ -182,26 +185,43 @@ class SMTPUnsupportedCommand < ProtocolError
182185 # Net::SMTP.start("your.smtp.server", 25,
183186 # auth: {type: :plain,
184187 # username: "authentication identity",
185- # password: password})
188+ # password: password,
189+ # authzid: "authorization identity"}) # optional
186190 #
187- # # LOGIN
188- # Net::SMTP.start('your.smtp.server', 25,
189- # user: 'Your Account', secret: 'Your Password', authtype: :login)
191+ # # SCRAM-SHA-256
190192 # Net::SMTP.start("your.smtp.server", 25,
191- # auth: {type: :login,
193+ # user: "authentication identity", secret: password,
194+ # authtype: :scram_sha_256)
195+ # Net::SMTP.start("your.smtp.server", 25,
196+ # auth: {type: :scram_sha_256,
192197 # username: "authentication identity",
193- # password: password})
198+ # password: password,
199+ # authzid: "authorization identity"}) # optional
194200 #
195- # # CRAM MD5
196- # Net::SMTP.start('your.smtp.server', 25,
197- # user: 'Your Account', secret: 'Your Password', authtype: :cram_md5)
201+ # # OAUTHBEARER
202+ # Net::SMTP.start("your.smtp.server", 25,
203+ # auth: {type: :oauthbearer,
204+ # oauth2_token: oauth2_access_token,
205+ # authzid: "authorization identity", # optional
206+ # host: "your.smtp.server", # optional
207+ # port: 25}) # optional
208+ #
209+ # # XOAUTH2
210+ # Net::SMTP.start("your.smtp.server", 25,
211+ # user: "username", secret: oauth2_access_token, authtype: :xoauth2)
198212 # Net::SMTP.start("your.smtp.server", 25,
199- # auth: {type: :cram_md5 ,
200- # username: 'Your Account' ,
201- # password: 'Your Password' })
213+ # auth: {type: :xoauth2 ,
214+ # username: "username" ,
215+ # oauth2_token: oauth2_token })
202216 #
203- # +LOGIN+, and +CRAM-MD5+ are still available for backwards compatibility, but
204- # are deprecated and should be avoided.
217+ # # EXTERNAL
218+ # Net::SMTP.start("your.smtp.server", 587,
219+ # starttls: :always, ssl_context_params: ssl_ctx_params,
220+ # authtype: "external")
221+ #
222+ # +DIGEST-MD5+, +LOGIN+, and +CRAM-MD5+ are still available for backwards
223+ # compatibility, but are deprecated and should be avoided. <em>Using a
224+ # deprecated authentication mechanisms will print a warning.</em>
205225 #
206226 class SMTP < Protocol
207227 VERSION = "0.4.0"
@@ -501,12 +521,6 @@ def debug_output=(arg)
501521 # +helo+ is the _HELO_ _domain_ provided by the client to the
502522 # server (see overview comments); it defaults to 'localhost'.
503523 #
504- # The remaining arguments are used for SMTP authentication, if required
505- # or desired. +user+ is the account name; +secret+ is your password
506- # or other authentication token; and +authtype+ is the authentication
507- # type, one of :plain, :login, or :cram_md5. See the discussion of
508- # SMTP Authentication in the overview notes.
509- #
510524 # If +tls+ is true, enable TLS. The default is false.
511525 # If +starttls+ is :always, enable STARTTLS, if +:auto+, use STARTTLS when the server supports it,
512526 # if false, disable STARTTLS.
@@ -520,6 +534,13 @@ def debug_output=(arg)
520534 #
521535 # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+.
522536 #
537+ # The remaining arguments are used for SMTP authentication, if required or
538+ # desired. +user+ or +username+ is the authentication or authorization
539+ # identity (depending on +authtype+); +secret+ or +password+ is your
540+ # password or other authentication token; and +authtype+ is the
541+ # authentication type. +auth+ is a hash of arbitrary keyword parameters for
542+ # #auth. See the discussion of SMTP Authentication in the overview notes.
543+ #
523544 # === Errors
524545 #
525546 # This method may raise:
@@ -565,10 +586,13 @@ def started?
565586 # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see
566587 # the discussion in the overview notes.
567588 #
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.
589+ # If +user+, +username+, +secret+, +password+, +authtype+, or +auth+ given,
590+ # SMTP authentication will be attempted using the #auth command. +authtype+
591+ # specifies the SASL mechanism to attempt; +user+ or +username+ is the
592+ # authentication or authorization identity (depending on +authtype+);
593+ # +secret+ or +password+ is your password or other authentication token;
594+ # +auth+ is a hash of arbitrary keyword parameters for #auth. See the
595+ # discussion of SMTP Authentication in the overview notes.
572596 #
573597 # === Block Usage
574598 #
@@ -871,15 +895,16 @@ def authenticate(user, secret, authtype = DEFAULT_AUTH_TYPE)
871895 # include +authcid+ for authentication identity, +authzid+ for authorization
872896 # identity, +username+ for either "authentication identity" or
873897 # "authorization identity" depending on the +mechanism+, and +password+.
898+ # Keyword arguments that do not apply to the +mechanism+ may be silently
899+ # ignored.
874900 def auth ( *args , **kwargs , &blk )
875901 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 ) }
902+ critical do
903+ Authenticator ::SASLAdapter . new ( self ) . authenticate ( *args , **kwargs , &blk )
904+ rescue SMTPServerBusy , SMTPSyntaxError , SMTPFatalError => error
905+ raise SMTPAuthenticationError . new ( error . response )
906+ rescue SASL ::AuthenticationIncomplete => error
907+ raise error . response . exception_class . new ( error . response )
883908 end
884909 end
885910
@@ -919,21 +944,28 @@ def merge_auth_params(user, secret, authtype, auth)
919944 auth
920945 end
921946
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
947+ def backward_compatible_auth_args ( authtype = nil , *args ,
948+ type : nil , secret : nil , **kwargs )
949+ type && authtype and
929950 raise ArgumentError , 'conflict between "type" keyword argument ' \
930951 'and positional argument'
931- type ||= _type || DEFAULT_AUTH_TYPE
952+ type ||= authtype || DEFAULT_AUTH_TYPE
932953 check_auth_method ( type )
954+ if secret
955+ secret_type = type . match? ( /\A X?OAUTH/i ) ? :oauth2_token : :password
956+ kwargs . key? ( secret_type ) and
957+ raise ArgumentError 'conflict between "secret" and %p keyword args' % [
958+ secret_type . to_s
959+ ]
960+ kwargs [ secret_type ] = secret
961+ end
933962 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
963+ if auth_class . is_a? ( Class ) && auth_class <= Authenticator ||
964+ type . match? ( /\A (?:LOGIN|CRAM[-_]MD5)\z /i )
965+ usernames = [ kwargs . delete ( :authcid ) , kwargs . delete ( :username ) ]
966+ secrets = [ kwargs . delete ( :password ) ]
967+ args [ 0 ] ||= usernames . compact . first
968+ args [ 1 ] ||= secrets . compact . first
937969 check_auth_args ( args [ 0 ] , args [ 1 ] , type )
938970 end
939971 [ [ type , *args ] , kwargs ]
@@ -1047,6 +1079,27 @@ def get_response(reqline)
10471079 recv_response ( )
10481080 end
10491081
1082+ # Returns a successful Response.
1083+ #
1084+ # Yields continuation data.
1085+ #
1086+ # This method may raise:
1087+ #
1088+ # * Net::SMTPAuthenticationError
1089+ # * Net::SMTPServerBusy
1090+ # * Net::SMTPSyntaxError
1091+ # * Net::SMTPFatalError
1092+ # * Net::SMTPUnknownError
1093+ def send_command_with_continuations ( *args )
1094+ server_resp = get_response args . join ( " " )
1095+ while server_resp . continue?
1096+ client_resp = yield server_resp . string . strip . split ( nil , 2 ) . last
1097+ server_resp = get_response client_resp
1098+ end
1099+ server_resp . success? or raise server_resp . exception_class . new ( server_resp )
1100+ server_resp
1101+ end
1102+
10501103 private
10511104
10521105 def validate_line ( line )
0 commit comments