net-smtp 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 78828322ead199854db6be7e42250f1a185f13979020922e49c9865c499c7305
4
+ data.tar.gz: 88387d583063d1ee6478621d4e2d622582f09a7b7a11f50e9bd067575da8ba35
5
+ SHA512:
6
+ metadata.gz: fc0c2ba9462420db511321f9206db5035ecc3778e9e4b526634c7a98ffbc089bae94f4056a4385582f2cfe734a15f9ce8724f355cdbfbeb0c299fa3331d2f9e0
7
+ data.tar.gz: 86b82c9b891c41fea7829ea8c201835930cf3ba93966b51c636d56aeb54f9e132d530264b9b2b93a42364cfe5d841b287df4173a56b9541ca22f3c0f9bbb4b60
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ Gemfile.lock
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.3
7
+ before_install: gem install bundler -v 2.0.2
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "bundler"
7
+ gem "rake"
8
+ gem "test-unit"
9
+ end
@@ -0,0 +1,22 @@
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.
@@ -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/hsbt/net-smtp.
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/test_*.rb']
8
+ end
9
+
10
+ task :default => [:test]
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "net/smtp"
5
+
6
+ require "irb"
7
+ IRB.start(__FILE__)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
@@ -0,0 +1,1078 @@
1
+ # frozen_string_literal: true
2
+ # = net/smtp.rb
3
+ #
4
+ # Copyright (c) 1999-2007 Yukihiro Matsumoto.
5
+ #
6
+ # Copyright (c) 1999-2007 Minero Aoki.
7
+ #
8
+ # Written & maintained by Minero Aoki <aamine@loveruby.net>.
9
+ #
10
+ # Documented by William Webber and Minero Aoki.
11
+ #
12
+ # This program is free software. You can re-distribute and/or
13
+ # modify this program under the same terms as Ruby itself.
14
+ #
15
+ # $Id$
16
+ #
17
+ # See Net::SMTP for documentation.
18
+ #
19
+
20
+ require 'net/protocol'
21
+ require 'digest/md5'
22
+ require 'timeout'
23
+ begin
24
+ require 'openssl'
25
+ rescue LoadError
26
+ end
27
+
28
+ module Net
29
+
30
+ # Module mixed in to all SMTP error classes
31
+ module SMTPError
32
+ # This *class* is a module for backward compatibility.
33
+ # In later release, this module becomes a class.
34
+ end
35
+
36
+ # Represents an SMTP authentication error.
37
+ class SMTPAuthenticationError < ProtoAuthError
38
+ include SMTPError
39
+ end
40
+
41
+ # Represents SMTP error code 4xx, a temporary error.
42
+ class SMTPServerBusy < ProtoServerError
43
+ include SMTPError
44
+ end
45
+
46
+ # Represents an SMTP command syntax error (error code 500)
47
+ class SMTPSyntaxError < ProtoSyntaxError
48
+ include SMTPError
49
+ end
50
+
51
+ # Represents a fatal SMTP error (error code 5xx, except for 500)
52
+ class SMTPFatalError < ProtoFatalError
53
+ include SMTPError
54
+ end
55
+
56
+ # Unexpected reply code returned from server.
57
+ class SMTPUnknownError < ProtoUnknownError
58
+ include SMTPError
59
+ end
60
+
61
+ # Command is not supported on server.
62
+ class SMTPUnsupportedCommand < ProtocolError
63
+ include SMTPError
64
+ end
65
+
66
+ #
67
+ # == What is This Library?
68
+ #
69
+ # This library provides functionality to send internet
70
+ # mail via SMTP, the Simple Mail Transfer Protocol. For details of
71
+ # SMTP itself, see [RFC2821] (http://www.ietf.org/rfc/rfc2821.txt).
72
+ #
73
+ # == What is This Library NOT?
74
+ #
75
+ # This library does NOT provide functions to compose internet mails.
76
+ # You must create them by yourself. If you want better mail support,
77
+ # try RubyMail or TMail or search for alternatives in
78
+ # {RubyGems.org}[https://rubygems.org/] or {The Ruby
79
+ # Toolbox}[https://www.ruby-toolbox.com/].
80
+ #
81
+ # FYI: the official documentation on internet mail is: [RFC2822] (http://www.ietf.org/rfc/rfc2822.txt).
82
+ #
83
+ # == Examples
84
+ #
85
+ # === Sending Messages
86
+ #
87
+ # You must open a connection to an SMTP server before sending messages.
88
+ # The first argument is the address of your SMTP server, and the second
89
+ # argument is the port number. Using SMTP.start with a block is the simplest
90
+ # way to do this. This way, the SMTP connection is closed automatically
91
+ # after the block is executed.
92
+ #
93
+ # require 'net/smtp'
94
+ # Net::SMTP.start('your.smtp.server', 25) do |smtp|
95
+ # # Use the SMTP object smtp only in this block.
96
+ # end
97
+ #
98
+ # Replace 'your.smtp.server' with your SMTP server. Normally
99
+ # your system manager or internet provider supplies a server
100
+ # for you.
101
+ #
102
+ # Then you can send messages.
103
+ #
104
+ # msgstr = <<END_OF_MESSAGE
105
+ # From: Your Name <your@mail.address>
106
+ # To: Destination Address <someone@example.com>
107
+ # Subject: test message
108
+ # Date: Sat, 23 Jun 2001 16:26:43 +0900
109
+ # Message-Id: <unique.message.id.string@example.com>
110
+ #
111
+ # This is a test message.
112
+ # END_OF_MESSAGE
113
+ #
114
+ # require 'net/smtp'
115
+ # Net::SMTP.start('your.smtp.server', 25) do |smtp|
116
+ # smtp.send_message msgstr,
117
+ # 'your@mail.address',
118
+ # 'his_address@example.com'
119
+ # end
120
+ #
121
+ # === Closing the Session
122
+ #
123
+ # You MUST close the SMTP session after sending messages, by calling
124
+ # the #finish method:
125
+ #
126
+ # # using SMTP#finish
127
+ # smtp = Net::SMTP.start('your.smtp.server', 25)
128
+ # smtp.send_message msgstr, 'from@address', 'to@address'
129
+ # smtp.finish
130
+ #
131
+ # You can also use the block form of SMTP.start/SMTP#start. This closes
132
+ # the SMTP session automatically:
133
+ #
134
+ # # using block form of SMTP.start
135
+ # Net::SMTP.start('your.smtp.server', 25) do |smtp|
136
+ # smtp.send_message msgstr, 'from@address', 'to@address'
137
+ # end
138
+ #
139
+ # I strongly recommend this scheme. This form is simpler and more robust.
140
+ #
141
+ # === HELO domain
142
+ #
143
+ # In almost all situations, you must provide a third argument
144
+ # to SMTP.start/SMTP#start. This is the domain name which you are on
145
+ # (the host to send mail from). It is called the "HELO domain".
146
+ # The SMTP server will judge whether it should send or reject
147
+ # the SMTP session by inspecting the HELO domain.
148
+ #
149
+ # Net::SMTP.start('your.smtp.server', 25,
150
+ # 'mail.from.domain') { |smtp| ... }
151
+ #
152
+ # === SMTP Authentication
153
+ #
154
+ # The Net::SMTP class supports three authentication schemes;
155
+ # PLAIN, LOGIN and CRAM MD5. (SMTP Authentication: [RFC2554])
156
+ # To use SMTP authentication, pass extra arguments to
157
+ # SMTP.start/SMTP#start.
158
+ #
159
+ # # PLAIN
160
+ # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
161
+ # 'Your Account', 'Your Password', :plain)
162
+ # # LOGIN
163
+ # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
164
+ # 'Your Account', 'Your Password', :login)
165
+ #
166
+ # # CRAM MD5
167
+ # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
168
+ # 'Your Account', 'Your Password', :cram_md5)
169
+ #
170
+ class SMTP < Protocol
171
+
172
+ Revision = %q$Revision$.split[1]
173
+
174
+ # The default SMTP port number, 25.
175
+ def SMTP.default_port
176
+ 25
177
+ end
178
+
179
+ # The default mail submission port number, 587.
180
+ def SMTP.default_submission_port
181
+ 587
182
+ end
183
+
184
+ # The default SMTPS port number, 465.
185
+ def SMTP.default_tls_port
186
+ 465
187
+ end
188
+
189
+ class << self
190
+ alias default_ssl_port default_tls_port
191
+ end
192
+
193
+ def SMTP.default_ssl_context
194
+ OpenSSL::SSL::SSLContext.new
195
+ end
196
+
197
+ #
198
+ # Creates a new Net::SMTP object.
199
+ #
200
+ # +address+ is the hostname or ip address of your SMTP
201
+ # server. +port+ is the port to connect to; it defaults to
202
+ # port 25.
203
+ #
204
+ # This method does not open the TCP connection. You can use
205
+ # SMTP.start instead of SMTP.new if you want to do everything
206
+ # at once. Otherwise, follow SMTP.new with SMTP#start.
207
+ #
208
+ def initialize(address, port = nil)
209
+ @address = address
210
+ @port = (port || SMTP.default_port)
211
+ @esmtp = true
212
+ @capabilities = nil
213
+ @socket = nil
214
+ @started = false
215
+ @open_timeout = 30
216
+ @read_timeout = 60
217
+ @error_occurred = false
218
+ @debug_output = nil
219
+ @tls = false
220
+ @starttls = false
221
+ @ssl_context = nil
222
+ end
223
+
224
+ # Provide human-readable stringification of class state.
225
+ def inspect
226
+ "#<#{self.class} #{@address}:#{@port} started=#{@started}>"
227
+ end
228
+
229
+ #
230
+ # Set whether to use ESMTP or not. This should be done before
231
+ # calling #start. Note that if #start is called in ESMTP mode,
232
+ # and the connection fails due to a ProtocolError, the SMTP
233
+ # object will automatically switch to plain SMTP mode and
234
+ # retry (but not vice versa).
235
+ #
236
+ attr_accessor :esmtp
237
+
238
+ # +true+ if the SMTP object uses ESMTP (which it does by default).
239
+ alias :esmtp? :esmtp
240
+
241
+ # true if server advertises STARTTLS.
242
+ # You cannot get valid value before opening SMTP session.
243
+ def capable_starttls?
244
+ capable?('STARTTLS')
245
+ end
246
+
247
+ def capable?(key)
248
+ return nil unless @capabilities
249
+ @capabilities[key] ? true : false
250
+ end
251
+ private :capable?
252
+
253
+ # true if server advertises AUTH PLAIN.
254
+ # You cannot get valid value before opening SMTP session.
255
+ def capable_plain_auth?
256
+ auth_capable?('PLAIN')
257
+ end
258
+
259
+ # true if server advertises AUTH LOGIN.
260
+ # You cannot get valid value before opening SMTP session.
261
+ def capable_login_auth?
262
+ auth_capable?('LOGIN')
263
+ end
264
+
265
+ # true if server advertises AUTH CRAM-MD5.
266
+ # You cannot get valid value before opening SMTP session.
267
+ def capable_cram_md5_auth?
268
+ auth_capable?('CRAM-MD5')
269
+ end
270
+
271
+ def auth_capable?(type)
272
+ return nil unless @capabilities
273
+ return false unless @capabilities['AUTH']
274
+ @capabilities['AUTH'].include?(type)
275
+ end
276
+ private :auth_capable?
277
+
278
+ # Returns supported authentication methods on this server.
279
+ # You cannot get valid value before opening SMTP session.
280
+ def capable_auth_types
281
+ return [] unless @capabilities
282
+ return [] unless @capabilities['AUTH']
283
+ @capabilities['AUTH']
284
+ end
285
+
286
+ # true if this object uses SMTP/TLS (SMTPS).
287
+ def tls?
288
+ @tls
289
+ end
290
+
291
+ alias ssl? tls?
292
+
293
+ # Enables SMTP/TLS (SMTPS: SMTP over direct TLS connection) for
294
+ # this object. Must be called before the connection is established
295
+ # to have any effect. +context+ is a OpenSSL::SSL::SSLContext object.
296
+ def enable_tls(context = SMTP.default_ssl_context)
297
+ raise 'openssl library not installed' unless defined?(OpenSSL)
298
+ raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @starttls
299
+ @tls = true
300
+ @ssl_context = context
301
+ end
302
+
303
+ alias enable_ssl enable_tls
304
+
305
+ # Disables SMTP/TLS for this object. Must be called before the
306
+ # connection is established to have any effect.
307
+ def disable_tls
308
+ @tls = false
309
+ @ssl_context = nil
310
+ end
311
+
312
+ alias disable_ssl disable_tls
313
+
314
+ # Returns truth value if this object uses STARTTLS.
315
+ # If this object always uses STARTTLS, returns :always.
316
+ # If this object uses STARTTLS when the server support TLS, returns :auto.
317
+ def starttls?
318
+ @starttls
319
+ end
320
+
321
+ # true if this object uses STARTTLS.
322
+ def starttls_always?
323
+ @starttls == :always
324
+ end
325
+
326
+ # true if this object uses STARTTLS when server advertises STARTTLS.
327
+ def starttls_auto?
328
+ @starttls == :auto
329
+ end
330
+
331
+ # Enables SMTP/TLS (STARTTLS) for this object.
332
+ # +context+ is a OpenSSL::SSL::SSLContext object.
333
+ def enable_starttls(context = SMTP.default_ssl_context)
334
+ raise 'openssl library not installed' unless defined?(OpenSSL)
335
+ raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls
336
+ @starttls = :always
337
+ @ssl_context = context
338
+ end
339
+
340
+ # Enables SMTP/TLS (STARTTLS) for this object if server accepts.
341
+ # +context+ is a OpenSSL::SSL::SSLContext object.
342
+ def enable_starttls_auto(context = SMTP.default_ssl_context)
343
+ raise 'openssl library not installed' unless defined?(OpenSSL)
344
+ raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls
345
+ @starttls = :auto
346
+ @ssl_context = context
347
+ end
348
+
349
+ # Disables SMTP/TLS (STARTTLS) for this object. Must be called
350
+ # before the connection is established to have any effect.
351
+ def disable_starttls
352
+ @starttls = false
353
+ @ssl_context = nil
354
+ end
355
+
356
+ # The address of the SMTP server to connect to.
357
+ attr_reader :address
358
+
359
+ # The port number of the SMTP server to connect to.
360
+ attr_reader :port
361
+
362
+ # Seconds to wait while attempting to open a connection.
363
+ # If the connection cannot be opened within this time, a
364
+ # Net::OpenTimeout is raised. The default value is 30 seconds.
365
+ attr_accessor :open_timeout
366
+
367
+ # Seconds to wait while reading one block (by one read(2) call).
368
+ # If the read(2) call does not complete within this time, a
369
+ # Net::ReadTimeout is raised. The default value is 60 seconds.
370
+ attr_reader :read_timeout
371
+
372
+ # Set the number of seconds to wait until timing-out a read(2)
373
+ # call.
374
+ def read_timeout=(sec)
375
+ @socket.read_timeout = sec if @socket
376
+ @read_timeout = sec
377
+ end
378
+
379
+ #
380
+ # WARNING: This method causes serious security holes.
381
+ # Use this method for only debugging.
382
+ #
383
+ # Set an output stream for debug logging.
384
+ # You must call this before #start.
385
+ #
386
+ # # example
387
+ # smtp = Net::SMTP.new(addr, port)
388
+ # smtp.set_debug_output $stderr
389
+ # smtp.start do |smtp|
390
+ # ....
391
+ # end
392
+ #
393
+ def debug_output=(arg)
394
+ @debug_output = arg
395
+ end
396
+
397
+ alias set_debug_output debug_output=
398
+
399
+ #
400
+ # SMTP session control
401
+ #
402
+
403
+ #
404
+ # Creates a new Net::SMTP object and connects to the server.
405
+ #
406
+ # This method is equivalent to:
407
+ #
408
+ # Net::SMTP.new(address, port).start(helo_domain, account, password, authtype)
409
+ #
410
+ # === Example
411
+ #
412
+ # Net::SMTP.start('your.smtp.server') do |smtp|
413
+ # smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
414
+ # end
415
+ #
416
+ # === Block Usage
417
+ #
418
+ # If called with a block, the newly-opened Net::SMTP object is yielded
419
+ # to the block, and automatically closed when the block finishes. If called
420
+ # without a block, the newly-opened Net::SMTP object is returned to
421
+ # the caller, and it is the caller's responsibility to close it when
422
+ # finished.
423
+ #
424
+ # === Parameters
425
+ #
426
+ # +address+ is the hostname or ip address of your smtp server.
427
+ #
428
+ # +port+ is the port to connect to; it defaults to port 25.
429
+ #
430
+ # +helo+ is the _HELO_ _domain_ provided by the client to the
431
+ # server (see overview comments); it defaults to 'localhost'.
432
+ #
433
+ # The remaining arguments are used for SMTP authentication, if required
434
+ # or desired. +user+ is the account name; +secret+ is your password
435
+ # or other authentication token; and +authtype+ is the authentication
436
+ # type, one of :plain, :login, or :cram_md5. See the discussion of
437
+ # SMTP Authentication in the overview notes.
438
+ #
439
+ # === Errors
440
+ #
441
+ # This method may raise:
442
+ #
443
+ # * Net::SMTPAuthenticationError
444
+ # * Net::SMTPServerBusy
445
+ # * Net::SMTPSyntaxError
446
+ # * Net::SMTPFatalError
447
+ # * Net::SMTPUnknownError
448
+ # * Net::OpenTimeout
449
+ # * Net::ReadTimeout
450
+ # * IOError
451
+ #
452
+ def SMTP.start(address, port = nil, helo = 'localhost',
453
+ user = nil, secret = nil, authtype = nil,
454
+ &block) # :yield: smtp
455
+ new(address, port).start(helo, user, secret, authtype, &block)
456
+ end
457
+
458
+ # +true+ if the SMTP session has been started.
459
+ def started?
460
+ @started
461
+ end
462
+
463
+ #
464
+ # Opens a TCP connection and starts the SMTP session.
465
+ #
466
+ # === Parameters
467
+ #
468
+ # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see
469
+ # the discussion in the overview notes.
470
+ #
471
+ # If both of +user+ and +secret+ are given, SMTP authentication
472
+ # will be attempted using the AUTH command. +authtype+ specifies
473
+ # the type of authentication to attempt; it must be one of
474
+ # :login, :plain, and :cram_md5. See the notes on SMTP Authentication
475
+ # in the overview.
476
+ #
477
+ # === Block Usage
478
+ #
479
+ # When this methods is called with a block, the newly-started SMTP
480
+ # object is yielded to the block, and automatically closed after
481
+ # the block call finishes. Otherwise, it is the caller's
482
+ # responsibility to close the session when finished.
483
+ #
484
+ # === Example
485
+ #
486
+ # This is very similar to the class method SMTP.start.
487
+ #
488
+ # require 'net/smtp'
489
+ # smtp = Net::SMTP.new('smtp.mail.server', 25)
490
+ # smtp.start(helo_domain, account, password, authtype) do |smtp|
491
+ # smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
492
+ # end
493
+ #
494
+ # The primary use of this method (as opposed to SMTP.start)
495
+ # is probably to set debugging (#set_debug_output) or ESMTP
496
+ # (#esmtp=), which must be done before the session is
497
+ # started.
498
+ #
499
+ # === Errors
500
+ #
501
+ # If session has already been started, an IOError will be raised.
502
+ #
503
+ # This method may raise:
504
+ #
505
+ # * Net::SMTPAuthenticationError
506
+ # * Net::SMTPServerBusy
507
+ # * Net::SMTPSyntaxError
508
+ # * Net::SMTPFatalError
509
+ # * Net::SMTPUnknownError
510
+ # * Net::OpenTimeout
511
+ # * Net::ReadTimeout
512
+ # * IOError
513
+ #
514
+ def start(helo = 'localhost',
515
+ user = nil, secret = nil, authtype = nil) # :yield: smtp
516
+ if block_given?
517
+ begin
518
+ do_start helo, user, secret, authtype
519
+ return yield(self)
520
+ ensure
521
+ do_finish
522
+ end
523
+ else
524
+ do_start helo, user, secret, authtype
525
+ return self
526
+ end
527
+ end
528
+
529
+ # Finishes the SMTP session and closes TCP connection.
530
+ # Raises IOError if not started.
531
+ def finish
532
+ raise IOError, 'not yet started' unless started?
533
+ do_finish
534
+ end
535
+
536
+ private
537
+
538
+ def tcp_socket(address, port)
539
+ TCPSocket.open address, port
540
+ end
541
+
542
+ def do_start(helo_domain, user, secret, authtype)
543
+ raise IOError, 'SMTP session already started' if @started
544
+ if user or secret
545
+ check_auth_method(authtype || DEFAULT_AUTH_TYPE)
546
+ check_auth_args user, secret
547
+ end
548
+ s = Timeout.timeout(@open_timeout, Net::OpenTimeout) do
549
+ tcp_socket(@address, @port)
550
+ end
551
+ logging "Connection opened: #{@address}:#{@port}"
552
+ @socket = new_internet_message_io(tls? ? tlsconnect(s) : s)
553
+ check_response critical { recv_response() }
554
+ do_helo helo_domain
555
+ if starttls_always? or (capable_starttls? and starttls_auto?)
556
+ unless capable_starttls?
557
+ raise SMTPUnsupportedCommand,
558
+ "STARTTLS is not supported on this server"
559
+ end
560
+ starttls
561
+ @socket = new_internet_message_io(tlsconnect(s))
562
+ # helo response may be different after STARTTLS
563
+ do_helo helo_domain
564
+ end
565
+ authenticate user, secret, (authtype || DEFAULT_AUTH_TYPE) if user
566
+ @started = true
567
+ ensure
568
+ unless @started
569
+ # authentication failed, cancel connection.
570
+ s.close if s
571
+ @socket = nil
572
+ end
573
+ end
574
+
575
+ def ssl_socket(socket, context)
576
+ OpenSSL::SSL::SSLSocket.new socket, context
577
+ end
578
+
579
+ def tlsconnect(s)
580
+ verified = false
581
+ s = ssl_socket(s, @ssl_context)
582
+ logging "TLS connection started"
583
+ s.sync_close = true
584
+ ssl_socket_connect(s, @open_timeout)
585
+ if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE
586
+ s.post_connection_check(@address)
587
+ end
588
+ verified = true
589
+ s
590
+ ensure
591
+ s.close unless verified
592
+ end
593
+
594
+ def new_internet_message_io(s)
595
+ InternetMessageIO.new(s, read_timeout: @read_timeout,
596
+ debug_output: @debug_output)
597
+ end
598
+
599
+ def do_helo(helo_domain)
600
+ res = @esmtp ? ehlo(helo_domain) : helo(helo_domain)
601
+ @capabilities = res.capabilities
602
+ rescue SMTPError
603
+ if @esmtp
604
+ @esmtp = false
605
+ @error_occurred = false
606
+ retry
607
+ end
608
+ raise
609
+ end
610
+
611
+ def do_finish
612
+ quit if @socket and not @socket.closed? and not @error_occurred
613
+ ensure
614
+ @started = false
615
+ @error_occurred = false
616
+ @socket.close if @socket
617
+ @socket = nil
618
+ end
619
+
620
+ #
621
+ # Message Sending
622
+ #
623
+
624
+ public
625
+
626
+ #
627
+ # Sends +msgstr+ as a message. Single CR ("\r") and LF ("\n") found
628
+ # in the +msgstr+, are converted into the CR LF pair. You cannot send a
629
+ # binary message with this method. +msgstr+ should include both
630
+ # the message headers and body.
631
+ #
632
+ # +from_addr+ is a String representing the source mail address.
633
+ #
634
+ # +to_addr+ is a String or Strings or Array of Strings, representing
635
+ # the destination mail address or addresses.
636
+ #
637
+ # === Example
638
+ #
639
+ # Net::SMTP.start('smtp.example.com') do |smtp|
640
+ # smtp.send_message msgstr,
641
+ # 'from@example.com',
642
+ # ['dest@example.com', 'dest2@example.com']
643
+ # end
644
+ #
645
+ # === Errors
646
+ #
647
+ # This method may raise:
648
+ #
649
+ # * Net::SMTPServerBusy
650
+ # * Net::SMTPSyntaxError
651
+ # * Net::SMTPFatalError
652
+ # * Net::SMTPUnknownError
653
+ # * Net::ReadTimeout
654
+ # * IOError
655
+ #
656
+ def send_message(msgstr, from_addr, *to_addrs)
657
+ raise IOError, 'closed session' unless @socket
658
+ mailfrom from_addr
659
+ rcptto_list(to_addrs) {data msgstr}
660
+ end
661
+
662
+ alias send_mail send_message
663
+ alias sendmail send_message # obsolete
664
+
665
+ #
666
+ # Opens a message writer stream and gives it to the block.
667
+ # The stream is valid only in the block, and has these methods:
668
+ #
669
+ # puts(str = ''):: outputs STR and CR LF.
670
+ # print(str):: outputs STR.
671
+ # printf(fmt, *args):: outputs sprintf(fmt,*args).
672
+ # write(str):: outputs STR and returns the length of written bytes.
673
+ # <<(str):: outputs STR and returns self.
674
+ #
675
+ # If a single CR ("\r") or LF ("\n") is found in the message,
676
+ # it is converted to the CR LF pair. You cannot send a binary
677
+ # message with this method.
678
+ #
679
+ # === Parameters
680
+ #
681
+ # +from_addr+ is a String representing the source mail address.
682
+ #
683
+ # +to_addr+ is a String or Strings or Array of Strings, representing
684
+ # the destination mail address or addresses.
685
+ #
686
+ # === Example
687
+ #
688
+ # Net::SMTP.start('smtp.example.com', 25) do |smtp|
689
+ # smtp.open_message_stream('from@example.com', ['dest@example.com']) do |f|
690
+ # f.puts 'From: from@example.com'
691
+ # f.puts 'To: dest@example.com'
692
+ # f.puts 'Subject: test message'
693
+ # f.puts
694
+ # f.puts 'This is a test message.'
695
+ # end
696
+ # end
697
+ #
698
+ # === Errors
699
+ #
700
+ # This method may raise:
701
+ #
702
+ # * Net::SMTPServerBusy
703
+ # * Net::SMTPSyntaxError
704
+ # * Net::SMTPFatalError
705
+ # * Net::SMTPUnknownError
706
+ # * Net::ReadTimeout
707
+ # * IOError
708
+ #
709
+ def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream
710
+ raise IOError, 'closed session' unless @socket
711
+ mailfrom from_addr
712
+ rcptto_list(to_addrs) {data(&block)}
713
+ end
714
+
715
+ alias ready open_message_stream # obsolete
716
+
717
+ #
718
+ # Authentication
719
+ #
720
+
721
+ public
722
+
723
+ DEFAULT_AUTH_TYPE = :plain
724
+
725
+ def authenticate(user, secret, authtype = DEFAULT_AUTH_TYPE)
726
+ check_auth_method authtype
727
+ check_auth_args user, secret
728
+ send auth_method(authtype), user, secret
729
+ end
730
+
731
+ def auth_plain(user, secret)
732
+ check_auth_args user, secret
733
+ res = critical {
734
+ get_response('AUTH PLAIN ' + base64_encode("\0#{user}\0#{secret}"))
735
+ }
736
+ check_auth_response res
737
+ res
738
+ end
739
+
740
+ def auth_login(user, secret)
741
+ check_auth_args user, secret
742
+ res = critical {
743
+ check_auth_continue get_response('AUTH LOGIN')
744
+ check_auth_continue get_response(base64_encode(user))
745
+ get_response(base64_encode(secret))
746
+ }
747
+ check_auth_response res
748
+ res
749
+ end
750
+
751
+ def auth_cram_md5(user, secret)
752
+ check_auth_args user, secret
753
+ res = critical {
754
+ res0 = get_response('AUTH CRAM-MD5')
755
+ check_auth_continue res0
756
+ crammed = cram_md5_response(secret, res0.cram_md5_challenge)
757
+ get_response(base64_encode("#{user} #{crammed}"))
758
+ }
759
+ check_auth_response res
760
+ res
761
+ end
762
+
763
+ private
764
+
765
+ def check_auth_method(type)
766
+ unless respond_to?(auth_method(type), true)
767
+ raise ArgumentError, "wrong authentication type #{type}"
768
+ end
769
+ end
770
+
771
+ def auth_method(type)
772
+ "auth_#{type.to_s.downcase}".intern
773
+ end
774
+
775
+ def check_auth_args(user, secret, authtype = DEFAULT_AUTH_TYPE)
776
+ unless user
777
+ raise ArgumentError, 'SMTP-AUTH requested but missing user name'
778
+ end
779
+ unless secret
780
+ raise ArgumentError, 'SMTP-AUTH requested but missing secret phrase'
781
+ end
782
+ end
783
+
784
+ def base64_encode(str)
785
+ # expects "str" may not become too long
786
+ [str].pack('m0')
787
+ end
788
+
789
+ IMASK = 0x36
790
+ OMASK = 0x5c
791
+
792
+ # CRAM-MD5: [RFC2195]
793
+ def cram_md5_response(secret, challenge)
794
+ tmp = Digest::MD5.digest(cram_secret(secret, IMASK) + challenge)
795
+ Digest::MD5.hexdigest(cram_secret(secret, OMASK) + tmp)
796
+ end
797
+
798
+ CRAM_BUFSIZE = 64
799
+
800
+ def cram_secret(secret, mask)
801
+ secret = Digest::MD5.digest(secret) if secret.size > CRAM_BUFSIZE
802
+ buf = secret.ljust(CRAM_BUFSIZE, "\0")
803
+ 0.upto(buf.size - 1) do |i|
804
+ buf[i] = (buf[i].ord ^ mask).chr
805
+ end
806
+ buf
807
+ end
808
+
809
+ #
810
+ # SMTP command dispatcher
811
+ #
812
+
813
+ public
814
+
815
+ # Aborts the current mail transaction
816
+
817
+ def rset
818
+ getok('RSET')
819
+ end
820
+
821
+ def starttls
822
+ getok('STARTTLS')
823
+ end
824
+
825
+ def helo(domain)
826
+ getok("HELO #{domain}")
827
+ end
828
+
829
+ def ehlo(domain)
830
+ getok("EHLO #{domain}")
831
+ end
832
+
833
+ def mailfrom(from_addr)
834
+ if $SAFE > 0
835
+ raise SecurityError, 'tainted from_addr' if from_addr.tainted?
836
+ end
837
+ getok("MAIL FROM:<#{from_addr}>")
838
+ end
839
+
840
+ def rcptto_list(to_addrs)
841
+ raise ArgumentError, 'mail destination not given' if to_addrs.empty?
842
+ ok_users = []
843
+ unknown_users = []
844
+ to_addrs.flatten.each do |addr|
845
+ begin
846
+ rcptto addr
847
+ rescue SMTPAuthenticationError
848
+ unknown_users << addr.dump
849
+ else
850
+ ok_users << addr
851
+ end
852
+ end
853
+ raise ArgumentError, 'mail destination not given' if ok_users.empty?
854
+ ret = yield
855
+ unless unknown_users.empty?
856
+ raise SMTPAuthenticationError, "failed to deliver for #{unknown_users.join(', ')}"
857
+ end
858
+ ret
859
+ end
860
+
861
+ def rcptto(to_addr)
862
+ if $SAFE > 0
863
+ raise SecurityError, 'tainted to_addr' if to_addr.tainted?
864
+ end
865
+ getok("RCPT TO:<#{to_addr}>")
866
+ end
867
+
868
+ # This method sends a message.
869
+ # If +msgstr+ is given, sends it as a message.
870
+ # If block is given, yield a message writer stream.
871
+ # You must write message before the block is closed.
872
+ #
873
+ # # Example 1 (by string)
874
+ # smtp.data(<<EndMessage)
875
+ # From: john@example.com
876
+ # To: betty@example.com
877
+ # Subject: I found a bug
878
+ #
879
+ # Check vm.c:58879.
880
+ # EndMessage
881
+ #
882
+ # # Example 2 (by block)
883
+ # smtp.data {|f|
884
+ # f.puts "From: john@example.com"
885
+ # f.puts "To: betty@example.com"
886
+ # f.puts "Subject: I found a bug"
887
+ # f.puts ""
888
+ # f.puts "Check vm.c:58879."
889
+ # }
890
+ #
891
+ def data(msgstr = nil, &block) #:yield: stream
892
+ if msgstr and block
893
+ raise ArgumentError, "message and block are exclusive"
894
+ end
895
+ unless msgstr or block
896
+ raise ArgumentError, "message or block is required"
897
+ end
898
+ res = critical {
899
+ check_continue get_response('DATA')
900
+ socket_sync_bak = @socket.io.sync
901
+ begin
902
+ @socket.io.sync = false
903
+ if msgstr
904
+ @socket.write_message msgstr
905
+ else
906
+ @socket.write_message_by_block(&block)
907
+ end
908
+ ensure
909
+ @socket.io.flush
910
+ @socket.io.sync = socket_sync_bak
911
+ end
912
+ recv_response()
913
+ }
914
+ check_response res
915
+ res
916
+ end
917
+
918
+ def quit
919
+ getok('QUIT')
920
+ end
921
+
922
+ private
923
+
924
+ def validate_line(line)
925
+ # A bare CR or LF is not allowed in RFC5321.
926
+ if /[\r\n]/ =~ line
927
+ raise ArgumentError, "A line must not contain CR or LF"
928
+ end
929
+ end
930
+
931
+ def getok(reqline)
932
+ validate_line reqline
933
+ res = critical {
934
+ @socket.writeline reqline
935
+ recv_response()
936
+ }
937
+ check_response res
938
+ res
939
+ end
940
+
941
+ def get_response(reqline)
942
+ validate_line reqline
943
+ @socket.writeline reqline
944
+ recv_response()
945
+ end
946
+
947
+ def recv_response
948
+ buf = ''.dup
949
+ while true
950
+ line = @socket.readline
951
+ buf << line << "\n"
952
+ break unless line[3,1] == '-' # "210-PIPELINING"
953
+ end
954
+ Response.parse(buf)
955
+ end
956
+
957
+ def critical
958
+ return Response.parse('200 dummy reply code') if @error_occurred
959
+ begin
960
+ return yield()
961
+ rescue Exception
962
+ @error_occurred = true
963
+ raise
964
+ end
965
+ end
966
+
967
+ def check_response(res)
968
+ unless res.success?
969
+ raise res.exception_class, res.message
970
+ end
971
+ end
972
+
973
+ def check_continue(res)
974
+ unless res.continue?
975
+ raise SMTPUnknownError, "could not get 3xx (#{res.status}: #{res.string})"
976
+ end
977
+ end
978
+
979
+ def check_auth_response(res)
980
+ unless res.success?
981
+ raise SMTPAuthenticationError, res.message
982
+ end
983
+ end
984
+
985
+ def check_auth_continue(res)
986
+ unless res.continue?
987
+ raise res.exception_class, res.message
988
+ end
989
+ end
990
+
991
+ # This class represents a response received by the SMTP server. Instances
992
+ # of this class are created by the SMTP class; they should not be directly
993
+ # created by the user. For more information on SMTP responses, view
994
+ # {Section 4.2 of RFC 5321}[http://tools.ietf.org/html/rfc5321#section-4.2]
995
+ class Response
996
+ # Parses the received response and separates the reply code and the human
997
+ # readable reply text
998
+ def self.parse(str)
999
+ new(str[0,3], str)
1000
+ end
1001
+
1002
+ # Creates a new instance of the Response class and sets the status and
1003
+ # string attributes
1004
+ def initialize(status, string)
1005
+ @status = status
1006
+ @string = string
1007
+ end
1008
+
1009
+ # The three digit reply code of the SMTP response
1010
+ attr_reader :status
1011
+
1012
+ # The human readable reply text of the SMTP response
1013
+ attr_reader :string
1014
+
1015
+ # Takes the first digit of the reply code to determine the status type
1016
+ def status_type_char
1017
+ @status[0, 1]
1018
+ end
1019
+
1020
+ # Determines whether the response received was a Positive Completion
1021
+ # reply (2xx reply code)
1022
+ def success?
1023
+ status_type_char() == '2'
1024
+ end
1025
+
1026
+ # Determines whether the response received was a Positive Intermediate
1027
+ # reply (3xx reply code)
1028
+ def continue?
1029
+ status_type_char() == '3'
1030
+ end
1031
+
1032
+ # The first line of the human readable reply text
1033
+ def message
1034
+ @string.lines.first
1035
+ end
1036
+
1037
+ # Creates a CRAM-MD5 challenge. You can view more information on CRAM-MD5
1038
+ # on Wikipedia: https://en.wikipedia.org/wiki/CRAM-MD5
1039
+ def cram_md5_challenge
1040
+ @string.split(/ /)[1].unpack1('m')
1041
+ end
1042
+
1043
+ # Returns a hash of the human readable reply text in the response if it
1044
+ # is multiple lines. It does not return the first line. The key of the
1045
+ # hash is the first word the value of the hash is an array with each word
1046
+ # thereafter being a value in the array
1047
+ def capabilities
1048
+ return {} unless @string[3, 1] == '-'
1049
+ h = {}
1050
+ @string.lines.drop(1).each do |line|
1051
+ k, *v = line[4..-1].chomp.split
1052
+ h[k] = v
1053
+ end
1054
+ h
1055
+ end
1056
+
1057
+ # Determines whether there was an error and raises the appropriate error
1058
+ # based on the reply code of the response
1059
+ def exception_class
1060
+ case @status
1061
+ when /\A4/ then SMTPServerBusy
1062
+ when /\A50/ then SMTPSyntaxError
1063
+ when /\A53/ then SMTPAuthenticationError
1064
+ when /\A5/ then SMTPFatalError
1065
+ else SMTPUnknownError
1066
+ end
1067
+ end
1068
+ end
1069
+
1070
+ def logging(msg)
1071
+ @debug_output << msg + "\n" if @debug_output
1072
+ end
1073
+
1074
+ end # class SMTP
1075
+
1076
+ SMTPSession = SMTP # :nodoc:
1077
+
1078
+ end
@@ -0,0 +1,5 @@
1
+ module Net
2
+ module SMTP
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,25 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "net/smtp/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "net-smtp"
7
+ spec.version = Net::SMTP::VERSION
8
+ spec.authors = ["Hiroshi SHIBATA"]
9
+ spec.email = ["hsbt@ruby-lang.org"]
10
+
11
+ spec.summary = %q{Simple Mail Transfer Protocol client library for Ruby.}
12
+ spec.description = %q{Simple Mail Transfer Protocol client library for Ruby.}
13
+ spec.homepage = "https://github.com/ruby/net-smtp"
14
+ spec.license = "BSD-2-Clause"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: net-smtp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Hiroshi SHIBATA
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-11-06 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Simple Mail Transfer Protocol client library for Ruby.
14
+ email:
15
+ - hsbt@ruby-lang.org
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".travis.yml"
22
+ - Gemfile
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - bin/console
27
+ - bin/setup
28
+ - lib/net/smtp.rb
29
+ - lib/net/smtp/version.rb
30
+ - net-smtp.gemspec
31
+ homepage: https://github.com/ruby/net-smtp
32
+ licenses:
33
+ - BSD-2-Clause
34
+ metadata:
35
+ homepage_uri: https://github.com/ruby/net-smtp
36
+ source_code_uri: https://github.com/ruby/net-smtp
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubygems_version: 3.0.3
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Simple Mail Transfer Protocol client library for Ruby.
56
+ test_files: []