@@ -171,8 +171,11 @@ class SMTPUnsupportedCommand < ProtocolError
171
171
#
172
172
# === SMTP Authentication
173
173
#
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
+ #
176
179
# To use SMTP authentication, pass extra arguments to
177
180
# SMTP.start or SMTP#start.
178
181
#
@@ -182,26 +185,43 @@ class SMTPUnsupportedCommand < ProtocolError
182
185
# Net::SMTP.start("your.smtp.server", 25,
183
186
# auth: {type: :plain,
184
187
# username: "authentication identity",
185
- # password: password})
188
+ # password: password,
189
+ # authzid: "authorization identity"}) # optional
186
190
#
187
- # # LOGIN
188
- # Net::SMTP.start('your.smtp.server', 25,
189
- # user: 'Your Account', secret: 'Your Password', authtype: :login)
191
+ # # SCRAM-SHA-256
190
192
# 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,
192
197
# username: "authentication identity",
193
- # password: password})
198
+ # password: password,
199
+ # authzid: "authorization identity"}) # optional
194
200
#
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)
198
212
# 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 })
202
216
#
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>
205
225
#
206
226
class SMTP < Protocol
207
227
VERSION = "0.4.0"
@@ -501,12 +521,6 @@ def debug_output=(arg)
501
521
# +helo+ is the _HELO_ _domain_ provided by the client to the
502
522
# server (see overview comments); it defaults to 'localhost'.
503
523
#
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
- #
510
524
# If +tls+ is true, enable TLS. The default is false.
511
525
# If +starttls+ is :always, enable STARTTLS, if +:auto+, use STARTTLS when the server supports it,
512
526
# if false, disable STARTTLS.
@@ -520,6 +534,13 @@ def debug_output=(arg)
520
534
#
521
535
# +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+.
522
536
#
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
+ #
523
544
# === Errors
524
545
#
525
546
# This method may raise:
@@ -565,10 +586,13 @@ def started?
565
586
# +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see
566
587
# the discussion in the overview notes.
567
588
#
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.
572
596
#
573
597
# === Block Usage
574
598
#
@@ -871,15 +895,16 @@ def authenticate(user, secret, authtype = DEFAULT_AUTH_TYPE)
871
895
# include +authcid+ for authentication identity, +authzid+ for authorization
872
896
# identity, +username+ for either "authentication identity" or
873
897
# "authorization identity" depending on the +mechanism+, and +password+.
898
+ # Keyword arguments that do not apply to the +mechanism+ may be silently
899
+ # ignored.
874
900
def auth ( *args , **kwargs , &blk )
875
901
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 )
883
908
end
884
909
end
885
910
@@ -919,21 +944,28 @@ def merge_auth_params(user, secret, authtype, auth)
919
944
auth
920
945
end
921
946
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
929
950
raise ArgumentError , 'conflict between "type" keyword argument ' \
930
951
'and positional argument'
931
- type ||= _type || DEFAULT_AUTH_TYPE
952
+ type ||= authtype || DEFAULT_AUTH_TYPE
932
953
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
933
962
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
937
969
check_auth_args ( args [ 0 ] , args [ 1 ] , type )
938
970
end
939
971
[ [ type , *args ] , kwargs ]
@@ -1047,6 +1079,27 @@ def get_response(reqline)
1047
1079
recv_response ( )
1048
1080
end
1049
1081
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
+
1050
1103
private
1051
1104
1052
1105
def validate_line ( line )
0 commit comments