net-smtp 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbf12984985ffef0271f09805060b2cfa5e72eb3955c8ce51ee8076b427e1a02
4
- data.tar.gz: 17c6420b495abe8b17446b1e667681b2680b02d894b3ce1e4ef6cffaa26ad1df
3
+ metadata.gz: 1fcccbe34b29672c33aa7515de88e88dba31882e26a4b6c0a5fc30c5a4eb697d
4
+ data.tar.gz: 5c47e6453d2cb69365edc7b4e31b17106b73e254e1bc10258f4b14ea780ba889
5
5
  SHA512:
6
- metadata.gz: 4d9372a9673256cc280600a1367a0c92bed91250209736b4b54c7219c115b1006e9fc66d465367729f53ef393b9a8077b33f11f7578365d9cb84051e44b86976
7
- data.tar.gz: 0f7da80c1d40216684ff0927637b14dfb5597638913a0aa3a8f2ca68a3fc89724926736c719715171b7bcab915a9504016f10356b2622bc6da9e5f51238f8e52
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,21 +13,16 @@
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
 
20
19
  require 'net/protocol'
21
- require 'digest/md5'
22
- require 'timeout'
23
20
  begin
24
21
  require 'openssl'
25
22
  rescue LoadError
26
23
  end
27
24
 
28
25
  module Net
29
-
30
26
  # Module mixed in to all SMTP error classes
31
27
  module SMTPError
32
28
  # This *class* is a module for backward compatibility.
@@ -35,8 +31,13 @@ module Net
35
31
  attr_reader :response
36
32
 
37
33
  def initialize(response, message: nil)
38
- @response = response
39
- @message = message
34
+ if response.is_a?(::Net::SMTP::Response)
35
+ @response = response
36
+ @message = message
37
+ else
38
+ @response = nil
39
+ @message = message || response
40
+ end
40
41
  end
41
42
 
42
43
  def message
@@ -78,25 +79,34 @@ module Net
78
79
  # == What is This Library?
79
80
  #
80
81
  # This library provides functionality to send internet
81
- # mail via SMTP, the Simple Mail Transfer Protocol. For details of
82
- # 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.
83
91
  #
84
92
  # == What is This Library NOT?
85
93
  #
86
94
  # This library does NOT provide functions to compose internet mails.
87
95
  # You must create them by yourself. If you want better mail support,
88
- # 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
89
98
  # {RubyGems.org}[https://rubygems.org/] or {The Ruby
90
99
  # Toolbox}[https://www.ruby-toolbox.com/].
91
100
  #
92
- # 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]].
93
103
  #
94
104
  # == Examples
95
105
  #
96
106
  # === Sending Messages
97
107
  #
98
- # You must open a connection to an SMTP server before sending messages.
99
- # 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
100
110
  # argument is the port number. Using SMTP.start with a block is the simplest
101
111
  # way to do this. This way, the SMTP connection is closed automatically
102
112
  # after the block is executed.
@@ -106,7 +116,7 @@ module Net
106
116
  # # Use the SMTP object smtp only in this block.
107
117
  # end
108
118
  #
109
- # Replace 'your.smtp.server' with your SMTP server. Normally
119
+ # Replace 'your.smtp.server' with your \SMTP server. Normally
110
120
  # your system manager or internet provider supplies a server
111
121
  # for you.
112
122
  #
@@ -139,7 +149,7 @@ module Net
139
149
  # smtp.send_message msgstr, 'from@address', 'to@address'
140
150
  # smtp.finish
141
151
  #
142
- # 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
143
153
  # the SMTP session automatically:
144
154
  #
145
155
  # # using block form of SMTP.start
@@ -152,36 +162,37 @@ module Net
152
162
  # === HELO domain
153
163
  #
154
164
  # In almost all situations, you must provide a third argument
155
- # 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
156
166
  # (the host to send mail from). It is called the "HELO domain".
157
- # The SMTP server will judge whether it should send or reject
167
+ # The \SMTP server will judge whether it should send or reject
158
168
  # the SMTP session by inspecting the HELO domain.
159
169
  #
160
- # Net::SMTP.start('your.smtp.server', 25
161
- # 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
162
175
  #
163
- # === 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)_.
164
180
  #
165
- # The Net::SMTP class supports three authentication schemes;
166
- # PLAIN, LOGIN and CRAM MD5. (SMTP Authentication: [RFC2554])
167
- # To use SMTP authentication, pass extra arguments to
168
- # SMTP.start/SMTP#start.
181
+ # To use \SMTP authentication, pass extra arguments to
182
+ # SMTP.start or SMTP#start.
169
183
  #
170
184
  # # PLAIN
171
- # Net::SMTP.start('your.smtp.server', 25
185
+ # Net::SMTP.start('your.smtp.server', 25,
172
186
  # user: 'Your Account', secret: 'Your Password', authtype: :plain)
173
- # # LOGIN
174
- # Net::SMTP.start('your.smtp.server', 25
175
- # user: 'Your Account', secret: 'Your Password', authtype: :login)
176
187
  #
177
- # # CRAM MD5
178
- # Net::SMTP.start('your.smtp.server', 25
179
- # 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.
180
193
  #
181
194
  class SMTP < Protocol
182
- VERSION = "0.3.1"
183
-
184
- Revision = %q$Revision$.split[1]
195
+ VERSION = "0.5.0"
185
196
 
186
197
  # The default SMTP port number, 25.
187
198
  def SMTP.default_port
@@ -204,7 +215,7 @@ module Net
204
215
 
205
216
  def SMTP.default_ssl_context(ssl_context_params = nil)
206
217
  context = OpenSSL::SSL::SSLContext.new
207
- context.set_params(ssl_context_params ? ssl_context_params : {})
218
+ context.set_params(ssl_context_params || {})
208
219
  context
209
220
  end
210
221
 
@@ -223,10 +234,13 @@ module Net
223
234
  # If the hostname in the server certificate is different from +address+,
224
235
  # it can be specified with +tls_hostname+.
225
236
  #
226
- # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to
227
- # +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>.
228
243
  #
229
- # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+.
230
244
  # This method does not open the TCP connection. You can use
231
245
  # SMTP.start instead of SMTP.new if you want to do everything
232
246
  # at once. Otherwise, follow SMTP.new with SMTP#start.
@@ -275,7 +289,7 @@ module Net
275
289
  attr_accessor :esmtp
276
290
 
277
291
  # +true+ if the SMTP object uses ESMTP (which it does by default).
278
- alias :esmtp? :esmtp
292
+ alias esmtp? esmtp
279
293
 
280
294
  # true if server advertises STARTTLS.
281
295
  # You cannot get valid value before opening SMTP session.
@@ -310,12 +324,13 @@ module Net
310
324
  auth_capable?('CRAM-MD5')
311
325
  end
312
326
 
327
+ # Returns whether the server advertises support for the authentication type.
328
+ # You cannot get valid result before opening SMTP session.
313
329
  def auth_capable?(type)
314
330
  return nil unless @capabilities
315
331
  return false unless @capabilities['AUTH']
316
332
  @capabilities['AUTH'].include?(type)
317
333
  end
318
- private :auth_capable?
319
334
 
320
335
  # Returns supported authentication methods on this server.
321
336
  # You cannot get valid value before opening SMTP session.
@@ -332,7 +347,7 @@ module Net
332
347
 
333
348
  alias ssl? tls?
334
349
 
335
- # Enables SMTP/TLS (SMTPS: SMTP over direct TLS connection) for
350
+ # Enables SMTP/TLS (SMTPS: \SMTP over direct TLS connection) for
336
351
  # this object. Must be called before the connection is established
337
352
  # to have any effect. +context+ is a OpenSSL::SSL::SSLContext object.
338
353
  def enable_tls(context = nil)
@@ -451,7 +466,10 @@ module Net
451
466
  #
452
467
  # This method is equivalent to:
453
468
  #
454
- # 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
455
473
  #
456
474
  # === Example
457
475
  #
@@ -476,12 +494,6 @@ module Net
476
494
  # +helo+ is the _HELO_ _domain_ provided by the client to the
477
495
  # server (see overview comments); it defaults to 'localhost'.
478
496
  #
479
- # The remaining arguments are used for SMTP authentication, if required
480
- # or desired. +user+ is the account name; +secret+ is your password
481
- # or other authentication token; and +authtype+ is the authentication
482
- # type, one of :plain, :login, or :cram_md5. See the discussion of
483
- # SMTP Authentication in the overview notes.
484
- #
485
497
  # If +tls+ is true, enable TLS. The default is false.
486
498
  # If +starttls+ is :always, enable STARTTLS, if +:auto+, use STARTTLS when the server supports it,
487
499
  # if false, disable STARTTLS.
@@ -490,10 +502,26 @@ module Net
490
502
  # If the hostname in the server certificate is different from +address+,
491
503
  # it can be specified with +tls_hostname+.
492
504
  #
493
- # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to
494
- # +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>.
511
+ #
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.
495
520
  #
496
- # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+.
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.
497
525
  #
498
526
  # === Errors
499
527
  #
@@ -521,7 +549,7 @@ module Net
521
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)
522
550
  end
523
551
 
524
- # +true+ if the SMTP session has been started.
552
+ # +true+ if the \SMTP session has been started.
525
553
  def started?
526
554
  @started
527
555
  end
@@ -538,11 +566,21 @@ module Net
538
566
  # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see
539
567
  # the discussion in the overview notes.
540
568
  #
541
- # If both of +user+ and +secret+ are given, SMTP authentication
542
- # will be attempted using the AUTH command. +authtype+ specifies
543
- # the type of authentication to attempt; it must be one of
544
- # :login, :plain, and :cram_md5. See the notes on SMTP Authentication
545
- # 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
546
584
  #
547
585
  # === Block Usage
548
586
  #
@@ -622,28 +660,24 @@ module Net
622
660
  private
623
661
 
624
662
  def tcp_socket(address, port)
625
- begin
626
- Socket.tcp address, port, nil, nil, connect_timeout: @open_timeout
627
- rescue Errno::ETIMEDOUT #raise Net:OpenTimeout instead for compatibility with previous versions
628
- raise Net::OpenTimeout, "Timeout to open TCP connection to "\
629
- "#{address}:#{port} (exceeds #{@open_timeout} seconds)"
630
- end
663
+ TCPSocket.open address, port
631
664
  end
632
665
 
633
666
  def do_start(helo_domain, user, secret, authtype)
634
667
  raise IOError, 'SMTP session already started' if @started
635
- if user or secret
636
- check_auth_method(authtype || DEFAULT_AUTH_TYPE)
637
- 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)
638
673
  end
639
- s = tcp_socket(@address, @port)
640
674
  logging "Connection opened: #{@address}:#{@port}"
641
675
  @socket = new_internet_message_io(tls? ? tlsconnect(s, @ssl_context_tls) : s)
642
676
  check_response critical { recv_response() }
643
677
  do_helo helo_domain
644
678
  if ! tls? and (starttls_always? or (capable_starttls? and starttls_auto?))
645
679
  unless capable_starttls?
646
- raise SMTPUnsupportedCommand.new(nil, message: "STARTTLS is not supported on this server")
680
+ raise SMTPUnsupportedCommand, "STARTTLS is not supported on this server"
647
681
  end
648
682
  starttls
649
683
  @socket = new_internet_message_io(tlsconnect(s, @ssl_context_starttls))
@@ -703,6 +737,18 @@ module Net
703
737
  @socket = nil
704
738
  end
705
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
+
706
752
  #
707
753
  # Message Sending
708
754
  #
@@ -746,7 +792,9 @@ module Net
746
792
  # * IOError
747
793
  #
748
794
  def send_message(msgstr, from_addr, *to_addrs)
795
+ to_addrs.flatten!
749
796
  raise IOError, 'closed session' unless @socket
797
+ from_addr = Address.new(from_addr, 'SMTPUTF8') if any_require_smtputf8(to_addrs) && capable?('SMTPUTF8')
750
798
  mailfrom from_addr
751
799
  rcptto_list(to_addrs) {data msgstr}
752
800
  end
@@ -799,7 +847,9 @@ module Net
799
847
  # * IOError
800
848
  #
801
849
  def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream
850
+ to_addrs.flatten!
802
851
  raise IOError, 'closed session' unless @socket
852
+ from_addr = Address.new(from_addr, 'SMTPUTF8') if any_require_smtputf8(to_addrs) && capable?('SMTPUTF8')
803
853
  mailfrom from_addr
804
854
  rcptto_list(to_addrs) {data(&block)}
805
855
  end
@@ -810,92 +860,28 @@ module Net
810
860
  # Authentication
811
861
  #
812
862
 
813
- public
814
-
815
863
  DEFAULT_AUTH_TYPE = :plain
816
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.
817
872
  def authenticate(user, secret, authtype = DEFAULT_AUTH_TYPE)
818
- check_auth_method authtype
819
- check_auth_args user, secret
820
- public_send auth_method(authtype), user, secret
821
- end
822
-
823
- def auth_plain(user, secret)
824
- check_auth_args user, secret
825
- res = critical {
826
- get_response('AUTH PLAIN ' + base64_encode("\0#{user}\0#{secret}"))
827
- }
828
- check_auth_response res
829
- res
830
- end
831
-
832
- def auth_login(user, secret)
833
- check_auth_args user, secret
834
- res = critical {
835
- check_auth_continue get_response('AUTH LOGIN')
836
- check_auth_continue get_response(base64_encode(user))
837
- get_response(base64_encode(secret))
838
- }
839
- check_auth_response res
840
- res
841
- end
842
-
843
- def auth_cram_md5(user, secret)
844
- check_auth_args user, secret
845
- res = critical {
846
- res0 = get_response('AUTH CRAM-MD5')
847
- check_auth_continue res0
848
- crammed = cram_md5_response(secret, res0.cram_md5_challenge)
849
- get_response(base64_encode("#{user} #{crammed}"))
850
- }
851
- check_auth_response res
852
- res
873
+ check_auth_args authtype, user, secret
874
+ authenticator = Authenticator.auth_class(authtype).new(self)
875
+ authenticator.auth(user, secret)
853
876
  end
854
877
 
855
878
  private
856
879
 
857
- def check_auth_method(type)
858
- 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
859
883
  raise ArgumentError, "wrong authentication type #{type}"
860
- end
861
- end
862
-
863
- def auth_method(type)
864
- "auth_#{type.to_s.downcase}".intern
865
- end
866
-
867
- def check_auth_args(user, secret, authtype = DEFAULT_AUTH_TYPE)
868
- unless user
869
- raise ArgumentError, 'SMTP-AUTH requested but missing user name'
870
- end
871
- unless secret
872
- raise ArgumentError, 'SMTP-AUTH requested but missing secret phrase'
873
- end
874
- end
875
-
876
- def base64_encode(str)
877
- # expects "str" may not become too long
878
- [str].pack('m0')
879
- end
880
-
881
- IMASK = 0x36
882
- OMASK = 0x5c
883
-
884
- # CRAM-MD5: [RFC2195]
885
- def cram_md5_response(secret, challenge)
886
- tmp = Digest::MD5.digest(cram_secret(secret, IMASK) + challenge)
887
- Digest::MD5.hexdigest(cram_secret(secret, OMASK) + tmp)
888
- end
889
-
890
- CRAM_BUFSIZE = 64
891
-
892
- def cram_secret(secret, mask)
893
- secret = Digest::MD5.digest(secret) if secret.size > CRAM_BUFSIZE
894
- buf = secret.ljust(CRAM_BUFSIZE, "\0")
895
- 0.upto(buf.size - 1) do |i|
896
- buf[i] = (buf[i].ord ^ mask).chr
897
- end
898
- buf
884
+ klass.check_args(*args, **kwargs)
899
885
  end
900
886
 
901
887
  #
@@ -924,29 +910,20 @@ module Net
924
910
 
925
911
  # +from_addr+ is +String+ or +Net::SMTP::Address+
926
912
  def mailfrom(from_addr)
927
- 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
928
918
  getok((["MAIL FROM:<#{addr.address}>"] + addr.parameters).join(' '))
929
919
  end
930
920
 
931
921
  def rcptto_list(to_addrs)
932
922
  raise ArgumentError, 'mail destination not given' if to_addrs.empty?
933
- ok_users = []
934
- unknown_users = []
935
923
  to_addrs.flatten.each do |addr|
936
- begin
937
- rcptto addr
938
- rescue SMTPAuthenticationError
939
- unknown_users << addr.to_s.dump
940
- else
941
- ok_users << addr
942
- end
924
+ rcptto addr
943
925
  end
944
- raise ArgumentError, 'mail destination not given' if ok_users.empty?
945
- ret = yield
946
- unless unknown_users.empty?
947
- raise SMTPAuthenticationError, "failed to deliver for #{unknown_users.join(', ')}"
948
- end
949
- ret
926
+ yield
950
927
  end
951
928
 
952
929
  # +to_addr+ is +String+ or +Net::SMTP::Address+
@@ -1009,6 +986,12 @@ module Net
1009
986
  getok('QUIT')
1010
987
  end
1011
988
 
989
+ def get_response(reqline)
990
+ validate_line reqline
991
+ @socket.writeline reqline
992
+ recv_response()
993
+ end
994
+
1012
995
  private
1013
996
 
1014
997
  def validate_line(line)
@@ -1028,12 +1011,6 @@ module Net
1028
1011
  res
1029
1012
  end
1030
1013
 
1031
- def get_response(reqline)
1032
- validate_line reqline
1033
- @socket.writeline reqline
1034
- recv_response()
1035
- end
1036
-
1037
1014
  def recv_response
1038
1015
  buf = ''.dup
1039
1016
  while true
@@ -1066,18 +1043,6 @@ module Net
1066
1043
  end
1067
1044
  end
1068
1045
 
1069
- def check_auth_response(res)
1070
- unless res.success?
1071
- raise SMTPAuthenticationError.new(res)
1072
- end
1073
- end
1074
-
1075
- def check_auth_continue(res)
1076
- unless res.continue?
1077
- raise res.exception_class.new(res)
1078
- end
1079
- end
1080
-
1081
1046
  # This class represents a response received by the SMTP server. Instances
1082
1047
  # of this class are created by the SMTP class; they should not be directly
1083
1048
  # created by the user. For more information on SMTP responses, view
@@ -1165,7 +1130,7 @@ module Net
1165
1130
  class Address
1166
1131
  # mail address [String]
1167
1132
  attr_reader :address
1168
- # paramters [Array<String>]
1133
+ # parameters [Array<String>]
1169
1134
  attr_reader :parameters
1170
1135
 
1171
1136
  # :call-seq:
@@ -1179,17 +1144,21 @@ module Net
1179
1144
  @parameters = address.parameters
1180
1145
  else
1181
1146
  @address = address
1182
- @parameters = (args + [kw_args]).map{|param| Array(param)}.flatten(1).map{|param| Array(param).compact.join('=')}
1147
+ @parameters = []
1183
1148
  end
1149
+ @parameters = (parameters + args + [kw_args]).map{|param| Array(param)}.flatten(1).map{|param| Array(param).compact.join('=')}.uniq
1184
1150
  end
1185
1151
 
1186
1152
  def to_s
1187
1153
  @address
1188
1154
  end
1189
1155
  end
1190
-
1191
1156
  end # class SMTP
1192
1157
 
1193
1158
  SMTPSession = SMTP # :nodoc:
1159
+ end
1194
1160
 
1161
+ require_relative 'smtp/authenticator'
1162
+ Dir.glob("#{__dir__}/smtp/auth_*.rb") do |r|
1163
+ require_relative r
1195
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.1
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: 2021-12-12 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
@@ -24,34 +24,6 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: digest
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: timeout
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
27
  description: Simple Mail Transfer Protocol client library for Ruby.
56
28
  email:
57
29
  - matz@ruby-lang.org
@@ -60,8 +32,14 @@ extensions: []
60
32
  extra_rdoc_files: []
61
33
  files:
62
34
  - LICENSE.txt
35
+ - NEWS.md
36
+ - README.md
63
37
  - lib/net/smtp.rb
64
- - 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
65
43
  homepage: https://github.com/ruby/net-smtp
66
44
  licenses:
67
45
  - Ruby
@@ -84,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
84
62
  - !ruby/object:Gem::Version
85
63
  version: '0'
86
64
  requirements: []
87
- rubygems_version: 3.3.0.dev
65
+ rubygems_version: 3.5.3
88
66
  signing_key:
89
67
  specification_version: 4
90
68
  summary: Simple Mail Transfer Protocol client library for Ruby.
data/net-smtp.gemspec DELETED
@@ -1,35 +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
- spec.add_dependency "digest"
34
- spec.add_dependency "timeout"
35
- end