lib/net/smtp.rb in net-smtp-0.1.0 vs lib/net/smtp.rb in net-smtp-0.2.0

- old
+ new

@@ -144,32 +144,33 @@ # to SMTP.start/SMTP#start. This is the domain name which you are on # (the host to send mail from). It is called the "HELO domain". # The SMTP server will judge whether it should send or reject # the SMTP session by inspecting the HELO domain. # - # Net::SMTP.start('your.smtp.server', 25, - # 'mail.from.domain') { |smtp| ... } + # Net::SMTP.start('your.smtp.server', 25 + # helo: 'mail.from.domain') { |smtp| ... } # # === SMTP Authentication # # The Net::SMTP class supports three authentication schemes; # PLAIN, LOGIN and CRAM MD5. (SMTP Authentication: [RFC2554]) # To use SMTP authentication, pass extra arguments to # SMTP.start/SMTP#start. # # # PLAIN - # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain', - # 'Your Account', 'Your Password', :plain) + # Net::SMTP.start('your.smtp.server', 25 + # user: 'Your Account', secret: 'Your Password', authtype: :plain) # # LOGIN - # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain', - # 'Your Account', 'Your Password', :login) + # Net::SMTP.start('your.smtp.server', 25 + # user: 'Your Account', secret: 'Your Password', authtype: :login) # # # CRAM MD5 - # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain', - # 'Your Account', 'Your Password', :cram_md5) + # Net::SMTP.start('your.smtp.server', 25 + # user: 'Your Account', secret: 'Your Password', authtype: :cram_md5) # class SMTP < Protocol + VERSION = "0.2.0" Revision = %q$Revision$.split[1] # The default SMTP port number, 25. def SMTP.default_port @@ -188,12 +189,17 @@ class << self alias default_ssl_port default_tls_port end - def SMTP.default_ssl_context - OpenSSL::SSL::SSLContext.new + def SMTP.default_ssl_context(verify_peer=true) + context = OpenSSL::SSL::SSLContext.new + context.verify_mode = verify_peer ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE + store = OpenSSL::X509::Store.new + store.set_default_paths + context.cert_store = store + context end # # Creates a new Net::SMTP object. # @@ -215,12 +221,13 @@ @open_timeout = 30 @read_timeout = 60 @error_occurred = false @debug_output = nil @tls = false - @starttls = false - @ssl_context = nil + @starttls = :auto + @ssl_context_tls = nil + @ssl_context_starttls = nil end # Provide human-readable stringification of class state. def inspect "#<#{self.class} #{@address}:#{@port} started=#{@started}>" @@ -291,24 +298,24 @@ alias ssl? tls? # Enables SMTP/TLS (SMTPS: SMTP over direct TLS connection) for # this object. Must be called before the connection is established # to have any effect. +context+ is a OpenSSL::SSL::SSLContext object. - def enable_tls(context = SMTP.default_ssl_context) + def enable_tls(context = nil) raise 'openssl library not installed' unless defined?(OpenSSL) - raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @starttls + raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @starttls == :always @tls = true - @ssl_context = context + @ssl_context_tls = context end alias enable_ssl enable_tls # Disables SMTP/TLS for this object. Must be called before the # connection is established to have any effect. def disable_tls @tls = false - @ssl_context = nil + @ssl_context_tls = nil end alias disable_ssl disable_tls # Returns truth value if this object uses STARTTLS. @@ -328,31 +335,31 @@ @starttls == :auto end # Enables SMTP/TLS (STARTTLS) for this object. # +context+ is a OpenSSL::SSL::SSLContext object. - def enable_starttls(context = SMTP.default_ssl_context) + def enable_starttls(context = nil) raise 'openssl library not installed' unless defined?(OpenSSL) raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls @starttls = :always - @ssl_context = context + @ssl_context_starttls = context end # Enables SMTP/TLS (STARTTLS) for this object if server accepts. # +context+ is a OpenSSL::SSL::SSLContext object. - def enable_starttls_auto(context = SMTP.default_ssl_context) + def enable_starttls_auto(context = nil) raise 'openssl library not installed' unless defined?(OpenSSL) raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls @starttls = :auto - @ssl_context = context + @ssl_context_starttls = context end # Disables SMTP/TLS (STARTTLS) for this object. Must be called # before the connection is established to have any effect. def disable_starttls @starttls = false - @ssl_context = nil + @ssl_context_starttls = nil end # The address of the SMTP server to connect to. attr_reader :address @@ -399,15 +406,19 @@ # # SMTP session control # # + # :call-seq: + # start(address, port = nil, helo: 'localhost', user: nil, secret: nil, authtype: nil, tls_verify: true, tls_hostname: nil) { |smtp| ... } + # start(address, port = nil, helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... } + # # Creates a new Net::SMTP object and connects to the server. # # This method is equivalent to: # - # Net::SMTP.new(address, port).start(helo_domain, account, password, authtype) + # Net::SMTP.new(address, port).start(helo: helo_domain, user: account, secret: password, authtype: authtype, tls_verify: flag, tls_hostname: hostname) # # === Example # # Net::SMTP.start('your.smtp.server') do |smtp| # smtp.send_message msgstr, 'from@example.com', ['dest@example.com'] @@ -433,10 +444,13 @@ # The remaining arguments are used for SMTP authentication, if required # or desired. +user+ is the account name; +secret+ is your password # or other authentication token; and +authtype+ is the authentication # type, one of :plain, :login, or :cram_md5. See the discussion of # SMTP Authentication in the overview notes. + # If +tls_verify+ is true, verify the server's certificate. The default is true. + # If the hostname in the server certificate is different from +address+, + # it can be specified with +tls_hostname+. # # === Errors # # This method may raise: # @@ -447,22 +461,32 @@ # * Net::SMTPUnknownError # * Net::OpenTimeout # * Net::ReadTimeout # * IOError # - def SMTP.start(address, port = nil, helo = 'localhost', - user = nil, secret = nil, authtype = nil, - &block) # :yield: smtp - new(address, port).start(helo, user, secret, authtype, &block) + def SMTP.start(address, port = nil, *args, helo: nil, + user: nil, secret: nil, password: nil, authtype: nil, + tls_verify: true, tls_hostname: nil, + &block) + raise ArgumentError, "wrong number of arguments (given #{args.size + 2}, expected 1..6)" if args.size > 4 + helo ||= args[0] || 'localhost' + user ||= args[1] + secret ||= password || args[2] + authtype ||= args[3] + new(address, port).start(helo: helo, user: user, secret: secret, authtype: authtype, tls_verify: tls_verify, tls_hostname: tls_hostname, &block) end # +true+ if the SMTP session has been started. def started? @started end # + # :call-seq: + # start(helo: 'localhost', user: nil, secret: nil, authtype: nil, tls_verify: true, tls_hostname: nil) { |smtp| ... } + # start(helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... } + # # Opens a TCP connection and starts the SMTP session. # # === Parameters # # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see @@ -471,10 +495,13 @@ # If both of +user+ and +secret+ are given, SMTP authentication # will be attempted using the AUTH command. +authtype+ specifies # the type of authentication to attempt; it must be one of # :login, :plain, and :cram_md5. See the notes on SMTP Authentication # in the overview. + # If +tls_verify+ is true, verify the server's certificate. The default is true. + # If the hostname in the server certificate is different from +address+, + # it can be specified with +tls_hostname+. # # === Block Usage # # When this methods is called with a block, the newly-started SMTP # object is yielded to the block, and automatically closed after @@ -485,11 +512,11 @@ # # This is very similar to the class method SMTP.start. # # require 'net/smtp' # smtp = Net::SMTP.new('smtp.mail.server', 25) - # smtp.start(helo_domain, account, password, authtype) do |smtp| + # smtp.start(helo: helo_domain, user: account, secret: password, authtype: authtype) do |smtp| # smtp.send_message msgstr, 'from@example.com', ['dest@example.com'] # end # # The primary use of this method (as opposed to SMTP.start) # is probably to set debugging (#set_debug_output) or ESMTP @@ -509,12 +536,24 @@ # * Net::SMTPUnknownError # * Net::OpenTimeout # * Net::ReadTimeout # * IOError # - def start(helo = 'localhost', - user = nil, secret = nil, authtype = nil) # :yield: smtp + def start(*args, helo: nil, + user: nil, secret: nil, password: nil, authtype: nil, tls_verify: true, tls_hostname: nil) + raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..4)" if args.size > 4 + helo ||= args[0] || 'localhost' + user ||= args[1] + secret ||= password || args[2] + authtype ||= args[3] + if @tls && @ssl_context_tls.nil? + @ssl_context_tls = SMTP.default_ssl_context(tls_verify) + end + if @starttls && @ssl_context_starttls.nil? + @ssl_context_starttls = SMTP.default_ssl_context(tls_verify) + end + @tls_hostname = tls_hostname if block_given? begin do_start helo, user, secret, authtype return yield(self) ensure @@ -547,20 +586,20 @@ end s = Timeout.timeout(@open_timeout, Net::OpenTimeout) do tcp_socket(@address, @port) end logging "Connection opened: #{@address}:#{@port}" - @socket = new_internet_message_io(tls? ? tlsconnect(s) : s) + @socket = new_internet_message_io(tls? ? tlsconnect(s, @ssl_context_tls) : s) check_response critical { recv_response() } do_helo helo_domain - if starttls_always? or (capable_starttls? and starttls_auto?) + if ! tls? and (starttls_always? or (capable_starttls? and starttls_auto?)) unless capable_starttls? raise SMTPUnsupportedCommand, "STARTTLS is not supported on this server" end starttls - @socket = new_internet_message_io(tlsconnect(s)) + @socket = new_internet_message_io(tlsconnect(s, @ssl_context_starttls)) # helo response may be different after STARTTLS do_helo helo_domain end authenticate user, secret, (authtype || DEFAULT_AUTH_TYPE) if user @started = true @@ -574,18 +613,19 @@ def ssl_socket(socket, context) OpenSSL::SSL::SSLSocket.new socket, context end - def tlsconnect(s) + def tlsconnect(s, context) verified = false - s = ssl_socket(s, @ssl_context) + s = ssl_socket(s, context) logging "TLS connection started" s.sync_close = true + s.hostname = @tls_hostname || @address if s.respond_to? :hostname= ssl_socket_connect(s, @open_timeout) - if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE - s.post_connection_check(@address) + if context.verify_mode && context.verify_mode != OpenSSL::SSL::VERIFY_NONE + s.post_connection_check(@tls_hostname || @address) end verified = true s ensure s.close unless verified @@ -829,13 +869,10 @@ def ehlo(domain) getok("EHLO #{domain}") end def mailfrom(from_addr) - if $SAFE > 0 - raise SecurityError, 'tainted from_addr' if from_addr.tainted? - end getok("MAIL FROM:<#{from_addr}>") end def rcptto_list(to_addrs) raise ArgumentError, 'mail destination not given' if to_addrs.empty? @@ -857,13 +894,10 @@ end ret end def rcptto(to_addr) - if $SAFE > 0 - raise SecurityError, 'tainted to_addr' if to_addr.tainted? - end getok("RCPT TO:<#{to_addr}>") end # This method sends a message. # If +msgstr+ is given, sends it as a message. @@ -1046,10 +1080,10 @@ # thereafter being a value in the array def capabilities return {} unless @string[3, 1] == '-' h = {} @string.lines.drop(1).each do |line| - k, *v = line[4..-1].chomp.split + k, *v = line[4..-1].split(' ') h[k] = v end h end