tlsmail 0.0.1

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