mirror of
https://github.com/ruby/ruby.git
synced 2025-09-21 19:44:01 +02:00

The helo argument is not important, but the helo argument must be
specified to specify the user and secret arguments.
If helo, user, secret, and authtype arguments are keyword arguments,
it is not necessary to specify the helo argument.
269774deac
282 lines
8.2 KiB
Ruby
282 lines
8.2 KiB
Ruby
# frozen_string_literal: true
|
|
require 'net/smtp'
|
|
require 'stringio'
|
|
require 'test/unit'
|
|
|
|
module Net
|
|
class TestSMTP < Test::Unit::TestCase
|
|
CA_FILE = File.expand_path("../fixtures/cacert.pem", __dir__)
|
|
SERVER_KEY = File.expand_path("../fixtures/server.key", __dir__)
|
|
SERVER_CERT = File.expand_path("../fixtures/server.crt", __dir__)
|
|
|
|
class FakeSocket
|
|
attr_reader :write_io
|
|
|
|
def initialize out = "250 OK\n"
|
|
@write_io = StringIO.new
|
|
@read_io = StringIO.new out
|
|
end
|
|
|
|
def writeline line
|
|
@write_io.write "#{line}\r\n"
|
|
end
|
|
|
|
def readline
|
|
line = @read_io.gets
|
|
raise 'ran out of input' unless line
|
|
line.chop
|
|
end
|
|
end
|
|
|
|
def test_critical
|
|
smtp = Net::SMTP.new 'localhost', 25
|
|
|
|
assert_raise RuntimeError do
|
|
smtp.send :critical do
|
|
raise 'fail on purpose'
|
|
end
|
|
end
|
|
|
|
assert_kind_of Net::SMTP::Response, smtp.send(:critical),
|
|
'[Bug #9125]'
|
|
end
|
|
|
|
def test_esmtp
|
|
smtp = Net::SMTP.new 'localhost', 25
|
|
assert smtp.esmtp
|
|
assert smtp.esmtp?
|
|
|
|
smtp.esmtp = 'omg'
|
|
assert_equal 'omg', smtp.esmtp
|
|
assert_equal 'omg', smtp.esmtp?
|
|
end
|
|
|
|
def test_rset
|
|
smtp = Net::SMTP.new 'localhost', 25
|
|
smtp.instance_variable_set :@socket, FakeSocket.new
|
|
|
|
assert smtp.rset
|
|
end
|
|
|
|
def test_mailfrom
|
|
sock = FakeSocket.new
|
|
smtp = Net::SMTP.new 'localhost', 25
|
|
smtp.instance_variable_set :@socket, sock
|
|
assert smtp.mailfrom("foo@example.com").success?
|
|
assert_equal "MAIL FROM:<foo@example.com>\r\n", sock.write_io.string
|
|
end
|
|
|
|
def test_rcptto
|
|
sock = FakeSocket.new
|
|
smtp = Net::SMTP.new 'localhost', 25
|
|
smtp.instance_variable_set :@socket, sock
|
|
assert smtp.rcptto("foo@example.com").success?
|
|
assert_equal "RCPT TO:<foo@example.com>\r\n", sock.write_io.string
|
|
end
|
|
|
|
def test_auth_plain
|
|
sock = FakeSocket.new
|
|
smtp = Net::SMTP.new 'localhost', 25
|
|
smtp.instance_variable_set :@socket, sock
|
|
assert smtp.auth_plain("foo", "bar").success?
|
|
assert_equal "AUTH PLAIN AGZvbwBiYXI=\r\n", sock.write_io.string
|
|
end
|
|
|
|
def test_crlf_injection
|
|
smtp = Net::SMTP.new 'localhost', 25
|
|
smtp.instance_variable_set :@socket, FakeSocket.new
|
|
|
|
assert_raise(ArgumentError) do
|
|
smtp.mailfrom("foo\r\nbar")
|
|
end
|
|
|
|
assert_raise(ArgumentError) do
|
|
smtp.mailfrom("foo\rbar")
|
|
end
|
|
|
|
assert_raise(ArgumentError) do
|
|
smtp.mailfrom("foo\nbar")
|
|
end
|
|
|
|
assert_raise(ArgumentError) do
|
|
smtp.rcptto("foo\r\nbar")
|
|
end
|
|
end
|
|
|
|
def test_tls_connect
|
|
servers = Socket.tcp_server_sockets("localhost", 0)
|
|
ctx = OpenSSL::SSL::SSLContext.new
|
|
ctx.ca_file = CA_FILE
|
|
ctx.key = File.open(SERVER_KEY) { |f|
|
|
OpenSSL::PKey::RSA.new(f)
|
|
}
|
|
ctx.cert = File.open(SERVER_CERT) { |f|
|
|
OpenSSL::X509::Certificate.new(f)
|
|
}
|
|
begin
|
|
sock = nil
|
|
Thread.start do
|
|
s = accept(servers)
|
|
sock = OpenSSL::SSL::SSLSocket.new(s, ctx)
|
|
sock.sync_close = true
|
|
sock.accept
|
|
sock.write("220 localhost Service ready\r\n")
|
|
sock.gets
|
|
sock.write("250 localhost\r\n")
|
|
sock.gets
|
|
sock.write("221 localhost Service closing transmission channel\r\n")
|
|
end
|
|
smtp = Net::SMTP.new("localhost", servers[0].local_address.ip_port)
|
|
smtp.enable_tls
|
|
smtp.open_timeout = 1
|
|
smtp.start do
|
|
end
|
|
ensure
|
|
sock.close if sock
|
|
servers.each(&:close)
|
|
end
|
|
rescue LoadError
|
|
# skip (require openssl)
|
|
end
|
|
|
|
def test_tls_connect_timeout
|
|
servers = Socket.tcp_server_sockets("localhost", 0)
|
|
begin
|
|
sock = nil
|
|
Thread.start do
|
|
sock = accept(servers)
|
|
end
|
|
smtp = Net::SMTP.new("localhost", servers[0].local_address.ip_port)
|
|
smtp.enable_tls
|
|
smtp.open_timeout = 0.1
|
|
assert_raise(Net::OpenTimeout) do
|
|
smtp.start do
|
|
end
|
|
end
|
|
rescue LoadError
|
|
# skip (require openssl)
|
|
ensure
|
|
sock.close if sock
|
|
servers.each(&:close)
|
|
end
|
|
end
|
|
|
|
def test_eof_error_backtrace
|
|
bug13018 = '[ruby-core:78550] [Bug #13018]'
|
|
servers = Socket.tcp_server_sockets("localhost", 0)
|
|
begin
|
|
sock = nil
|
|
t = Thread.start do
|
|
sock = accept(servers)
|
|
sock.close
|
|
end
|
|
smtp = Net::SMTP.new("localhost", servers[0].local_address.ip_port)
|
|
e = assert_raise(EOFError, bug13018) do
|
|
smtp.start do
|
|
end
|
|
end
|
|
assert_equal(EOFError, e.class, bug13018)
|
|
assert(e.backtrace.grep(%r"\bnet/smtp\.rb:").size > 0, bug13018)
|
|
ensure
|
|
sock.close if sock
|
|
servers.each(&:close)
|
|
t.join
|
|
end
|
|
end
|
|
|
|
def test_start
|
|
port = fake_server_start
|
|
smtp = Net::SMTP.start('localhost', port)
|
|
smtp.quit
|
|
end
|
|
|
|
def test_start_with_position_argument
|
|
port = fake_server_start(helo: 'myname', user: 'account', password: 'password')
|
|
smtp = Net::SMTP.start('localhost', port, 'myname', 'account', 'password', :plain)
|
|
smtp.quit
|
|
end
|
|
|
|
def test_start_with_keyword_argument
|
|
port = fake_server_start(helo: 'myname', user: 'account', password: 'password')
|
|
smtp = Net::SMTP.start('localhost', port, helo: 'myname', user: 'account', secret: 'password', authtype: :plain)
|
|
smtp.quit
|
|
end
|
|
|
|
def test_start_password_is_secret
|
|
port = fake_server_start(helo: 'myname', user: 'account', password: 'password')
|
|
smtp = Net::SMTP.start('localhost', port, helo: 'myname', user: 'account', password: 'password', authtype: :plain)
|
|
smtp.quit
|
|
end
|
|
|
|
def test_start_invalid_number_of_arguments
|
|
err = assert_raise ArgumentError do
|
|
Net::SMTP.start('localhost', 25, 'myname', 'account', 'password', :plain, :invalid_arg)
|
|
end
|
|
assert_equal('wrong number of arguments (given 7, expected 1..6)', err.message)
|
|
end
|
|
|
|
def test_start_instance
|
|
port = fake_server_start
|
|
smtp = Net::SMTP.new('localhost', port)
|
|
smtp.start
|
|
smtp.quit
|
|
end
|
|
|
|
def test_start_instance_with_position_argument
|
|
port = fake_server_start(helo: 'myname', user: 'account', password: 'password')
|
|
smtp = Net::SMTP.new('localhost', port)
|
|
smtp.start('myname', 'account', 'password', :plain)
|
|
smtp.quit
|
|
end
|
|
|
|
def test_start_instance_with_keyword_argument
|
|
port = fake_server_start(helo: 'myname', user: 'account', password: 'password')
|
|
smtp = Net::SMTP.new('localhost', port)
|
|
smtp.start(helo: 'myname', user: 'account', secret: 'password', authtype: :plain)
|
|
smtp.quit
|
|
end
|
|
|
|
def test_start_instance_password_is_secret
|
|
port = fake_server_start(helo: 'myname', user: 'account', password: 'password')
|
|
smtp = Net::SMTP.new('localhost', port)
|
|
smtp.start(helo: 'myname', user: 'account', password: 'password', authtype: :plain)
|
|
smtp.quit
|
|
end
|
|
|
|
def test_start_instance_invalid_number_of_arguments
|
|
smtp = Net::SMTP.new('localhost')
|
|
err = assert_raise ArgumentError do
|
|
smtp.start('myname', 'account', 'password', :plain, :invalid_arg)
|
|
end
|
|
assert_equal('wrong number of arguments (given 5, expected 0..4)', err.message)
|
|
end
|
|
|
|
private
|
|
|
|
def accept(servers)
|
|
Socket.accept_loop(servers) { |s, _| break s }
|
|
end
|
|
|
|
def fake_server_start(helo: 'localhost', user: nil, password: nil)
|
|
servers = Socket.tcp_server_sockets('localhost', 0)
|
|
Thread.start do
|
|
Thread.current.abort_on_exception = true
|
|
sock = accept(servers)
|
|
sock.puts "220 ready\r\n"
|
|
assert_equal("EHLO #{helo}\r\n", sock.gets)
|
|
sock.puts "220-servername\r\n220 AUTH PLAIN\r\n"
|
|
if user
|
|
credential = ["\0#{user}\0#{password}"].pack('m0')
|
|
assert_equal("AUTH PLAIN #{credential}\r\n", sock.gets)
|
|
sock.puts "235 2.7.0 Authentication successful\r\n"
|
|
end
|
|
assert_equal("QUIT\r\n", sock.gets)
|
|
sock.puts "221 2.0.0 Bye\r\n"
|
|
sock.close
|
|
servers.each(&:close)
|
|
end
|
|
port = servers[0].local_address.ip_port
|
|
return port
|
|
end
|
|
end
|
|
end
|