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 44980 2014-02-15 15:45:14Z nagachika $
18#
19# See Net::SMTP for documentation.
20#
21
22require 'net/protocol'
23require 'digest/md5'
24require 'timeout'
25begin
26  require 'openssl'
27rescue LoadError
28end
29
30module 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  # == What is This Library?
70  #
71  # This library provides functionality to send internet
72  # mail via SMTP, the Simple Mail Transfer Protocol. For details of
73  # SMTP itself, see [RFC2821] (http://www.ietf.org/rfc/rfc2821.txt).
74  #
75  # == What is This Library NOT?
76  #
77  # This library does NOT provide functions to compose internet mails.
78  # You must create them by yourself. If you want better mail support,
79  # try RubyMail or TMail or search for alternatives in
80  # {RubyGems.org}[https://rubygems.org/] or {The Ruby
81  # Toolbox}[https://www.ruby-toolbox.com/].
82  #
83  # FYI: the official documentation on internet mail is: [RFC2822] (http://www.ietf.org/rfc/rfc2822.txt).
84  #
85  # == Examples
86  #
87  # === Sending Messages
88  #
89  # You must open a connection to an SMTP server before sending messages.
90  # The first argument is the address of your SMTP server, and the second
91  # argument is the port number. Using SMTP.start with a block is the simplest
92  # way to do this. This way, the SMTP connection is closed automatically
93  # after the block is executed.
94  #
95  #     require 'net/smtp'
96  #     Net::SMTP.start('your.smtp.server', 25) do |smtp|
97  #       # Use the SMTP object smtp only in this block.
98  #     end
99  #
100  # Replace 'your.smtp.server' with your SMTP server. Normally
101  # your system manager or internet provider supplies a server
102  # for you.
103  #
104  # Then you can send messages.
105  #
106  #     msgstr = <<END_OF_MESSAGE
107  #     From: Your Name <your@mail.address>
108  #     To: Destination Address <someone@example.com>
109  #     Subject: test message
110  #     Date: Sat, 23 Jun 2001 16:26:43 +0900
111  #     Message-Id: <unique.message.id.string@example.com>
112  #
113  #     This is a test message.
114  #     END_OF_MESSAGE
115  #
116  #     require 'net/smtp'
117  #     Net::SMTP.start('your.smtp.server', 25) do |smtp|
118  #       smtp.send_message msgstr,
119  #                         'your@mail.address',
120  #                         'his_address@example.com'
121  #     end
122  #
123  # === Closing the Session
124  #
125  # You MUST close the SMTP session after sending messages, by calling
126  # the #finish method:
127  #
128  #     # using SMTP#finish
129  #     smtp = Net::SMTP.start('your.smtp.server', 25)
130  #     smtp.send_message msgstr, 'from@address', 'to@address'
131  #     smtp.finish
132  #
133  # You can also use the block form of SMTP.start/SMTP#start.  This closes
134  # the SMTP session automatically:
135  #
136  #     # using block form of SMTP.start
137  #     Net::SMTP.start('your.smtp.server', 25) do |smtp|
138  #       smtp.send_message msgstr, 'from@address', 'to@address'
139  #     end
140  #
141  # I strongly recommend this scheme.  This form is simpler and more robust.
142  #
143  # === HELO domain
144  #
145  # In almost all situations, you must provide a third argument
146  # to SMTP.start/SMTP#start. This is the domain name which you are on
147  # (the host to send mail from). It is called the "HELO domain".
148  # The SMTP server will judge whether it should send or reject
149  # the SMTP session by inspecting the HELO domain.
150  #
151  #     Net::SMTP.start('your.smtp.server', 25,
152  #                     'mail.from.domain') { |smtp| ... }
153  #
154  # === SMTP Authentication
155  #
156  # The Net::SMTP class supports three authentication schemes;
157  # PLAIN, LOGIN and CRAM MD5.  (SMTP Authentication: [RFC2554])
158  # To use SMTP authentication, pass extra arguments to
159  # SMTP.start/SMTP#start.
160  #
161  #     # PLAIN
162  #     Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
163  #                     'Your Account', 'Your Password', :plain)
164  #     # LOGIN
165  #     Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
166  #                     'Your Account', 'Your Password', :login)
167  #
168  #     # CRAM MD5
169  #     Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
170  #                     'Your Account', 'Your Password', :cram_md5)
171  #
172  class SMTP
173
174    Revision = %q$Revision: 44980 $.split[1]
175
176    # The default SMTP port number, 25.
177    def SMTP.default_port
178      25
179    end
180
181    # The default mail submission port number, 587.
182    def SMTP.default_submission_port
183      587
184    end
185
186    # The default SMTPS port number, 465.
187    def SMTP.default_tls_port
188      465
189    end
190
191    class << self
192      alias default_ssl_port default_tls_port
193    end
194
195    def SMTP.default_ssl_context
196      OpenSSL::SSL::SSLContext.new
197    end
198
199    #
200    # Creates a new Net::SMTP object.
201    #
202    # +address+ is the hostname or ip address of your SMTP
203    # server.  +port+ is the port to connect to; it defaults to
204    # port 25.
205    #
206    # This method does not open the TCP connection.  You can use
207    # SMTP.start instead of SMTP.new if you want to do everything
208    # at once.  Otherwise, follow SMTP.new with SMTP#start.
209    #
210    def initialize(address, port = nil)
211      @address = address
212      @port = (port || SMTP.default_port)
213      @esmtp = true
214      @capabilities = nil
215      @socket = nil
216      @started = false
217      @open_timeout = 30
218      @read_timeout = 60
219      @error_occurred = false
220      @debug_output = nil
221      @tls = false
222      @starttls = false
223      @ssl_context = nil
224    end
225
226    # Provide human-readable stringification of class state.
227    def inspect
228      "#<#{self.class} #{@address}:#{@port} started=#{@started}>"
229    end
230
231    #
232    # Set whether to use ESMTP or not.  This should be done before
233    # calling #start.  Note that if #start is called in ESMTP mode,
234    # and the connection fails due to a ProtocolError, the SMTP
235    # object will automatically switch to plain SMTP mode and
236    # retry (but not vice versa).
237    #
238    attr_accessor :esmtp
239
240    # +true+ if the SMTP object uses ESMTP (which it does by default).
241    alias :esmtp? :esmtp
242
243    # true if server advertises STARTTLS.
244    # You cannot get valid value before opening SMTP session.
245    def capable_starttls?
246      capable?('STARTTLS')
247    end
248
249    def capable?(key)
250      return nil unless @capabilities
251      @capabilities[key] ? true : false
252    end
253    private :capable?
254
255    # true if server advertises AUTH PLAIN.
256    # You cannot get valid value before opening SMTP session.
257    def capable_plain_auth?
258      auth_capable?('PLAIN')
259    end
260
261    # true if server advertises AUTH LOGIN.
262    # You cannot get valid value before opening SMTP session.
263    def capable_login_auth?
264      auth_capable?('LOGIN')
265    end
266
267    # true if server advertises AUTH CRAM-MD5.
268    # You cannot get valid value before opening SMTP session.
269    def capable_cram_md5_auth?
270      auth_capable?('CRAM-MD5')
271    end
272
273    def auth_capable?(type)
274      return nil unless @capabilities
275      return false unless @capabilities['AUTH']
276      @capabilities['AUTH'].include?(type)
277    end
278    private :auth_capable?
279
280    # Returns supported authentication methods on this server.
281    # You cannot get valid value before opening SMTP session.
282    def capable_auth_types
283      return [] unless @capabilities
284      return [] unless @capabilities['AUTH']
285      @capabilities['AUTH']
286    end
287
288    # true if this object uses SMTP/TLS (SMTPS).
289    def tls?
290      @tls
291    end
292
293    alias ssl? tls?
294
295    # Enables SMTP/TLS (SMTPS: SMTP over direct TLS connection) for
296    # this object.  Must be called before the connection is established
297    # to have any effect.  +context+ is a OpenSSL::SSL::SSLContext object.
298    def enable_tls(context = SMTP.default_ssl_context)
299      raise 'openssl library not installed' unless defined?(OpenSSL)
300      raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @starttls
301      @tls = true
302      @ssl_context = context
303    end
304
305    alias enable_ssl enable_tls
306
307    # Disables SMTP/TLS for this object.  Must be called before the
308    # connection is established to have any effect.
309    def disable_tls
310      @tls = false
311      @ssl_context = nil
312    end
313
314    alias disable_ssl disable_tls
315
316    # Returns truth value if this object uses STARTTLS.
317    # If this object always uses STARTTLS, returns :always.
318    # If this object uses STARTTLS when the server support TLS, returns :auto.
319    def starttls?
320      @starttls
321    end
322
323    # true if this object uses STARTTLS.
324    def starttls_always?
325      @starttls == :always
326    end
327
328    # true if this object uses STARTTLS when server advertises STARTTLS.
329    def starttls_auto?
330      @starttls == :auto
331    end
332
333    # Enables SMTP/TLS (STARTTLS) for this object.
334    # +context+ is a OpenSSL::SSL::SSLContext object.
335    def enable_starttls(context = SMTP.default_ssl_context)
336      raise 'openssl library not installed' unless defined?(OpenSSL)
337      raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls
338      @starttls = :always
339      @ssl_context = context
340    end
341
342    # Enables SMTP/TLS (STARTTLS) for this object if server accepts.
343    # +context+ is a OpenSSL::SSL::SSLContext object.
344    def enable_starttls_auto(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 = :auto
348      @ssl_context = context
349    end
350
351    # Disables SMTP/TLS (STARTTLS) for this object.  Must be called
352    # before the connection is established to have any effect.
353    def disable_starttls
354      @starttls = false
355      @ssl_context = nil
356    end
357
358    # The address of the SMTP server to connect to.
359    attr_reader :address
360
361    # The port number of the SMTP server to connect to.
362    attr_reader :port
363
364    # Seconds to wait while attempting to open a connection.
365    # If the connection cannot be opened within this time, a
366    # Net::OpenTimeout is raised. The default value is 30 seconds.
367    attr_accessor :open_timeout
368
369    # Seconds to wait while reading one block (by one read(2) call).
370    # If the read(2) call does not complete within this time, a
371    # Net::ReadTimeout is raised. The default value is 60 seconds.
372    attr_reader :read_timeout
373
374    # Set the number of seconds to wait until timing-out a read(2)
375    # call.
376    def read_timeout=(sec)
377      @socket.read_timeout = sec if @socket
378      @read_timeout = sec
379    end
380
381    #
382    # WARNING: This method causes serious security holes.
383    # Use this method for only debugging.
384    #
385    # Set an output stream for debug logging.
386    # You must call this before #start.
387    #
388    #   # example
389    #   smtp = Net::SMTP.new(addr, port)
390    #   smtp.set_debug_output $stderr
391    #   smtp.start do |smtp|
392    #     ....
393    #   end
394    #
395    def debug_output=(arg)
396      @debug_output = arg
397    end
398
399    alias set_debug_output debug_output=
400
401    #
402    # SMTP session control
403    #
404
405    #
406    # Creates a new Net::SMTP object and connects to the server.
407    #
408    # This method is equivalent to:
409    #
410    #   Net::SMTP.new(address, port).start(helo_domain, account, password, authtype)
411    #
412    # === Example
413    #
414    #     Net::SMTP.start('your.smtp.server') do |smtp|
415    #       smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
416    #     end
417    #
418    # === Block Usage
419    #
420    # If called with a block, the newly-opened Net::SMTP object is yielded
421    # to the block, and automatically closed when the block finishes.  If called
422    # without a block, the newly-opened Net::SMTP object is returned to
423    # the caller, and it is the caller's responsibility to close it when
424    # finished.
425    #
426    # === Parameters
427    #
428    # +address+ is the hostname or ip address of your smtp server.
429    #
430    # +port+ is the port to connect to; it defaults to port 25.
431    #
432    # +helo+ is the _HELO_ _domain_ provided by the client to the
433    # server (see overview comments); it defaults to 'localhost'.
434    #
435    # The remaining arguments are used for SMTP authentication, if required
436    # or desired.  +user+ is the account name; +secret+ is your password
437    # or other authentication token; and +authtype+ is the authentication
438    # type, one of :plain, :login, or :cram_md5.  See the discussion of
439    # SMTP Authentication in the overview notes.
440    #
441    # === Errors
442    #
443    # This method may raise:
444    #
445    # * Net::SMTPAuthenticationError
446    # * Net::SMTPServerBusy
447    # * Net::SMTPSyntaxError
448    # * Net::SMTPFatalError
449    # * Net::SMTPUnknownError
450    # * Net::OpenTimeout
451    # * Net::ReadTimeout
452    # * IOError
453    #
454    def SMTP.start(address, port = nil, helo = 'localhost',
455                   user = nil, secret = nil, authtype = nil,
456                   &block)   # :yield: smtp
457      new(address, port).start(helo, user, secret, authtype, &block)
458    end
459
460    # +true+ if the SMTP session has been started.
461    def started?
462      @started
463    end
464
465    #
466    # Opens a TCP connection and starts the SMTP session.
467    #
468    # === Parameters
469    #
470    # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see
471    # the discussion in the overview notes.
472    #
473    # If both of +user+ and +secret+ are given, SMTP authentication
474    # will be attempted using the AUTH command.  +authtype+ specifies
475    # the type of authentication to attempt; it must be one of
476    # :login, :plain, and :cram_md5.  See the notes on SMTP Authentication
477    # in the overview.
478    #
479    # === Block Usage
480    #
481    # When this methods is called with a block, the newly-started SMTP
482    # object is yielded to the block, and automatically closed after
483    # the block call finishes.  Otherwise, it is the caller's
484    # responsibility to close the session when finished.
485    #
486    # === Example
487    #
488    # This is very similar to the class method SMTP.start.
489    #
490    #     require 'net/smtp'
491    #     smtp = Net::SMTP.new('smtp.mail.server', 25)
492    #     smtp.start(helo_domain, account, password, authtype) do |smtp|
493    #       smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
494    #     end
495    #
496    # The primary use of this method (as opposed to SMTP.start)
497    # is probably to set debugging (#set_debug_output) or ESMTP
498    # (#esmtp=), which must be done before the session is
499    # started.
500    #
501    # === Errors
502    #
503    # If session has already been started, an IOError will be raised.
504    #
505    # This method may raise:
506    #
507    # * Net::SMTPAuthenticationError
508    # * Net::SMTPServerBusy
509    # * Net::SMTPSyntaxError
510    # * Net::SMTPFatalError
511    # * Net::SMTPUnknownError
512    # * Net::OpenTimeout
513    # * Net::ReadTimeout
514    # * IOError
515    #
516    def start(helo = 'localhost',
517              user = nil, secret = nil, authtype = nil)   # :yield: smtp
518      if block_given?
519        begin
520          do_start helo, user, secret, authtype
521          return yield(self)
522        ensure
523          do_finish
524        end
525      else
526        do_start helo, user, secret, authtype
527        return self
528      end
529    end
530
531    # Finishes the SMTP session and closes TCP connection.
532    # Raises IOError if not started.
533    def finish
534      raise IOError, 'not yet started' unless started?
535      do_finish
536    end
537
538    private
539
540    def tcp_socket(address, port)
541      TCPSocket.open address, port
542    end
543
544    def do_start(helo_domain, user, secret, authtype)
545      raise IOError, 'SMTP session already started' if @started
546      if user or secret
547        check_auth_method(authtype || DEFAULT_AUTH_TYPE)
548        check_auth_args user, secret
549      end
550      s = Timeout.timeout(@open_timeout, Net::OpenTimeout) do
551        tcp_socket(@address, @port)
552      end
553      logging "Connection opened: #{@address}:#{@port}"
554      @socket = new_internet_message_io(tls? ? tlsconnect(s) : s)
555      check_response critical { recv_response() }
556      do_helo helo_domain
557      if starttls_always? or (capable_starttls? and starttls_auto?)
558        unless capable_starttls?
559          raise SMTPUnsupportedCommand,
560              "STARTTLS is not supported on this server"
561        end
562        starttls
563        @socket = new_internet_message_io(tlsconnect(s))
564        # helo response may be different after STARTTLS
565        do_helo helo_domain
566      end
567      authenticate user, secret, (authtype || DEFAULT_AUTH_TYPE) if user
568      @started = true
569    ensure
570      unless @started
571        # authentication failed, cancel connection.
572        s.close if s and not s.closed?
573        @socket = nil
574      end
575    end
576
577    def ssl_socket(socket, context)
578      OpenSSL::SSL::SSLSocket.new socket, context
579    end
580
581    def tlsconnect(s)
582      verified = false
583      s = ssl_socket(s, @ssl_context)
584      logging "TLS connection started"
585      s.sync_close = true
586      s.connect
587      if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE
588        s.post_connection_check(@address)
589      end
590      verified = true
591      s
592    ensure
593      s.close unless verified
594    end
595
596    def new_internet_message_io(s)
597      io = InternetMessageIO.new(s)
598      io.read_timeout = @read_timeout
599      io.debug_output = @debug_output
600      io
601    end
602
603    def do_helo(helo_domain)
604      res = @esmtp ? ehlo(helo_domain) : helo(helo_domain)
605      @capabilities = res.capabilities
606    rescue SMTPError
607      if @esmtp
608        @esmtp = false
609        @error_occurred = false
610        retry
611      end
612      raise
613    end
614
615    def do_finish
616      quit if @socket and not @socket.closed? and not @error_occurred
617    ensure
618      @started = false
619      @error_occurred = false
620      @socket.close if @socket and not @socket.closed?
621      @socket = nil
622    end
623
624    #
625    # Message Sending
626    #
627
628    public
629
630    #
631    # Sends +msgstr+ as a message.  Single CR ("\r") and LF ("\n") found
632    # in the +msgstr+, are converted into the CR LF pair.  You cannot send a
633    # binary message with this method. +msgstr+ should include both
634    # the message headers and body.
635    #
636    # +from_addr+ is a String representing the source mail address.
637    #
638    # +to_addr+ is a String or Strings or Array of Strings, representing
639    # the destination mail address or addresses.
640    #
641    # === Example
642    #
643    #     Net::SMTP.start('smtp.example.com') do |smtp|
644    #       smtp.send_message msgstr,
645    #                         'from@example.com',
646    #                         ['dest@example.com', 'dest2@example.com']
647    #     end
648    #
649    # === Errors
650    #
651    # This method may raise:
652    #
653    # * Net::SMTPServerBusy
654    # * Net::SMTPSyntaxError
655    # * Net::SMTPFatalError
656    # * Net::SMTPUnknownError
657    # * Net::ReadTimeout
658    # * IOError
659    #
660    def send_message(msgstr, from_addr, *to_addrs)
661      raise IOError, 'closed session' unless @socket
662      mailfrom from_addr
663      rcptto_list(to_addrs) {data msgstr}
664    end
665
666    alias send_mail send_message
667    alias sendmail send_message   # obsolete
668
669    #
670    # Opens a message writer stream and gives it to the block.
671    # The stream is valid only in the block, and has these methods:
672    #
673    # puts(str = '')::       outputs STR and CR LF.
674    # print(str)::           outputs STR.
675    # printf(fmt, *args)::   outputs sprintf(fmt,*args).
676    # write(str)::           outputs STR and returns the length of written bytes.
677    # <<(str)::              outputs STR and returns self.
678    #
679    # If a single CR ("\r") or LF ("\n") is found in the message,
680    # it is converted to the CR LF pair.  You cannot send a binary
681    # message with this method.
682    #
683    # === Parameters
684    #
685    # +from_addr+ is a String representing the source mail address.
686    #
687    # +to_addr+ is a String or Strings or Array of Strings, representing
688    # the destination mail address or addresses.
689    #
690    # === Example
691    #
692    #     Net::SMTP.start('smtp.example.com', 25) do |smtp|
693    #       smtp.open_message_stream('from@example.com', ['dest@example.com']) do |f|
694    #         f.puts 'From: from@example.com'
695    #         f.puts 'To: dest@example.com'
696    #         f.puts 'Subject: test message'
697    #         f.puts
698    #         f.puts 'This is a test message.'
699    #       end
700    #     end
701    #
702    # === Errors
703    #
704    # This method may raise:
705    #
706    # * Net::SMTPServerBusy
707    # * Net::SMTPSyntaxError
708    # * Net::SMTPFatalError
709    # * Net::SMTPUnknownError
710    # * Net::ReadTimeout
711    # * IOError
712    #
713    def open_message_stream(from_addr, *to_addrs, &block)   # :yield: stream
714      raise IOError, 'closed session' unless @socket
715      mailfrom from_addr
716      rcptto_list(to_addrs) {data(&block)}
717    end
718
719    alias ready open_message_stream   # obsolete
720
721    #
722    # Authentication
723    #
724
725    public
726
727    DEFAULT_AUTH_TYPE = :plain
728
729    def authenticate(user, secret, authtype = DEFAULT_AUTH_TYPE)
730      check_auth_method authtype
731      check_auth_args user, secret
732      send auth_method(authtype), user, secret
733    end
734
735    def auth_plain(user, secret)
736      check_auth_args user, secret
737      res = critical {
738        get_response('AUTH PLAIN ' + base64_encode("\0#{user}\0#{secret}"))
739      }
740      check_auth_response res
741      res
742    end
743
744    def auth_login(user, secret)
745      check_auth_args user, secret
746      res = critical {
747        check_auth_continue get_response('AUTH LOGIN')
748        check_auth_continue get_response(base64_encode(user))
749        get_response(base64_encode(secret))
750      }
751      check_auth_response res
752      res
753    end
754
755    def auth_cram_md5(user, secret)
756      check_auth_args user, secret
757      res = critical {
758        res0 = get_response('AUTH CRAM-MD5')
759        check_auth_continue res0
760        crammed = cram_md5_response(secret, res0.cram_md5_challenge)
761        get_response(base64_encode("#{user} #{crammed}"))
762      }
763      check_auth_response res
764      res
765    end
766
767    private
768
769    def check_auth_method(type)
770      unless respond_to?(auth_method(type), true)
771        raise ArgumentError, "wrong authentication type #{type}"
772      end
773    end
774
775    def auth_method(type)
776      "auth_#{type.to_s.downcase}".intern
777    end
778
779    def check_auth_args(user, secret, authtype = DEFAULT_AUTH_TYPE)
780      unless user
781        raise ArgumentError, 'SMTP-AUTH requested but missing user name'
782      end
783      unless secret
784        raise ArgumentError, 'SMTP-AUTH requested but missing secret phrase'
785      end
786    end
787
788    def base64_encode(str)
789      # expects "str" may not become too long
790      [str].pack('m').gsub(/\s+/, '')
791    end
792
793    IMASK = 0x36
794    OMASK = 0x5c
795
796    # CRAM-MD5: [RFC2195]
797    def cram_md5_response(secret, challenge)
798      tmp = Digest::MD5.digest(cram_secret(secret, IMASK) + challenge)
799      Digest::MD5.hexdigest(cram_secret(secret, OMASK) + tmp)
800    end
801
802    CRAM_BUFSIZE = 64
803
804    def cram_secret(secret, mask)
805      secret = Digest::MD5.digest(secret) if secret.size > CRAM_BUFSIZE
806      buf = secret.ljust(CRAM_BUFSIZE, "\0")
807      0.upto(buf.size - 1) do |i|
808        buf[i] = (buf[i].ord ^ mask).chr
809      end
810      buf
811    end
812
813    #
814    # SMTP command dispatcher
815    #
816
817    public
818
819    def starttls
820      getok('STARTTLS')
821    end
822
823    def helo(domain)
824      getok("HELO #{domain}")
825    end
826
827    def ehlo(domain)
828      getok("EHLO #{domain}")
829    end
830
831    def mailfrom(from_addr)
832      if $SAFE > 0
833        raise SecurityError, 'tainted from_addr' if from_addr.tainted?
834      end
835      getok("MAIL FROM:<#{from_addr}>")
836    end
837
838    def rcptto_list(to_addrs)
839      raise ArgumentError, 'mail destination not given' if to_addrs.empty?
840      ok_users = []
841      unknown_users = []
842      to_addrs.flatten.each do |addr|
843        begin
844          rcptto addr
845        rescue SMTPAuthenticationError
846          unknown_users << addr.dump
847        else
848          ok_users << addr
849        end
850      end
851      raise ArgumentError, 'mail destination not given' if ok_users.empty?
852      ret = yield
853      unless unknown_users.empty?
854        raise SMTPAuthenticationError, "failed to deliver for #{unknown_users.join(', ')}"
855      end
856      ret
857    end
858
859    def rcptto(to_addr)
860      if $SAFE > 0
861        raise SecurityError, 'tainted to_addr' if to_addr.tainted?
862      end
863      getok("RCPT TO:<#{to_addr}>")
864    end
865
866    # This method sends a message.
867    # If +msgstr+ is given, sends it as a message.
868    # If block is given, yield a message writer stream.
869    # You must write message before the block is closed.
870    #
871    #   # Example 1 (by string)
872    #   smtp.data(<<EndMessage)
873    #   From: john@example.com
874    #   To: betty@example.com
875    #   Subject: I found a bug
876    #
877    #   Check vm.c:58879.
878    #   EndMessage
879    #
880    #   # Example 2 (by block)
881    #   smtp.data {|f|
882    #     f.puts "From: john@example.com"
883    #     f.puts "To: betty@example.com"
884    #     f.puts "Subject: I found a bug"
885    #     f.puts ""
886    #     f.puts "Check vm.c:58879."
887    #   }
888    #
889    def data(msgstr = nil, &block)   #:yield: stream
890      if msgstr and block
891        raise ArgumentError, "message and block are exclusive"
892      end
893      unless msgstr or block
894        raise ArgumentError, "message or block is required"
895      end
896      res = critical {
897        check_continue get_response('DATA')
898        if msgstr
899          @socket.write_message msgstr
900        else
901          @socket.write_message_by_block(&block)
902        end
903        recv_response()
904      }
905      check_response res
906      res
907    end
908
909    def quit
910      getok('QUIT')
911    end
912
913    private
914
915    def getok(reqline)
916      res = critical {
917        @socket.writeline reqline
918        recv_response()
919      }
920      check_response res
921      res
922    end
923
924    def get_response(reqline)
925      @socket.writeline reqline
926      recv_response()
927    end
928
929    def recv_response
930      buf = ''
931      while true
932        line = @socket.readline
933        buf << line << "\n"
934        break unless line[3,1] == '-'   # "210-PIPELINING"
935      end
936      Response.parse(buf)
937    end
938
939    def critical
940      return Response.parse('200 dummy reply code') if @error_occurred
941      begin
942        return yield()
943      rescue Exception
944        @error_occurred = true
945        raise
946      end
947    end
948
949    def check_response(res)
950      unless res.success?
951        raise res.exception_class, res.message
952      end
953    end
954
955    def check_continue(res)
956      unless res.continue?
957        raise SMTPUnknownError, "could not get 3xx (#{res.status}: #{res.string})"
958      end
959    end
960
961    def check_auth_response(res)
962      unless res.success?
963        raise SMTPAuthenticationError, res.message
964      end
965    end
966
967    def check_auth_continue(res)
968      unless res.continue?
969        raise res.exception_class, res.message
970      end
971    end
972
973    # This class represents a response received by the SMTP server. Instances
974    # of this class are created by the SMTP class; they should not be directly
975    # created by the user. For more information on SMTP responses, view
976    # {Section 4.2 of RFC 5321}[http://tools.ietf.org/html/rfc5321#section-4.2]
977    class Response
978      # Parses the received response and separates the reply code and the human
979      # readable reply text
980      def self.parse(str)
981        new(str[0,3], str)
982      end
983
984      # Creates a new instance of the Response class and sets the status and
985      # string attributes
986      def initialize(status, string)
987        @status = status
988        @string = string
989      end
990
991      # The three digit reply code of the SMTP response
992      attr_reader :status
993
994      # The human readable reply text of the SMTP response
995      attr_reader :string
996
997      # Takes the first digit of the reply code to determine the status type
998      def status_type_char
999        @status[0, 1]
1000      end
1001
1002      # Determines whether the response received was a Positive Completion
1003      # reply (2xx reply code)
1004      def success?
1005        status_type_char() == '2'
1006      end
1007
1008      # Determines whether the response received was a Positive Intermediate
1009      # reply (3xx reply code)
1010      def continue?
1011        status_type_char() == '3'
1012      end
1013
1014      # The first line of the human readable reply text
1015      def message
1016        @string.lines.first
1017      end
1018
1019      # Creates a CRAM-MD5 challenge. You can view more information on CRAM-MD5
1020      # on Wikipedia: http://en.wikipedia.org/wiki/CRAM-MD5
1021      def cram_md5_challenge
1022        @string.split(/ /)[1].unpack('m')[0]
1023      end
1024
1025      # Returns a hash of the human readable reply text in the response if it
1026      # is multiple lines. It does not return the first line. The key of the
1027      # hash is the first word the value of the hash is an array with each word
1028      # thereafter being a value in the array
1029      def capabilities
1030        return {} unless @string[3, 1] == '-'
1031        h = {}
1032        @string.lines.drop(1).each do |line|
1033          k, *v = line[4..-1].chomp.split
1034          h[k] = v
1035        end
1036        h
1037      end
1038
1039      # Determines whether there was an error and raies the appropriate error
1040      # based on the reply code of the response
1041      def exception_class
1042        case @status
1043        when /\A4/  then SMTPServerBusy
1044        when /\A50/ then SMTPSyntaxError
1045        when /\A53/ then SMTPAuthenticationError
1046        when /\A5/  then SMTPFatalError
1047        else             SMTPUnknownError
1048        end
1049      end
1050    end
1051
1052    def logging(msg)
1053      @debug_output << msg + "\n" if @debug_output
1054    end
1055
1056  end   # class SMTP
1057
1058  SMTPSession = SMTP # :nodoc:
1059
1060end
1061