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