rubysl-net-smtp 1.0.0

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