mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
* lib/net/smtp.rb: support SMTP/SSL. Thanks Kazuhiro NISHIYAMA.
* lib/net/smtp.rb: new method SMTP.use_ssl? * lib/net/smtp.rb: new method SMTP.enable_ssl. * lib/net/smtp.rb: new method SMTP.disable_ssl. * lib/net/smtp.rb: new method SMTP.default_ssl_port. * lib/net/smtp.rb: new method SMTP.default_tls_port. * lib/net/smtp.rb: now SMTP#enable_tls accepts a SSLContext object, instead of a verity and cert. [FEATURE CHANGE] * lib/net/smtp.rb: new method SMTP.ssl_context. * lib/net/smtp.rb: new method SMTP.default_ssl_context. * lib/net/smtp.rb: export SMTP.authenticate. * lib/net/smtp.rb: export SMTP.auth_plain. * lib/net/smtp.rb: export SMTP.auth_login. * lib/net/smtp.rb: export SMTP.auth_cram_md5. * lib/net/smtp.rb: export SMTP.starttls. * lib/net/smtp.rb: export SMTP.helo. * lib/net/smtp.rb: export SMTP.ehlo. * lib/net/smtp.rb: export SMTP.mailfrom. * lib/net/smtp.rb: export SMTP.rcptto. * lib/net/smtp.rb: export SMTP.rcptto_list. * lib/net/smtp.rb: export SMTP.data. * lib/net/smtp.rb: export SMTP.quit. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@10726 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
e86b8053d2
commit
a03909606e
2 changed files with 315 additions and 159 deletions
45
ChangeLog
45
ChangeLog
|
@ -1,3 +1,48 @@
|
||||||
|
Tue Aug 15 11:21:08 2006 Minero Aoki <aamine@loveruby.net>
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: support SMTP/SSL. Thanks Kazuhiro NISHIYAMA.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: new method SMTP.use_ssl?
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: new method SMTP.enable_ssl.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: new method SMTP.disable_ssl.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: new method SMTP.default_ssl_port.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: new method SMTP.default_tls_port.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: now SMTP#enable_tls accepts a SSLContext
|
||||||
|
object, instead of a verity and cert. [FEATURE CHANGE]
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: new method SMTP.ssl_context.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: new method SMTP.default_ssl_context.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: export SMTP.authenticate.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: export SMTP.auth_plain.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: export SMTP.auth_login.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: export SMTP.auth_cram_md5.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: export SMTP.starttls.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: export SMTP.helo.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: export SMTP.ehlo.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: export SMTP.mailfrom.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: export SMTP.rcptto.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: export SMTP.rcptto_list.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: export SMTP.data.
|
||||||
|
|
||||||
|
* lib/net/smtp.rb: export SMTP.quit.
|
||||||
|
|
||||||
Sat Aug 12 22:33:06 2006 Eric Hodel <drbrain@segment7.net>
|
Sat Aug 12 22:33:06 2006 Eric Hodel <drbrain@segment7.net>
|
||||||
|
|
||||||
* string.c (String#split): Describe grouping behavior. Patch by Jan
|
* string.c (String#split): Describe grouping behavior. Patch by Jan
|
||||||
|
|
421
lib/net/smtp.rb
421
lib/net/smtp.rb
|
@ -1,8 +1,8 @@
|
||||||
# = net/smtp.rb
|
# = net/smtp.rb
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999-2004 Yukihiro Matsumoto.
|
# Copyright (c) 1999-2006 Yukihiro Matsumoto.
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999-2004 Minero Aoki.
|
# Copyright (c) 1999-2006 Minero Aoki.
|
||||||
#
|
#
|
||||||
# Written & maintained by Minero Aoki <aamine@loveruby.net>.
|
# Written & maintained by Minero Aoki <aamine@loveruby.net>.
|
||||||
#
|
#
|
||||||
|
@ -23,7 +23,7 @@ require 'net/protocol'
|
||||||
require 'digest/md5'
|
require 'digest/md5'
|
||||||
require 'timeout'
|
require 'timeout'
|
||||||
begin
|
begin
|
||||||
require "openssl"
|
require 'openssl'
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -174,38 +174,68 @@ module Net
|
||||||
25
|
25
|
||||||
end
|
end
|
||||||
|
|
||||||
@use_tls = false
|
# The default SMTP/SSL port, port 465.
|
||||||
@verify = nil
|
def SMTP.default_ssl_port
|
||||||
@certs = nil
|
465
|
||||||
|
|
||||||
# Enable SSL for all new instances.
|
|
||||||
# +verify+ is the type of verification to do on the Server Cert; Defaults
|
|
||||||
# to OpenSSL::SSL::VERIFY_PEER.
|
|
||||||
# +certs+ is a file or directory holding CA certs to use to verify the
|
|
||||||
# server cert; Defaults to nil.
|
|
||||||
def SMTP.enable_tls(verify = OpenSSL::SSL::VERIFY_PEER, certs = nil)
|
|
||||||
@use_tls = true
|
|
||||||
@verify = verify
|
|
||||||
@certs = certs
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Disable SSL for all new instances.
|
# The default SMTP/TLS (STARTTLS) port, port 587.
|
||||||
|
def SMTP.default_tls_port
|
||||||
|
587
|
||||||
|
end
|
||||||
|
|
||||||
|
@ssl = false
|
||||||
|
@tls = false
|
||||||
|
@ssl_context = nil
|
||||||
|
|
||||||
|
# Enables SMTP/SSL for all new objects.
|
||||||
|
# +context+ is a OpenSSL::SSL::SSLContext object.
|
||||||
|
def SMTP.enable_ssl(context = SMTP.default_ssl_context)
|
||||||
|
raise 'openssl library not installed' unless defined?(OpenSSL)
|
||||||
|
raise ArgumentError, "SSL and TLS is exclusive" if @tls
|
||||||
|
@ssl = true
|
||||||
|
@ssl_context = context
|
||||||
|
end
|
||||||
|
|
||||||
|
# Disables SMTP/SSL for all new objects.
|
||||||
|
def SMTP.disable_ssl
|
||||||
|
@ssl = false
|
||||||
|
@ssl_context = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# true if new objects use SMTP/SSL.
|
||||||
|
def SMTP.use_ssl?
|
||||||
|
@ssl
|
||||||
|
end
|
||||||
|
|
||||||
|
# Enables SMTP/SSL for all new objects.
|
||||||
|
# +context+ is a OpenSSL::SSL::Context object.
|
||||||
|
def SMTP.enable_tls(context = SMTP.default_ssl_context)
|
||||||
|
raise 'openssl library not installed' unless defined?(OpenSSL)
|
||||||
|
raise ArgumentError, "SSL and TLS is exclusive" if @ssl
|
||||||
|
@tls = false
|
||||||
|
@ssl_context = context
|
||||||
|
end
|
||||||
|
|
||||||
|
# Disable SMTP/TLS for all new objects.
|
||||||
def SMTP.disable_tls
|
def SMTP.disable_tls
|
||||||
@use_tls = nil
|
@tls = false
|
||||||
@verify = nil
|
@ssl_context = nil
|
||||||
@certs = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# true if new objects use SMTP/TLS.
|
||||||
def SMTP.use_tls?
|
def SMTP.use_tls?
|
||||||
@use_tls
|
@tls
|
||||||
end
|
end
|
||||||
|
|
||||||
def SMTP.verify
|
def SMTP.ssl_context
|
||||||
@verify
|
@ssl_context
|
||||||
end
|
end
|
||||||
|
|
||||||
def SMTP.certs
|
def SMTP.default_ssl_context
|
||||||
@certs
|
ctx = OpenSSL::SSL::SSLContext.new
|
||||||
|
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||||
|
ctx
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -229,10 +259,17 @@ module Net
|
||||||
@read_timeout = 60
|
@read_timeout = 60
|
||||||
@error_occured = false
|
@error_occured = false
|
||||||
@debug_output = nil
|
@debug_output = nil
|
||||||
@use_tls = SMTP.use_tls?
|
if SMTP.use_ssl? or SMTP.use_tls?
|
||||||
|
@ssl = true
|
||||||
|
if SMTP.use_ssl?
|
||||||
|
@ssl_mode = :ssl
|
||||||
|
else
|
||||||
|
@ssl_mode = :tls
|
||||||
|
end
|
||||||
@certs = SMTP.certs
|
@certs = SMTP.certs
|
||||||
@verify = SMTP.verify
|
@verify = SMTP.verify
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Provide human-readable stringification of class state.
|
# Provide human-readable stringification of class state.
|
||||||
def inspect
|
def inspect
|
||||||
|
@ -257,26 +294,47 @@ module Net
|
||||||
|
|
||||||
alias esmtp esmtp?
|
alias esmtp esmtp?
|
||||||
|
|
||||||
# does this instance use SSL?
|
# true if this object uses SMTP/SSL.
|
||||||
|
def use_ssl?
|
||||||
|
@ssl
|
||||||
|
end
|
||||||
|
|
||||||
|
# true if this object uses SMTP/TLS
|
||||||
def use_tls?
|
def use_tls?
|
||||||
@use_tls
|
@tls
|
||||||
end
|
end
|
||||||
|
|
||||||
# Enables STARTTLS for this instance.
|
# Enables SMTP/SSL for this object. Must be called before the
|
||||||
# +verify+ is the type of verification to do on the Server Cert; Defaults
|
# connection is established to have any effect.
|
||||||
# to OpenSSL::SSL::VERIFY_PEER.
|
# +context+ is a OpenSSL::SSL::SSLContext object.
|
||||||
# +certs+ is a file or directory holding CA certs to use to verify the
|
def enable_ssl(context = SMTP.default_ssl_context)
|
||||||
# server cert; Defaults to nil.
|
raise 'openssl library not installed' unless defined?(OpenSSL)
|
||||||
def enable_tls(verify = OpenSSL::SSL::VERIFY_PEER, certs = nil)
|
raise ArgumentError, "SSL and TLS is exclusive" if @tls
|
||||||
@use_tls = true
|
@ssl = true
|
||||||
@verify = verify
|
@ssl_context = context
|
||||||
@certs = certs
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Disables SMTP/SSL for this object. Must be called before the
|
||||||
|
# connection is established to have any effect.
|
||||||
|
def disable_ssl
|
||||||
|
@ssl = false
|
||||||
|
@ssl_context = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# Enables SMTP/TLS (STARTTLS) for this object.
|
||||||
|
# +context+ is a OpenSSL::SSL::SSLContext object.
|
||||||
|
def enable_tls(context = SMTP.default_ssl_context)
|
||||||
|
raise 'openssl library not installed' unless defined?(OpenSSL)
|
||||||
|
raise ArgumentError, "SSL and TLS is exclusive" if @ssl
|
||||||
|
@tls = true
|
||||||
|
@ssl_context = context
|
||||||
|
end
|
||||||
|
|
||||||
|
# Disables SMTP/TLS (STARTTLS) for this object. Must be called
|
||||||
|
# before the connection is established to have any effect.
|
||||||
def disable_tls
|
def disable_tls
|
||||||
@use_tls = false
|
@ssl = false
|
||||||
@verify = nil
|
@ssl_context = nil
|
||||||
@certs = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# The address of the SMTP server to connect to.
|
# The address of the SMTP server to connect to.
|
||||||
|
@ -316,10 +374,12 @@ module Net
|
||||||
# ....
|
# ....
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
def set_debug_output(arg)
|
def debug_output=(arg)
|
||||||
@debug_output = arg
|
@debug_output = arg
|
||||||
end
|
end
|
||||||
|
|
||||||
|
alias set_debug_output debug_output=
|
||||||
|
|
||||||
#
|
#
|
||||||
# SMTP session control
|
# SMTP session control
|
||||||
#
|
#
|
||||||
|
@ -437,54 +497,51 @@ module Net
|
||||||
user = nil, secret = nil, authtype = nil) # :yield: smtp
|
user = nil, secret = nil, authtype = nil) # :yield: smtp
|
||||||
if block_given?
|
if block_given?
|
||||||
begin
|
begin
|
||||||
do_start(helo, user, secret, authtype)
|
do_start helo, user, secret, authtype
|
||||||
return yield(self)
|
return yield(self)
|
||||||
ensure
|
ensure
|
||||||
do_finish
|
do_finish
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
do_start(helo, user, secret, authtype)
|
do_start helo, user, secret, authtype
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_start(helodomain, user, secret, authtype)
|
# 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 do_start(helo_domain, user, secret, authtype)
|
||||||
raise IOError, 'SMTP session already started' if @started
|
raise IOError, 'SMTP session already started' if @started
|
||||||
check_auth_args user, secret, authtype if user or secret
|
if user or secret
|
||||||
|
check_auth_method authtype
|
||||||
|
check_auth_args user, secret
|
||||||
|
end
|
||||||
s = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
|
s = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
|
||||||
@socket = InternetMessageIO.new(s)
|
logging "Connection opened: #{@address}:#{@port}"
|
||||||
|
if use_ssl?
|
||||||
logging "SMTP session opened: #{@address}:#{@port}"
|
s = new_ssl_socket(s)
|
||||||
@socket.read_timeout = @read_timeout
|
s.connect
|
||||||
@socket.debug_output = @debug_output
|
logging "SMTP/SSL started"
|
||||||
|
end
|
||||||
|
@socket = new_internet_message_io(s)
|
||||||
check_response(critical { recv_response() })
|
check_response(critical { recv_response() })
|
||||||
do_helo(helodomain)
|
do_helo helo_domain
|
||||||
|
if use_tls?
|
||||||
if @use_tls
|
s = new_ssl_socket(s)
|
||||||
raise 'openssl library not installed' unless defined?(OpenSSL)
|
|
||||||
context = OpenSSL::SSL::SSLContext.new
|
|
||||||
context.verify_mode = @verify
|
|
||||||
if @certs
|
|
||||||
if File.file?(@certs)
|
|
||||||
context.ca_file = @certs
|
|
||||||
elsif File.directory?(@certs)
|
|
||||||
context.ca_path = @certs
|
|
||||||
else
|
|
||||||
raise ArgumentError, "certs given but is not file or directory: #{@certs}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
s = OpenSSL::SSL::SSLSocket.new(s, context)
|
|
||||||
s.sync_close = true
|
|
||||||
starttls
|
starttls
|
||||||
s.connect
|
s.connect
|
||||||
logging 'TLS started'
|
logging "SMTP/TLS started"
|
||||||
@socket = InternetMessageIO.new(s)
|
@socket = new_internet_message_io(s)
|
||||||
@socket.read_timeout = @read_timeout
|
|
||||||
@socket.debug_output = @debug_output
|
|
||||||
# helo response may be different after STARTTLS
|
# helo response may be different after STARTTLS
|
||||||
do_helo(helodomain)
|
do_helo helo_domain
|
||||||
end
|
end
|
||||||
|
|
||||||
authenticate user, secret, authtype if user
|
authenticate user, secret, authtype if user
|
||||||
@started = true
|
@started = true
|
||||||
ensure
|
ensure
|
||||||
|
@ -494,17 +551,26 @@ module Net
|
||||||
@socket = nil
|
@socket = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
private :do_start
|
|
||||||
|
|
||||||
# method to send helo or ehlo based on defaults and to
|
def new_internet_message_io(s)
|
||||||
# retry with helo if server doesn't like ehlo.
|
io = InternetMessageIO.new(s)
|
||||||
#
|
io.read_timeout = @read_timeout
|
||||||
def do_helo(helodomain)
|
io.debug_output = @debug_output
|
||||||
|
io
|
||||||
|
end
|
||||||
|
|
||||||
|
def new_ssl_socket(s)
|
||||||
|
s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
|
||||||
|
s.sync_close = true
|
||||||
|
s
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_helo(helo_domain)
|
||||||
begin
|
begin
|
||||||
if @esmtp
|
if @esmtp
|
||||||
ehlo helodomain
|
ehlo helo_domain
|
||||||
else
|
else
|
||||||
helo helodomain
|
helo helo_domain
|
||||||
end
|
end
|
||||||
rescue ProtocolError
|
rescue ProtocolError
|
||||||
if @esmtp
|
if @esmtp
|
||||||
|
@ -516,14 +582,6 @@ module Net
|
||||||
end
|
end
|
||||||
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
|
|
||||||
|
|
||||||
def do_finish
|
def do_finish
|
||||||
quit if @socket and not @socket.closed? and not @error_occured
|
quit if @socket and not @socket.closed? and not @error_occured
|
||||||
ensure
|
ensure
|
||||||
|
@ -532,10 +590,9 @@ module Net
|
||||||
@socket.close if @socket and not @socket.closed?
|
@socket.close if @socket and not @socket.closed?
|
||||||
@socket = nil
|
@socket = nil
|
||||||
end
|
end
|
||||||
private :do_finish
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# message send
|
# Message Sending
|
||||||
#
|
#
|
||||||
|
|
||||||
public
|
public
|
||||||
|
@ -571,9 +628,10 @@ module Net
|
||||||
# * TimeoutError
|
# * TimeoutError
|
||||||
#
|
#
|
||||||
def send_message(msgstr, from_addr, *to_addrs)
|
def send_message(msgstr, from_addr, *to_addrs)
|
||||||
send0(from_addr, to_addrs.flatten) {
|
raise IOError, 'closed session' unless @socket
|
||||||
@socket.write_message msgstr
|
mailfrom from_addr
|
||||||
}
|
rcptto_list to_addrs
|
||||||
|
data msgstr
|
||||||
end
|
end
|
||||||
|
|
||||||
alias send_mail send_message
|
alias send_mail send_message
|
||||||
|
@ -624,71 +682,45 @@ module Net
|
||||||
# * TimeoutError
|
# * TimeoutError
|
||||||
#
|
#
|
||||||
def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream
|
def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream
|
||||||
send0(from_addr, to_addrs.flatten) {
|
raise IOError, 'closed session' unless @socket
|
||||||
@socket.write_message_by_block(&block)
|
mailfrom from_addr
|
||||||
}
|
rcptto_list to_addrs
|
||||||
|
data(&block)
|
||||||
end
|
end
|
||||||
|
|
||||||
alias ready open_message_stream # obsolete
|
alias ready open_message_stream # obsolete
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def send0(from_addr, to_addrs)
|
|
||||||
raise IOError, 'closed session' unless @socket
|
|
||||||
raise ArgumentError, 'mail destination not given' if to_addrs.empty?
|
|
||||||
if $SAFE > 0
|
|
||||||
raise SecurityError, 'tainted from_addr' if from_addr.tainted?
|
|
||||||
to_addrs.each do |to|
|
|
||||||
raise SecurityError, 'tainted to_addr' if to.tainted?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
mailfrom from_addr
|
|
||||||
to_addrs.each do |to|
|
|
||||||
rcptto to
|
|
||||||
end
|
|
||||||
res = critical {
|
|
||||||
check_response(get_response('DATA'), true)
|
|
||||||
yield
|
|
||||||
recv_response()
|
|
||||||
}
|
|
||||||
check_response(res)
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# auth
|
# Authentication
|
||||||
#
|
#
|
||||||
|
|
||||||
private
|
public
|
||||||
|
|
||||||
def check_auth_args(user, secret, authtype)
|
|
||||||
raise ArgumentError, 'both user and secret are required'\
|
|
||||||
unless user and secret
|
|
||||||
auth_method = "auth_#{authtype || 'cram_md5'}"
|
|
||||||
raise ArgumentError, "wrong auth type #{authtype}"\
|
|
||||||
unless respond_to?(auth_method, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def authenticate(user, secret, authtype)
|
def authenticate(user, secret, authtype)
|
||||||
__send__("auth_#{authtype || 'cram_md5'}", user, secret)
|
check_auth_method authtype
|
||||||
|
check_auth_args user, secret
|
||||||
|
funcall "auth_#{authtype || 'cram_md5'}", user, secret
|
||||||
end
|
end
|
||||||
|
|
||||||
def auth_plain(user, secret)
|
def auth_plain(user, secret)
|
||||||
|
check_auth_args user, secret
|
||||||
res = critical { get_response('AUTH PLAIN %s',
|
res = critical { get_response('AUTH PLAIN %s',
|
||||||
base64_encode("\0#{user}\0#{secret}")) }
|
base64_encode("\0#{user}\0#{secret}")) }
|
||||||
raise SMTPAuthenticationError, res unless /\A2../ === res
|
raise SMTPAuthenticationError, res unless /\A2../ =~ res
|
||||||
end
|
end
|
||||||
|
|
||||||
def auth_login(user, secret)
|
def auth_login(user, secret)
|
||||||
|
check_auth_args user, secret
|
||||||
res = critical {
|
res = critical {
|
||||||
check_response(get_response('AUTH LOGIN'), true)
|
check_response(get_response('AUTH LOGIN'), true)
|
||||||
check_response(get_response(base64_encode(user)), true)
|
check_response(get_response(base64_encode(user)), true)
|
||||||
get_response(base64_encode(secret))
|
get_response(base64_encode(secret))
|
||||||
}
|
}
|
||||||
raise SMTPAuthenticationError, res unless /\A2../ === res
|
raise SMTPAuthenticationError, res unless /\A2../ =~ res
|
||||||
end
|
end
|
||||||
|
|
||||||
def auth_cram_md5(user, secret)
|
def auth_cram_md5(user, secret)
|
||||||
|
check_auth_args user, secret
|
||||||
# CRAM-MD5: [RFC2195]
|
# CRAM-MD5: [RFC2195]
|
||||||
res = nil
|
res = nil
|
||||||
critical {
|
critical {
|
||||||
|
@ -709,7 +741,25 @@ module Net
|
||||||
|
|
||||||
res = get_response(base64_encode(user + ' ' + tmp))
|
res = get_response(base64_encode(user + ' ' + tmp))
|
||||||
}
|
}
|
||||||
raise SMTPAuthenticationError, res unless /\A2../ === res
|
raise SMTPAuthenticationError, res unless /\A2../ =~ res
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def check_auth_method(type)
|
||||||
|
mid = "auth_#{type || 'cram_md5'}"
|
||||||
|
unless respond_to?(mid, true)
|
||||||
|
raise ArgumentError, "wrong authentication type #{type}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_auth_args(user, secret)
|
||||||
|
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
|
end
|
||||||
|
|
||||||
def base64_encode(str)
|
def base64_encode(str)
|
||||||
|
@ -721,7 +771,11 @@ module Net
|
||||||
# SMTP command dispatcher
|
# SMTP command dispatcher
|
||||||
#
|
#
|
||||||
|
|
||||||
private
|
public
|
||||||
|
|
||||||
|
def starttls
|
||||||
|
getok('STARTTLS')
|
||||||
|
end
|
||||||
|
|
||||||
def helo(domain)
|
def helo(domain)
|
||||||
getok('HELO %s', domain)
|
getok('HELO %s', domain)
|
||||||
|
@ -731,25 +785,73 @@ module Net
|
||||||
getok('EHLO %s', domain)
|
getok('EHLO %s', domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def mailfrom(fromaddr)
|
def mailfrom(from_addr)
|
||||||
getok('MAIL FROM:<%s>', fromaddr)
|
if $SAFE > 0
|
||||||
|
raise SecurityError, 'tainted from_addr' if from_addr.tainted?
|
||||||
|
end
|
||||||
|
getok('MAIL FROM:<%s>', from_addr)
|
||||||
end
|
end
|
||||||
|
|
||||||
def rcptto(to)
|
def rcptto_list(to_addrs)
|
||||||
getok('RCPT TO:<%s>', to)
|
raise ArgumentError, 'mail destination not given' if to_addrs.empty?
|
||||||
|
to_addrs.each do |addr|
|
||||||
|
rcptto addr
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def rcptto(to_addr)
|
||||||
|
if $SAFE > 0
|
||||||
|
raise SecurityError, 'tainted to_addr' if to.tainted?
|
||||||
|
end
|
||||||
|
getok('RCPT TO:<%s>', 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_response(get_response('DATA'), true)
|
||||||
|
if msgstr
|
||||||
|
@socket.write_message msgstr
|
||||||
|
else
|
||||||
|
@socket.write_message_by_block(&block)
|
||||||
|
end
|
||||||
|
recv_response()
|
||||||
|
}
|
||||||
|
check_response(res)
|
||||||
end
|
end
|
||||||
|
|
||||||
def quit
|
def quit
|
||||||
getok('QUIT')
|
getok('QUIT')
|
||||||
end
|
end
|
||||||
|
|
||||||
def starttls
|
|
||||||
getok('STARTTLS')
|
|
||||||
end
|
|
||||||
#
|
|
||||||
# row level library
|
|
||||||
#
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def getok(fmt, *args)
|
def getok(fmt, *args)
|
||||||
|
@ -776,15 +878,24 @@ module Net
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_response(res, allow_continue = false)
|
def check_response(res, allow_continue = false)
|
||||||
return res if /\A2/ === res
|
case res
|
||||||
return res if allow_continue and /\A3/ === res
|
when /\A2/
|
||||||
err = case res
|
return res
|
||||||
when /\A4/ then SMTPServerBusy
|
when /\A3/
|
||||||
when /\A50/ then SMTPSyntaxError
|
unless allow_continue
|
||||||
when /\A55/ then SMTPFatalError
|
raise SMTPUnknownError,
|
||||||
else SMTPUnknownError
|
"got response 3xx but not DATA: #{res.inspect}"
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
when /\A4/
|
||||||
|
raise SMTPServerBusy, res
|
||||||
|
when /\A50/
|
||||||
|
raise SMTPSyntaxError, res
|
||||||
|
when /\A55/
|
||||||
|
raise SMTPFatalError, res
|
||||||
|
else
|
||||||
|
raise SMTPUnknownError, res
|
||||||
end
|
end
|
||||||
raise err, res
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def critical(&block)
|
def critical(&block)
|
||||||
|
@ -805,4 +916,4 @@ module Net
|
||||||
|
|
||||||
SMTPSession = SMTP
|
SMTPSession = SMTP
|
||||||
|
|
||||||
end # module Net
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue