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