tlsmail 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,808 @@
1
+ # = net/smtp.rb
2
+ #
3
+ # Copyright (c) 1999-2004 Yukihiro Matsumoto.
4
+ #
5
+ # Copyright (c) 1999-2004 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/index.cgi?cmd=view;name=net%2Fsmtp.rb
16
+ #
17
+ # $Id: smtp.rb 10709 2006-08-09 07:56:30Z matz $
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 module for some reason.
35
+ # In ruby 1.9.x, 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
+ #
64
+ # = Net::SMTP
65
+ #
66
+ # == What is This Library?
67
+ #
68
+ # This library provides functionality to send internet
69
+ # mail via SMTP, the Simple Mail Transfer Protocol. For details of
70
+ # SMTP itself, see [RFC2821] (http://www.ietf.org/rfc/rfc2821.txt).
71
+ #
72
+ # == What is This Library NOT?
73
+ #
74
+ # This library does NOT provide functions to compose internet mails.
75
+ # You must create them by yourself. If you want better mail support,
76
+ # try RubyMail or TMail. You can get both libraries from RAA.
77
+ # (http://www.ruby-lang.org/en/raa.html)
78
+ #
79
+ # FYI: the official documentation on internet mail is: [RFC2822] (http://www.ietf.org/rfc/rfc2822.txt).
80
+ #
81
+ # == Examples
82
+ #
83
+ # === Sending Messages
84
+ #
85
+ # You must open a connection to an SMTP server before sending messages.
86
+ # The first argument is the address of your SMTP server, and the second
87
+ # argument is the port number. Using SMTP.start with a block is the simplest
88
+ # way to do this. This way, the SMTP connection is closed automatically
89
+ # after the block is executed.
90
+ #
91
+ # require 'net/smtp'
92
+ # Net::SMTP.start('your.smtp.server', 25) do |smtp|
93
+ # # Use the SMTP object smtp only in this block.
94
+ # end
95
+ #
96
+ # Replace 'your.smtp.server' with your SMTP server. Normally
97
+ # your system manager or internet provider supplies a server
98
+ # for you.
99
+ #
100
+ # Then you can send messages.
101
+ #
102
+ # msgstr = <<END_OF_MESSAGE
103
+ # From: Your Name <your@mail.address>
104
+ # To: Destination Address <someone@example.com>
105
+ # Subject: test message
106
+ # Date: Sat, 23 Jun 2001 16:26:43 +0900
107
+ # Message-Id: <unique.message.id.string@example.com>
108
+ #
109
+ # This is a test message.
110
+ # END_OF_MESSAGE
111
+ #
112
+ # require 'net/smtp'
113
+ # Net::SMTP.start('your.smtp.server', 25) do |smtp|
114
+ # smtp.send_message msgstr,
115
+ # 'your@mail.address',
116
+ # 'his_addess@example.com'
117
+ # end
118
+ #
119
+ # === Closing the Session
120
+ #
121
+ # You MUST close the SMTP session after sending messages, by calling
122
+ # the #finish method:
123
+ #
124
+ # # using SMTP#finish
125
+ # smtp = Net::SMTP.start('your.smtp.server', 25)
126
+ # smtp.send_message msgstr, 'from@address', 'to@address'
127
+ # smtp.finish
128
+ #
129
+ # You can also use the block form of SMTP.start/SMTP#start. This closes
130
+ # the SMTP session automatically:
131
+ #
132
+ # # using block form of SMTP.start
133
+ # Net::SMTP.start('your.smtp.server', 25) do |smtp|
134
+ # smtp.send_message msgstr, 'from@address', 'to@address'
135
+ # end
136
+ #
137
+ # I strongly recommend this scheme. This form is simpler and more robust.
138
+ #
139
+ # === HELO domain
140
+ #
141
+ # In almost all situations, you must provide a third argument
142
+ # to SMTP.start/SMTP#start. This is the domain name which you are on
143
+ # (the host to send mail from). It is called the "HELO domain".
144
+ # The SMTP server will judge whether it should send or reject
145
+ # the SMTP session by inspecting the HELO domain.
146
+ #
147
+ # Net::SMTP.start('your.smtp.server', 25,
148
+ # 'mail.from.domain') { |smtp| ... }
149
+ #
150
+ # === SMTP Authentication
151
+ #
152
+ # The Net::SMTP class supports three authentication schemes;
153
+ # PLAIN, LOGIN and CRAM MD5. (SMTP Authentication: [RFC2554])
154
+ # To use SMTP authentication, pass extra arguments to
155
+ # SMTP.start/SMTP#start.
156
+ #
157
+ # # PLAIN
158
+ # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
159
+ # 'Your Account', 'Your Password', :plain)
160
+ # # LOGIN
161
+ # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
162
+ # 'Your Account', 'Your Password', :login)
163
+ #
164
+ # # CRAM MD5
165
+ # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
166
+ # 'Your Account', 'Your Password', :cram_md5)
167
+ #
168
+ class SMTP
169
+
170
+ Revision = %q$Revision: 10709 $.split[1]
171
+
172
+ # The default SMTP port, port 25.
173
+ def SMTP.default_port
174
+ 25
175
+ end
176
+
177
+ @use_tls = false
178
+ @verify = nil
179
+ @certs = nil
180
+
181
+ # Enable SSL for all new instances.
182
+ # +verify+ is the type of verification to do on the Server Cert; Defaults
183
+ # to OpenSSL::SSL::VERIFY_PEER.
184
+ # +certs+ is a file or directory holding CA certs to use to verify the
185
+ # server cert; Defaults to nil.
186
+ def SMTP.enable_tls(verify = OpenSSL::SSL::VERIFY_PEER, certs = nil)
187
+ @use_tls = true
188
+ @verify = verify
189
+ @certs = certs
190
+ end
191
+
192
+ # Disable SSL for all new instances.
193
+ def SMTP.disable_tls
194
+ @use_tls = nil
195
+ @verify = nil
196
+ @certs = nil
197
+ end
198
+
199
+ def SMTP.use_tls?
200
+ @use_tls
201
+ end
202
+
203
+ def SMTP.verify
204
+ @verify
205
+ end
206
+
207
+ def SMTP.certs
208
+ @certs
209
+ end
210
+
211
+ #
212
+ # Creates a new Net::SMTP object.
213
+ #
214
+ # +address+ is the hostname or ip address of your SMTP
215
+ # server. +port+ is the port to connect to; it defaults to
216
+ # port 25.
217
+ #
218
+ # This method does not open the TCP connection. You can use
219
+ # SMTP.start instead of SMTP.new if you want to do everything
220
+ # at once. Otherwise, follow SMTP.new with SMTP#start.
221
+ #
222
+ def initialize(address, port = nil)
223
+ @address = address
224
+ @port = (port || SMTP.default_port)
225
+ @esmtp = true
226
+ @socket = nil
227
+ @started = false
228
+ @open_timeout = 30
229
+ @read_timeout = 60
230
+ @error_occured = false
231
+ @debug_output = nil
232
+ @use_tls = SMTP.use_tls?
233
+ @certs = SMTP.certs
234
+ @verify = SMTP.verify
235
+ end
236
+
237
+ # Provide human-readable stringification of class state.
238
+ def inspect
239
+ "#<#{self.class} #{@address}:#{@port} started=#{@started}>"
240
+ end
241
+
242
+ # +true+ if the SMTP object uses ESMTP (which it does by default).
243
+ def esmtp?
244
+ @esmtp
245
+ end
246
+
247
+ #
248
+ # Set whether to use ESMTP or not. This should be done before
249
+ # calling #start. Note that if #start is called in ESMTP mode,
250
+ # and the connection fails due to a ProtocolError, the SMTP
251
+ # object will automatically switch to plain SMTP mode and
252
+ # retry (but not vice versa).
253
+ #
254
+ def esmtp=(bool)
255
+ @esmtp = bool
256
+ end
257
+
258
+ alias esmtp esmtp?
259
+
260
+ # does this instance use SSL?
261
+ def use_tls?
262
+ @use_tls
263
+ end
264
+
265
+ # Enables STARTTLS for this instance.
266
+ # +verify+ is the type of verification to do on the Server Cert; Defaults
267
+ # to OpenSSL::SSL::VERIFY_PEER.
268
+ # +certs+ is a file or directory holding CA certs to use to verify the
269
+ # server cert; Defaults to nil.
270
+ def enable_tls(verify = OpenSSL::SSL::VERIFY_PEER, certs = nil)
271
+ @use_tls = true
272
+ @verify = verify
273
+ @certs = certs
274
+ end
275
+
276
+ def disable_tls
277
+ @use_tls = false
278
+ @verify = nil
279
+ @certs = nil
280
+ end
281
+
282
+ # The address of the SMTP server to connect to.
283
+ attr_reader :address
284
+
285
+ # The port number of the SMTP server to connect to.
286
+ attr_reader :port
287
+
288
+ # Seconds to wait while attempting to open a connection.
289
+ # If the connection cannot be opened within this time, a
290
+ # TimeoutError is raised.
291
+ attr_accessor :open_timeout
292
+
293
+ # Seconds to wait while reading one block (by one read(2) call).
294
+ # If the read(2) call does not complete within this time, a
295
+ # TimeoutError is raised.
296
+ attr_reader :read_timeout
297
+
298
+ # Set the number of seconds to wait until timing-out a read(2)
299
+ # call.
300
+ def read_timeout=(sec)
301
+ @socket.read_timeout = sec if @socket
302
+ @read_timeout = sec
303
+ end
304
+
305
+ #
306
+ # WARNING: This method causes serious security holes.
307
+ # Use this method for only debugging.
308
+ #
309
+ # Set an output stream for debug logging.
310
+ # You must call this before #start.
311
+ #
312
+ # # example
313
+ # smtp = Net::SMTP.new(addr, port)
314
+ # smtp.set_debug_output $stderr
315
+ # smtp.start do |smtp|
316
+ # ....
317
+ # end
318
+ #
319
+ def set_debug_output(arg)
320
+ @debug_output = arg
321
+ end
322
+
323
+ #
324
+ # SMTP session control
325
+ #
326
+
327
+ #
328
+ # Creates a new Net::SMTP object and connects to the server.
329
+ #
330
+ # This method is equivalent to:
331
+ #
332
+ # Net::SMTP.new(address, port).start(helo_domain, account, password, authtype)
333
+ #
334
+ # === Example
335
+ #
336
+ # Net::SMTP.start('your.smtp.server') do |smtp|
337
+ # smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
338
+ # end
339
+ #
340
+ # === Block Usage
341
+ #
342
+ # If called with a block, the newly-opened Net::SMTP object is yielded
343
+ # to the block, and automatically closed when the block finishes. If called
344
+ # without a block, the newly-opened Net::SMTP object is returned to
345
+ # the caller, and it is the caller's responsibility to close it when
346
+ # finished.
347
+ #
348
+ # === Parameters
349
+ #
350
+ # +address+ is the hostname or ip address of your smtp server.
351
+ #
352
+ # +port+ is the port to connect to; it defaults to port 25.
353
+ #
354
+ # +helo+ is the _HELO_ _domain_ provided by the client to the
355
+ # server (see overview comments); it defaults to 'localhost.localdomain'.
356
+ #
357
+ # The remaining arguments are used for SMTP authentication, if required
358
+ # or desired. +user+ is the account name; +secret+ is your password
359
+ # or other authentication token; and +authtype+ is the authentication
360
+ # type, one of :plain, :login, or :cram_md5. See the discussion of
361
+ # SMTP Authentication in the overview notes.
362
+ #
363
+ # === Errors
364
+ #
365
+ # This method may raise:
366
+ #
367
+ # * Net::SMTPAuthenticationError
368
+ # * Net::SMTPServerBusy
369
+ # * Net::SMTPSyntaxError
370
+ # * Net::SMTPFatalError
371
+ # * Net::SMTPUnknownError
372
+ # * IOError
373
+ # * TimeoutError
374
+ #
375
+ def SMTP.start(address, port = nil, helo = 'localhost.localdomain',
376
+ user = nil, secret = nil, authtype = nil,
377
+ &block) # :yield: smtp
378
+ new(address, port).start(helo, user, secret, authtype, &block)
379
+ end
380
+
381
+ # +true+ if the SMTP session has been started.
382
+ def started?
383
+ @started
384
+ end
385
+
386
+ #
387
+ # Opens a TCP connection and starts the SMTP session.
388
+ #
389
+ # === Parameters
390
+ #
391
+ # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see
392
+ # the discussion in the overview notes.
393
+ #
394
+ # If both of +user+ and +secret+ are given, SMTP authentication
395
+ # will be attempted using the AUTH command. +authtype+ specifies
396
+ # the type of authentication to attempt; it must be one of
397
+ # :login, :plain, and :cram_md5. See the notes on SMTP Authentication
398
+ # in the overview.
399
+ #
400
+ # === Block Usage
401
+ #
402
+ # When this methods is called with a block, the newly-started SMTP
403
+ # object is yielded to the block, and automatically closed after
404
+ # the block call finishes. Otherwise, it is the caller's
405
+ # responsibility to close the session when finished.
406
+ #
407
+ # === Example
408
+ #
409
+ # This is very similar to the class method SMTP.start.
410
+ #
411
+ # require 'net/smtp'
412
+ # smtp = Net::SMTP.new('smtp.mail.server', 25)
413
+ # smtp.start(helo_domain, account, password, authtype) do |smtp|
414
+ # smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
415
+ # end
416
+ #
417
+ # The primary use of this method (as opposed to SMTP.start)
418
+ # is probably to set debugging (#set_debug_output) or ESMTP
419
+ # (#esmtp=), which must be done before the session is
420
+ # started.
421
+ #
422
+ # === Errors
423
+ #
424
+ # If session has already been started, an IOError will be raised.
425
+ #
426
+ # This method may raise:
427
+ #
428
+ # * Net::SMTPAuthenticationError
429
+ # * Net::SMTPServerBusy
430
+ # * Net::SMTPSyntaxError
431
+ # * Net::SMTPFatalError
432
+ # * Net::SMTPUnknownError
433
+ # * IOError
434
+ # * TimeoutError
435
+ #
436
+ def start(helo = 'localhost.localdomain',
437
+ user = nil, secret = nil, authtype = nil) # :yield: smtp
438
+ if block_given?
439
+ begin
440
+ do_start(helo, user, secret, authtype)
441
+ return yield(self)
442
+ ensure
443
+ do_finish
444
+ end
445
+ else
446
+ do_start(helo, user, secret, authtype)
447
+ return self
448
+ end
449
+ end
450
+
451
+ def do_start(helodomain, user, secret, authtype)
452
+ raise IOError, 'SMTP session already started' if @started
453
+ check_auth_args user, secret, authtype if user or secret
454
+ s = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
455
+ @socket = InternetMessageIO.new(s)
456
+
457
+ logging "SMTP session opened: #{@address}:#{@port}"
458
+ @socket.read_timeout = @read_timeout
459
+ @socket.debug_output = @debug_output
460
+ check_response(critical { recv_response() })
461
+ do_helo(helodomain)
462
+
463
+ if @use_tls
464
+ raise 'openssl library not installed' unless defined?(OpenSSL)
465
+ context = OpenSSL::SSL::SSLContext.new
466
+ context.verify_mode = @verify
467
+ if @certs
468
+ if File.file?(@certs)
469
+ context.ca_file = @certs
470
+ elsif File.directory?(@certs)
471
+ context.ca_path = @certs
472
+ else
473
+ raise ArgumentError, "certs given but is not file or directory: #{@certs}"
474
+ end
475
+ end
476
+ s = OpenSSL::SSL::SSLSocket.new(s, context)
477
+ s.sync_close = true
478
+ starttls
479
+ s.connect
480
+ logging 'TLS started'
481
+ @socket = InternetMessageIO.new(s)
482
+ @socket.read_timeout = @read_timeout
483
+ @socket.debug_output = @debug_output
484
+ # helo response may be different after STARTTLS
485
+ do_helo(helodomain)
486
+ end
487
+
488
+ authenticate user, secret, authtype if user
489
+ @started = true
490
+ ensure
491
+ unless @started
492
+ # authentication failed, cancel connection.
493
+ s.close if s and not s.closed?
494
+ @socket = nil
495
+ end
496
+ end
497
+ private :do_start
498
+
499
+ # method to send helo or ehlo based on defaults and to
500
+ # retry with helo if server doesn't like ehlo.
501
+ #
502
+ def do_helo(helodomain)
503
+ begin
504
+ if @esmtp
505
+ ehlo helodomain
506
+ else
507
+ helo helodomain
508
+ end
509
+ rescue ProtocolError
510
+ if @esmtp
511
+ @esmtp = false
512
+ @error_occured = false
513
+ retry
514
+ end
515
+ raise
516
+ end
517
+ end
518
+
519
+
520
+ # Finishes the SMTP session and closes TCP connection.
521
+ # Raises IOError if not started.
522
+ def finish
523
+ raise IOError, 'not yet started' unless started?
524
+ do_finish
525
+ end
526
+
527
+ def do_finish
528
+ quit if @socket and not @socket.closed? and not @error_occured
529
+ ensure
530
+ @started = false
531
+ @error_occured = false
532
+ @socket.close if @socket and not @socket.closed?
533
+ @socket = nil
534
+ end
535
+ private :do_finish
536
+
537
+ #
538
+ # message send
539
+ #
540
+
541
+ public
542
+
543
+ #
544
+ # Sends +msgstr+ as a message. Single CR ("\r") and LF ("\n") found
545
+ # in the +msgstr+, are converted into the CR LF pair. You cannot send a
546
+ # binary message with this method. +msgstr+ should include both
547
+ # the message headers and body.
548
+ #
549
+ # +from_addr+ is a String representing the source mail address.
550
+ #
551
+ # +to_addr+ is a String or Strings or Array of Strings, representing
552
+ # the destination mail address or addresses.
553
+ #
554
+ # === Example
555
+ #
556
+ # Net::SMTP.start('smtp.example.com') do |smtp|
557
+ # smtp.send_message msgstr,
558
+ # 'from@example.com',
559
+ # ['dest@example.com', 'dest2@example.com']
560
+ # end
561
+ #
562
+ # === Errors
563
+ #
564
+ # This method may raise:
565
+ #
566
+ # * Net::SMTPServerBusy
567
+ # * Net::SMTPSyntaxError
568
+ # * Net::SMTPFatalError
569
+ # * Net::SMTPUnknownError
570
+ # * IOError
571
+ # * TimeoutError
572
+ #
573
+ def send_message(msgstr, from_addr, *to_addrs)
574
+ send0(from_addr, to_addrs.flatten) {
575
+ @socket.write_message msgstr
576
+ }
577
+ end
578
+
579
+ alias send_mail send_message
580
+ alias sendmail send_message # obsolete
581
+
582
+ #
583
+ # Opens a message writer stream and gives it to the block.
584
+ # The stream is valid only in the block, and has these methods:
585
+ #
586
+ # puts(str = ''):: outputs STR and CR LF.
587
+ # print(str):: outputs STR.
588
+ # printf(fmt, *args):: outputs sprintf(fmt,*args).
589
+ # write(str):: outputs STR and returns the length of written bytes.
590
+ # <<(str):: outputs STR and returns self.
591
+ #
592
+ # If a single CR ("\r") or LF ("\n") is found in the message,
593
+ # it is converted to the CR LF pair. You cannot send a binary
594
+ # message with this method.
595
+ #
596
+ # === Parameters
597
+ #
598
+ # +from_addr+ is a String representing the source mail address.
599
+ #
600
+ # +to_addr+ is a String or Strings or Array of Strings, representing
601
+ # the destination mail address or addresses.
602
+ #
603
+ # === Example
604
+ #
605
+ # Net::SMTP.start('smtp.example.com', 25) do |smtp|
606
+ # smtp.open_message_stream('from@example.com', ['dest@example.com']) do |f|
607
+ # f.puts 'From: from@example.com'
608
+ # f.puts 'To: dest@example.com'
609
+ # f.puts 'Subject: test message'
610
+ # f.puts
611
+ # f.puts 'This is a test message.'
612
+ # end
613
+ # end
614
+ #
615
+ # === Errors
616
+ #
617
+ # This method may raise:
618
+ #
619
+ # * Net::SMTPServerBusy
620
+ # * Net::SMTPSyntaxError
621
+ # * Net::SMTPFatalError
622
+ # * Net::SMTPUnknownError
623
+ # * IOError
624
+ # * TimeoutError
625
+ #
626
+ def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream
627
+ send0(from_addr, to_addrs.flatten) {
628
+ @socket.write_message_by_block(&block)
629
+ }
630
+ end
631
+
632
+ alias ready open_message_stream # obsolete
633
+
634
+ private
635
+
636
+ def send0(from_addr, to_addrs)
637
+ raise IOError, 'closed session' unless @socket
638
+ raise ArgumentError, 'mail destination not given' if to_addrs.empty?
639
+ if $SAFE > 0
640
+ raise SecurityError, 'tainted from_addr' if from_addr.tainted?
641
+ to_addrs.each do |to|
642
+ raise SecurityError, 'tainted to_addr' if to.tainted?
643
+ end
644
+ end
645
+
646
+ mailfrom from_addr
647
+ to_addrs.each do |to|
648
+ rcptto to
649
+ end
650
+ res = critical {
651
+ check_response(get_response('DATA'), true)
652
+ yield
653
+ recv_response()
654
+ }
655
+ check_response(res)
656
+ end
657
+
658
+ #
659
+ # auth
660
+ #
661
+
662
+ private
663
+
664
+ def check_auth_args(user, secret, authtype)
665
+ raise ArgumentError, 'both user and secret are required'\
666
+ unless user and secret
667
+ auth_method = "auth_#{authtype || 'cram_md5'}"
668
+ raise ArgumentError, "wrong auth type #{authtype}"\
669
+ unless respond_to?(auth_method, true)
670
+ end
671
+
672
+ def authenticate(user, secret, authtype)
673
+ __send__("auth_#{authtype || 'cram_md5'}", user, secret)
674
+ end
675
+
676
+ def auth_plain(user, secret)
677
+ res = critical { get_response('AUTH PLAIN %s',
678
+ base64_encode("\0#{user}\0#{secret}")) }
679
+ raise SMTPAuthenticationError, res unless /\A2../ === res
680
+ end
681
+
682
+ def auth_login(user, secret)
683
+ res = critical {
684
+ check_response(get_response('AUTH LOGIN'), true)
685
+ check_response(get_response(base64_encode(user)), true)
686
+ get_response(base64_encode(secret))
687
+ }
688
+ raise SMTPAuthenticationError, res unless /\A2../ === res
689
+ end
690
+
691
+ def auth_cram_md5(user, secret)
692
+ # CRAM-MD5: [RFC2195]
693
+ res = nil
694
+ critical {
695
+ res = check_response(get_response('AUTH CRAM-MD5'), true)
696
+ challenge = res.split(/ /)[1].unpack('m')[0]
697
+ secret = Digest::MD5.digest(secret) if secret.size > 64
698
+
699
+ isecret = secret + "\0" * (64 - secret.size)
700
+ osecret = isecret.dup
701
+ 0.upto(63) do |i|
702
+ c = isecret[i].ord ^ 0x36
703
+ isecret[i] = c.chr
704
+ c = osecret[i].ord ^ 0x5c
705
+ osecret[i] = c.chr
706
+ end
707
+ tmp = Digest::MD5.digest(isecret + challenge)
708
+ tmp = Digest::MD5.hexdigest(osecret + tmp)
709
+
710
+ res = get_response(base64_encode(user + ' ' + tmp))
711
+ }
712
+ raise SMTPAuthenticationError, res unless /\A2../ === res
713
+ end
714
+
715
+ def base64_encode(str)
716
+ # expects "str" may not become too long
717
+ [str].pack('m').gsub(/\s+/, '')
718
+ end
719
+
720
+ #
721
+ # SMTP command dispatcher
722
+ #
723
+
724
+ private
725
+
726
+ def helo(domain)
727
+ getok('HELO %s', domain)
728
+ end
729
+
730
+ def ehlo(domain)
731
+ getok('EHLO %s', domain)
732
+ end
733
+
734
+ def mailfrom(fromaddr)
735
+ getok('MAIL FROM:<%s>', fromaddr)
736
+ end
737
+
738
+ def rcptto(to)
739
+ getok('RCPT TO:<%s>', to)
740
+ end
741
+
742
+ def quit
743
+ getok('QUIT')
744
+ end
745
+
746
+ def starttls
747
+ getok('STARTTLS')
748
+ end
749
+ #
750
+ # row level library
751
+ #
752
+
753
+ private
754
+
755
+ def getok(fmt, *args)
756
+ res = critical {
757
+ @socket.writeline sprintf(fmt, *args)
758
+ recv_response()
759
+ }
760
+ return check_response(res)
761
+ end
762
+
763
+ def get_response(fmt, *args)
764
+ @socket.writeline sprintf(fmt, *args)
765
+ recv_response()
766
+ end
767
+
768
+ def recv_response
769
+ res = ''
770
+ while true
771
+ line = @socket.readline
772
+ res << line << "\n"
773
+ break unless line[3] == ?- # "210-PIPELINING"
774
+ end
775
+ res
776
+ end
777
+
778
+ def check_response(res, allow_continue = false)
779
+ return res if /\A2/ === res
780
+ return res if allow_continue and /\A3/ === res
781
+ err = case res
782
+ when /\A4/ then SMTPServerBusy
783
+ when /\A50/ then SMTPSyntaxError
784
+ when /\A55/ then SMTPFatalError
785
+ else SMTPUnknownError
786
+ end
787
+ raise err, res
788
+ end
789
+
790
+ def critical(&block)
791
+ return '200 dummy reply code' if @error_occured
792
+ begin
793
+ return yield()
794
+ rescue Exception
795
+ @error_occured = true
796
+ raise
797
+ end
798
+ end
799
+
800
+ def logging(msg)
801
+ @debug_output << msg + "\n" if @debug_output
802
+ end
803
+
804
+ end # class SMTP
805
+
806
+ SMTPSession = SMTP
807
+
808
+ end # module Net