lib/net/smtp.rb in net-smtp-0.2.1 vs lib/net/smtp.rb in net-smtp-0.2.2
- old
+ new
@@ -29,10 +29,21 @@
# Module mixed in to all SMTP error classes
module SMTPError
# This *class* is a module for backward compatibility.
# In later release, this module becomes a class.
+
+ attr_reader :response
+
+ def initialize(response, message: nil)
+ @response = response
+ @message = message
+ end
+
+ def message
+ @message || response.message
+ end
end
# Represents an SMTP authentication error.
class SMTPAuthenticationError < ProtoAuthError
include SMTPError
@@ -166,11 +177,11 @@
# # CRAM MD5
# Net::SMTP.start('your.smtp.server', 25
# user: 'Your Account', secret: 'Your Password', authtype: :cram_md5)
#
class SMTP < Protocol
- VERSION = "0.2.1"
+ VERSION = "0.2.2"
Revision = %q$Revision$.split[1]
# The default SMTP port number, 25.
def SMTP.default_port
@@ -189,16 +200,13 @@
class << self
alias default_ssl_port default_tls_port
end
- def SMTP.default_ssl_context(verify_peer=true)
+ def SMTP.default_ssl_context(ssl_context_params = nil)
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.set_params(ssl_context_params ? ssl_context_params : {})
context
end
#
# Creates a new Net::SMTP object.
@@ -299,11 +307,11 @@
# 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 = nil)
- raise 'openssl library not installed' unless defined?(OpenSSL)
+ raise 'openssl library not installed' unless defined?(OpenSSL::VERSION)
raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @starttls == :always
@tls = true
@ssl_context_tls = context
end
@@ -336,20 +344,20 @@
end
# Enables SMTP/TLS (STARTTLS) for this object.
# +context+ is a OpenSSL::SSL::SSLContext object.
def enable_starttls(context = nil)
- raise 'openssl library not installed' unless defined?(OpenSSL)
+ raise 'openssl library not installed' unless defined?(OpenSSL::VERSION)
raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls
@starttls = :always
@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 = nil)
- raise 'openssl library not installed' unless defined?(OpenSSL)
+ raise 'openssl library not installed' unless defined?(OpenSSL::VERSION)
raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls
@starttls = :auto
@ssl_context_starttls = context
end
@@ -407,18 +415,18 @@
# 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, tls_verify: true, tls_hostname: nil, ssl_context_params: 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: helo_domain, user: account, secret: password, authtype: authtype, tls_verify: flag, tls_hostname: hostname)
+ # Net::SMTP.new(address, port).start(helo: helo_domain, user: account, secret: password, authtype: authtype, tls_verify: flag, tls_hostname: hostname, ssl_context_params: nil)
#
# === Example
#
# Net::SMTP.start('your.smtp.server') do |smtp|
# smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
@@ -448,10 +456,15 @@
# 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+.
#
+ # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to
+ # +OpenSSL::SSL::SSLContext#set_params+
+ #
+ # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+.
+ #
# === Errors
#
# This method may raise:
#
# * Net::SMTPAuthenticationError
@@ -463,28 +476,28 @@
# * Net::ReadTimeout
# * IOError
#
def SMTP.start(address, port = nil, *args, helo: nil,
user: nil, secret: nil, password: nil, authtype: nil,
- tls_verify: true, tls_hostname: nil,
+ tls_verify: true, tls_hostname: nil, ssl_context_params: 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)
+ new(address, port).start(helo: helo, user: user, secret: secret, authtype: authtype, tls_verify: tls_verify, tls_hostname: tls_hostname, ssl_context_params: ssl_context_params, &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, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... }
# start(helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... }
#
# Opens a TCP connection and starts the SMTP session.
#
# === Parameters
@@ -499,10 +512,15 @@
# 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+.
#
+ # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to
+ # +OpenSSL::SSL::SSLContext#set_params+
+ #
+ # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+.
+ #
# === Block Usage
#
# When this methods is called with a block, the newly-started SMTP
# object is yielded to the block, and automatically closed after
# the block call finishes. Otherwise, it is the caller's
@@ -537,23 +555,29 @@
# * Net::OpenTimeout
# * Net::ReadTimeout
# * IOError
#
def start(*args, helo: nil,
- user: nil, secret: nil, password: nil, authtype: nil, tls_verify: true, tls_hostname: nil)
+ user: nil, secret: nil, password: nil, authtype: nil, tls_verify: true, tls_hostname: nil, ssl_context_params: 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)
+ if defined?(OpenSSL::VERSION)
+ ssl_context_params = ssl_context_params ? ssl_context_params : {}
+ unless ssl_context_params.has_key?(:verify_mode)
+ ssl_context_params[:verify_mode] = tls_verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
+ end
+ if @tls && @ssl_context_tls.nil?
+ @ssl_context_tls = SMTP.default_ssl_context(ssl_context_params)
+ end
+ if @starttls && @ssl_context_starttls.nil?
+ @ssl_context_starttls = SMTP.default_ssl_context(ssl_context_params)
+ end
+ @tls_hostname = tls_hostname
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
@@ -573,30 +597,32 @@
end
private
def tcp_socket(address, port)
- TCPSocket.open address, port
+ begin
+ Socket.tcp address, port, nil, nil, connect_timeout: @open_timeout
+ rescue Errno::ETIMEDOUT #raise Net:OpenTimeout instead for compatibility with previous versions
+ raise Net::OpenTimeout, "Timeout to open TCP connection to "\
+ "#{address}:#{port} (exceeds #{@open_timeout} seconds)"
+ end
end
def do_start(helo_domain, user, secret, authtype)
raise IOError, 'SMTP session already started' if @started
if user or secret
check_auth_method(authtype || DEFAULT_AUTH_TYPE)
check_auth_args user, secret
end
- s = Timeout.timeout(@open_timeout, Net::OpenTimeout) do
- tcp_socket(@address, @port)
- end
+ s = tcp_socket(@address, @port)
logging "Connection opened: #{@address}:#{@port}"
@socket = new_internet_message_io(tls? ? tlsconnect(s, @ssl_context_tls) : s)
check_response critical { recv_response() }
do_helo helo_domain
if ! tls? and (starttls_always? or (capable_starttls? and starttls_auto?))
unless capable_starttls?
- raise SMTPUnsupportedCommand,
- "STARTTLS is not supported on this server"
+ raise SMTPUnsupportedCommand.new(nil, message: "STARTTLS is not supported on this server")
end
starttls
@socket = new_internet_message_io(tlsconnect(s, @ssl_context_starttls))
# helo response may be different after STARTTLS
do_helo helo_domain
@@ -618,15 +644,12 @@
def tlsconnect(s, context)
verified = false
s = ssl_socket(s, context)
logging "TLS connection started"
s.sync_close = true
- s.hostname = @tls_hostname || @address if s.respond_to? :hostname=
+ s.hostname = @tls_hostname || @address
ssl_socket_connect(s, @open_timeout)
- 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
end
@@ -998,28 +1021,28 @@
end
end
def check_response(res)
unless res.success?
- raise res.exception_class, res.message
+ raise res.exception_class.new(res)
end
end
def check_continue(res)
unless res.continue?
- raise SMTPUnknownError, "could not get 3xx (#{res.status}: #{res.string})"
+ raise SMTPUnknownError.new(res, message: "could not get 3xx (#{res.status}: #{res.string})")
end
end
def check_auth_response(res)
unless res.success?
- raise SMTPAuthenticationError, res.message
+ raise SMTPAuthenticationError.new(res)
end
end
def check_auth_continue(res)
unless res.continue?
- raise res.exception_class, res.message
+ raise res.exception_class.new(res)
end
end
# This class represents a response received by the SMTP server. Instances
# of this class are created by the SMTP class; they should not be directly