From: Daniel Hobe Date: 2004-03-27T14:41:45+09:00 Subject: Proposed patch to add SSL support to net/pop.rb --Boundary-00=_VQRZAHn85ZLATsE Content-Type: Multipart/Mixed; boundary="Boundary-00=_VQRZAHn85ZLATsE" --Boundary-00=_VQRZAHn85ZLATsE Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline This patch adds support to Net::POP for doing POP over SSL. Modeled on how net/imap.rb handles SSL. The main change is this: def initialize( addr, port = nil, isapop = false ) def initialize( addr, port = nil, isapop = false, usessl = false, certs = nil, verify = nil) How does this look? -- Daniel Hobe http://www.nightrunner.com --Boundary-00=_VQRZAHn85ZLATsE Content-Type: text/x-diff; charset="us-ascii"; name="pop.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="pop.patch" --- pop.rb 2004-03-25 19:38:12.000000000 -0800 +++ /home/hobe/bin/ruby/lib/ruby/1.9/net/pop.rb 2004-03-25 22:20:37.000000000 -0800 @@ -141,10 +141,25 @@ # # # 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. +# See documentaion of Net::POP3.new() for argument descriptions +# To use SSL: +# +# require 'net/pop' +# +# +# pop = Net::POP3.start('pop.example.com', 995, +# 'YourAccount', 'YourPassword',false,true, +# '/etc/ssl/certs',OpenSSL::SSL::VERIFY_PEER) {|pop| +# # Rest code is same. +# } +# +# # === Fetch Only Selected Mail Using `UIDL' POP Command # # If your POP server provides UIDL functionality, @@ -170,6 +185,11 @@ require 'digest/md5' require 'timeout' +begin + require "openssl" +rescue LoadError +end + module Net # Non-authentication POP3 protocol error @@ -200,6 +220,12 @@ def POP3.default_port 110 end + + # The default port for POP3S connections, port 995 + def POP3.default_ssl_port + 995 + end + def POP3.socket_type #:nodoc: obsolete Net::InternetMessageIO @@ -228,7 +254,8 @@ # yielding it to the +block+. # This method is equivalent to: # - # Net::POP3.start(address, port, account, password) {|pop| + # Net::POP3.start(address, port, account, password, + # isapop, usessl, certs, verify) {|pop| # pop.each_mail do |m| # yield m # end @@ -245,8 +272,10 @@ # def POP3.foreach( address, port = nil, account = nil, password = nil, - isapop = false, &block ) # :yields: message - start(address, port, account, password, isapop) {|pop| + isapop = false, usessl = false, certs = nil, + verify = nil, &block ) # :yields: message + start(address, port, account, password, isapop, usessl, certs, verify) { + |pop| pop.each_mail(&block) } end @@ -265,8 +294,10 @@ # def POP3.delete_all( address, port = nil, account = nil, password = nil, - isapop = false, &block ) - start(address, port, account, password, isapop) {|pop| + isapop = false, usessl = false, certs = nil, + verify = nil, &block ) + start(address, port, account, password, isapop, usessl, certs, verify) { + |pop| pop.delete_all(&block) } end @@ -282,19 +313,27 @@ # # Example 2: APOP # Net::POP3.auth_only('pop.example.com', 110, # 'YourAccount', 'YourPassword', true) - # + # + # # Example 3: POP over SSL + # Net::POP3.auth_only('pop.example.com', 995, + # 'YourAccount', 'YourPassword',false,true, + # '/etc/ssl/certs',OpenSSL::SSL::VERIFY_PEER) + # def POP3.auth_only( address, port = nil, account = nil, password = nil, - isapop = false ) - new(address, port, isapop).auth_only account, password + isapop = false, usessl = false, certs = nil, + verify = nil) + new(address, port, isapop, usessl, certs, verify).auth_only( + account, password) end # Starts a pop3 session, attempts authentication, and quits. # This method must not be called while POP3 session is opened. # This method raises POPAuthenticationError if authentication fails. - def auth_only( account, password ) + def auth_only( account, password, usessl = false, + certs = nil, verify = nil) raise IOError, 'opening already opened POP session' if started? - start(account, password) { + start(account, password, usessl, certs, verify) { ; } end @@ -304,7 +343,7 @@ # # Creates a new POP3 object and open the connection. Equivalent to - # Net::POP3.new(address, port, isapop).start(account, password) + # Net::POP3.new(address, port, isapop, usessl, certs, verify).start(account, password) # # If +block+ is provided, yields the newly-opened POP3 object to it, # and automatically closes it at the end of the session. @@ -320,21 +359,36 @@ # def POP3.start( address, port = nil, account = nil, password = nil, - isapop = false, &block ) # :yield: pop - new(address, port, isapop).start(account, password, &block) + isapop = false, usessl = false, certs = nil, + verify = nil, &block ) # :yield: pop + new(address, port, isapop, usessl, certs, verify).start( + account, password, &block) 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. + # The optional +port+ is the port to connect to; it defaults to 110 or 995 + # if +usessl+ = +true+. # The optional +isapop+ specifies whether this connection is going # to use APOP authentication; it defaults to +false+. + # The optional +usessl+ specifies whether to use ssl to connect to the + # POP3 server. Defaults to +false+. + # The optional +certs+ indicates the path or file containing the CA + # cert of the server. Defaults to +nil+. + # The optional +verify+ parameter determines what kind of verification to + # perform on the server certificate. Defaults to OpenSSL::SSL::VERIFY_PEER # This method does *not* open the TCP connection. - def initialize( addr, port = nil, isapop = false ) + def initialize( addr, port = nil, isapop = false, + usessl = false, certs = nil, verify = nil) + @address = addr - @port = port || self.class.default_port + @usessl = usessl + @port = port || @usessl ? self.class.default_ssl_port : self.class.default_port @apop = isapop - + + @certs = certs + @verify = verify + @command = nil @socket = nil @started = false @@ -408,7 +462,8 @@ # closes the session after block call finishes. # # This method raises a POPAuthenticationError if authentication fails. - def start( account, password ) # :yield: pop + def start( account, password, usessl = false, + certs = nil, verify = nil) # :yield: pop raise IOError, 'POP session already started' if @started if block_given? @@ -425,9 +480,23 @@ 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 + @verify = OpenSSL::SSL::VERIFY_PEER if @verify.nil? + 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 --Boundary-00=_VQRZAHn85ZLATsE-- --Boundary-00=_VQRZAHn85ZLATsE--