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