net-smtp 0.3.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 314f553cc9e988f56b964e62836e95e323974b6a66c26e2f02d1e6a7cc08e886
4
- data.tar.gz: 43a203a0622f49e2fb32a6c8bc0f644cb703e3bc3bc1f25aa114434c1a9cc8e5
3
+ metadata.gz: 1fcccbe34b29672c33aa7515de88e88dba31882e26a4b6c0a5fc30c5a4eb697d
4
+ data.tar.gz: 5c47e6453d2cb69365edc7b4e31b17106b73e254e1bc10258f4b14ea780ba889
5
5
  SHA512:
6
- metadata.gz: 486381bf1eee2cbd7cb4f12ca0253ee5b06fc2b1a012b3786bda9b30456c338c5de40889e639275cfcf22a83064d7c468aa06c8e21438adb1d1ab2133a3933eb
7
- data.tar.gz: 97659f9e6505ea3aa10db92d5a99dc39d01ca55468ca28934b77061ccedd7ce74d8652927304ddf7fe4a21622e3572cca3bb92f19bae3a1b004bc7909783b530
6
+ metadata.gz: 6576c5b7dc21982576fa6c3a6e08641e973136148d5d5bcb27fe1e17b4e5fdd802c5f5a6f9f116dd15bf4f2a0e5f95f071ca19d47119cf60c7da9230a64d3a4c
7
+ data.tar.gz: 9b1c54b2d9a48e384d3b1a67f1aa8b8a2614a383dc31b83d49170f3ed93d69a2058f3c310f72d443f54a0947c1066249105087a1a3addc92965c289354c8156a
data/NEWS.md ADDED
@@ -0,0 +1,133 @@
1
+ # NEWS
2
+
3
+ ## Version 0.5.0 (2024-03-27)
4
+
5
+ ### Improvements
6
+
7
+ * Allow case-insensitive strings for SASL mechanism <https://github.com/ruby/net-smtp/pull/64>
8
+ * Make #auth_capable? public <https://github.com/ruby/net-smtp/pull/63>
9
+ * Add XOAUTH2 authenticator <https://github.com/ruby/net-smtp/pull/80>
10
+
11
+ ### Others
12
+
13
+ * Remove unused private auth_method <https://github.com/ruby/net-smtp/pull/67>
14
+ * Delegate checking auth args to the authenticator <https://github.com/ruby/net-smtp/pull/73>
15
+ * Updated docs, especially TLS and SASL-related <https://github.com/ruby/net-smtp/pull/66>
16
+ * Renew test certificates <https://github.com/ruby/net-smtp/pull/75>
17
+ * Fix version extraction to work with non ASCII characters with any LANG <https://github.com/ruby/net-smtp/pull/76>
18
+ * Replace non-ASCII EM DASH (U+2014) with ASCII hyphen (U+002D) <https://github.com/ruby/net-smtp/pull/78>
19
+ * Use reusing workflow for Ruby versions <https://github.com/ruby/net-smtp/pull/79>
20
+ * Make the test suite compatible with --enable-frozen-string-literal <https://github.com/ruby/net-smtp/pull/81>
21
+
22
+ ## Version 0.4.0 (2023-09-20)
23
+
24
+ ### Improvements
25
+
26
+ * add Net::SMTP::Authenticator class and auth_* methods are separated from the Net::SMTP class. <https://github.com/ruby/net-smtp/pull/53>
27
+ This allows you to add a new authentication method to Net::SMTP.
28
+ Create a class with an `auth` method that inherits Net::SMTP::Authenticator.
29
+ The `auth` method has two arguments, `user` and `secret`.
30
+ Send an instruction to the SMTP server by using the `continue` or `finish` method.
31
+ For more information, see lib/net/smtp/auto _*.rb.
32
+ * Add SMTPUTF8 support <https://github.com/ruby/net-smtp/pull/49>
33
+
34
+ ### Fixes
35
+
36
+ * Revert "Replace Timeout.timeout with socket timeout" <https://github.com/ruby/net-smtp/pull/51>
37
+ * Fixed issue sending emails to unaffected recipients on 53x error <https://github.com/ruby/net-smtp/pull/56>
38
+
39
+ ### Others
40
+
41
+ * Removed unnecessary Subversion keywords <https://github.com/ruby/net-smtp/pull/57>
42
+
43
+ ## Version 0.3.3 (2022-10-29)
44
+
45
+ * No timeout library required <https://github.com/ruby/net-smtp/pull/44>
46
+ * Make the digest library optional <https://github.com/ruby/net-smtp/pull/45>
47
+
48
+ ## Version 0.3.2 (2022-09-28)
49
+
50
+ * Make exception API compatible with what Ruby expects <https://github.com/ruby/net-smtp/pull/42>
51
+
52
+ ## Version 0.3.1 (2021-12-12)
53
+
54
+ ### Improvements
55
+
56
+ * add Net::SMTP::Address.
57
+ * add Net::SMTP#capable? and Net::SMTP#capabilities.
58
+ * add Net::SMTP#tls_verify, Net::SMTP#tls_hostname, Net::SMTP#ssl_context_params
59
+
60
+ ## Version 0.3.0 (2021-10-14)
61
+
62
+ ### Improvements
63
+
64
+ * Add `tls`, `starttls` keyword arguments.
65
+ ```ruby
66
+ # always use TLS connection for port 465.
67
+ Net::SMTP.start(hostname, 465, tls: true)
68
+
69
+ # do not use starttls for localhost
70
+ Net::SMTP.start('localhost', starttls: false)
71
+ ```
72
+
73
+ ### Incompatible changes
74
+
75
+ * The tls_* paramter has been moved from start() to initialize().
76
+
77
+ ## Version 0.2.2 (2021-10-09)
78
+
79
+ * Add `response` to SMTPError exceptions.
80
+ * `Net::SMTP.start()` and `#start()` accepts `ssl_context_params` keyword argument.
81
+ * Replace `Timeout.timeout` with socket timeout.
82
+ * Remove needless files from gem.
83
+ * Add dependency on digest, timeout.
84
+
85
+ ## Version 0.2.1 (2020-11-18)
86
+
87
+ ### Fixes
88
+
89
+ * Update the license for the default gems to dual licenses.
90
+ * Add dependency for net-protocol.
91
+
92
+ ## Version 0.2.0 (2020-11-15)
93
+
94
+ ### Incompatible changes
95
+
96
+ * Verify the server's certificate by default.
97
+ If you don't want verification, specify `start(tls_verify: false)`.
98
+ <https://github.com/ruby/net-smtp/pull/12>
99
+
100
+ * Use STARTTLS by default if possible.
101
+ If you don't want starttls, specify:
102
+ ```
103
+ smtp = Net::SMTP.new(hostname, port)
104
+ smtp.disable_starttls
105
+ smtp.start do |s|
106
+ s.send_message ....
107
+ end
108
+ ```
109
+ <https://github.com/ruby/net-smtp/pull/9>
110
+
111
+ ### Improvements
112
+
113
+ * Net::SMTP.start and Net::SMTP#start arguments are keyword arguments.
114
+ ```
115
+ start(address, port = nil, helo: 'localhost', user: nil, secret: nil, authtype: nil) { |smtp| ... }
116
+ ```
117
+ `password` is an alias of `secret`.
118
+ <https://github.com/ruby/net-smtp/pull/7>
119
+
120
+ * Add `tls_hostname` parameter to `start()`.
121
+ If you want to use a different hostname than the certificate for the connection, you can specify the certificate hostname with `tls_hostname`.
122
+ <https://github.com/ruby/net-smtp/pull/14>
123
+
124
+ * Add SNI support to net/smtp <https://github.com/ruby/net-smtp/pull/4>
125
+
126
+ ### Fixes
127
+
128
+ * enable_starttls before disable_tls causes an error. <https://github.com/ruby/net-smtp/pull/10>
129
+ * TLS should not check the hostname when verify_mode is disabled. <https://github.com/ruby/net-smtp/pull/6>
130
+
131
+ ## Version 0.1.0 (2019-12-03)
132
+
133
+ This is the first release of net-smtp gem.
data/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # Net::SMTP
2
+
3
+ This library provides functionality to send internet mail via SMTP, the Simple Mail Transfer Protocol.
4
+
5
+ For details of SMTP itself, see [RFC2821](http://www.ietf.org/rfc/rfc2821.txt).
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'net-smtp'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install net-smtp
22
+
23
+ ## Usage
24
+
25
+ ### Sending Messages
26
+
27
+ You must open a connection to an SMTP server before sending messages.
28
+ The first argument is the address of your SMTP server, and the second
29
+ argument is the port number. Using SMTP.start with a block is the simplest
30
+ way to do this. This way, the SMTP connection is closed automatically
31
+ after the block is executed.
32
+
33
+ ```ruby
34
+ require 'net/smtp'
35
+ Net::SMTP.start('your.smtp.server', 25) do |smtp|
36
+ # Use the SMTP object smtp only in this block.
37
+ end
38
+ ```
39
+
40
+ Replace 'your.smtp.server' with your SMTP server. Normally
41
+ your system manager or internet provider supplies a server
42
+ for you.
43
+
44
+ Then you can send messages.
45
+
46
+ ```ruby
47
+ msgstr = <<END_OF_MESSAGE
48
+ From: Your Name <your@mail.address>
49
+ To: Destination Address <someone@example.com>
50
+ Subject: test message
51
+ Date: Sat, 23 Jun 2001 16:26:43 +0900
52
+ Message-Id: <unique.message.id.string@example.com>
53
+
54
+ This is a test message.
55
+ END_OF_MESSAGE
56
+
57
+ require 'net/smtp'
58
+ Net::SMTP.start('your.smtp.server', 25) do |smtp|
59
+ smtp.send_message msgstr,
60
+ 'your@mail.address',
61
+ 'his_address@example.com'
62
+ end
63
+ ```
64
+
65
+ ### Closing the Session
66
+
67
+ You MUST close the SMTP session after sending messages, by calling
68
+ the #finish method:
69
+
70
+ ```ruby
71
+ # using SMTP#finish
72
+ smtp = Net::SMTP.start('your.smtp.server', 25)
73
+ smtp.send_message msgstr, 'from@address', 'to@address'
74
+ smtp.finish
75
+ ```
76
+
77
+ You can also use the block form of SMTP.start/SMTP#start. This closes
78
+ the SMTP session automatically:
79
+
80
+ ```ruby
81
+ # using block form of SMTP.start
82
+ Net::SMTP.start('your.smtp.server', 25) do |smtp|
83
+ smtp.send_message msgstr, 'from@address', 'to@address'
84
+ end
85
+ ```
86
+
87
+ I strongly recommend this scheme. This form is simpler and more robust.
88
+
89
+ ## Development
90
+
91
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
92
+
93
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
94
+
95
+ ## Contributing
96
+
97
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/net-smtp.
@@ -0,0 +1,48 @@
1
+ unless defined? OpenSSL
2
+ begin
3
+ require 'digest/md5'
4
+ rescue LoadError
5
+ end
6
+ end
7
+
8
+ class Net::SMTP
9
+ class AuthCramMD5 < Net::SMTP::Authenticator
10
+ auth_type :cram_md5
11
+
12
+ def auth(user, secret)
13
+ challenge = continue('AUTH CRAM-MD5')
14
+ crammed = cram_md5_response(secret, challenge.unpack1('m'))
15
+ finish(base64_encode("#{user} #{crammed}"))
16
+ end
17
+
18
+ IMASK = 0x36
19
+ OMASK = 0x5c
20
+
21
+ # CRAM-MD5: [RFC2195]
22
+ def cram_md5_response(secret, challenge)
23
+ tmp = digest_class::MD5.digest(cram_secret(secret, IMASK) + challenge)
24
+ digest_class::MD5.hexdigest(cram_secret(secret, OMASK) + tmp)
25
+ end
26
+
27
+ CRAM_BUFSIZE = 64
28
+
29
+ def cram_secret(secret, mask)
30
+ secret = digest_class::MD5.digest(secret) if secret.size > CRAM_BUFSIZE
31
+ buf = secret.ljust(CRAM_BUFSIZE, "\0")
32
+ 0.upto(buf.size - 1) do |i|
33
+ buf[i] = (buf[i].ord ^ mask).chr
34
+ end
35
+ buf
36
+ end
37
+
38
+ def digest_class
39
+ @digest_class ||= if defined?(OpenSSL::Digest)
40
+ OpenSSL::Digest
41
+ elsif defined?(::Digest)
42
+ ::Digest
43
+ else
44
+ raise '"openssl" or "digest" library is required'
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,11 @@
1
+ class Net::SMTP
2
+ class AuthLogin < Net::SMTP::Authenticator
3
+ auth_type :login
4
+
5
+ def auth(user, secret)
6
+ continue('AUTH LOGIN')
7
+ continue(base64_encode(user))
8
+ finish(base64_encode(secret))
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ class Net::SMTP
2
+ class AuthPlain < Net::SMTP::Authenticator
3
+ auth_type :plain
4
+
5
+ def auth(user, secret)
6
+ finish('AUTH PLAIN ' + base64_encode("\0#{user}\0#{secret}"))
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ class Net::SMTP
2
+ class AuthXoauth2 < Net::SMTP::Authenticator
3
+ auth_type :xoauth2
4
+
5
+ def auth(user, secret)
6
+ token = xoauth2_string(user, secret)
7
+
8
+ finish("AUTH XOAUTH2 #{base64_encode(token)}")
9
+ end
10
+
11
+ private
12
+
13
+ def xoauth2_string(user, secret)
14
+ "user=#{user}\1auth=Bearer #{secret}\1\1"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,57 @@
1
+ module Net
2
+ class SMTP
3
+ class Authenticator
4
+ def self.auth_classes
5
+ @classes ||= {}
6
+ end
7
+
8
+ def self.auth_type(type)
9
+ type = type.to_s.upcase.tr(?_, ?-).to_sym
10
+ Authenticator.auth_classes[type] = self
11
+ end
12
+
13
+ def self.auth_class(type)
14
+ type = type.to_s.upcase.tr(?_, ?-).to_sym
15
+ Authenticator.auth_classes[type]
16
+ end
17
+
18
+ def self.check_args(user_arg = nil, secret_arg = nil, *, **)
19
+ unless user_arg
20
+ raise ArgumentError, 'SMTP-AUTH requested but missing user name'
21
+ end
22
+ unless secret_arg
23
+ raise ArgumentError, 'SMTP-AUTH requested but missing secret phrase'
24
+ end
25
+ end
26
+
27
+ attr_reader :smtp
28
+
29
+ def initialize(smtp)
30
+ @smtp = smtp
31
+ end
32
+
33
+ # @param arg [String] message to server
34
+ # @return [String] message from server
35
+ def continue(arg)
36
+ res = smtp.get_response arg
37
+ raise res.exception_class.new(res) unless res.continue?
38
+ res.string.split[1]
39
+ end
40
+
41
+ # @param arg [String] message to server
42
+ # @return [Net::SMTP::Response] response from server
43
+ def finish(arg)
44
+ res = smtp.get_response arg
45
+ raise SMTPAuthenticationError.new(res) unless res.success?
46
+ res
47
+ end
48
+
49
+ # @param str [String]
50
+ # @return [String] Base64 encoded string
51
+ def base64_encode(str)
52
+ # expects "str" may not become too long
53
+ [str].pack('m0')
54
+ end
55
+ end
56
+ end
57
+ end
data/lib/net/smtp.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  # = net/smtp.rb
3
4
  #
4
5
  # Copyright (c) 1999-2007 Yukihiro Matsumoto.
@@ -12,8 +13,6 @@
12
13
  # This program is free software. You can re-distribute and/or
13
14
  # modify this program under the same terms as Ruby itself.
14
15
  #
15
- # $Id$
16
- #
17
16
  # See Net::SMTP for documentation.
18
17
  #
19
18
 
@@ -21,14 +20,9 @@ require 'net/protocol'
21
20
  begin
22
21
  require 'openssl'
23
22
  rescue LoadError
24
- begin
25
- require 'digest/md5'
26
- rescue LoadError
27
- end
28
23
  end
29
24
 
30
25
  module Net
31
-
32
26
  # Module mixed in to all SMTP error classes
33
27
  module SMTPError
34
28
  # This *class* is a module for backward compatibility.
@@ -42,7 +36,7 @@ module Net
42
36
  @message = message
43
37
  else
44
38
  @response = nil
45
- @message = message || response
39
+ @message = message || response
46
40
  end
47
41
  end
48
42
 
@@ -85,25 +79,34 @@ module Net
85
79
  # == What is This Library?
86
80
  #
87
81
  # This library provides functionality to send internet
88
- # mail via SMTP, the Simple Mail Transfer Protocol. For details of
89
- # SMTP itself, see [RFC2821] (http://www.ietf.org/rfc/rfc2821.txt).
82
+ # mail via \SMTP, the Simple Mail Transfer Protocol. For details of
83
+ # \SMTP itself, see [RFC5321[https://www.rfc-editor.org/rfc/rfc5321.txt]].
84
+ # This library also implements \SMTP authentication, which is often
85
+ # necessary for message composers to submit messages to their
86
+ # outgoing \SMTP server, see
87
+ # [RFC6409[https://www.rfc-editor.org/rfc/rfc6409.html]],
88
+ # and [SMTPUTF8[https://www.rfc-editor.org/rfc/rfc6531.txt]], which is
89
+ # necessary to send messages to/from addresses containing characters
90
+ # outside the ASCII range.
90
91
  #
91
92
  # == What is This Library NOT?
92
93
  #
93
94
  # This library does NOT provide functions to compose internet mails.
94
95
  # You must create them by yourself. If you want better mail support,
95
- # try RubyMail or TMail or search for alternatives in
96
+ # try the mail[https://rubygems.org/gems/mail] or
97
+ # rmail[https://rubygems.org/gems/rmail] gems, or search for alternatives in
96
98
  # {RubyGems.org}[https://rubygems.org/] or {The Ruby
97
99
  # Toolbox}[https://www.ruby-toolbox.com/].
98
100
  #
99
- # FYI: the official documentation on internet mail is: [RFC2822] (http://www.ietf.org/rfc/rfc2822.txt).
101
+ # FYI: the official specification on internet mail is:
102
+ # [RFC5322[https://www.rfc-editor.org/rfc/rfc5322.txt]].
100
103
  #
101
104
  # == Examples
102
105
  #
103
106
  # === Sending Messages
104
107
  #
105
- # You must open a connection to an SMTP server before sending messages.
106
- # The first argument is the address of your SMTP server, and the second
108
+ # You must open a connection to an \SMTP server before sending messages.
109
+ # The first argument is the address of your \SMTP server, and the second
107
110
  # argument is the port number. Using SMTP.start with a block is the simplest
108
111
  # way to do this. This way, the SMTP connection is closed automatically
109
112
  # after the block is executed.
@@ -113,7 +116,7 @@ module Net
113
116
  # # Use the SMTP object smtp only in this block.
114
117
  # end
115
118
  #
116
- # Replace 'your.smtp.server' with your SMTP server. Normally
119
+ # Replace 'your.smtp.server' with your \SMTP server. Normally
117
120
  # your system manager or internet provider supplies a server
118
121
  # for you.
119
122
  #
@@ -146,7 +149,7 @@ module Net
146
149
  # smtp.send_message msgstr, 'from@address', 'to@address'
147
150
  # smtp.finish
148
151
  #
149
- # You can also use the block form of SMTP.start/SMTP#start. This closes
152
+ # You can also use the block form of SMTP.start or SMTP#start. This closes
150
153
  # the SMTP session automatically:
151
154
  #
152
155
  # # using block form of SMTP.start
@@ -159,36 +162,37 @@ module Net
159
162
  # === HELO domain
160
163
  #
161
164
  # In almost all situations, you must provide a third argument
162
- # to SMTP.start/SMTP#start. This is the domain name which you are on
165
+ # to SMTP.start or SMTP#start. This is the domain name which you are on
163
166
  # (the host to send mail from). It is called the "HELO domain".
164
- # The SMTP server will judge whether it should send or reject
167
+ # The \SMTP server will judge whether it should send or reject
165
168
  # the SMTP session by inspecting the HELO domain.
166
169
  #
167
- # Net::SMTP.start('your.smtp.server', 25
168
- # helo: 'mail.from.domain') { |smtp| ... }
170
+ # Net::SMTP.start('your.smtp.server', 25, helo: 'mail.from.domain') do |smtp|
171
+ # smtp.send_message msgstr, 'from@address', 'to@address'
172
+ # end
173
+ #
174
+ # === \SMTP Authentication
169
175
  #
170
- # === SMTP Authentication
176
+ # The Net::SMTP class supports the \SMTP extension for SASL Authentication
177
+ # [RFC4954[https://www.rfc-editor.org/rfc/rfc4954.html]] and the following
178
+ # SASL mechanisms: +PLAIN+, +LOGIN+ _(deprecated)_, and +CRAM-MD5+
179
+ # _(deprecated)_.
171
180
  #
172
- # The Net::SMTP class supports three authentication schemes;
173
- # PLAIN, LOGIN and CRAM MD5. (SMTP Authentication: [RFC2554])
174
- # To use SMTP authentication, pass extra arguments to
175
- # SMTP.start/SMTP#start.
181
+ # To use \SMTP authentication, pass extra arguments to
182
+ # SMTP.start or SMTP#start.
176
183
  #
177
184
  # # PLAIN
178
- # Net::SMTP.start('your.smtp.server', 25
185
+ # Net::SMTP.start('your.smtp.server', 25,
179
186
  # user: 'Your Account', secret: 'Your Password', authtype: :plain)
180
- # # LOGIN
181
- # Net::SMTP.start('your.smtp.server', 25
182
- # user: 'Your Account', secret: 'Your Password', authtype: :login)
183
187
  #
184
- # # CRAM MD5
185
- # Net::SMTP.start('your.smtp.server', 25
186
- # user: 'Your Account', secret: 'Your Password', authtype: :cram_md5)
188
+ # Support for other SASL mechanisms-such as +EXTERNAL+, +OAUTHBEARER+,
189
+ # +SCRAM-SHA-256+, and +XOAUTH2+-will be added in a future release.
190
+ #
191
+ # The +LOGIN+ and +CRAM-MD5+ mechanisms are still available for backwards
192
+ # compatibility, but are deprecated and should be avoided.
187
193
  #
188
194
  class SMTP < Protocol
189
- VERSION = "0.3.3"
190
-
191
- Revision = %q$Revision$.split[1]
195
+ VERSION = "0.5.0"
192
196
 
193
197
  # The default SMTP port number, 25.
194
198
  def SMTP.default_port
@@ -211,7 +215,7 @@ module Net
211
215
 
212
216
  def SMTP.default_ssl_context(ssl_context_params = nil)
213
217
  context = OpenSSL::SSL::SSLContext.new
214
- context.set_params(ssl_context_params ? ssl_context_params : {})
218
+ context.set_params(ssl_context_params || {})
215
219
  context
216
220
  end
217
221
 
@@ -230,10 +234,13 @@ module Net
230
234
  # If the hostname in the server certificate is different from +address+,
231
235
  # it can be specified with +tls_hostname+.
232
236
  #
233
- # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to
234
- # +OpenSSL::SSL::SSLContext#set_params+
237
+ # Additional SSLContext[https://ruby.github.io/openssl/OpenSSL/SSL/SSLContext.html]
238
+ # params can be added to the +ssl_context_params+ hash argument and are
239
+ # passed to {OpenSSL::SSL::SSLContext#set_params}[https://ruby.github.io/openssl/OpenSSL/SSL/SSLContext.html#method-i-set_params].
240
+ #
241
+ # <tt>tls_verify: true</tt> is equivalent to <tt>ssl_context_params: {
242
+ # verify_mode: OpenSSL::SSL::VERIFY_PEER }</tt>.
235
243
  #
236
- # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+.
237
244
  # This method does not open the TCP connection. You can use
238
245
  # SMTP.start instead of SMTP.new if you want to do everything
239
246
  # at once. Otherwise, follow SMTP.new with SMTP#start.
@@ -282,7 +289,7 @@ module Net
282
289
  attr_accessor :esmtp
283
290
 
284
291
  # +true+ if the SMTP object uses ESMTP (which it does by default).
285
- alias :esmtp? :esmtp
292
+ alias esmtp? esmtp
286
293
 
287
294
  # true if server advertises STARTTLS.
288
295
  # You cannot get valid value before opening SMTP session.
@@ -317,12 +324,13 @@ module Net
317
324
  auth_capable?('CRAM-MD5')
318
325
  end
319
326
 
327
+ # Returns whether the server advertises support for the authentication type.
328
+ # You cannot get valid result before opening SMTP session.
320
329
  def auth_capable?(type)
321
330
  return nil unless @capabilities
322
331
  return false unless @capabilities['AUTH']
323
332
  @capabilities['AUTH'].include?(type)
324
333
  end
325
- private :auth_capable?
326
334
 
327
335
  # Returns supported authentication methods on this server.
328
336
  # You cannot get valid value before opening SMTP session.
@@ -339,7 +347,7 @@ module Net
339
347
 
340
348
  alias ssl? tls?
341
349
 
342
- # Enables SMTP/TLS (SMTPS: SMTP over direct TLS connection) for
350
+ # Enables SMTP/TLS (SMTPS: \SMTP over direct TLS connection) for
343
351
  # this object. Must be called before the connection is established
344
352
  # to have any effect. +context+ is a OpenSSL::SSL::SSLContext object.
345
353
  def enable_tls(context = nil)
@@ -458,7 +466,10 @@ module Net
458
466
  #
459
467
  # This method is equivalent to:
460
468
  #
461
- # Net::SMTP.new(address, port).start(helo: helo_domain, user: account, secret: password, authtype: authtype, tls_verify: flag, tls_hostname: hostname, ssl_context_params: nil)
469
+ # Net::SMTP.new(address, port, tls_verify: flag, tls_hostname: hostname, ssl_context_params: nil)
470
+ # .start(helo: helo_domain, user: account, secret: password, authtype: authtype)
471
+ #
472
+ # See also: Net::SMTP.new, #start
462
473
  #
463
474
  # === Example
464
475
  #
@@ -483,12 +494,6 @@ module Net
483
494
  # +helo+ is the _HELO_ _domain_ provided by the client to the
484
495
  # server (see overview comments); it defaults to 'localhost'.
485
496
  #
486
- # The remaining arguments are used for SMTP authentication, if required
487
- # or desired. +user+ is the account name; +secret+ is your password
488
- # or other authentication token; and +authtype+ is the authentication
489
- # type, one of :plain, :login, or :cram_md5. See the discussion of
490
- # SMTP Authentication in the overview notes.
491
- #
492
497
  # If +tls+ is true, enable TLS. The default is false.
493
498
  # If +starttls+ is :always, enable STARTTLS, if +:auto+, use STARTTLS when the server supports it,
494
499
  # if false, disable STARTTLS.
@@ -497,10 +502,26 @@ module Net
497
502
  # If the hostname in the server certificate is different from +address+,
498
503
  # it can be specified with +tls_hostname+.
499
504
  #
500
- # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to
501
- # +OpenSSL::SSL::SSLContext#set_params+
505
+ # Additional SSLContext[https://ruby.github.io/openssl/OpenSSL/SSL/SSLContext.html]
506
+ # params can be added to the +ssl_context_params+ hash argument and are
507
+ # passed to {OpenSSL::SSL::SSLContext#set_params}[https://ruby.github.io/openssl/OpenSSL/SSL/SSLContext.html#method-i-set_params].
508
+ #
509
+ # <tt>tls_verify: true</tt> is equivalent to <tt>ssl_context_params: {
510
+ # verify_mode: OpenSSL::SSL::VERIFY_PEER }</tt>.
502
511
  #
503
- # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+.
512
+ # The remaining arguments are used for \SMTP authentication, if required or
513
+ # desired.
514
+ #
515
+ # +authtype+ is the SASL authentication mechanism.
516
+ #
517
+ # +user+ is the authentication or authorization identity.
518
+ #
519
+ # +secret+ or +password+ is your password or other authentication token.
520
+ #
521
+ # These will be sent to #authenticate as positional arguments-the exact
522
+ # semantics are dependent on the +authtype+.
523
+ #
524
+ # See the discussion of Net::SMTP@SMTP+Authentication in the overview notes.
504
525
  #
505
526
  # === Errors
506
527
  #
@@ -528,7 +549,7 @@ module Net
528
549
  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)
529
550
  end
530
551
 
531
- # +true+ if the SMTP session has been started.
552
+ # +true+ if the \SMTP session has been started.
532
553
  def started?
533
554
  @started
534
555
  end
@@ -545,11 +566,21 @@ module Net
545
566
  # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see
546
567
  # the discussion in the overview notes.
547
568
  #
548
- # If both of +user+ and +secret+ are given, SMTP authentication
549
- # will be attempted using the AUTH command. +authtype+ specifies
550
- # the type of authentication to attempt; it must be one of
551
- # :login, :plain, and :cram_md5. See the notes on SMTP Authentication
552
- # in the overview.
569
+ # The remaining arguments are used for \SMTP authentication, if required or
570
+ # desired.
571
+ #
572
+ # +authtype+ is the SASL authentication mechanism.
573
+ #
574
+ # +user+ is the authentication or authorization identity.
575
+ #
576
+ # +secret+ or +password+ is your password or other authentication token.
577
+ #
578
+ # These will be sent to #authenticate as positional arguments-the exact
579
+ # semantics are dependent on the +authtype+.
580
+ #
581
+ # See the discussion of Net::SMTP@SMTP+Authentication in the overview notes.
582
+ #
583
+ # See also: Net::SMTP.start
553
584
  #
554
585
  # === Block Usage
555
586
  #
@@ -628,32 +659,18 @@ module Net
628
659
 
629
660
  private
630
661
 
631
- def digest_class
632
- @digest_class ||= if defined?(OpenSSL::Digest)
633
- OpenSSL::Digest
634
- elsif defined?(::Digest)
635
- ::Digest
636
- else
637
- raise '"openssl" or "digest" library is required'
638
- end
639
- end
640
-
641
662
  def tcp_socket(address, port)
642
- begin
643
- Socket.tcp address, port, nil, nil, connect_timeout: @open_timeout
644
- rescue Errno::ETIMEDOUT #raise Net:OpenTimeout instead for compatibility with previous versions
645
- raise Net::OpenTimeout, "Timeout to open TCP connection to "\
646
- "#{address}:#{port} (exceeds #{@open_timeout} seconds)"
647
- end
663
+ TCPSocket.open address, port
648
664
  end
649
665
 
650
666
  def do_start(helo_domain, user, secret, authtype)
651
667
  raise IOError, 'SMTP session already started' if @started
652
- if user or secret
653
- check_auth_method(authtype || DEFAULT_AUTH_TYPE)
654
- check_auth_args user, secret
668
+ if user || secret || authtype
669
+ check_auth_args authtype, user, secret
670
+ end
671
+ s = Timeout.timeout(@open_timeout, Net::OpenTimeout) do
672
+ tcp_socket(@address, @port)
655
673
  end
656
- s = tcp_socket(@address, @port)
657
674
  logging "Connection opened: #{@address}:#{@port}"
658
675
  @socket = new_internet_message_io(tls? ? tlsconnect(s, @ssl_context_tls) : s)
659
676
  check_response critical { recv_response() }
@@ -720,6 +737,18 @@ module Net
720
737
  @socket = nil
721
738
  end
722
739
 
740
+ def requires_smtputf8(address)
741
+ if address.kind_of? Address
742
+ !address.address.ascii_only?
743
+ else
744
+ !address.ascii_only?
745
+ end
746
+ end
747
+
748
+ def any_require_smtputf8(addresses)
749
+ addresses.any?{ |a| requires_smtputf8(a) }
750
+ end
751
+
723
752
  #
724
753
  # Message Sending
725
754
  #
@@ -763,7 +792,9 @@ module Net
763
792
  # * IOError
764
793
  #
765
794
  def send_message(msgstr, from_addr, *to_addrs)
795
+ to_addrs.flatten!
766
796
  raise IOError, 'closed session' unless @socket
797
+ from_addr = Address.new(from_addr, 'SMTPUTF8') if any_require_smtputf8(to_addrs) && capable?('SMTPUTF8')
767
798
  mailfrom from_addr
768
799
  rcptto_list(to_addrs) {data msgstr}
769
800
  end
@@ -816,7 +847,9 @@ module Net
816
847
  # * IOError
817
848
  #
818
849
  def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream
850
+ to_addrs.flatten!
819
851
  raise IOError, 'closed session' unless @socket
852
+ from_addr = Address.new(from_addr, 'SMTPUTF8') if any_require_smtputf8(to_addrs) && capable?('SMTPUTF8')
820
853
  mailfrom from_addr
821
854
  rcptto_list(to_addrs) {data(&block)}
822
855
  end
@@ -827,92 +860,28 @@ module Net
827
860
  # Authentication
828
861
  #
829
862
 
830
- public
831
-
832
863
  DEFAULT_AUTH_TYPE = :plain
833
864
 
865
+ # Authenticates with the server, using the "AUTH" command.
866
+ #
867
+ # +authtype+ is the name of a SASL authentication mechanism.
868
+ #
869
+ # All arguments-other than +authtype+-are forwarded to the authenticator.
870
+ # Different authenticators may interpret the +user+ and +secret+
871
+ # arguments differently.
834
872
  def authenticate(user, secret, authtype = DEFAULT_AUTH_TYPE)
835
- check_auth_method authtype
836
- check_auth_args user, secret
837
- public_send auth_method(authtype), user, secret
838
- end
839
-
840
- def auth_plain(user, secret)
841
- check_auth_args user, secret
842
- res = critical {
843
- get_response('AUTH PLAIN ' + base64_encode("\0#{user}\0#{secret}"))
844
- }
845
- check_auth_response res
846
- res
847
- end
848
-
849
- def auth_login(user, secret)
850
- check_auth_args user, secret
851
- res = critical {
852
- check_auth_continue get_response('AUTH LOGIN')
853
- check_auth_continue get_response(base64_encode(user))
854
- get_response(base64_encode(secret))
855
- }
856
- check_auth_response res
857
- res
858
- end
859
-
860
- def auth_cram_md5(user, secret)
861
- check_auth_args user, secret
862
- res = critical {
863
- res0 = get_response('AUTH CRAM-MD5')
864
- check_auth_continue res0
865
- crammed = cram_md5_response(secret, res0.cram_md5_challenge)
866
- get_response(base64_encode("#{user} #{crammed}"))
867
- }
868
- check_auth_response res
869
- res
873
+ check_auth_args authtype, user, secret
874
+ authenticator = Authenticator.auth_class(authtype).new(self)
875
+ authenticator.auth(user, secret)
870
876
  end
871
877
 
872
878
  private
873
879
 
874
- def check_auth_method(type)
875
- unless respond_to?(auth_method(type), true)
880
+ def check_auth_args(type, *args, **kwargs)
881
+ type ||= DEFAULT_AUTH_TYPE
882
+ klass = Authenticator.auth_class(type) or
876
883
  raise ArgumentError, "wrong authentication type #{type}"
877
- end
878
- end
879
-
880
- def auth_method(type)
881
- "auth_#{type.to_s.downcase}".intern
882
- end
883
-
884
- def check_auth_args(user, secret, authtype = DEFAULT_AUTH_TYPE)
885
- unless user
886
- raise ArgumentError, 'SMTP-AUTH requested but missing user name'
887
- end
888
- unless secret
889
- raise ArgumentError, 'SMTP-AUTH requested but missing secret phrase'
890
- end
891
- end
892
-
893
- def base64_encode(str)
894
- # expects "str" may not become too long
895
- [str].pack('m0')
896
- end
897
-
898
- IMASK = 0x36
899
- OMASK = 0x5c
900
-
901
- # CRAM-MD5: [RFC2195]
902
- def cram_md5_response(secret, challenge)
903
- tmp = digest_class::MD5.digest(cram_secret(secret, IMASK) + challenge)
904
- digest_class::MD5.hexdigest(cram_secret(secret, OMASK) + tmp)
905
- end
906
-
907
- CRAM_BUFSIZE = 64
908
-
909
- def cram_secret(secret, mask)
910
- secret = digest_class::MD5.digest(secret) if secret.size > CRAM_BUFSIZE
911
- buf = secret.ljust(CRAM_BUFSIZE, "\0")
912
- 0.upto(buf.size - 1) do |i|
913
- buf[i] = (buf[i].ord ^ mask).chr
914
- end
915
- buf
884
+ klass.check_args(*args, **kwargs)
916
885
  end
917
886
 
918
887
  #
@@ -941,29 +910,20 @@ module Net
941
910
 
942
911
  # +from_addr+ is +String+ or +Net::SMTP::Address+
943
912
  def mailfrom(from_addr)
944
- addr = Address.new(from_addr)
913
+ addr = if requires_smtputf8(from_addr) && capable?("SMTPUTF8")
914
+ Address.new(from_addr, "SMTPUTF8")
915
+ else
916
+ Address.new(from_addr)
917
+ end
945
918
  getok((["MAIL FROM:<#{addr.address}>"] + addr.parameters).join(' '))
946
919
  end
947
920
 
948
921
  def rcptto_list(to_addrs)
949
922
  raise ArgumentError, 'mail destination not given' if to_addrs.empty?
950
- ok_users = []
951
- unknown_users = []
952
923
  to_addrs.flatten.each do |addr|
953
- begin
954
- rcptto addr
955
- rescue SMTPAuthenticationError
956
- unknown_users << addr.to_s.dump
957
- else
958
- ok_users << addr
959
- end
924
+ rcptto addr
960
925
  end
961
- raise ArgumentError, 'mail destination not given' if ok_users.empty?
962
- ret = yield
963
- unless unknown_users.empty?
964
- raise SMTPAuthenticationError, "failed to deliver for #{unknown_users.join(', ')}"
965
- end
966
- ret
926
+ yield
967
927
  end
968
928
 
969
929
  # +to_addr+ is +String+ or +Net::SMTP::Address+
@@ -1026,6 +986,12 @@ module Net
1026
986
  getok('QUIT')
1027
987
  end
1028
988
 
989
+ def get_response(reqline)
990
+ validate_line reqline
991
+ @socket.writeline reqline
992
+ recv_response()
993
+ end
994
+
1029
995
  private
1030
996
 
1031
997
  def validate_line(line)
@@ -1045,12 +1011,6 @@ module Net
1045
1011
  res
1046
1012
  end
1047
1013
 
1048
- def get_response(reqline)
1049
- validate_line reqline
1050
- @socket.writeline reqline
1051
- recv_response()
1052
- end
1053
-
1054
1014
  def recv_response
1055
1015
  buf = ''.dup
1056
1016
  while true
@@ -1083,18 +1043,6 @@ module Net
1083
1043
  end
1084
1044
  end
1085
1045
 
1086
- def check_auth_response(res)
1087
- unless res.success?
1088
- raise SMTPAuthenticationError.new(res)
1089
- end
1090
- end
1091
-
1092
- def check_auth_continue(res)
1093
- unless res.continue?
1094
- raise res.exception_class.new(res)
1095
- end
1096
- end
1097
-
1098
1046
  # This class represents a response received by the SMTP server. Instances
1099
1047
  # of this class are created by the SMTP class; they should not be directly
1100
1048
  # created by the user. For more information on SMTP responses, view
@@ -1196,17 +1144,21 @@ module Net
1196
1144
  @parameters = address.parameters
1197
1145
  else
1198
1146
  @address = address
1199
- @parameters = (args + [kw_args]).map{|param| Array(param)}.flatten(1).map{|param| Array(param).compact.join('=')}
1147
+ @parameters = []
1200
1148
  end
1149
+ @parameters = (parameters + args + [kw_args]).map{|param| Array(param)}.flatten(1).map{|param| Array(param).compact.join('=')}.uniq
1201
1150
  end
1202
1151
 
1203
1152
  def to_s
1204
1153
  @address
1205
1154
  end
1206
1155
  end
1207
-
1208
1156
  end # class SMTP
1209
1157
 
1210
1158
  SMTPSession = SMTP # :nodoc:
1159
+ end
1211
1160
 
1161
+ require_relative 'smtp/authenticator'
1162
+ Dir.glob("#{__dir__}/smtp/auth_*.rb") do |r|
1163
+ require_relative r
1212
1164
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-smtp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yukihiro Matsumoto
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-29 00:00:00.000000000 Z
11
+ date: 2024-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-protocol
@@ -32,8 +32,14 @@ extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
34
  - LICENSE.txt
35
+ - NEWS.md
36
+ - README.md
35
37
  - lib/net/smtp.rb
36
- - net-smtp.gemspec
38
+ - lib/net/smtp/auth_cram_md5.rb
39
+ - lib/net/smtp/auth_login.rb
40
+ - lib/net/smtp/auth_plain.rb
41
+ - lib/net/smtp/auth_xoauth2.rb
42
+ - lib/net/smtp/authenticator.rb
37
43
  homepage: https://github.com/ruby/net-smtp
38
44
  licenses:
39
45
  - Ruby
@@ -56,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
62
  - !ruby/object:Gem::Version
57
63
  version: '0'
58
64
  requirements: []
59
- rubygems_version: 3.4.0.dev
65
+ rubygems_version: 3.5.3
60
66
  signing_key:
61
67
  specification_version: 4
62
68
  summary: Simple Mail Transfer Protocol client library for Ruby.
data/net-smtp.gemspec DELETED
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- name = File.basename(__FILE__, ".gemspec")
4
- version = ["lib", Array.new(name.count("-"), "..").join("/")].find do |dir|
5
- break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
6
- /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
7
- end rescue nil
8
- end
9
-
10
- Gem::Specification.new do |spec|
11
- spec.name = name
12
- spec.version = version
13
- spec.authors = ["Yukihiro Matsumoto"]
14
- spec.email = ["matz@ruby-lang.org"]
15
-
16
- spec.summary = %q{Simple Mail Transfer Protocol client library for Ruby.}
17
- spec.description = %q{Simple Mail Transfer Protocol client library for Ruby.}
18
- spec.homepage = "https://github.com/ruby/net-smtp"
19
- spec.licenses = ["Ruby", "BSD-2-Clause"]
20
- spec.required_ruby_version = ">= 2.6.0"
21
-
22
- spec.metadata["homepage_uri"] = spec.homepage
23
- spec.metadata["source_code_uri"] = spec.homepage
24
-
25
- spec.files = %w[
26
- LICENSE.txt
27
- lib/net/smtp.rb
28
- net-smtp.gemspec
29
- ]
30
- spec.require_paths = ["lib"]
31
-
32
- spec.add_dependency "net-protocol"
33
- end