Project

General

Profile

RE: Problems setting up email notifications » smtp.rb

Konstantin Strelkov, 2012-10-17 11:31

 
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: smtp.rb 31710 2011-05-23 00:21:10Z drbrain $
18
#
19
# See Net::SMTP for documentation.
20
#
21

    
22
require 'net/protocol'
23
require 'digest/md5'
24
require 'timeout'
25
require 'net/ntlm'
26
begin
27
  require 'openssl'
28
rescue LoadError
29
end
30

    
31
module Net
32

    
33
  # Module mixed in to all SMTP error classes
34
  module SMTPError
35
    # This *class* is a module for backward compatibility.
36
    # In later release, this module becomes a class.
37
  end
38

    
39
  # Represents an SMTP authentication error.
40
  class SMTPAuthenticationError < ProtoAuthError
41
    include SMTPError
42
  end
43

    
44
  # Represents SMTP error code 420 or 450, a temporary error.
45
  class SMTPServerBusy < ProtoServerError
46
    include SMTPError
47
  end
48

    
49
  # Represents an SMTP command syntax error (error code 500)
50
  class SMTPSyntaxError < ProtoSyntaxError
51
    include SMTPError
52
  end
53

    
54
  # Represents a fatal SMTP error (error code 5xx, except for 500)
55
  class SMTPFatalError < ProtoFatalError
56
    include SMTPError
57
  end
58

    
59
  # Unexpected reply code returned from server.
60
  class SMTPUnknownError < ProtoUnknownError
61
    include SMTPError
62
  end
63

    
64
  # Command is not supported on server.
65
  class SMTPUnsupportedCommand < ProtocolError
66
    include SMTPError
67
  end
68

    
69
  #
70
  # = Net::SMTP
71
  #
72
  # == What is This Library?
73
  #
74
  # This library provides functionality to send internet
75
  # mail via SMTP, the Simple Mail Transfer Protocol. For details of
76
  # SMTP itself, see [RFC2821] (http://www.ietf.org/rfc/rfc2821.txt).
77
  #
78
  # == What is This Library NOT?
79
  #
80
  # This library does NOT provide functions to compose internet mails.
81
  # You must create them by yourself. If you want better mail support,
82
  # try RubyMail or TMail. You can get both libraries from RAA.
83
  # (http://www.ruby-lang.org/en/raa.html)
84
  #
85
  # FYI: the official documentation on internet mail is: [RFC2822] (http://www.ietf.org/rfc/rfc2822.txt).
86
  #
87
  # == Examples
88
  #
89
  # === Sending Messages
90
  #
91
  # You must open a connection to an SMTP server before sending messages.
92
  # The first argument is the address of your SMTP server, and the second
93
  # argument is the port number. Using SMTP.start with a block is the simplest
94
  # way to do this. This way, the SMTP connection is closed automatically
95
  # after the block is executed.
96
  #
97
  #     require 'net/smtp'
98
  #     Net::SMTP.start('your.smtp.server', 25) do |smtp|
99
  #       # Use the SMTP object smtp only in this block.
100
  #     end
101
  #
102
  # Replace 'your.smtp.server' with your SMTP server. Normally
103
  # your system manager or internet provider supplies a server
104
  # for you.
105
  #
106
  # Then you can send messages.
107
  #
108
  #     msgstr = <<END_OF_MESSAGE
109
  #     From: Your Name <your@mail.address>
110
  #     To: Destination Address <someone@example.com>
111
  #     Subject: test message
112
  #     Date: Sat, 23 Jun 2001 16:26:43 +0900
113
  #     Message-Id: <unique.message.id.string@example.com>
114
  #
115
  #     This is a test message.
116
  #     END_OF_MESSAGE
117
  #
118
  #     require 'net/smtp'
119
  #     Net::SMTP.start('your.smtp.server', 25) do |smtp|
120
  #       smtp.send_message msgstr,
121
  #                         'your@mail.address',
122
  #                         'his_address@example.com'
123
  #     end
124
  #
125
  # === Closing the Session
126
  #
127
  # You MUST close the SMTP session after sending messages, by calling
128
  # the #finish method:
129
  #
130
  #     # using SMTP#finish
131
  #     smtp = Net::SMTP.start('your.smtp.server', 25)
132
  #     smtp.send_message msgstr, 'from@address', 'to@address'
133
  #     smtp.finish
134
  #
135
  # You can also use the block form of SMTP.start/SMTP#start.  This closes
136
  # the SMTP session automatically:
137
  #
138
  #     # using block form of SMTP.start
139
  #     Net::SMTP.start('your.smtp.server', 25) do |smtp|
140
  #       smtp.send_message msgstr, 'from@address', 'to@address'
141
  #     end
142
  #
143
  # I strongly recommend this scheme.  This form is simpler and more robust.
144
  #
145
  # === HELO domain
146
  #
147
  # In almost all situations, you must provide a third argument
148
  # to SMTP.start/SMTP#start. This is the domain name which you are on
149
  # (the host to send mail from). It is called the "HELO domain".
150
  # The SMTP server will judge whether it should send or reject
151
  # the SMTP session by inspecting the HELO domain.
152
  #
153
  #     Net::SMTP.start('your.smtp.server', 25,
154
  #                     'mail.from.domain') { |smtp| ... }
155
  #
156
  # === SMTP Authentication
157
  #
158
  # The Net::SMTP class supports three authentication schemes;
159
  # PLAIN, LOGIN and CRAM MD5.  (SMTP Authentication: [RFC2554])
160
  # To use SMTP authentication, pass extra arguments to
161
  # SMTP.start/SMTP#start.
162
  #
163
  #     # PLAIN
164
  #     Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
165
  #                     'Your Account', 'Your Password', :plain)
166
  #     # LOGIN
167
  #     Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
168
  #                     'Your Account', 'Your Password', :login)
169
  #
170
  #     # CRAM MD5
171
  #     Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
172
  #                     'Your Account', 'Your Password', :cram_md5)
173
  #
174
  class SMTP
175

    
176
    Revision = %q$Revision: 31710 $.split[1]
177

    
178
    # The default SMTP port number, 25.
179
    def SMTP.default_port
180
      25
181
    end
182

    
183
    # The default mail submission port number, 587.
184
    def SMTP.default_submission_port
185
      587
186
    end
187

    
188
    # The default SMTPS port number, 465.
189
    def SMTP.default_tls_port
190
      465
191
    end
192

    
193
    class << self
194
      alias default_ssl_port default_tls_port
195
    end
196

    
197
    def SMTP.default_ssl_context
198
      OpenSSL::SSL::SSLContext.new
199
    end
200

    
201
    #
202
    # Creates a new Net::SMTP object.
203
    #
204
    # +address+ is the hostname or ip address of your SMTP
205
    # server.  +port+ is the port to connect to; it defaults to
206
    # port 25.
207
    #
208
    # This method does not open the TCP connection.  You can use
209
    # SMTP.start instead of SMTP.new if you want to do everything
210
    # at once.  Otherwise, follow SMTP.new with SMTP#start.
211
    #
212
    def initialize(address, port = nil)
213
      @address = address
214
      @port = (port || SMTP.default_port)
215
      @esmtp = true
216
      @capabilities = nil
217
      @socket = nil
218
      @started = false
219
      @open_timeout = 30
220
      @read_timeout = 60
221
      @error_occured = false
222
      @debug_output = nil
223
      @tls = false
224
      @starttls = false
225
      @ssl_context = nil
226
    end
227

    
228
    # Provide human-readable stringification of class state.
229
    def inspect
230
      "#<#{self.class} #{@address}:#{@port} started=#{@started}>"
231
    end
232

    
233
    #
234
    # Set whether to use ESMTP or not.  This should be done before
235
    # calling #start.  Note that if #start is called in ESMTP mode,
236
    # and the connection fails due to a ProtocolError, the SMTP
237
    # object will automatically switch to plain SMTP mode and
238
    # retry (but not vice versa).
239
    #
240
    attr_accessor :esmtp
241

    
242
    # +true+ if the SMTP object uses ESMTP (which it does by default).
243
    alias :esmtp? :esmtp
244

    
245
    # true if server advertises STARTTLS.
246
    # You cannot get valid value before opening SMTP session.
247
    def capable_starttls?
248
      capable?('STARTTLS')
249
    end
250

    
251
    def capable?(key)
252
      return nil unless @capabilities
253
      @capabilities[key] ? true : false
254
    end
255
    private :capable?
256

    
257
    # true if server advertises AUTH PLAIN.
258
    # You cannot get valid value before opening SMTP session.
259
    def capable_plain_auth?
260
      auth_capable?('PLAIN')
261
    end
262

    
263
    # true if server advertises AUTH LOGIN.
264
    # You cannot get valid value before opening SMTP session.
265
    def capable_login_auth?
266
      auth_capable?('LOGIN')
267
    end
268

    
269
    # true if server advertises AUTH CRAM-MD5.
270
    # You cannot get valid value before opening SMTP session.
271
    def capable_cram_md5_auth?
272
      auth_capable?('CRAM-MD5')
273
    end
274
    
275
    # true if server advertises AUTH NTLM.
276
    # You cannot get valid value before opening SMTP session.
277
    def capable_login_auth?
278
      auth_capable?('NTLM')
279
    end
280
                          
281

    
282
    def auth_capable?(type)
283
      return nil unless @capabilities
284
      return false unless @capabilities['AUTH']
285
      @capabilities['AUTH'].include?(type)
286
    end
287
    private :auth_capable?
288

    
289
    # Returns supported authentication methods on this server.
290
    # You cannot get valid value before opening SMTP session.
291
    def capable_auth_types
292
      return [] unless @capabilities
293
      return [] unless @capabilities['AUTH']
294
      @capabilities['AUTH']
295
    end
296

    
297
    # true if this object uses SMTP/TLS (SMTPS).
298
    def tls?
299
      @tls
300
    end
301

    
302
    alias ssl? tls?
303

    
304
    # Enables SMTP/TLS (SMTPS: SMTP over direct TLS connection) for
305
    # this object.  Must be called before the connection is established
306
    # to have any effect.  +context+ is a OpenSSL::SSL::SSLContext object.
307
    def enable_tls(context = SMTP.default_ssl_context)
308
      raise 'openssl library not installed' unless defined?(OpenSSL)
309
      raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @starttls
310
      @tls = true
311
      @ssl_context = context
312
    end
313

    
314
    alias enable_ssl enable_tls
315

    
316
    # Disables SMTP/TLS for this object.  Must be called before the
317
    # connection is established to have any effect.
318
    def disable_tls
319
      @tls = false
320
      @ssl_context = nil
321
    end
322

    
323
    alias disable_ssl disable_tls
324

    
325
    # Returns truth value if this object uses STARTTLS.
326
    # If this object always uses STARTTLS, returns :always.
327
    # If this object uses STARTTLS when the server support TLS, returns :auto.
328
    def starttls?
329
      @starttls
330
    end
331

    
332
    # true if this object uses STARTTLS.
333
    def starttls_always?
334
      @starttls == :always
335
    end
336

    
337
    # true if this object uses STARTTLS when server advertises STARTTLS.
338
    def starttls_auto?
339
      @starttls == :auto
340
    end
341

    
342
    # Enables SMTP/TLS (STARTTLS) for this object.
343
    # +context+ is a OpenSSL::SSL::SSLContext object.
344
    def enable_starttls(context = SMTP.default_ssl_context)
345
      raise 'openssl library not installed' unless defined?(OpenSSL)
346
      raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls
347
      @starttls = :always
348
      @ssl_context = context
349
    end
350

    
351
    # Enables SMTP/TLS (STARTTLS) for this object if server accepts.
352
    # +context+ is a OpenSSL::SSL::SSLContext object.
353
    def enable_starttls_auto(context = SMTP.default_ssl_context)
354
      raise 'openssl library not installed' unless defined?(OpenSSL)
355
      raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls
356
      @starttls = :auto
357
      @ssl_context = context
358
    end
359

    
360
    # Disables SMTP/TLS (STARTTLS) for this object.  Must be called
361
    # before the connection is established to have any effect.
362
    def disable_starttls
363
      @starttls = false
364
      @ssl_context = nil
365
    end
366

    
367
    # The address of the SMTP server to connect to.
368
    attr_reader :address
369

    
370
    # The port number of the SMTP server to connect to.
371
    attr_reader :port
372

    
373
    # Seconds to wait while attempting to open a connection.
374
    # If the connection cannot be opened within this time, a
375
    # TimeoutError is raised.
376
    attr_accessor :open_timeout
377

    
378
    # Seconds to wait while reading one block (by one read(2) call).
379
    # If the read(2) call does not complete within this time, a
380
    # TimeoutError is raised.
381
    attr_reader :read_timeout
382

    
383
    # Set the number of seconds to wait until timing-out a read(2)
384
    # call.
385
    def read_timeout=(sec)
386
      @socket.read_timeout = sec if @socket
387
      @read_timeout = sec
388
    end
389

    
390
    #
391
    # WARNING: This method causes serious security holes.
392
    # Use this method for only debugging.
393
    #
394
    # Set an output stream for debug logging.
395
    # You must call this before #start.
396
    #
397
    #   # example
398
    #   smtp = Net::SMTP.new(addr, port)
399
    #   smtp.set_debug_output $stderr
400
    #   smtp.start do |smtp|
401
    #     ....
402
    #   end
403
    #
404
    def debug_output=(arg)
405
      @debug_output = arg
406
    end
407

    
408
    alias set_debug_output debug_output=
409

    
410
    #
411
    # SMTP session control
412
    #
413

    
414
    #
415
    # Creates a new Net::SMTP object and connects to the server.
416
    #
417
    # This method is equivalent to:
418
    #
419
    #   Net::SMTP.new(address, port).start(helo_domain, account, password, authtype)
420
    #
421
    # === Example
422
    #
423
    #     Net::SMTP.start('your.smtp.server') do |smtp|
424
    #       smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
425
    #     end
426
    #
427
    # === Block Usage
428
    #
429
    # If called with a block, the newly-opened Net::SMTP object is yielded
430
    # to the block, and automatically closed when the block finishes.  If called
431
    # without a block, the newly-opened Net::SMTP object is returned to
432
    # the caller, and it is the caller's responsibility to close it when
433
    # finished.
434
    #
435
    # === Parameters
436
    #
437
    # +address+ is the hostname or ip address of your smtp server.
438
    #
439
    # +port+ is the port to connect to; it defaults to port 25.
440
    #
441
    # +helo+ is the _HELO_ _domain_ provided by the client to the
442
    # server (see overview comments); it defaults to 'localhost'.
443
    #
444
    # The remaining arguments are used for SMTP authentication, if required
445
    # or desired.  +user+ is the account name; +secret+ is your password
446
    # or other authentication token; and +authtype+ is the authentication
447
    # type, one of :plain, :login, or :cram_md5.  See the discussion of
448
    # SMTP Authentication in the overview notes.
449
    #
450
    # === Errors
451
    #
452
    # This method may raise:
453
    #
454
    # * Net::SMTPAuthenticationError
455
    # * Net::SMTPServerBusy
456
    # * Net::SMTPSyntaxError
457
    # * Net::SMTPFatalError
458
    # * Net::SMTPUnknownError
459
    # * IOError
460
    # * TimeoutError
461
    #
462
    def SMTP.start(address, port = nil, helo = 'localhost',
463
                   user = nil, secret = nil, authtype = nil,
464
                   &block)   # :yield: smtp
465
      new(address, port).start(helo, user, secret, authtype, &block)
466
    end
467

    
468
    # +true+ if the SMTP session has been started.
469
    def started?
470
      @started
471
    end
472

    
473
    #
474
    # Opens a TCP connection and starts the SMTP session.
475
    #
476
    # === Parameters
477
    #
478
    # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see
479
    # the discussion in the overview notes.
480
    #
481
    # If both of +user+ and +secret+ are given, SMTP authentication
482
    # will be attempted using the AUTH command.  +authtype+ specifies
483
    # the type of authentication to attempt; it must be one of
484
    # :login, :plain, and :cram_md5.  See the notes on SMTP Authentication
485
    # in the overview.
486
    #
487
    # === Block Usage
488
    #
489
    # When this methods is called with a block, the newly-started SMTP
490
    # object is yielded to the block, and automatically closed after
491
    # the block call finishes.  Otherwise, it is the caller's
492
    # responsibility to close the session when finished.
493
    #
494
    # === Example
495
    #
496
    # This is very similar to the class method SMTP.start.
497
    #
498
    #     require 'net/smtp'
499
    #     smtp = Net::SMTP.new('smtp.mail.server', 25)
500
    #     smtp.start(helo_domain, account, password, authtype) do |smtp|
501
    #       smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
502
    #     end
503
    #
504
    # The primary use of this method (as opposed to SMTP.start)
505
    # is probably to set debugging (#set_debug_output) or ESMTP
506
    # (#esmtp=), which must be done before the session is
507
    # started.
508
    #
509
    # === Errors
510
    #
511
    # If session has already been started, an IOError will be raised.
512
    #
513
    # This method may raise:
514
    #
515
    # * Net::SMTPAuthenticationError
516
    # * Net::SMTPServerBusy
517
    # * Net::SMTPSyntaxError
518
    # * Net::SMTPFatalError
519
    # * Net::SMTPUnknownError
520
    # * IOError
521
    # * TimeoutError
522
    #
523
    def start(helo = 'localhost',
524
              user = nil, secret = nil, authtype = nil)   # :yield: smtp
525
      if block_given?
526
        begin
527
          do_start helo, user, secret, authtype
528
          return yield(self)
529
        ensure
530
          do_finish
531
        end
532
      else
533
        do_start helo, user, secret, authtype
534
        return self
535
      end
536
    end
537

    
538
    # Finishes the SMTP session and closes TCP connection.
539
    # Raises IOError if not started.
540
    def finish
541
      raise IOError, 'not yet started' unless started?
542
      do_finish
543
    end
544

    
545
    private
546

    
547
    def tcp_socket(address, port)
548
      TCPSocket.open address, port
549
    end
550

    
551
    def do_start(helo_domain, user, secret, authtype)
552
      raise IOError, 'SMTP session already started' if @started
553
      if user or secret
554
        check_auth_method(authtype || DEFAULT_AUTH_TYPE)
555
        check_auth_args user, secret
556
      end
557
      s = timeout(@open_timeout) { tcp_socket(@address, @port) }
558
      logging "Connection opened: #{@address}:#{@port}"
559
      @socket = new_internet_message_io(tls? ? tlsconnect(s) : s)
560
      check_response critical { recv_response() }
561
      do_helo helo_domain
562
      if starttls_always? or (capable_starttls? and starttls_auto?)
563
        unless capable_starttls?
564
          raise SMTPUnsupportedCommand,
565
              "STARTTLS is not supported on this server"
566
        end
567
        starttls
568
        @socket = new_internet_message_io(tlsconnect(s))
569
        # helo response may be different after STARTTLS
570
        do_helo helo_domain
571
      end
572
      authenticate user, secret, (authtype || DEFAULT_AUTH_TYPE) if user
573
      @started = true
574
    ensure
575
      unless @started
576
        # authentication failed, cancel connection.
577
        s.close if s and not s.closed?
578
        @socket = nil
579
      end
580
    end
581

    
582
    def ssl_socket(socket, context)
583
      OpenSSL::SSL::SSLSocket.new socket, context
584
    end
585

    
586
    def tlsconnect(s)
587
      verified = false
588
      s = ssl_socket(s, @ssl_context)
589
      logging "TLS connection started"
590
      s.sync_close = true
591
      s.connect
592
      if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE
593
        s.post_connection_check(@address)
594
      end
595
      verified = true
596
      s
597
    ensure
598
      s.close unless verified
599
    end
600

    
601
    def new_internet_message_io(s)
602
      io = InternetMessageIO.new(s)
603
      io.read_timeout = @read_timeout
604
      io.debug_output = @debug_output
605
      io
606
    end
607

    
608
    def do_helo(helo_domain)
609
      res = @esmtp ? ehlo(helo_domain) : helo(helo_domain)
610
      @capabilities = res.capabilities
611
    rescue SMTPError
612
      if @esmtp
613
        @esmtp = false
614
        @error_occured = false
615
        retry
616
      end
617
      raise
618
    end
619

    
620
    def do_finish
621
      quit if @socket and not @socket.closed? and not @error_occured
622
    ensure
623
      @started = false
624
      @error_occured = false
625
      @socket.close if @socket and not @socket.closed?
626
      @socket = nil
627
    end
628

    
629
    #
630
    # Message Sending
631
    #
632

    
633
    public
634

    
635
    #
636
    # Sends +msgstr+ as a message.  Single CR ("\r") and LF ("\n") found
637
    # in the +msgstr+, are converted into the CR LF pair.  You cannot send a
638
    # binary message with this method. +msgstr+ should include both
639
    # the message headers and body.
640
    #
641
    # +from_addr+ is a String representing the source mail address.
642
    #
643
    # +to_addr+ is a String or Strings or Array of Strings, representing
644
    # the destination mail address or addresses.
645
    #
646
    # === Example
647
    #
648
    #     Net::SMTP.start('smtp.example.com') do |smtp|
649
    #       smtp.send_message msgstr,
650
    #                         'from@example.com',
651
    #                         ['dest@example.com', 'dest2@example.com']
652
    #     end
653
    #
654
    # === Errors
655
    #
656
    # This method may raise:
657
    #
658
    # * Net::SMTPServerBusy
659
    # * Net::SMTPSyntaxError
660
    # * Net::SMTPFatalError
661
    # * Net::SMTPUnknownError
662
    # * IOError
663
    # * TimeoutError
664
    #
665
    def send_message(msgstr, from_addr, *to_addrs)
666
      raise IOError, 'closed session' unless @socket
667
      mailfrom from_addr
668
      rcptto_list(to_addrs) {data msgstr}
669
    end
670

    
671
    alias send_mail send_message
672
    alias sendmail send_message   # obsolete
673

    
674
    #
675
    # Opens a message writer stream and gives it to the block.
676
    # The stream is valid only in the block, and has these methods:
677
    #
678
    # puts(str = '')::       outputs STR and CR LF.
679
    # print(str)::           outputs STR.
680
    # printf(fmt, *args)::   outputs sprintf(fmt,*args).
681
    # write(str)::           outputs STR and returns the length of written bytes.
682
    # <<(str)::              outputs STR and returns self.
683
    #
684
    # If a single CR ("\r") or LF ("\n") is found in the message,
685
    # it is converted to the CR LF pair.  You cannot send a binary
686
    # message with this method.
687
    #
688
    # === Parameters
689
    #
690
    # +from_addr+ is a String representing the source mail address.
691
    #
692
    # +to_addr+ is a String or Strings or Array of Strings, representing
693
    # the destination mail address or addresses.
694
    #
695
    # === Example
696
    #
697
    #     Net::SMTP.start('smtp.example.com', 25) do |smtp|
698
    #       smtp.open_message_stream('from@example.com', ['dest@example.com']) do |f|
699
    #         f.puts 'From: from@example.com'
700
    #         f.puts 'To: dest@example.com'
701
    #         f.puts 'Subject: test message'
702
    #         f.puts
703
    #         f.puts 'This is a test message.'
704
    #       end
705
    #     end
706
    #
707
    # === Errors
708
    #
709
    # This method may raise:
710
    #
711
    # * Net::SMTPServerBusy
712
    # * Net::SMTPSyntaxError
713
    # * Net::SMTPFatalError
714
    # * Net::SMTPUnknownError
715
    # * IOError
716
    # * TimeoutError
717
    #
718
    def open_message_stream(from_addr, *to_addrs, &block)   # :yield: stream
719
      raise IOError, 'closed session' unless @socket
720
      mailfrom from_addr
721
      rcptto_list(to_addrs) {data(&block)}
722
    end
723

    
724
    alias ready open_message_stream   # obsolete
725

    
726
    #
727
    # Authentication
728
    #
729

    
730
    public
731

    
732
    DEFAULT_AUTH_TYPE = :plain
733

    
734
    def authenticate(user, secret, authtype = DEFAULT_AUTH_TYPE)
735
      check_auth_method authtype
736
      check_auth_args user, secret
737
      send auth_method(authtype), user, secret
738
    end
739

    
740
    def auth_plain(user, secret)
741
      check_auth_args user, secret
742
      res = critical {
743
        get_response('AUTH PLAIN ' + base64_encode("\0#{user}\0#{secret}"))
744
      }
745
      check_auth_response res
746
      res
747
    end
748

    
749
    def auth_ntlm(user, secret)
750
      check_auth_args user, secret
751
      res = critical {
752
        # send type1 message
753
        t1 = Net::NTLM::Message::Type1.new()
754
        t1.domain = user[/^[^\\]+/]
755
        @socket.writeline "AUTH NTLM " + t1.encode64
756

    
757
        # receive type2 message
758
        line = @socket.readline
759
        unless /334 (.+)/ =~ line
760
          raise RuntimeError, "SMTP AUTH don't recognize this: #{line}"
761
        end
762
        t2 = Net::NTLM::Message.decode64($1)
763

    
764
        # send Type3 Message
765
        t3 = t2.response({:user => user[/[^\\]+$/], :password => secret}, {:ntlmv2 => true})
766
        get_response(t3.encode64)
767
      }
768
      check_auth_response res
769
      res
770
    end
771

    
772
    def auth_login(user, secret)
773
      check_auth_args user, secret
774
      res = critical {
775
        check_auth_continue get_response('AUTH LOGIN')
776
        check_auth_continue get_response(base64_encode(user))
777
        get_response(base64_encode(secret))
778
      }
779
      check_auth_response res
780
      res
781
    end
782

    
783
    def auth_cram_md5(user, secret)
784
      check_auth_args user, secret
785
      res = critical {
786
        res0 = get_response('AUTH CRAM-MD5')
787
        check_auth_continue res0
788
        crammed = cram_md5_response(secret, res0.cram_md5_challenge)
789
        get_response(base64_encode("#{user} #{crammed}"))
790
      }
791
      check_auth_response res
792
      res
793
    end
794

    
795
    private
796

    
797
    def check_auth_method(type)
798
      unless respond_to?(auth_method(type), true)
799
        raise ArgumentError, "wrong authentication type #{type}"
800
      end
801
    end
802

    
803
    def auth_method(type)
804
      "auth_#{type.to_s.downcase}".intern
805
    end
806

    
807
    def check_auth_args(user, secret, authtype = DEFAULT_AUTH_TYPE)
808
      unless user
809
        raise ArgumentError, 'SMTP-AUTH requested but missing user name'
810
      end
811
      unless secret
812
        raise ArgumentError, 'SMTP-AUTH requested but missing secret phrase'
813
      end
814
    end
815

    
816
    def base64_encode(str)
817
      # expects "str" may not become too long
818
      [str].pack('m').gsub(/\s+/, '')
819
    end
820

    
821
    IMASK = 0x36
822
    OMASK = 0x5c
823

    
824
    # CRAM-MD5: [RFC2195]
825
    def cram_md5_response(secret, challenge)
826
      tmp = Digest::MD5.digest(cram_secret(secret, IMASK) + challenge)
827
      Digest::MD5.hexdigest(cram_secret(secret, OMASK) + tmp)
828
    end
829

    
830
    CRAM_BUFSIZE = 64
831

    
832
    def cram_secret(secret, mask)
833
      secret = Digest::MD5.digest(secret) if secret.size > CRAM_BUFSIZE
834
      buf = secret.ljust(CRAM_BUFSIZE, "\0")
835
      0.upto(buf.size - 1) do |i|
836
        buf[i] = (buf[i].ord ^ mask).chr
837
      end
838
      buf
839
    end
840

    
841
    #
842
    # SMTP command dispatcher
843
    #
844

    
845
    public
846

    
847
    def starttls
848
      getok('STARTTLS')
849
    end
850

    
851
    def helo(domain)
852
      getok("HELO #{domain}")
853
    end
854

    
855
    def ehlo(domain)
856
      getok("EHLO #{domain}")
857
    end
858

    
859
    def mailfrom(from_addr)
860
      if $SAFE > 0
861
        raise SecurityError, 'tainted from_addr' if from_addr.tainted?
862
      end
863
      getok("MAIL FROM:<#{from_addr}>")
864
    end
865

    
866
    def rcptto_list(to_addrs)
867
      raise ArgumentError, 'mail destination not given' if to_addrs.empty?
868
      ok_users = []
869
      unknown_users = []
870
      to_addrs.flatten.each do |addr|
871
        begin
872
          rcptto addr
873
        rescue SMTPAuthenticationError
874
          unknown_users << addr.dump
875
        else
876
          ok_users << addr
877
        end
878
      end
879
      raise ArgumentError, 'mail destination not given' if ok_users.empty?
880
      ret = yield
881
      unless unknown_users.empty?
882
        raise SMTPAuthenticationError, "failed to deliver for #{unknown_users.join(', ')}"
883
      end
884
      ret
885
    end
886

    
887
    def rcptto(to_addr)
888
      if $SAFE > 0
889
        raise SecurityError, 'tainted to_addr' if to_addr.tainted?
890
      end
891
      getok("RCPT TO:<#{to_addr}>")
892
    end
893

    
894
    # This method sends a message.
895
    # If +msgstr+ is given, sends it as a message.
896
    # If block is given, yield a message writer stream.
897
    # You must write message before the block is closed.
898
    #
899
    #   # Example 1 (by string)
900
    #   smtp.data(<<EndMessage)
901
    #   From: john@example.com
902
    #   To: betty@example.com
903
    #   Subject: I found a bug
904
    #
905
    #   Check vm.c:58879.
906
    #   EndMessage
907
    #
908
    #   # Example 2 (by block)
909
    #   smtp.data {|f|
910
    #     f.puts "From: john@example.com"
911
    #     f.puts "To: betty@example.com"
912
    #     f.puts "Subject: I found a bug"
913
    #     f.puts ""
914
    #     f.puts "Check vm.c:58879."
915
    #   }
916
    #
917
    def data(msgstr = nil, &block)   #:yield: stream
918
      if msgstr and block
919
        raise ArgumentError, "message and block are exclusive"
920
      end
921
      unless msgstr or block
922
        raise ArgumentError, "message or block is required"
923
      end
924
      res = critical {
925
        check_continue get_response('DATA')
926
        if msgstr
927
          @socket.write_message msgstr
928
        else
929
          @socket.write_message_by_block(&block)
930
        end
931
        recv_response()
932
      }
933
      check_response res
934
      res
935
    end
936

    
937
    def quit
938
      getok('QUIT')
939
    end
940

    
941
    private
942

    
943
    def getok(reqline)
944
      res = critical {
945
        @socket.writeline reqline
946
        recv_response()
947
      }
948
      check_response res
949
      res
950
    end
951

    
952
    def get_response(reqline)
953
      @socket.writeline reqline
954
      recv_response()
955
    end
956

    
957
    def recv_response
958
      buf = ''
959
      while true
960
        line = @socket.readline
961
        buf << line << "\n"
962
        break unless line[3,1] == '-'   # "210-PIPELINING"
963
      end
964
      Response.parse(buf)
965
    end
966

    
967
    def critical(&block)
968
      return '200 dummy reply code' if @error_occured
969
      begin
970
        return yield()
971
      rescue Exception
972
        @error_occured = true
973
        raise
974
      end
975
    end
976

    
977
    def check_response(res)
978
      unless res.success?
979
        raise res.exception_class, res.message
980
      end
981
    end
982

    
983
    def check_continue(res)
984
      unless res.continue?
985
        raise SMTPUnknownError, "could not get 3xx (#{res.status})"
986
      end
987
    end
988

    
989
    def check_auth_response(res)
990
      unless res.success?
991
        raise SMTPAuthenticationError, res.message
992
      end
993
    end
994

    
995
    def check_auth_continue(res)
996
      unless res.continue?
997
        raise res.exception_class, res.message
998
      end
999
    end
1000

    
1001
    # This class represents a response received by the SMTP server. Instances
1002
    # of this class are created by the SMTP class; they should not be directly
1003
    # created by the user. For more information on SMTP responses, view
1004
    # {Section 4.2 of RFC 5321}[http://tools.ietf.org/html/rfc5321#section-4.2]
1005
    class Response
1006
      # Parses the received response and separates the reply code and the human
1007
      # readable reply text
1008
      def self.parse(str)
1009
        new(str[0,3], str)
1010
      end
1011

    
1012
      # Creates a new instance of the Response class and sets the status and
1013
      # string attributes
1014
      def initialize(status, string)
1015
        @status = status
1016
        @string = string
1017
      end
1018

    
1019
      # The three digit reply code of the SMTP response
1020
      attr_reader :status
1021

    
1022
      # The human readable reply text of the SMTP response
1023
      attr_reader :string
1024

    
1025
      # Takes the first digit of the reply code to determine the status type
1026
      def status_type_char
1027
        @status[0, 1]
1028
      end
1029

    
1030
      # Determines whether the response received was a Positive Completion
1031
      # reply (2xx reply code)
1032
      def success?
1033
        status_type_char() == '2'
1034
      end
1035

    
1036
      # Determines whether the response received was a Positive Intermediate
1037
      # reply (3xx reply code)
1038
      def continue?
1039
        status_type_char() == '3'
1040
      end
1041

    
1042
      # The first line of the human readable reply text
1043
      def message
1044
        @string.lines.first
1045
      end
1046

    
1047
      # Creates a CRAM-MD5 challenge. You can view more information on CRAM-MD5
1048
      # on Wikipedia: http://en.wikipedia.org/wiki/CRAM-MD5
1049
      def cram_md5_challenge
1050
        @string.split(/ /)[1].unpack('m')[0]
1051
      end
1052

    
1053
      # Returns a hash of the human readable reply text in the response if it
1054
      # is multiple lines. It does not return the first line. The key of the
1055
      # hash is the first word the value of the hash is an array with each word
1056
      # thereafter being a value in the array
1057
      def capabilities
1058
        return {} unless @string[3, 1] == '-'
1059
        h = {}
1060
        @string.lines.drop(1).each do |line|
1061
          k, *v = line[4..-1].chomp.split
1062
          h[k] = v
1063
        end
1064
        h
1065
      end
1066

    
1067
      # Determines whether there was an error and raies the appropriate error
1068
      # based on the reply code of the response
1069
      def exception_class
1070
        case @status
1071
        when /\A4/  then SMTPServerBusy
1072
        when /\A50/ then SMTPSyntaxError
1073
        when /\A53/ then SMTPAuthenticationError
1074
        when /\A5/  then SMTPFatalError
1075
        else             SMTPUnknownError
1076
        end
1077
      end
1078
    end
1079

    
1080
    def logging(msg)
1081
      @debug_output << msg + "\n" if @debug_output
1082
    end
1083

    
1084
  end   # class SMTP
1085

    
1086
  SMTPSession = SMTP
1087

    
1088
end
(2-2/2)