Ruby 1.8 does’nt have code to access POP3 servers using SSL. But there is code for accessing HTTP servers using SSL. So I thought I can tweak the same code to use with Net::POP3 class. Since these days gmail is so popular that most of the people started using it. Gmail only provides POP3 over SSL. So this tweak may help those who want to access Gmail using Ruby.
The dynamic removal of methods from ruby classes helped me with this. I had to even remove the constructor to keep the changes minimal. But ruby is smart enough to throw warning message for removing the constructor. I ignored the warning because in our case it is not going to create any havoc.
Even though this code resides in a new file pops.rb, the changes are made to the class POP3 which is defined in pop.rb. Actually you will be using a modified version of POP3 class rather than a new version of it. The only additional thing that need to be taken care of is while creating an instance you have to pass the SSL port and also after that you need to set the value of use_ssl attribute of POP3 class to true. If use_ssl is false then the POP3 behaves normally. In the code itself there is an example.
Below is the code for net/pops.rb.
require 'net/pop'
require 'openssl'
module Net
# == Examples
#
# === Retrieving Messages
#
# This example retrieves messages from the server and deletes them
# on the server.
#
# Messages are written to files named 'inbox/1', 'inbox/2', ....
# Replace 'pop.example.com' with your POP3 server address, and
# 'YourAccount' and 'YourPassword' with the appropriate account
# details.
#
# require 'net/pops'
#
# pop = Net::POP3.new('pop.example.com', pop3_ssl_port)
# pop.use_ssl = true
# pop.start('YourAccount', 'YourPassword') # (1)
# if pop.mails.empty?
# puts 'No mail.'
# else
# i = 0
# pop.each_mail do |m| # or "pop.mails.each ..." # (2)
# File.open("inbox/#{i}", 'w') do |f|
# f.write m.pop
# end
# m.delete
# i += 1
# end
# puts "#{pop.mails.size} mails popped."
# end
# pop.finish # (3)
#
# 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.
class POP3
remove_method :do_start
remove_method :initialize
def initialize( addr, port = nil, isapop = false )
@address = addr
@port = port || self.class.default_port
@apop = isapop
@command = nil
@socket = nil
@started = false
@open_timeout = 30
@read_timeout = 60
@debug_output = nil
@mails = nil
@n_mails = nil
@n_bytes = nil
@use_ssl = false
@ssl_context = nil
end
def do_start( account, password )
s = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
if use_ssl?
unless @ssl_context.verify_mode
warn "warning: peer certificate won't be verified in this SSL session"
@ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
s.sync_close = true
end
@socket = Net::InternetMessageIO.new(s)
@socket.read_timeout = @read_timeout
@socket.debug_output = @debug_output
if use_ssl?
s.connect
end
on_connect
@command = POP3Command.new(@socket)
if apop?
@command.apop account, password
else
@command.auth account, password
end
@started = true
ensure
do_finish if not @started
end
private :do_start
def use_ssl?
@use_ssl
end
# For backward compatibility.
alias use_ssl use_ssl?
# Turn on/off SSL.
# This flag must be set before starting session.
# If you change use_ssl value after session started,
# a Net::HTTP object raises IOError.
def use_ssl=(flag)
flag = (flag ? true : false)
raise IOError, "use_ssl value changed, but session already started" \
if started? and @use_ssl != flag
if flag and not @ssl_context
@ssl_context = OpenSSL::SSL::SSLContext.new
end
@use_ssl = flag
end
def self.ssl_context_accessor(name)
module_eval(<<-End, __FILE__, __LINE__ + 1)
def #{name}
return nil unless @ssl_context
@ssl_context.#{name}
end
def #{name}=(val)
@ssl_context ||= OpenSSL::SSL::SSLContext.new
@ssl_context.#{name} = val
end
End
end
ssl_context_accessor :key
ssl_context_accessor :cert
ssl_context_accessor :ca_file
ssl_context_accessor :ca_path
ssl_context_accessor :verify_mode
ssl_context_accessor :verify_callback
ssl_context_accessor :verify_depth
ssl_context_accessor :cert_store
def ssl_timeout
return nil unless @ssl_context
@ssl_context.timeout
end
def ssl_timeout=(sec)
raise ArgumentError, 'Net::POP3#ssl_timeout= called but use_ssl=false' \
unless use_ssl?
@ssl_context ||= OpenSSL::SSL::SSLContext.new
@ssl_context.timeout = sec
end
# For backward compatibility
alias timeout= ssl_timeout=
def peer_cert
return nil if not use_ssl? or not @socket
@socket.io.peer_cert
end
end
end
Another way is to use socat. You can download it from internet or can be installed it using your favourite package manager.
Here is how the command is to be used.
socat TCP4-L:3000 OPENSSL:pop.gmail.com:995,verify=0
Now insted of using ‘net/pops’ you can use ‘net/pop’ itself and communicate with POP3 servers using SSL. But the only change will be that, instead of using the target server in the constructor you need to use the localhost with the port that you mention for TCP4-L in socat.
pop = Net::POP3.new('localhost', 3000)
I don’t know whether this will help people. If it does, then drop me a mail. I think ruby 1.9 will have this built inside ‘net/pop’.
Have fun!