STARTTLS support for net/smtp
From:
"Daniel Hobe" <daniel@...>
Date:
2004-04-18 17:50:38 UTC
List:
ruby-core #2789
This patch adds STARTTLS support to net/smtp. The new methods are modeled
on my previous patch to add SSL support to pop.
Comments?
From the added docs:
# === STARTTLS support
#
# The Net::SMTP class supports STARTTLS.
#
# # Per Instance STARTTLS
# smtp = Net::SMTP.new('smtp.example.com',25)
# smtp.enable_tls(verify, certs) if $use_tls #(1)
# smtp.start('your host','username','password') { |s|
# s.send_message msgstr,
# 'your@mail.address',
# 'recipient@example.com'
# }
# smtp.finish
#
# 1. +verify+ tells the openssl library how to verify the server
# certificate. 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.
#
#
# # USE STARTTLS for all subsequent instances
# Net::SMTP.enable_tls
# # We will now use starttls for all connections.
# Net::SMTP.start('your.smtp.server', 25, 'mail.from,domain',
# 'Your Account', 'Your Password', :plain) {|smtp|
# smtp.send_message msgstr,
# 'your@mail.address',
# 'his_addess@example.com'
# }
#
--
Daniel Hobe <daniel@nightrunner.com>
Attachments (1)
smtp_tls.diff
(7.12 KB, text/x-patch)
--- ../../../ruby.orig/lib/net/smtp.rb 2004-03-28 23:54:37.000000000 -0800
+++ smtp.rb 2004-04-18 10:27:32.000000000 -0700
@@ -104,7 +104,8 @@
# 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.
+# SMTP.start/SMTP#start. Use in conjunction with STARTTLS to
+# prevent authentication information passing in the clear.
#
# # PLAIN
# Net::SMTP.start('your.smtp.server', 25, 'mail.from,domain',
@@ -116,11 +117,46 @@
# # CRAM MD5
# Net::SMTP.start('your.smtp.server', 25, 'mail.from,domain',
# 'Your Account', 'Your Password', :cram_md5)
-
+#
+# === STARTTLS support
+#
+# The Net::SMTP class supports STARTTLS.
+#
+# # Per Instance STARTTLS
+# smtp = Net::SMTP.new('smtp.example.com',25)
+# smtp.enable_tls(verify, certs) if $use_tls #(1)
+# smtp.start('your host','username','password') { |s|
+# s.send_message msgstr,
+# 'your@mail.address',
+# 'recipient@example.com'
+# }
+# smtp.finish
+#
+# 1. +verify+ tells the openssl library how to verify the server
+# certificate. 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.
+#
+#
+# # USE STARTTLS for all subsequent instances
+# Net::SMTP.enable_tls
+# # We will now use starttls for all connections.
+# Net::SMTP.start('your.smtp.server', 25, 'mail.from,domain',
+# 'Your Account', 'Your Password', :plain) {|smtp|
+# smtp.send_message msgstr,
+# 'your@mail.address',
+# 'his_addess@example.com'
+# }
+#
require 'net/protocol'
require 'digest/md5'
require 'timeout'
+begin
+ require "openssl"
+rescue LoadError
+end
+
module Net # :nodoc:
# Module mixed in to all SMTP error classes
@@ -163,11 +199,33 @@
Revision = %q$Revision: 1.71 $.split[1]
+ @@usetls = false
+ @@verify = nil
+ @@certs = nil
+
# The default SMTP port, port 25.
def SMTP.default_port
25
end
+ # 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)
+ @@usetls = true
+ @@verify = verify
+ @@certs = certs
+ end
+
+ # Disable SSL for all new instances.
+ def SMTP.disable_tls
+ @@usetls = nil
+ @@verify = nil
+ @@certs = nil
+ 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.
@@ -182,8 +240,33 @@
@read_timeout = 60
@error_occured = false
@debug_output = nil
+ @usetls = @@usetls
+ @certs = @@certs
+ @verify = @@verify
+ end
+
+ # does this instance use SSL?
+ def use_tls?
+ @usetls
+ end
+
+ # Enables STARTTLS for this instance.
+ # +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 enable_tls(verify = OpenSSL::SSL::VERIFY_PEER, certs = nil)
+ @usetls = true
+ @verify = verify
+ @certs = certs
+ end
+
+ def disable_tls
+ @usetls = nil
+ @verify = nil
+ @certs = nil
end
-
+
# Provide human-readable stringification of class state.
def inspect
"#<#{self.class} #{@address}:#{@port} started=#{@started}>"
@@ -252,12 +335,12 @@
#
# This method is equivalent to:
#
- # Net::SMTP.new(address,port).start(helo_domain,account,password,authtype)
+ # Net::SMTP.new(address,port).start(helo_domain,account,password,authtype)
#
- # # example
- # Net::SMTP.start('your.smtp.server') {
- # smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
- # }
+ # # example
+ # Net::SMTP.start('your.smtp.server') {
+ # smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
+ # }
#
# 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
@@ -341,15 +424,47 @@
def do_start( helodomain, user, secret, authtype )
raise IOError, 'SMTP session already started' if @started
check_auth_args user, secret, authtype if user or secret
+ s = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
+ @socket = InternetMessageIO.new(s)
- @socket = InternetMessageIO.new(timeout(@open_timeout) {
- TCPSocket.open(@address, @port)
- })
logging "SMTP session opened: #{@address}:#{@port}"
@socket.read_timeout = @read_timeout
@socket.debug_output = @debug_output
check_response(critical { recv_response() })
- begin
+ do_helo(helodomain)
+
+ if @usetls
+ unless defined?(OpenSSL)
+ raise "SSL extension not installed"
+ end
+ sslctx = OpenSSL::SSL::SSLContext.new
+ sslctx.verify_mode = @verify
+ sslctx.ca_file = @certs if @certs && FileTest::file?(@certs)
+ sslctx.ca_path = @certs if @certs && FileTest::directory?(@certs)
+ s = OpenSSL::SSL::SSLSocket.new(s, sslctx)
+ s.sync_close = true
+ starttls
+ s.connect
+ logging "STARTTLS Enabled. Connection now encrypted.\n"
+ @socket = InternetMessageIO.new(s)
+ @socket.read_timeout = @read_timeout
+ @socket.debug_output = @debug_output
+ # helo response may be different after STARTTLS
+ do_helo(helodomain)
+ end
+
+ authenticate user, secret, authtype if user
+ @started = true
+ ensure
+ @socket.close if not @started and @socket and not @socket.closed?
+ end
+ private :do_start
+
+ # method to send helo or ehlo based on defaults and to
+ # retry with helo if server doesn't like ehlo.
+ #
+ def do_helo(helodomain)
+ begin
if @esmtp
ehlo helodomain
else
@@ -363,12 +478,8 @@
end
raise
end
- authenticate user, secret, authtype if user
- @started = true
- ensure
- @socket.close if not @started and @socket and not @socket.closed?
end
- private :do_start
+
# Finishes the SMTP session and closes TCP connection.
# Raises IOError if not started.
@@ -580,6 +691,9 @@
getok('QUIT')
end
+ def starttls
+ getok('STARTTLS')
+ end
#
# row level library
#