summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/maintainers.rdoc6
-rw-r--r--doc/standard_library.rdoc2
-rw-r--r--gems/bundled_gems1
-rw-r--r--lib/net/net-smtp.gemspec37
-rw-r--r--lib/net/smtp.rb1128
-rwxr-xr-xmisc/expand_tabs.rb1
-rw-r--r--test/net/smtp/test_response.rb100
-rw-r--r--test/net/smtp/test_smtp.rb302
-rw-r--r--test/net/smtp/test_ssl_socket.rb99
-rw-r--r--test/net/smtp/test_sslcontext.rb129
-rw-r--r--test/net/smtp/test_starttls.rb122
-rw-r--r--tool/sync_default_gems.rb6
12 files changed, 4 insertions, 1929 deletions
diff --git a/doc/maintainers.rdoc b/doc/maintainers.rdoc
index 591b5c4e12..dc893ecdf1 100644
--- a/doc/maintainers.rdoc
+++ b/doc/maintainers.rdoc
@@ -158,10 +158,6 @@ Yukihiro Matsumoto (matz)
NARUSE, Yui (naruse)
https://github.com/ruby/net-http
https://rubygems.org/gems/net-http
-[lib/net/smtp.rb]
- TOMITA Masahiro (tmtm)
- https://github.com/ruby/net-smtp
- https://rubygems.org/gems/net-smtp
[lib/net/protocol.rb]
_unmaintained_
https://github.com/ruby/net-protocol
@@ -385,6 +381,8 @@ Yukihiro Matsumoto (matz)
https://github.com/ruby/net-imap
[net-pop]
https://github.com/ruby/net-pop
+[net-smtp]
+ https://github.com/ruby/net-smtp
[matrix]
https://github.com/ruby/matrix
[prime]
diff --git a/doc/standard_library.rdoc b/doc/standard_library.rdoc
index 089b253e1c..1d3580163e 100644
--- a/doc/standard_library.rdoc
+++ b/doc/standard_library.rdoc
@@ -47,7 +47,6 @@ OptionParser:: Ruby-oriented class for command-line option analysis
Logger:: Provides a simple logging utility for outputting messages
Mutex_m:: Mixin to extend objects to be handled like a Mutex
Net::HTTP:: HTTP client api for Ruby
-Net::SMTP:: Simple Mail Transfer Protocol client library for Ruby
Observable:: Provides a mechanism for publish/subscribe pattern in Ruby
Open3:: Provides access to stdin, stdout and stderr when running other programs
OpenStruct:: Class to build custom data structures, similar to a Hash
@@ -109,6 +108,7 @@ RSS:: Family of libraries that support various formats of XML "feeds"
Net::FTP:: Support for the File Transfer Protocol
Net::IMAP:: Ruby client api for Internet Message Access Protocol
Net::POP3:: Ruby client library for POP3
+Net::SMTP:: Simple Mail Transfer Protocol client library for Ruby
Matrix:: Represents a mathematical matrix.
Prime:: Prime numbers and factorization library
RBS:: RBS is a language to describe the structure of Ruby programs
diff --git a/gems/bundled_gems b/gems/bundled_gems
index 48477eceee..32b62fe8c4 100644
--- a/gems/bundled_gems
+++ b/gems/bundled_gems
@@ -8,6 +8,7 @@ rss 0.2.9 https://github.com/ruby/rss 0.2.9
net-ftp 0.1.2 https://github.com/ruby/net-ftp
net-imap 0.2.1 https://github.com/ruby/net-imap
net-pop 0.1.1 https://github.com/ruby/net-pop
+net-smtp 0.2.1 https://github.com/ruby/net-smtp
matrix 0.4.1 https://github.com/ruby/matrix
prime 0.1.2 https://github.com/ruby/prime
typeprof 0.14.1 https://github.com/ruby/typeprof
diff --git a/lib/net/net-smtp.gemspec b/lib/net/net-smtp.gemspec
deleted file mode 100644
index 3c210429b9..0000000000
--- a/lib/net/net-smtp.gemspec
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-name = File.basename(__FILE__, ".gemspec")
-version = ["lib", Array.new(name.count("-"), "..").join("/")].find do |dir|
- break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
- /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
- end rescue nil
-end
-
-Gem::Specification.new do |spec|
- spec.name = name
- spec.version = version
- spec.authors = ["Yukihiro Matsumoto"]
- spec.email = ["matz@ruby-lang.org"]
-
- spec.summary = %q{Simple Mail Transfer Protocol client library for Ruby.}
- spec.description = %q{Simple Mail Transfer Protocol client library for Ruby.}
- spec.homepage = "https://github.com/ruby/net-smtp"
- spec.licenses = ["Ruby", "BSD-2-Clause"]
- spec.required_ruby_version = ">= 2.5.0"
-
- spec.metadata["homepage_uri"] = spec.homepage
- spec.metadata["source_code_uri"] = spec.homepage
-
- spec.files = %w[
- LICENSE.txt
- lib/net/smtp.rb
- net-smtp.gemspec
- ]
- spec.bindir = "exe"
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
- spec.require_paths = ["lib"]
-
- spec.add_dependency "net-protocol"
- spec.add_dependency "digest"
- spec.add_dependency "timeout"
-end
diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb
deleted file mode 100644
index 56820befc6..0000000000
--- a/lib/net/smtp.rb
+++ /dev/null
@@ -1,1128 +0,0 @@
-# frozen_string_literal: true
-# = net/smtp.rb
-#
-# Copyright (c) 1999-2007 Yukihiro Matsumoto.
-#
-# Copyright (c) 1999-2007 Minero Aoki.
-#
-# Written & maintained by Minero Aoki <aamine@loveruby.net>.
-#
-# Documented by William Webber and Minero Aoki.
-#
-# This program is free software. You can re-distribute and/or
-# modify this program under the same terms as Ruby itself.
-#
-# $Id$
-#
-# See Net::SMTP for documentation.
-#
-
-require 'net/protocol'
-require 'digest/md5'
-require 'timeout'
-begin
- require 'openssl'
-rescue LoadError
-end
-
-module Net
-
- # 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.
- end
-
- # Represents an SMTP authentication error.
- class SMTPAuthenticationError < ProtoAuthError
- include SMTPError
- end
-
- # Represents SMTP error code 4xx, a temporary error.
- class SMTPServerBusy < ProtoServerError
- include SMTPError
- end
-
- # Represents an SMTP command syntax error (error code 500)
- class SMTPSyntaxError < ProtoSyntaxError
- include SMTPError
- end
-
- # Represents a fatal SMTP error (error code 5xx, except for 500)
- class SMTPFatalError < ProtoFatalError
- include SMTPError
- end
-
- # Unexpected reply code returned from server.
- class SMTPUnknownError < ProtoUnknownError
- include SMTPError
- end
-
- # Command is not supported on server.
- class SMTPUnsupportedCommand < ProtocolError
- include SMTPError
- end
-
- #
- # == What is This Library?
- #
- # This library provides functionality to send internet
- # mail via SMTP, the Simple Mail Transfer Protocol. For details of
- # SMTP itself, see [RFC2821] (http://www.ietf.org/rfc/rfc2821.txt).
- #
- # == What is This Library NOT?
- #
- # This library does NOT provide functions to compose internet mails.
- # You must create them by yourself. If you want better mail support,
- # try RubyMail or TMail or search for alternatives in
- # {RubyGems.org}[https://rubygems.org/] or {The Ruby
- # Toolbox}[https://www.ruby-toolbox.com/].
- #
- # FYI: the official documentation on internet mail is: [RFC2822] (http://www.ietf.org/rfc/rfc2822.txt).
- #
- # == Examples
- #
- # === Sending Messages
- #
- # You must open a connection to an SMTP server before sending messages.
- # The first argument is the address of your SMTP server, and the second
- # argument is the port number. Using SMTP.start with a block is the simplest
- # way to do this. This way, the SMTP connection is closed automatically
- # after the block is executed.
- #
- # require 'net/smtp'
- # Net::SMTP.start('your.smtp.server', 25) do |smtp|
- # # Use the SMTP object smtp only in this block.
- # end
- #
- # Replace 'your.smtp.server' with your SMTP server. Normally
- # your system manager or internet provider supplies a server
- # for you.
- #
- # Then you can send messages.
- #
- # msgstr = <<END_OF_MESSAGE
- # From: Your Name <your@mail.address>
- # To: Destination Address <someone@example.com>
- # Subject: test message
- # Date: Sat, 23 Jun 2001 16:26:43 +0900
- # Message-Id: <unique.message.id.string@example.com>
- #
- # This is a test message.
- # END_OF_MESSAGE
- #
- # require 'net/smtp'
- # Net::SMTP.start('your.smtp.server', 25) do |smtp|
- # smtp.send_message msgstr,
- # 'your@mail.address',
- # 'his_address@example.com'
- # end
- #
- # === Closing the Session
- #
- # You MUST close the SMTP session after sending messages, by calling
- # the #finish method:
- #
- # # using SMTP#finish
- # smtp = Net::SMTP.start('your.smtp.server', 25)
- # smtp.send_message msgstr, 'from@address', 'to@address'
- # smtp.finish
- #
- # You can also use the block form of SMTP.start/SMTP#start. This closes
- # the SMTP session automatically:
- #
- # # using block form of SMTP.start
- # Net::SMTP.start('your.smtp.server', 25) do |smtp|
- # smtp.send_message msgstr, 'from@address', 'to@address'
- # end
- #
- # I strongly recommend this scheme. This form is simpler and more robust.
- #
- # === HELO domain
- #
- # In almost all situations, you must provide a third argument
- # 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
- # 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
- # user: 'Your Account', secret: 'Your Password', authtype: :plain)
- # # 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
- # user: 'Your Account', secret: 'Your Password', authtype: :cram_md5)
- #
- class SMTP < Protocol
- VERSION = "0.2.1-patch-ssl-context"
-
- Revision = %q$Revision$.split[1]
-
- # The default SMTP port number, 25.
- def SMTP.default_port
- 25
- end
-
- # The default mail submission port number, 587.
- def SMTP.default_submission_port
- 587
- end
-
- # The default SMTPS port number, 465.
- def SMTP.default_tls_port
- 465
- end
-
- class << self
- alias default_ssl_port default_tls_port
- end
-
- def SMTP.default_ssl_context(ssl_context_params = nil)
- context = OpenSSL::SSL::SSLContext.new
- context.set_params(ssl_context_params ? ssl_context_params : {})
- context
- end
-
- #
- # Creates a new Net::SMTP object.
- #
- # +address+ is the hostname or ip address of your SMTP
- # server. +port+ is the port to connect to; it defaults to
- # port 25.
- #
- # This method does not open the TCP connection. You can use
- # SMTP.start instead of SMTP.new if you want to do everything
- # at once. Otherwise, follow SMTP.new with SMTP#start.
- #
- def initialize(address, port = nil)
- @address = address
- @port = (port || SMTP.default_port)
- @esmtp = true
- @capabilities = nil
- @socket = nil
- @started = false
- @open_timeout = 30
- @read_timeout = 60
- @error_occurred = false
- @debug_output = nil
- @tls = false
- @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}>"
- end
-
- #
- # Set whether to use ESMTP or not. This should be done before
- # calling #start. Note that if #start is called in ESMTP mode,
- # and the connection fails due to a ProtocolError, the SMTP
- # object will automatically switch to plain SMTP mode and
- # retry (but not vice versa).
- #
- attr_accessor :esmtp
-
- # +true+ if the SMTP object uses ESMTP (which it does by default).
- alias :esmtp? :esmtp
-
- # true if server advertises STARTTLS.
- # You cannot get valid value before opening SMTP session.
- def capable_starttls?
- capable?('STARTTLS')
- end
-
- def capable?(key)
- return nil unless @capabilities
- @capabilities[key] ? true : false
- end
- private :capable?
-
- # true if server advertises AUTH PLAIN.
- # You cannot get valid value before opening SMTP session.
- def capable_plain_auth?
- auth_capable?('PLAIN')
- end
-
- # true if server advertises AUTH LOGIN.
- # You cannot get valid value before opening SMTP session.
- def capable_login_auth?
- auth_capable?('LOGIN')
- end
-
- # true if server advertises AUTH CRAM-MD5.
- # You cannot get valid value before opening SMTP session.
- def capable_cram_md5_auth?
- auth_capable?('CRAM-MD5')
- end
-
- def auth_capable?(type)
- return nil unless @capabilities
- return false unless @capabilities['AUTH']
- @capabilities['AUTH'].include?(type)
- end
- private :auth_capable?
-
- # Returns supported authentication methods on this server.
- # You cannot get valid value before opening SMTP session.
- def capable_auth_types
- return [] unless @capabilities
- return [] unless @capabilities['AUTH']
- @capabilities['AUTH']
- end
-
- # true if this object uses SMTP/TLS (SMTPS).
- def tls?
- @tls
- end
-
- 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 = nil)
- raise 'openssl library not installed' unless defined?(OpenSSL)
- raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @starttls == :always
- @tls = true
- @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_tls = nil
- end
-
- alias disable_ssl disable_tls
-
- # Returns truth value if this object uses STARTTLS.
- # If this object always uses STARTTLS, returns :always.
- # If this object uses STARTTLS when the server support TLS, returns :auto.
- def starttls?
- @starttls
- end
-
- # true if this object uses STARTTLS.
- def starttls_always?
- @starttls == :always
- end
-
- # true if this object uses STARTTLS when server advertises STARTTLS.
- def starttls_auto?
- @starttls == :auto
- 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 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 ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls
- @starttls = :auto
- @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_starttls = nil
- end
-
- # The address of the SMTP server to connect to.
- attr_reader :address
-
- # The port number of the SMTP server to connect to.
- attr_reader :port
-
- # Seconds to wait while attempting to open a connection.
- # If the connection cannot be opened within this time, a
- # Net::OpenTimeout is raised. The default value is 30 seconds.
- attr_accessor :open_timeout
-
- # Seconds to wait while reading one block (by one read(2) call).
- # If the read(2) call does not complete within this time, a
- # Net::ReadTimeout is raised. The default value is 60 seconds.
- attr_reader :read_timeout
-
- # Set the number of seconds to wait until timing-out a read(2)
- # call.
- def read_timeout=(sec)
- @socket.read_timeout = sec if @socket
- @read_timeout = sec
- end
-
- #
- # WARNING: This method causes serious security holes.
- # Use this method for only debugging.
- #
- # Set an output stream for debug logging.
- # You must call this before #start.
- #
- # # example
- # smtp = Net::SMTP.new(addr, port)
- # smtp.set_debug_output $stderr
- # smtp.start do |smtp|
- # ....
- # end
- #
- def debug_output=(arg)
- @debug_output = arg
- end
-
- alias set_debug_output debug_output=
-
- #
- # SMTP session control
- #
-
- #
- # :call-seq:
- # 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, ssl_context_params: nil)
- #
- # === Example
- #
- # Net::SMTP.start('your.smtp.server') do |smtp|
- # smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
- # end
- #
- # === Block Usage
- #
- # If called with a block, the newly-opened Net::SMTP object is yielded
- # to the block, and automatically closed when the block finishes. If called
- # without a block, the newly-opened Net::SMTP object is returned to
- # the caller, and it is the caller's responsibility to close it when
- # finished.
- #
- # === Parameters
- #
- # +address+ is the hostname or ip address of your smtp server.
- #
- # +port+ is the port to connect to; it defaults to port 25.
- #
- # +helo+ is the _HELO_ _domain_ provided by the client to the
- # server (see overview comments); it defaults to 'localhost'.
- #
- # 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+.
- #
- # 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
- # * Net::SMTPServerBusy
- # * Net::SMTPSyntaxError
- # * Net::SMTPFatalError
- # * Net::SMTPUnknownError
- # * Net::OpenTimeout
- # * 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, 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, 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, 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
- #
- # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see
- # the discussion in the overview notes.
- #
- # 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+.
- #
- # 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
- # responsibility to close the session when finished.
- #
- # === Example
- #
- # This is very similar to the class method SMTP.start.
- #
- # require 'net/smtp'
- # smtp = Net::SMTP.new('smtp.mail.server', 25)
- # 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
- # (#esmtp=), which must be done before the session is
- # started.
- #
- # === Errors
- #
- # If session has already been started, an IOError will be raised.
- #
- # This method may raise:
- #
- # * Net::SMTPAuthenticationError
- # * Net::SMTPServerBusy
- # * Net::SMTPSyntaxError
- # * Net::SMTPFatalError
- # * Net::SMTPUnknownError
- # * Net::OpenTimeout
- # * Net::ReadTimeout
- # * IOError
- #
- def start(*args, helo: 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]
- ssl_context_params = ssl_context_params ? ssl_context_params : {}
- if ssl_context_params.has_key?(:verify_mode)
- tls_verify = ssl_context_params[:verify_mode] != OpenSSL::SSL::VERIFY_NONE
- else
- 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
- if block_given?
- begin
- do_start helo, user, secret, authtype
- return yield(self)
- ensure
- do_finish
- end
- else
- do_start helo, user, secret, authtype
- return self
- end
- end
-
- # Finishes the SMTP session and closes TCP connection.
- # Raises IOError if not started.
- def finish
- raise IOError, 'not yet started' unless started?
- do_finish
- end
-
- private
-
- def tcp_socket(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 = 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"
- end
- starttls
- @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
- ensure
- unless @started
- # authentication failed, cancel connection.
- s.close if s
- @socket = nil
- end
- end
-
- def ssl_socket(socket, context)
- OpenSSL::SSL::SSLSocket.new socket, context
- end
-
- 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=
- 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
-
- def new_internet_message_io(s)
- InternetMessageIO.new(s, read_timeout: @read_timeout,
- debug_output: @debug_output)
- end
-
- def do_helo(helo_domain)
- res = @esmtp ? ehlo(helo_domain) : helo(helo_domain)
- @capabilities = res.capabilities
- rescue SMTPError
- if @esmtp
- @esmtp = false
- @error_occurred = false
- retry
- end
- raise
- end
-
- def do_finish
- quit if @socket and not @socket.closed? and not @error_occurred
- ensure
- @started = false
- @error_occurred = false
- @socket.close if @socket
- @socket = nil
- end
-
- #
- # Message Sending
- #
-
- public
-
- #
- # Sends +msgstr+ as a message. Single CR ("\r") and LF ("\n") found
- # in the +msgstr+, are converted into the CR LF pair. You cannot send a
- # binary message with this method. +msgstr+ should include both
- # the message headers and body.
- #
- # +from_addr+ is a String representing the source mail address.
- #
- # +to_addr+ is a String or Strings or Array of Strings, representing
- # the destination mail address or addresses.
- #
- # === Example
- #
- # Net::SMTP.start('smtp.example.com') do |smtp|
- # smtp.send_message msgstr,
- # 'from@example.com',
- # ['dest@example.com', 'dest2@example.com']
- # end
- #
- # === Errors
- #
- # This method may raise:
- #
- # * Net::SMTPServerBusy
- # * Net::SMTPSyntaxError
- # * Net::SMTPFatalError
- # * Net::SMTPUnknownError
- # * Net::ReadTimeout
- # * IOError
- #
- def send_message(msgstr, from_addr, *to_addrs)
- raise IOError, 'closed session' unless @socket
- mailfrom from_addr
- rcptto_list(to_addrs) {data msgstr}
- end
-
- alias send_mail send_message
- alias sendmail send_message # obsolete
-
- #
- # Opens a message writer stream and gives it to the block.
- # The stream is valid only in the block, and has these methods:
- #
- # puts(str = ''):: outputs STR and CR LF.
- # print(str):: outputs STR.
- # printf(fmt, *args):: outputs sprintf(fmt,*args).
- # write(str):: outputs STR and returns the length of written bytes.
- # <<(str):: outputs STR and returns self.
- #
- # If a single CR ("\r") or LF ("\n") is found in the message,
- # it is converted to the CR LF pair. You cannot send a binary
- # message with this method.
- #
- # === Parameters
- #
- # +from_addr+ is a String representing the source mail address.
- #
- # +to_addr+ is a String or Strings or Array of Strings, representing
- # the destination mail address or addresses.
- #
- # === Example
- #
- # Net::SMTP.start('smtp.example.com', 25) do |smtp|
- # smtp.open_message_stream('from@example.com', ['dest@example.com']) do |f|
- # f.puts 'From: from@example.com'
- # f.puts 'To: dest@example.com'
- # f.puts 'Subject: test message'
- # f.puts
- # f.puts 'This is a test message.'
- # end
- # end
- #
- # === Errors
- #
- # This method may raise:
- #
- # * Net::SMTPServerBusy
- # * Net::SMTPSyntaxError
- # * Net::SMTPFatalError
- # * Net::SMTPUnknownError
- # * Net::ReadTimeout
- # * IOError
- #
- def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream
- raise IOError, 'closed session' unless @socket
- mailfrom from_addr
- rcptto_list(to_addrs) {data(&block)}
- end
-
- alias ready open_message_stream # obsolete
-
- #
- # Authentication
- #
-
- public
-
- DEFAULT_AUTH_TYPE = :plain
-
- def authenticate(user, secret, authtype = DEFAULT_AUTH_TYPE)
- check_auth_method authtype
- check_auth_args user, secret
- public_send auth_method(authtype), user, secret
- end
-
- def auth_plain(user, secret)
- check_auth_args user, secret
- res = critical {
- get_response('AUTH PLAIN ' + base64_encode("\0#{user}\0#{secret}"))
- }
- check_auth_response res
- res
- end
-
- def auth_login(user, secret)
- check_auth_args user, secret
- res = critical {
- check_auth_continue get_response('AUTH LOGIN')
- check_auth_continue get_response(base64_encode(user))
- get_response(base64_encode(secret))
- }
- check_auth_response res
- res
- end
-
- def auth_cram_md5(user, secret)
- check_auth_args user, secret
- res = critical {
- res0 = get_response('AUTH CRAM-MD5')
- check_auth_continue res0
- crammed = cram_md5_response(secret, res0.cram_md5_challenge)
- get_response(base64_encode("#{user} #{crammed}"))
- }
- check_auth_response res
- res
- end
-
- private
-
- def check_auth_method(type)
- unless respond_to?(auth_method(type), true)
- raise ArgumentError, "wrong authentication type #{type}"
- end
- end
-
- def auth_method(type)
- "auth_#{type.to_s.downcase}".intern
- end
-
- def check_auth_args(user, secret, authtype = DEFAULT_AUTH_TYPE)
- unless user
- raise ArgumentError, 'SMTP-AUTH requested but missing user name'
- end
- unless secret
- raise ArgumentError, 'SMTP-AUTH requested but missing secret phrase'
- end
- end
-
- def base64_encode(str)
- # expects "str" may not become too long
- [str].pack('m0')
- end
-
- IMASK = 0x36
- OMASK = 0x5c
-
- # CRAM-MD5: [RFC2195]
- def cram_md5_response(secret, challenge)
- tmp = Digest::MD5.digest(cram_secret(secret, IMASK) + challenge)
- Digest::MD5.hexdigest(cram_secret(secret, OMASK) + tmp)
- end
-
- CRAM_BUFSIZE = 64
-
- def cram_secret(secret, mask)
- secret = Digest::MD5.digest(secret) if secret.size > CRAM_BUFSIZE
- buf = secret.ljust(CRAM_BUFSIZE, "\0")
- 0.upto(buf.size - 1) do |i|
- buf[i] = (buf[i].ord ^ mask).chr
- end
- buf
- end
-
- #
- # SMTP command dispatcher
- #
-
- public
-
- # Aborts the current mail transaction
-
- def rset
- getok('RSET')
- end
-
- def starttls
- getok('STARTTLS')
- end
-
- def helo(domain)
- getok("HELO #{domain}")
- end
-
- def ehlo(domain)
- getok("EHLO #{domain}")
- end
-
- def mailfrom(from_addr)
- getok("MAIL FROM:<#{from_addr}>")
- end
-
- def rcptto_list(to_addrs)
- raise ArgumentError, 'mail destination not given' if to_addrs.empty?
- ok_users = []
- unknown_users = []
- to_addrs.flatten.each do |addr|
- begin
- rcptto addr
- rescue SMTPAuthenticationError
- unknown_users << addr.dump
- else
- ok_users << addr
- end
- end
- raise ArgumentError, 'mail destination not given' if ok_users.empty?
- ret = yield
- unless unknown_users.empty?
- raise SMTPAuthenticationError, "failed to deliver for #{unknown_users.join(', ')}"
- end
- ret
- end
-
- def rcptto(to_addr)
- getok("RCPT TO:<#{to_addr}>")
- end
-
- # This method sends a message.
- # If +msgstr+ is given, sends it as a message.
- # If block is given, yield a message writer stream.
- # You must write message before the block is closed.
- #
- # # Example 1 (by string)
- # smtp.data(<<EndMessage)
- # From: john@example.com
- # To: betty@example.com
- # Subject: I found a bug
- #
- # Check vm.c:58879.
- # EndMessage
- #
- # # Example 2 (by block)
- # smtp.data {|f|
- # f.puts "From: john@example.com"
- # f.puts "To: betty@example.com"
- # f.puts "Subject: I found a bug"
- # f.puts ""
- # f.puts "Check vm.c:58879."
- # }
- #
- def data(msgstr = nil, &block) #:yield: stream
- if msgstr and block
- raise ArgumentError, "message and block are exclusive"
- end
- unless msgstr or block
- raise ArgumentError, "message or block is required"
- end
- res = critical {
- check_continue get_response('DATA')
- socket_sync_bak = @socket.io.sync
- begin
- @socket.io.sync = false
- if msgstr
- @socket.write_message msgstr
- else
- @socket.write_message_by_block(&block)
- end
- ensure
- @socket.io.flush
- @socket.io.sync = socket_sync_bak
- end
- recv_response()
- }
- check_response res
- res
- end
-
- def quit
- getok('QUIT')
- end
-
- private
-
- def validate_line(line)
- # A bare CR or LF is not allowed in RFC5321.
- if /[\r\n]/ =~ line
- raise ArgumentError, "A line must not contain CR or LF"
- end
- end
-
- def getok(reqline)
- validate_line reqline
- res = critical {
- @socket.writeline reqline
- recv_response()
- }
- check_response res
- res
- end
-
- def get_response(reqline)
- validate_line reqline
- @socket.writeline reqline
- recv_response()
- end
-
- def recv_response
- buf = ''.dup
- while true
- line = @socket.readline
- buf << line << "\n"
- break unless line[3,1] == '-' # "210-PIPELINING"
- end
- Response.parse(buf)
- end
-
- def critical
- return Response.parse('200 dummy reply code') if @error_occurred
- begin
- return yield()
- rescue Exception
- @error_occurred = true
- raise
- end
- end
-
- def check_response(res)
- unless res.success?
- raise res.exception_class, res.message
- end
- end
-
- def check_continue(res)
- unless res.continue?
- raise SMTPUnknownError, "could not get 3xx (#{res.status}: #{res.string})"
- end
- end
-
- def check_auth_response(res)
- unless res.success?
- raise SMTPAuthenticationError, res.message
- end
- end
-
- def check_auth_continue(res)
- unless res.continue?
- raise res.exception_class, res.message
- 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
- # created by the user. For more information on SMTP responses, view
- # {Section 4.2 of RFC 5321}[http://tools.ietf.org/html/rfc5321#section-4.2]
- class Response
- # Parses the received response and separates the reply code and the human
- # readable reply text
- def self.parse(str)
- new(str[0,3], str)
- end
-
- # Creates a new instance of the Response class and sets the status and
- # string attributes
- def initialize(status, string)
- @status = status
- @string = string
- end
-
- # The three digit reply code of the SMTP response
- attr_reader :status
-
- # The human readable reply text of the SMTP response
- attr_reader :string
-
- # Takes the first digit of the reply code to determine the status type
- def status_type_char
- @status[0, 1]
- end
-
- # Determines whether the response received was a Positive Completion
- # reply (2xx reply code)
- def success?
- status_type_char() == '2'
- end
-
- # Determines whether the response received was a Positive Intermediate
- # reply (3xx reply code)
- def continue?
- status_type_char() == '3'
- end
-
- # The first line of the human readable reply text
- def message
- @string.lines.first
- end
-
- # Creates a CRAM-MD5 challenge. You can view more information on CRAM-MD5
- # on Wikipedia: https://en.wikipedia.org/wiki/CRAM-MD5
- def cram_md5_challenge
- @string.split(/ /)[1].unpack1('m')
- end
-
- # Returns a hash of the human readable reply text in the response if it
- # is multiple lines. It does not return the first line. The key of the
- # hash is the first word the value of the hash is an array with each word
- # 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].split(' ')
- h[k] = v
- end
- h
- end
-
- # Determines whether there was an error and raises the appropriate error
- # based on the reply code of the response
- def exception_class
- case @status
- when /\A4/ then SMTPServerBusy
- when /\A50/ then SMTPSyntaxError
- when /\A53/ then SMTPAuthenticationError
- when /\A5/ then SMTPFatalError
- else SMTPUnknownError
- end
- end
- end
-
- def logging(msg)
- @debug_output << msg + "\n" if @debug_output
- end
-
- end # class SMTP
-
- SMTPSession = SMTP # :nodoc:
-
-end
diff --git a/misc/expand_tabs.rb b/misc/expand_tabs.rb
index 5be8ce4c07..a94eea5046 100755
--- a/misc/expand_tabs.rb
+++ b/misc/expand_tabs.rb
@@ -81,7 +81,6 @@ DEFAULT_GEM_LIBS = %w[
mutex_m
net-http
net-protocol
- net-smtp
observer
open3
open-uri
diff --git a/test/net/smtp/test_response.rb b/test/net/smtp/test_response.rb
deleted file mode 100644
index 3cf909a762..0000000000
--- a/test/net/smtp/test_response.rb
+++ /dev/null
@@ -1,100 +0,0 @@
-# frozen_string_literal: true
-require 'net/smtp'
-require 'test/unit'
-
-module Net
- class SMTP
- class TestResponse < Test::Unit::TestCase
- def test_capabilities
- res = Response.parse("250-ubuntu-desktop\n250-PIPELINING\n250-SIZE 10240000\n250-VRFY\n250-ETRN\n250-STARTTLS\n250-ENHANCEDSTATUSCODES\n250 DSN\n")
-
- capabilities = res.capabilities
- %w{ PIPELINING SIZE VRFY STARTTLS ENHANCEDSTATUSCODES DSN}.each do |str|
- assert capabilities.key?(str), str
- end
- end
-
- def test_capabilities_default
- res = Response.parse("250-ubuntu-desktop\n250-PIPELINING\n250 DSN\n")
- assert_equal [], res.capabilities['PIPELINING']
- end
-
- def test_capabilities_value
- res = Response.parse("250-ubuntu-desktop\n250-SIZE 1234\n250 DSN\n")
- assert_equal ['1234'], res.capabilities['SIZE']
- end
-
- def test_capabilities_multi
- res = Response.parse("250-ubuntu-desktop\n250-SIZE 1 2 3\n250 DSN\n")
- assert_equal %w{1 2 3}, res.capabilities['SIZE']
- end
-
- def test_bad_string
- res = Response.parse("badstring")
- assert_equal({}, res.capabilities)
- end
-
- def test_success?
- res = Response.parse("250-ubuntu-desktop\n250-SIZE 1 2 3\n250 DSN\n")
- assert res.success?
- assert !res.continue?
- end
-
- # RFC 2821, Section 4.2.1
- def test_continue?
- res = Response.parse("3yz-ubuntu-desktop\n250-SIZE 1 2 3\n250 DSN\n")
- assert !res.success?
- assert res.continue?
- end
-
- def test_status_type_char
- res = Response.parse("3yz-ubuntu-desktop\n250-SIZE 1 2 3\n250 DSN\n")
- assert_equal '3', res.status_type_char
-
- res = Response.parse("250-ubuntu-desktop\n250-SIZE 1 2 3\n250 DSN\n")
- assert_equal '2', res.status_type_char
- end
-
- def test_message
- res = Response.parse("250-ubuntu-desktop\n250-SIZE 1 2 3\n250 DSN\n")
- assert_equal "250-ubuntu-desktop\n", res.message
- end
-
- def test_server_busy_exception
- res = Response.parse("400 omg busy")
- assert_equal Net::SMTPServerBusy, res.exception_class
- res = Response.parse("410 omg busy")
- assert_equal Net::SMTPServerBusy, res.exception_class
- end
-
- def test_syntax_error_exception
- res = Response.parse("500 omg syntax error")
- assert_equal Net::SMTPSyntaxError, res.exception_class
-
- res = Response.parse("501 omg syntax error")
- assert_equal Net::SMTPSyntaxError, res.exception_class
- end
-
- def test_authentication_exception
- res = Response.parse("530 omg auth error")
- assert_equal Net::SMTPAuthenticationError, res.exception_class
-
- res = Response.parse("531 omg auth error")
- assert_equal Net::SMTPAuthenticationError, res.exception_class
- end
-
- def test_fatal_error
- res = Response.parse("510 omg fatal error")
- assert_equal Net::SMTPFatalError, res.exception_class
-
- res = Response.parse("511 omg fatal error")
- assert_equal Net::SMTPFatalError, res.exception_class
- end
-
- def test_default_exception
- res = Response.parse("250 omg fatal error")
- assert_equal Net::SMTPUnknownError, res.exception_class
- end
- end
- end
-end
diff --git a/test/net/smtp/test_smtp.rb b/test/net/smtp/test_smtp.rb
deleted file mode 100644
index 91974d4dfe..0000000000
--- a/test/net/smtp/test_smtp.rb
+++ /dev/null
@@ -1,302 +0,0 @@
-# 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 setup
- # Avoid hanging at fake_server_start's IO.select on --jit-wait CI like http://ci.rvm.jp/results/trunk-mjit-wait@phosphorus-docker/3302796
- # Unfortunately there's no way to configure read_timeout for Net::SMTP.start.
- if defined?(RubyVM::JIT) && RubyVM::JIT.enabled?
- Net::SMTP.prepend Module.new {
- def initialize(*)
- super
- @read_timeout *= 5
- end
- }
- end
-
- @server_threads = []
- end
-
- def teardown
- @server_threads.each {|th| th.join }
- 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(tls_verify: false) 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)
- sleep 0.1
- 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.finish
- 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.finish
- 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.finish
- 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.finish
- 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.finish
- 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.finish
- 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.finish
- 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.finish
- 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)
- @server_threads << 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
diff --git a/test/net/smtp/test_ssl_socket.rb b/test/net/smtp/test_ssl_socket.rb
deleted file mode 100644
index dd9529f25e..0000000000
--- a/test/net/smtp/test_ssl_socket.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-# frozen_string_literal: true
-require 'net/smtp'
-require 'test/unit'
-
-module Net
- class TestSSLSocket < Test::Unit::TestCase
- class MySMTP < SMTP
- attr_accessor :fake_tcp, :fake_ssl
-
- def initialize(*args)
- super(*args)
- @open_timeout = nil
- end
-
- def tcp_socket address, port
- fake_tcp
- end
-
- def ssl_socket socket, context
- fake_ssl
- end
- end
-
- require 'stringio'
- class SSLSocket < StringIO
- attr_accessor :sync_close, :connected, :closed
-
- def initialize(*args)
- @connected = false
- @closed = true
- super
- end
-
- def connect
- self.connected = true
- self.closed = false
- end
-
- def close
- self.closed = true
- end
-
- def post_connection_check omg
- end
- end
-
- def test_ssl_socket_close_on_post_connection_check_fail
- tcp_socket = StringIO.new success_response
-
- ssl_socket = SSLSocket.new.extend Module.new {
- def post_connection_check omg
- raise OpenSSL::SSL::SSLError, 'hostname was not match with the server certificate'
- end
- }
-
- ssl_context = OpenSSL::SSL::SSLContext.new
- ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
- connection = MySMTP.new('localhost', 25)
- connection.enable_starttls_auto(ssl_context)
- connection.fake_tcp = tcp_socket
- connection.fake_ssl = ssl_socket
-
- assert_raise(OpenSSL::SSL::SSLError) do
- connection.start
- end
- assert_equal true, ssl_socket.closed
- end
-
- def test_ssl_socket_open_on_post_connection_check_success
- tcp_socket = StringIO.new success_response
-
- ssl_socket = SSLSocket.new success_response
-
- connection = MySMTP.new('localhost', 25)
- connection.enable_starttls_auto
- connection.fake_tcp = tcp_socket
- connection.fake_ssl = ssl_socket
-
- connection.start
- assert_equal false, ssl_socket.closed
- end
-
- def success_response
- [
- '220 smtp.example.com ESMTP Postfix',
- "250-ubuntu-desktop",
- "250-PIPELINING",
- "250-SIZE 10240000",
- "250-VRFY",
- "250-ETRN",
- "250-STARTTLS",
- "250-ENHANCEDSTATUSCODES",
- "250-8BITMIME",
- "250 DSN",
- "220 2.0.0 Ready to start TLS",
- ].join("\r\n") + "\r\n"
- end
- end
-end if defined?(OpenSSL)
diff --git a/test/net/smtp/test_sslcontext.rb b/test/net/smtp/test_sslcontext.rb
deleted file mode 100644
index 4f1dcfe669..0000000000
--- a/test/net/smtp/test_sslcontext.rb
+++ /dev/null
@@ -1,129 +0,0 @@
-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&.join
- @server_socket&.close
- @client_socket&.close
- end
-
- def start_smtpd(starttls)
- @server_socket, @client_socket = Object.const_defined?(:UNIXSocket) ?
- UNIXSocket.pair : Socket.pair(:INET, :STREAM, 0)
- @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
diff --git a/test/net/smtp/test_starttls.rb b/test/net/smtp/test_starttls.rb
deleted file mode 100644
index 4a29fa6d70..0000000000
--- a/test/net/smtp/test_starttls.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-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&.join
- @server_socket&.close
- @client_socket&.close
- end
-
- def start_smtpd(starttls)
- @server_socket, @client_socket = Object.const_defined?(:UNIXSocket) ?
- UNIXSocket.pair : Socket.pair(:INET, :STREAM, 0)
- @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
diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb
index 884ee64d47..a21543b983 100644
--- a/tool/sync_default_gems.rb
+++ b/tool/sync_default_gems.rb
@@ -35,7 +35,6 @@ REPOSITORIES = {
pstore: "ruby/pstore",
delegate: "ruby/delegate",
benchmark: "ruby/benchmark",
- "net-smtp": "ruby/net-smtp",
cgi: "ruby/cgi",
readline: "ruby/readline",
"readline-ext": "ruby/readline-ext",
@@ -243,11 +242,6 @@ def sync_default_gems(gem)
cp_r("#{upstream}/openssl.gemspec", "ext/openssl")
cp_r("#{upstream}/History.md", "ext/openssl")
`git checkout ext/openssl/depend`
- when "net-smtp"
- rm_rf(%w[lib/net/smtp.rb lib/net/net-smtp.gemspec test/net/smtp])
- cp_r("#{upstream}/lib/net/smtp.rb", "lib/net")
- cp_r("#{upstream}/test/net/smtp", "test/net")
- cp_r("#{upstream}/net-smtp.gemspec", "lib/net")
when "net-protocol"
rm_rf(%w[lib/net/protocol.rb lib/net/net-protocol.gemspec test/net/protocol])
cp_r("#{upstream}/lib/net/protocol.rb", "lib/net")