Reworked SSL for POP patch

From: Daniel Hobe <daniel@...>
Date: 2004-04-04 23:57:49 UTC
List: ruby-core #2745
I've reworked the patch to implement a few new methods and not add any extra 
arguments to existing methods.

3 new class vars:
@@usessl
@@certs
@@verify

New methods:
Toggle SSL connections for new instances.  Sets the above class variables.

POP3.enable_ssl( verify, certs )
POP3.disable_ssl

2 equivalent instance methods to enable / disable SSL on a per instance basis:

pop.enable_ssl(verify, certs, port)
pop.disable_ssl

Example usage:
Quick 'n dirty all connections are SSL:

Net::POP3.enable_ssl
Net::POP3.start(server,port,account,password) do |pop|
 ...
end

Use SSL on a case by case basis:

pop = Net::POP3.new(server)
pop.enable_ssl if use_ssl_on_this_server
pop.start(user,password) do |pop|
...
end

Do people like this better?


This way of handling SSL could be applied to SMTP and IMAP as well to get a 
consistant SSL API across all the mail services.


FYI, I'll be on vacation for the next week and away from a computer until I 
get back.
-- 
Daniel Hobe <daniel@nightrunner.com>
http://www.nightrunner.com

Attachments (1)

pop.patch_v4 (6.3 KB, text/x-diff)
--- /home/hobe/downloads/ruby/ruby/lib/net/pop.rb	2004-04-04 16:37:09.000000000 -0700
+++ pop.rb	2004-04-04 16:56:33.000000000 -0700
@@ -36,12 +36,13 @@
 #     require 'net/pop'
 # 
 #     pop = Net::POP3.new('pop.example.com')
-#     pop.start('YourAccount', 'YourPassword')             # (1)
+#     pop = pop.enable_ssl(verify, certs) if $use_ssl        # (1)
+#     pop.start('YourAccount', 'YourPassword')             # (2)
 #     if pop.mails.empty?
 #       puts 'no mail.'
 #     else
 #       i = 0
-#       pop.each_mail do |m|   # or "pop.mails.each ..."   # (2)
+#       pop.each_mail do |m|   # or "pop.mails.each ..."   # (3)
 #         File.open("inbox/#{i}", 'w') {|f|
 #           f.write m.pop
 #         }
@@ -50,11 +51,12 @@
 #       end
 #       puts "#{pop.mails.size} mails popped."
 #     end
-#     pop.finish                                           # (3)
+#     pop.finish                                           # (4)
 # 
-# 1. call Net::POP3#start and start POP session
-# 2. access messages by using POP3#each_mail and/or POP3#mails
-# 3. close POP session by calling POP3#finish or use the block form of #start.
+# 1. optionally enable SSL for this POP connection
+# 2. call Net::POP3#start and start POP session
+# 3. access messages by using POP3#each_mail and/or POP3#mails
+# 4. close POP session by calling POP3#finish or use the block form of #start.
 # 
 # === Shortened Code
 # 
@@ -141,10 +143,31 @@
 # 
 #     # Use APOP authentication if $isapop == true
 #     pop = Net::POP3.APOP($is_apop).new('apop.example.com', 110)
-#     pop.start(YourAccount', 'YourPassword') {|pop|
+#     pop.start('YourAccount', 'YourPassword') {|pop|
 #       # Rest code is same.
 #     }
+#
+# === Using SSL
+# The net/pop library supports POP3 over SSL.
+# To use SSL:
+#
+#   Example 1:
+#     require 'net/pop'
+#     
+#     pop = Net::POP3.APOP($is_apop)
+#     pop = pop.enable_ssl if $use_ssl
+#     pop.start(server, port, account, password) do |pop|
+#      ...
+#     end 
 # 
+#   Example 2:
+#     require 'net/pop'
+#     pop = Net::POP3.new('pop.example.com').enable_ssl
+#     pop.start(username, password) do |pop|
+#       ...
+#     end
+#     
+#
 # === Fetch Only Selected Mail Using `UIDL' POP Command
 # 
 # If your POP server provides UIDL functionality,
@@ -170,6 +193,11 @@
 require 'digest/md5'
 require 'timeout'
 
+begin
+  require "openssl"
+rescue LoadError
+end
+
 module Net
 
   # Non-authentication POP3 protocol error
@@ -196,9 +224,19 @@
     # Class Parameters
     #
 
+    @@usessl = nil
+    @@verify = nil
+    @@certs = nil
+    PORT = 110
+    SSL_PORT = 995
     # The default port for POP3 connections, port 110
     def POP3.default_port
-      110
+      PORT
+    end
+    
+    # The default port for POP3S connections, port 995
+    def POP3.default_ssl_port
+      SSL_PORT
     end
 
     def POP3.socket_type   #:nodoc: obsolete
@@ -324,17 +362,43 @@
       new(address, port, isapop).start(account, password, &block)
     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 POP3.enable_ssl( verify = OpenSSL::SSL::VERIFY_PEER, certs = nil )
+      @@usessl = true
+      @@verify = verify
+      @@certs = certs  
+    end
+
+    # Disable SSL for all new instances.
+    def POP3.disable_ssl
+      @@usessl = nil
+      @@verify = nil
+      @@certs = nil
+    end
+    
     # Creates a new POP3 object.
-    # +address+ is the hostname or ip address of your POP3 server.
-    # The optional +port+ is the port to connect to; it defaults to 110.
+    # +addr+ is the hostname or ip address of your POP3 server.
+    # The optional +port+ is the port to connect to.
     # The optional +isapop+ specifies whether this connection is going
     # to use APOP authentication; it defaults to +false+.
     # This method does *not* open the TCP connection.
     def initialize(addr, port = nil, isapop = false)
       @address = addr
-      @port = port || self.class.default_port
+      @usessl = @@usessl
+      if @usessl
+        @port = port || POP3::SSL_PORT
+      else
+        @port = port || POP3::PORT
+      end
       @apop = isapop
-
+      
+      @certs = @@certs
+      @verify = @@verify
+      
       @command = nil
       @socket = nil
       @started = false
@@ -352,6 +416,32 @@
       @apop
     end
 
+    # does this instance use SSL?
+    def usessl?
+      @usessl
+    end
+   
+    # Enables SSL for this instance.  Must be called before the connection is
+    # established to have any effect.
+    # +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.
+    # +port+ is port to establish the SSL conection on; Defaults to 995.
+    def enable_ssl(verify = OpenSSL::SSL::VERIFY_PEER, certs = nil, 
+                   port = POP3::SSL_PORT)
+      @usessl = true
+      @verify = verify
+      @certs = certs
+      @port = port
+    end
+    
+    def disable_ssl
+      @usessl = nil
+      @verify = nil
+      @certs = nil
+    end
+
     # Provide human-readable stringification of class state.
     def inspect
       "#<#{self.class} #{@address}:#{@port} open=#{@started}>"
@@ -424,9 +514,22 @@
     end
 
     def do_start(account, password)
-      @socket = InternetMessageIO.new(timeout(@open_timeout) {
-                  TCPSocket.open(@address, @port)
-                })
+      s = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
+      if @usessl
+        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
+        s.connect
+      end
+      
+      @socket = InternetMessageIO.new(s)
+      
       logging "POP session started: #{@address}:#{@port} (#{@apop ? 'APOP' : 'POP'})"
       @socket.read_timeout = @read_timeout
       @socket.debug_output = @debug_output

In This Thread

Prev Next