File: net/smtp.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: Net#30
has properties
constant: SMTPSession #1024
  module: SMTPError#33
  class: SMTPAuthenticationError#39
includes
  SMTPError ( Net )
inherits from
  ProtoAuthError ( Net )
  class: SMTPServerBusy#44
includes
  SMTPError ( Net )
inherits from
  ProtoServerError ( Net )
  class: SMTPSyntaxError#49
includes
  SMTPError ( Net )
inherits from
  ProtoSyntaxError ( Net )
  class: SMTPFatalError#54
includes
  SMTPError ( Net )
inherits from
  ProtoFatalError ( Net )
  class: SMTPUnknownError#59
includes
  SMTPError ( Net )
inherits from
  ProtoUnknownError ( Net )
  class: SMTPUnsupportedCommand#64
includes
  SMTPError ( Net )
inherits from
  ProtocolError ( Net )
  class: SMTP#173
inherits from
  Object ( Builtin-Module )
has properties
constant: Revision #175
class method: default_port #178
class method: default_submission_port #183
class method: default_tls_port #188
class alias: default_ssl_port default_tls_port #193
class method: default_ssl_context #196
method: initialize / 2 #211
method: inspect #228
method: esmtp? #233
method: esmtp= / 1 #244
alias: esmtp esmtp? #248
method: capable_starttls? #252
method: capable? / 1 #256
method: capable_plain_auth? #264
method: capable_login_auth? #270
method: capable_cram_md5_auth? #276
method: auth_capable? / 1 #280
method: capable_auth_types #289
method: tls? #296
alias: ssl? tls? #300
method: enable_tls / 1 #305
alias: enable_ssl enable_tls #312
method: disable_tls #316
alias: disable_ssl disable_tls #321
method: starttls? #326
method: starttls_always? #331
method: starttls_auto? #336
method: enable_starttls / 1 #342
method: enable_starttls_auto / 1 #351
method: disable_starttls #360
attribute: address [R] #366
attribute: port [R] #369
attribute: open_timeout [RW] #374
attribute: read_timeout [R] #379
method: read_timeout= / 1 #383
method: debug_output= / 1 #402
alias: set_debug_output debug_output #406
class method: start (1/2) / 7 #460
method: started? #467
method: start (2/E) / 4 #521
method: finish #538
method: do_start / 4 #545
method: tlsconnect / 1 #576
method: new_internet_message_io / 1 #587
method: do_helo / 1 #594
method: do_finish #606
method: send_message / 3 #651
alias: send_mail send_message #657
alias: sendmail send_message #658
method: open_message_stream / 3 #704
alias: ready open_message_stream #710
constant: DEFAULT_AUTH_TYPE #718
method: authenticate / 3 #720
method: auth_plain / 2 #726
method: auth_login / 2 #735
method: auth_cram_md5 / 2 #746
method: check_auth_method / 1 #760
method: auth_method / 1 #766
method: check_auth_args / 2 #770
method: base64_encode / 1 #779
constant: IMASK #784
constant: OMASK #785
method: cram_md5_response / 2 #788
constant: CRAM_BUFSIZE #793
method: cram_secret / 2 #795
method: starttls #810
method: helo / 1 #814
method: ehlo / 1 #818
method: mailfrom / 1 #822
method: rcptto_list / 1 #829
method: rcptto / 1 #850
method: data / 2 #880
method: quit #900
method: getok / 1 #906
method: get_response / 1 #915
method: recv_response #920
method: critical / 1 #930
method: check_response / 1 #940
method: check_continue / 1 #946
method: check_auth_response / 1 #952
method: check_auth_continue / 1 #958
method: logging / 1 #1018
  class: Response#964
inherits from
  Object ( Builtin-Module )
has properties
class method: parse / 1 #965
method: initialize / 2 #969
attribute: status [R] #974
attribute: string [R] #975
method: status_type_char #977
method: success? #981
method: continue? #985
method: message #989
method: cram_md5_challenge #993
method: capabilities #997
method: exception_class #1007

Class Hierarchy

Object ( Builtin-Module )
Exception ( Builtin-Module )
StandardError ( Builtin-Module )
ProtocolError ( Net )
ProtoSyntaxError ( Net )
  SMTPSyntaxError    #49
ProtoFatalError ( Net )
  SMTPFatalError    #54
ProtoUnknownError ( Net )
  SMTPUnknownError    #59
ProtoServerError ( Net )
  SMTPServerBusy    #44
ProtoAuthError ( Net )
  SMTPAuthenticationError    #39
SMTPUnsupportedCommand ( Net ) — #64
SMTP ( Net ) — #173
Response ( Net::SMTP ) — #964

Code

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