net-smtp 0.3.3 → 0.5.1

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: 314f553cc9e988f56b964e62836e95e323974b6a66c26e2f02d1e6a7cc08e886
4
- data.tar.gz: 43a203a0622f49e2fb32a6c8bc0f644cb703e3bc3bc1f25aa114434c1a9cc8e5
3
+ metadata.gz: bc084bee5387c51eea4508e77ce6ccd2b220f4d090f91bccfb4fc3626142a3f2
4
+ data.tar.gz: a011fe3a47fc5119090e7beed0900e83f62b34c5ecbd7b2fe8453c2c7518cdd0
5
5
  SHA512:
6
- metadata.gz: 486381bf1eee2cbd7cb4f12ca0253ee5b06fc2b1a012b3786bda9b30456c338c5de40889e639275cfcf22a83064d7c468aa06c8e21438adb1d1ab2133a3933eb
7
- data.tar.gz: 97659f9e6505ea3aa10db92d5a99dc39d01ca55468ca28934b77061ccedd7ce74d8652927304ddf7fe4a21622e3572cca3bb92f19bae3a1b004bc7909783b530
6
+ metadata.gz: 29c4a47233280c89cc17360933fbdcd460c6b7e70e9023fda38ffa20f89d8afdbbd1c3cbf2d4f7db99e88aeea0e4dab5160a7b9f2d5638918372469fb531632b
7
+ data.tar.gz: a24a46eaada8bfe12b8e8e3f07e0db393a00c6db847581276f9a4263e334c93d29d31fc0792f80002415e9ace43059e160d22723bab89721b275b604cbdb83f3
data/LICENSE.txt CHANGED
@@ -1,22 +1,2 @@
1
- Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
2
-
3
- Redistribution and use in source and binary forms, with or without
4
- modification, are permitted provided that the following conditions
5
- are met:
6
- 1. Redistributions of source code must retain the above copyright
7
- notice, this list of conditions and the following disclaimer.
8
- 2. Redistributions in binary form must reproduce the above copyright
9
- notice, this list of conditions and the following disclaimer in the
10
- documentation and/or other materials provided with the distribution.
11
-
12
- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
13
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
16
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
18
- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19
- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20
- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
21
- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
22
- SUCH DAMAGE.
1
+ All the files in this distribution are covered under either the Ruby license or
2
+ the BSD-2-Clause license (see the file COPYING).
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.1"
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
data/net-smtp.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  name = File.basename(__FILE__, ".gemspec")
4
4
  version = ["lib", Array.new(name.count("-"), "..").join("/")].find do |dir|
5
- break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
5
+ break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb"), :encoding => "UTF-8") do |line|
6
6
  /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
7
7
  end rescue nil
8
8
  end
@@ -22,11 +22,9 @@ Gem::Specification.new do |spec|
22
22
  spec.metadata["homepage_uri"] = spec.homepage
23
23
  spec.metadata["source_code_uri"] = spec.homepage
24
24
 
25
- spec.files = %w[
26
- LICENSE.txt
27
- lib/net/smtp.rb
28
- net-smtp.gemspec
29
- ]
25
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
26
+ `git ls-files README.md NEWS.md LICENSE.txt net-smtp.gemspec lib`.split
27
+ end
30
28
  spec.require_paths = ["lib"]
31
29
 
32
30
  spec.add_dependency "net-protocol"
metadata CHANGED
@@ -1,14 +1,13 @@
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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yukihiro Matsumoto
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2022-10-29 00:00:00.000000000 Z
10
+ date: 2025-02-05 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: net-protocol
@@ -32,7 +31,14 @@ extensions: []
32
31
  extra_rdoc_files: []
33
32
  files:
34
33
  - LICENSE.txt
34
+ - NEWS.md
35
+ - README.md
35
36
  - lib/net/smtp.rb
37
+ - lib/net/smtp/auth_cram_md5.rb
38
+ - lib/net/smtp/auth_login.rb
39
+ - lib/net/smtp/auth_plain.rb
40
+ - lib/net/smtp/auth_xoauth2.rb
41
+ - lib/net/smtp/authenticator.rb
36
42
  - net-smtp.gemspec
37
43
  homepage: https://github.com/ruby/net-smtp
38
44
  licenses:
@@ -41,7 +47,6 @@ licenses:
41
47
  metadata:
42
48
  homepage_uri: https://github.com/ruby/net-smtp
43
49
  source_code_uri: https://github.com/ruby/net-smtp
44
- post_install_message:
45
50
  rdoc_options: []
46
51
  require_paths:
47
52
  - lib
@@ -56,8 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
61
  - !ruby/object:Gem::Version
57
62
  version: '0'
58
63
  requirements: []
59
- rubygems_version: 3.4.0.dev
60
- signing_key:
64
+ rubygems_version: 3.7.0.dev
61
65
  specification_version: 4
62
66
  summary: Simple Mail Transfer Protocol client library for Ruby.
63
67
  test_files: []