net-smtp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []