Skip to content
This repository
Browse code

Adding empty file for the SMTP lib, adding files and materials for re…

…ference purposes.
  • Loading branch information...
commit bc4156590f5d16390aceb5e646b5b1099ba6a261 1 parent 29219c4
Micheil Smith authored
9 lib/smtp.js
... ... @@ -0,0 +1,9 @@
  1 +
  2 +
  3 +
  4 +
  5 +
  6 +
  7 +
  8 +
  9 +
BIN  reference/rfc2821.pdf
Binary file not shown
4,427 reference/rfc2821.txt
4,427 additions, 0 deletions not shown
1,014 reference/smtp.rb
... ... @@ -0,0 +1,1014 @@
  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$
  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$.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'.
  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',
  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',
  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
  655 + data msgstr
  656 + end
  657 +
  658 + alias send_mail send_message
  659 + alias sendmail send_message # obsolete
  660 +
  661 + #
  662 + # Opens a message writer stream and gives it to the block.
  663 + # The stream is valid only in the block, and has these methods:
  664 + #
  665 + # puts(str = ''):: outputs STR and CR LF.
  666 + # print(str):: outputs STR.
  667 + # printf(fmt, *args):: outputs sprintf(fmt,*args).
  668 + # write(str):: outputs STR and returns the length of written bytes.
  669 + # <<(str):: outputs STR and returns self.
  670 + #
  671 + # If a single CR ("\r") or LF ("\n") is found in the message,
  672 + # it is converted to the CR LF pair. You cannot send a binary
  673 + # message with this method.
  674 + #
  675 + # === Parameters
  676 + #
  677 + # +from_addr+ is a String representing the source mail address.
  678 + #
  679 + # +to_addr+ is a String or Strings or Array of Strings, representing
  680 + # the destination mail address or addresses.
  681 + #
  682 + # === Example
  683 + #
  684 + # Net::SMTP.start('smtp.example.com', 25) do |smtp|
  685 + # smtp.open_message_stream('from@example.com', ['dest@example.com']) do |f|
  686 + # f.puts 'From: from@example.com'
  687 + # f.puts 'To: dest@example.com'
  688 + # f.puts 'Subject: test message'
  689 + # f.puts
  690 + # f.puts 'This is a test message.'
  691 + # end
  692 + # end
  693 + #
  694 + # === Errors
  695 + #
  696 + # This method may raise:
  697 + #
  698 + # * Net::SMTPServerBusy
  699 + # * Net::SMTPSyntaxError
  700 + # * Net::SMTPFatalError
  701 + # * Net::SMTPUnknownError
  702 + # * IOError
  703 + # * TimeoutError
  704 + #
  705 + def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream
  706 + raise IOError, 'closed session' unless @socket
  707 + mailfrom from_addr
  708 + rcptto_list to_addrs
  709 + data(&block)
  710 + end
  711 +
  712 + alias ready open_message_stream # obsolete
  713 +
  714 + #
  715 + # Authentication
  716 + #
  717 +
  718 + public
  719 +
  720 + DEFAULT_AUTH_TYPE = :plain
  721 +
  722 + def authenticate(user, secret, authtype = DEFAULT_AUTH_TYPE)
  723 + check_auth_method authtype
  724 + check_auth_args user, secret
  725 + send auth_method(authtype), user, secret
  726 + end
  727 +
  728 + def auth_plain(user, secret)
  729 + check_auth_args user, secret
  730 + res = critical {
  731 + get_response('AUTH PLAIN ' + base64_encode("\0#{user}\0#{secret}"))
  732 + }
  733 + check_auth_response res
  734 + res
  735 + end
  736 +
  737 + def auth_login(user, secret)
  738 + check_auth_args user, secret
  739 + res = critical {
  740 + check_auth_continue get_response('AUTH LOGIN')
  741 + check_auth_continue get_response(base64_encode(user))
  742 + get_response(base64_encode(secret))
  743 + }
  744 + check_auth_response res
  745 + res
  746 + end
  747 +
  748 + def auth_cram_md5(user, secret)
  749 + check_auth_args user, secret
  750 + res = critical {
  751 + res0 = get_response('AUTH CRAM-MD5')
  752 + check_auth_continue res0
  753 + crammed = cram_md5_response(secret, res0.cram_md5_challenge)
  754 + get_response(base64_encode("#{user} #{crammed}"))
  755 + }
  756 + check_auth_response res
  757 + res
  758 + end
  759 +
  760 + private
  761 +
  762 + def check_auth_method(type)
  763 + unless respond_to?(auth_method(type), true)
  764 + raise ArgumentError, "wrong authentication type #{type}"
  765 + end
  766 + end
  767 +
  768 + def auth_method(type)
  769 + "auth_#{type.to_s.downcase}".intern
  770 + end
  771 +
  772 + def check_auth_args(user, secret)
  773 + unless user
  774 + raise ArgumentError, 'SMTP-AUTH requested but missing user name'
  775 + end
  776 + unless secret
  777 + raise ArgumentError, 'SMTP-AUTH requested but missing secret phrase'
  778 + end
  779 + end
  780 +
  781 + def base64_encode(str)
  782 + # expects "str" may not become too long
  783 + [str].pack('m').gsub(/\s+/, '')
  784 + end
  785 +
  786 + IMASK = 0x36
  787 + OMASK = 0x5c
  788 +
  789 + # CRAM-MD5: [RFC2195]
  790 + def cram_md5_response(secret, challenge)
  791 + tmp = Digest::MD5.digest(cram_secret(secret, IMASK) + challenge)
  792 + Digest::MD5.hexdigest(cram_secret(secret, OMASK) + tmp)
  793 + end
  794 +
  795 + CRAM_BUFSIZE = 64
  796 +
  797 + def cram_secret(secret, mask)
  798 + secret = Digest::MD5.digest(secret) if secret.size > CRAM_BUFSIZE
  799 + buf = secret.ljust(CRAM_BUFSIZE, "\0")
  800 + 0.upto(buf.size - 1) do |i|
  801 + buf[i] = (buf[i].ord ^ mask).chr
  802 + end
  803 + buf
  804 + end
  805 +
  806 + #
  807 + # SMTP command dispatcher
  808 + #
  809 +
  810 + public
  811 +
  812 + def starttls
  813 + getok('STARTTLS')
  814 + end
  815 +
  816 + def helo(domain)
  817 + getok("HELO #{domain}")
  818 + end
  819 +
  820 + def ehlo(domain)
  821 + getok("EHLO #{domain}")
  822 + end
  823 +
  824 + def mailfrom(from_addr)
  825 + if $SAFE > 0
  826 + raise SecurityError, 'tainted from_addr' if from_addr.tainted?
  827 + end
  828 + getok("MAIL FROM:<#{from_addr}>")
  829 + end
  830 +
  831 + def rcptto_list(to_addrs)
  832 + raise ArgumentError, 'mail destination not given' if to_addrs.empty?
  833 + to_addrs.flatten.each do |addr|
  834 + rcptto addr
  835 + end
  836 + end
  837 +
  838 + def rcptto(to_addr)
  839 + if $SAFE > 0
  840 + raise SecurityError, 'tainted to_addr' if to_addr.tainted?
  841 + end
  842 + getok("RCPT TO:<#{to_addr}>")
  843 + end
  844 +
  845 + # This method sends a message.
  846 + # If +msgstr+ is given, sends it as a message.
  847 + # If block is given, yield a message writer stream.
  848 + # You must write message before the block is closed.
  849 + #
  850 + # # Example 1 (by string)
  851 + # smtp.data(<<EndMessage)
  852 + # From: john@example.com
  853 + # To: betty@example.com
  854 + # Subject: I found a bug
  855 + #
  856 + # Check vm.c:58879.
  857 + # EndMessage
  858 + #
  859 + # # Example 2 (by block)
  860 + # smtp.data {|f|
  861 + # f.puts "From: john@example.com"
  862 + # f.puts "To: betty@example.com"
  863 + # f.puts "Subject: I found a bug"
  864 + # f.puts ""
  865 + # f.puts "Check vm.c:58879."
  866 + # }
  867 + #
  868 + def data(msgstr = nil, &block) #:yield: stream
  869 + if msgstr and block
  870 + raise ArgumentError, "message and block are exclusive"
  871 + end
  872 + unless msgstr or block
  873 + raise ArgumentError, "message or block is required"
  874 + end
  875 + res = critical {
  876 + check_continue get_response('DATA')
  877 + if msgstr
  878 + @socket.write_message msgstr
  879 + else
  880 + @socket.write_message_by_block(&block)
  881 + end
  882 + recv_response()
  883 + }
  884 + check_response res
  885 + res
  886 + end
  887 +
  888 + def quit
  889 + getok('QUIT')
  890 + end
  891 +
  892 + private
  893 +
  894 + def getok(reqline)
  895 + res = critical {
  896 + @socket.writeline reqline
  897 + recv_response()
  898 + }
  899 + check_response res
  900 + res
  901 + end
  902 +
  903 + def get_response(reqline)
  904 + @socket.writeline reqline
  905 + recv_response()
  906 + end
  907 +
  908 + def recv_response
  909 + buf = ''
  910 + while true
  911 + line = @socket.readline
  912 + buf << line << "\n"
  913 + break unless line[3,1] == '-' # "210-PIPELINING"
  914 + end
  915 + Response.parse(buf)
  916 + end
  917 +
  918 + def critical(&block)
  919 + return '200 dummy reply code' if @error_occured
  920 + begin
  921 + return yield()
  922 + rescue Exception
  923 + @error_occured = true
  924 + raise
  925 + end
  926 + end
  927 +
  928 + def check_response(res)
  929 + unless res.success?
  930 + raise res.exception_class, res.message
  931 + end
  932 + end
  933 +
  934 + def check_continue(res)
  935 + unless res.continue?
  936 + raise SMTPUnknownError, "could not get 3xx (#{res.status})"
  937 + end
  938 + end
  939 +
  940 + def check_auth_response(res)
  941 + unless res.success?
  942 + raise SMTPAuthenticationError, res.message
  943 + end
  944 + end
  945 +
  946 + def check_auth_continue(res)
  947 + unless res.continue?
  948 + raise res.exception_class, res.message
  949 + end
  950 + end
  951 +
  952 + class Response
  953 + def Response.parse(str)
  954 + new(str[0,3], str)
  955 + end
  956 +
  957 + def initialize(status, string)
  958 + @status = status
  959 + @string = string
  960 + end
  961 +
  962 + attr_reader :status
  963 + attr_reader :string
  964 +
  965 + def status_type_char
  966 + @status[0, 1]
  967 + end
  968 +
  969 + def success?
  970 + status_type_char() == '2'
  971 + end
  972 +
  973 + def continue?
  974 + status_type_char() == '3'
  975 + end
  976 +
  977 + def message
  978 + @string.lines.first
  979 + end
  980 +
  981 + def cram_md5_challenge
  982 + @string.split(/ /)[1].unpack('m')[0]
  983 + end
  984 +
  985 + def capabilities
  986 + return {} unless @string[3, 1] == '-'
  987 + h = {}
  988 + @string.lines.drop(1).each do |line|
  989 + k, *v = line[4..-1].chomp.split(nil)
  990 + h[k] = v
  991 + end
  992 + h
  993 + end
  994 +
  995 + def exception_class
  996 + case @status
  997 + when /\A4/ then SMTPServerBusy
  998 + when /\A50/ then SMTPSyntaxError
  999 + when /\A53/ then SMTPAuthenticationError
  1000 + when /\A5/ then SMTPFatalError
  1001 + else SMTPUnknownError
  1002 + end
  1003 + end
  1004 + end
  1005 +
  1006 + def logging(msg)
  1007 + @debug_output << msg + "\n" if @debug_output
  1008 + end
  1009 +
  1010 + end # class SMTP
  1011 +
  1012 + SMTPSession = SMTP
  1013 +
  1014 +end

0 comments on commit bc41565

Please sign in to comment.
Something went wrong with that request. Please try again.