зеркало из https://github.com/github/ruby.git
Import net-smtp-0.2.0 from https://github.com/ruby/net-smtp
This commit is contained in:
Родитель
fcc88da5eb
Коммит
cada6d85d0
|
@ -168,7 +168,7 @@ module Net
|
|||
# user: 'Your Account', secret: 'Your Password', authtype: :cram_md5)
|
||||
#
|
||||
class SMTP < Protocol
|
||||
VERSION = "0.1.0"
|
||||
VERSION = "0.2.0"
|
||||
|
||||
Revision = %q$Revision$.split[1]
|
||||
|
||||
|
@ -191,8 +191,13 @@ module Net
|
|||
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
|
||||
|
||||
#
|
||||
|
@ -218,8 +223,9 @@ module Net
|
|||
@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.
|
||||
|
@ -294,11 +300,11 @@ module Net
|
|||
# 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
|
||||
|
@ -307,7 +313,7 @@ module Net
|
|||
# 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
|
||||
|
@ -331,27 +337,27 @@ module Net
|
|||
|
||||
# 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.
|
||||
|
@ -403,14 +409,14 @@ module Net
|
|||
|
||||
#
|
||||
# :call-seq:
|
||||
# start(address, port = nil, helo: 'localhost', user: nil, secret: nil, authtype: nil) { |smtp| ... }
|
||||
# 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: helo_domain, user: account, secret: password, authtype: authtype)
|
||||
# Net::SMTP.new(address, port).start(helo: helo_domain, user: account, secret: password, authtype: authtype, tls_verify: flag, tls_hostname: hostname)
|
||||
#
|
||||
# === Example
|
||||
#
|
||||
|
@ -440,6 +446,9 @@ module Net
|
|||
# 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
|
||||
#
|
||||
|
@ -456,13 +465,14 @@ module Net
|
|||
#
|
||||
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, &block)
|
||||
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.
|
||||
|
@ -472,7 +482,7 @@ module Net
|
|||
|
||||
#
|
||||
# :call-seq:
|
||||
# start(helo: 'localhost', user: nil, secret: nil, authtype: nil) { |smtp| ... }
|
||||
# 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.
|
||||
|
@ -487,6 +497,9 @@ module Net
|
|||
# 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
|
||||
#
|
||||
|
@ -526,12 +539,19 @@ module Net
|
|||
# * IOError
|
||||
#
|
||||
def start(*args, helo: nil,
|
||||
user: nil, secret: nil, password: nil, authtype: 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
|
||||
|
@ -568,16 +588,16 @@ module Net
|
|||
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
|
||||
|
@ -595,15 +615,15 @@ module Net
|
|||
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 = @address if s.respond_to? :hostname=
|
||||
s.hostname = @tls_hostname || @address if s.respond_to? :hostname=
|
||||
ssl_socket_connect(s, @open_timeout)
|
||||
if @ssl_context.verify_mode && @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
|
||||
|
|
|
@ -137,7 +137,7 @@ module Net
|
|||
smtp = Net::SMTP.new("localhost", servers[0].local_address.ip_port)
|
||||
smtp.enable_tls
|
||||
smtp.open_timeout = 1
|
||||
smtp.start do
|
||||
smtp.start(tls_verify: false) do
|
||||
end
|
||||
ensure
|
||||
sock.close if sock
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
require 'net/smtp'
|
||||
require 'test/unit'
|
||||
|
||||
module Net
|
||||
class TestSSLContext < Test::Unit::TestCase
|
||||
class MySMTP < SMTP
|
||||
attr_reader :__ssl_context, :__tls_hostname
|
||||
|
||||
def initialize(socket)
|
||||
@fake_socket = socket
|
||||
super("smtp.example.com")
|
||||
end
|
||||
|
||||
def tcp_socket(*)
|
||||
@fake_socket
|
||||
end
|
||||
|
||||
def ssl_socket_connect(*)
|
||||
end
|
||||
|
||||
def tlsconnect(*)
|
||||
super
|
||||
@fake_socket
|
||||
end
|
||||
|
||||
def ssl_socket(socket, context)
|
||||
@__ssl_context = context
|
||||
s = super
|
||||
hostname = @__tls_hostname = ''
|
||||
s.define_singleton_method(:post_connection_check){ |name| hostname.replace(name) }
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
@server_thread&.exit
|
||||
@server_socket&.close
|
||||
@client_socket&.close
|
||||
end
|
||||
|
||||
def start_smtpd(starttls)
|
||||
@server_socket, @client_socket = UNIXSocket.pair
|
||||
@starttls_executed = false
|
||||
@server_thread = Thread.new(@server_socket) do |s|
|
||||
s.puts "220 fakeserver\r\n"
|
||||
while cmd = s.gets&.chomp
|
||||
case cmd
|
||||
when /\AEHLO /
|
||||
s.puts "250-fakeserver\r\n"
|
||||
s.puts "250-STARTTLS\r\n" if starttls
|
||||
s.puts "250 8BITMIME\r\n"
|
||||
when /\ASTARTTLS/
|
||||
@starttls_executed = true
|
||||
s.puts "220 2.0.0 Ready to start TLS\r\n"
|
||||
else
|
||||
raise "unsupported command: #{cmd}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@client_socket
|
||||
end
|
||||
|
||||
def test_default
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
smtp.start
|
||||
assert_equal(OpenSSL::SSL::VERIFY_PEER, smtp.__ssl_context.verify_mode)
|
||||
end
|
||||
|
||||
def test_enable_tls
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
context = OpenSSL::SSL::SSLContext.new
|
||||
smtp.enable_tls(context)
|
||||
smtp.start
|
||||
assert_equal(context, smtp.__ssl_context)
|
||||
end
|
||||
|
||||
def test_enable_tls_before_disable_starttls
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
context = OpenSSL::SSL::SSLContext.new
|
||||
smtp.enable_tls(context)
|
||||
smtp.disable_starttls
|
||||
smtp.start
|
||||
assert_equal(context, smtp.__ssl_context)
|
||||
end
|
||||
|
||||
def test_enable_starttls
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
context = OpenSSL::SSL::SSLContext.new
|
||||
smtp.enable_starttls(context)
|
||||
smtp.start
|
||||
assert_equal(context, smtp.__ssl_context)
|
||||
end
|
||||
|
||||
def test_enable_starttls_before_disable_tls
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
context = OpenSSL::SSL::SSLContext.new
|
||||
smtp.enable_starttls(context)
|
||||
smtp.disable_tls
|
||||
smtp.start
|
||||
assert_equal(context, smtp.__ssl_context)
|
||||
end
|
||||
|
||||
def test_start_with_tls_verify_true
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
smtp.start(tls_verify: true)
|
||||
assert_equal(OpenSSL::SSL::VERIFY_PEER, smtp.__ssl_context.verify_mode)
|
||||
end
|
||||
|
||||
def test_start_with_tls_verify_false
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
smtp.start(tls_verify: false)
|
||||
assert_equal(OpenSSL::SSL::VERIFY_NONE, smtp.__ssl_context.verify_mode)
|
||||
end
|
||||
|
||||
def test_start_with_tls_hostname
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
smtp.start(tls_hostname: "localhost")
|
||||
assert_equal("localhost", smtp.__tls_hostname)
|
||||
end
|
||||
|
||||
def test_start_without_tls_hostname
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
smtp.start
|
||||
assert_equal("smtp.example.com", smtp.__tls_hostname)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,121 @@
|
|||
require 'net/smtp'
|
||||
require 'test/unit'
|
||||
|
||||
module Net
|
||||
class TestStarttls < Test::Unit::TestCase
|
||||
class MySMTP < SMTP
|
||||
def initialize(socket)
|
||||
@fake_socket = socket
|
||||
super("smtp.example.com")
|
||||
end
|
||||
|
||||
def tcp_socket(*)
|
||||
@fake_socket
|
||||
end
|
||||
|
||||
def tlsconnect(*)
|
||||
@fake_socket
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
@server_thread&.exit
|
||||
@server_socket&.close
|
||||
@client_socket&.close
|
||||
end
|
||||
|
||||
def start_smtpd(starttls)
|
||||
@server_socket, @client_socket = UNIXSocket.pair
|
||||
@starttls_executed = false
|
||||
@server_thread = Thread.new(@server_socket) do |s|
|
||||
s.puts "220 fakeserver\r\n"
|
||||
while cmd = s.gets&.chomp
|
||||
case cmd
|
||||
when /\AEHLO /
|
||||
s.puts "250-fakeserver\r\n"
|
||||
s.puts "250-STARTTLS\r\n" if starttls
|
||||
s.puts "250 8BITMIME\r\n"
|
||||
when /\ASTARTTLS/
|
||||
@starttls_executed = true
|
||||
s.puts "220 2.0.0 Ready to start TLS\r\n"
|
||||
else
|
||||
raise "unsupported command: #{cmd}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@client_socket
|
||||
end
|
||||
|
||||
def test_default_with_starttls_capable
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
smtp.start
|
||||
assert(@starttls_executed)
|
||||
end
|
||||
|
||||
def test_default_without_starttls_capable
|
||||
smtp = MySMTP.new(start_smtpd(false))
|
||||
smtp.start
|
||||
assert(!@starttls_executed)
|
||||
end
|
||||
|
||||
def test_enable_starttls_with_starttls_capable
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
smtp.enable_starttls
|
||||
smtp.start
|
||||
assert(@starttls_executed)
|
||||
end
|
||||
|
||||
def test_enable_starttls_without_starttls_capable
|
||||
smtp = MySMTP.new(start_smtpd(false))
|
||||
smtp.enable_starttls
|
||||
err = assert_raise(Net::SMTPUnsupportedCommand) { smtp.start }
|
||||
assert_equal("STARTTLS is not supported on this server", err.message)
|
||||
end
|
||||
|
||||
def test_enable_starttls_auto_with_starttls_capable
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
smtp.enable_starttls_auto
|
||||
smtp.start
|
||||
assert(@starttls_executed)
|
||||
end
|
||||
|
||||
def test_tls_with_starttls_capable
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
smtp.enable_tls
|
||||
smtp.start
|
||||
assert(!@starttls_executed)
|
||||
end
|
||||
|
||||
def test_tls_without_starttls_capable
|
||||
smtp = MySMTP.new(start_smtpd(false))
|
||||
smtp.enable_tls
|
||||
end
|
||||
|
||||
def test_disable_starttls
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
smtp.disable_starttls
|
||||
smtp.start
|
||||
assert(!@starttls_executed)
|
||||
end
|
||||
|
||||
def test_enable_tls_and_enable_starttls
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
smtp.enable_tls
|
||||
err = assert_raise(ArgumentError) { smtp.enable_starttls }
|
||||
assert_equal("SMTPS and STARTTLS is exclusive", err.message)
|
||||
end
|
||||
|
||||
def test_enable_tls_and_enable_starttls_auto
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
smtp.enable_tls
|
||||
err = assert_raise(ArgumentError) { smtp.enable_starttls_auto }
|
||||
assert_equal("SMTPS and STARTTLS is exclusive", err.message)
|
||||
end
|
||||
|
||||
def test_enable_starttls_and_enable_starttls_auto
|
||||
smtp = MySMTP.new(start_smtpd(true))
|
||||
smtp.enable_starttls
|
||||
assert_nothing_raised { smtp.enable_starttls_auto }
|
||||
end
|
||||
end
|
||||
end
|
Загрузка…
Ссылка в новой задаче