Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby Mail, how to achieve SSL email

Tags:

email

ruby

I have successfully sent email to a remote server using port their port 25 (non-secure) with this script:

require 'rubygems'
require 'mail'

options = { :address              => "mail.domain.com",
            :port                 => 25,
            :domain               => 'mail.domain.com',
            :user_name            => '[email protected]',
            :password             => 'topsecret',
            :authentication       => 'login',
            :enable_starttls_auto => true  }
 Mail.defaults do
  delivery_method :smtp, options
end

 mail = Mail.new do
      from '[email protected]'
        to '[email protected]'
   subject 'This is a test email'
      body File.read('body.txt')
 end

puts mail.to_s
mail.deliver!

What I need to do now is use their SSL port 466. When I try it, I get the normal output detailing the message, then it pauses for about 2 minutes and coughs up this:

/usr/local/rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/timeout.rb:60:in `rbuf_fill': execution expired (Timeout::Error)
        from /usr/local/rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/net/protocol.rb:134:in `rbuf_fill'
        from /usr/local/rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/net/protocol.rb:116:in `readuntil'
        from /usr/local/rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/net/protocol.rb:126:in `readline'
        from /usr/local/rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/net/smtp.rb:911:in `recv_response'
        from /usr/local/rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/net/smtp.rb:554:in `do_start'
        from /usr/local/rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/net/smtp.rb:921:in `critical'
        from /usr/local/rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/net/smtp.rb:554:in `do_start'
        from /usr/local/rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/net/smtp.rb:525:in `start'
        from /usr/local/rvm/gems/ruby-1.8.7-p249/gems/mail-2.2.10/lib/mail/network/delivery_methods/smtp.rb:127:in `deliver!'
        from /usr/local/rvm/gems/ruby-1.8.7-p249/gems/mail-2.2.10/lib/mail/message.rb:243:in `deliver!'
        from testmail.rb:30

I think this is because it cannot even begin the SSL authentication process. How do I do it?

like image 907
Captain Claptrap Avatar asked Nov 30 '10 18:11

Captain Claptrap


1 Answers

Hmm reading the network/delivery_methods/smtp.rb, it doesn't look like it support Direct SSL. TLS isn't them same, as the connection starts out Plain Text and then Switches to SSL on the starttls command. Can you just use starttls on port 587?

pulling my comment up.

see

How to send mail with ruby over smtp with ssl (not with rails, no TLS for gmail)

Which suggests that you can monkey patch Net::SMTP to do it..

Ok kinda found the issue and can patch around it, but so far this solution is yucky.. but it does work :)

#!/usr/bin/env ruby

require 'rubygems'
require "openssl"
require "net/smtp"
require "mail"

Net::SMTP.class_eval do

  def self.start( address, port = nil,
                  helo = 'localhost.localdomain',
                  user = nil, secret = nil, authtype = nil, use_tls = false,
                  use_ssl = true, &block) # :yield: smtp
    new(address, port).start(helo, user, secret, authtype, use_tls, use_ssl, &block)
  end

  def start( helo = 'localhost.localdomain',
             user = nil, secret = nil, authtype = nil, use_tls = false, use_ssl = true ) # :yield: smtp
    start_method = use_tls ? :do_tls_start : use_ssl ? :do_ssl_start : :do_start
    if block_given?
      begin
        send start_method, helo, user, secret, authtype
        return yield(self)
      ensure
        do_finish
      end
    else
      send start_method, helo, user, secret, authtype
      return self
    end
  end

  private

  def do_tls_start(helodomain, user, secret, authtype)
    raise IOError, 'SMTP session already started' if @started

    check_auth_args user, secret

    sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
    @socket = Net::InternetMessageIO.new(sock)
    @socket.read_timeout = 60 #@read_timeout
    @socket.debug_output = STDERR #@debug_output

    check_response(critical { recv_response() })
    do_helo(helodomain)

    raise 'openssl library not installed' unless defined?(OpenSSL)
    starttls
    ssl = OpenSSL::SSL::SSLSocket.new(sock)
    ssl.sync_close = true
    ssl.connect
    @socket = Net::InternetMessageIO.new(ssl)
    @socket.read_timeout = 60 #@read_timeout
    @socket.debug_output = STDERR #@debug_output
    do_helo(helodomain)

    authenticate user, secret, authtype if user
    @started = true
  ensure
    unless @started
      # authentication failed, cancel connection.
        @socket.close if not @started and @socket and not @socket.closed?
      @socket = nil
    end
  end

  def do_ssl_start(helodomain, user, secret, authtype)
    raise IOError, 'SMTP session already started' if @started

    check_auth_args user, secret

    sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
    raise 'openssl library not installed' unless defined?(OpenSSL)
    ssl = OpenSSL::SSL::SSLSocket.new(sock)
    ssl.sync_close = true
    ssl.connect
    @socket = Net::InternetMessageIO.new(ssl)
    @socket.read_timeout = 60 #@read_timeout
    @socket.debug_output = STDERR #@debug_output

    check_response(critical { recv_response() })
    do_helo(helodomain)

    do_helo(helodomain)

    authenticate user, secret, authtype if user
    @started = true
  ensure
    unless @started
      # authentication failed, cancel connection.
        @socket.close if not @started and @socket and not @socket.closed?
      @socket = nil
    end
  end

  def do_helo(helodomain)
     begin
      if @esmtp
        ehlo helodomain
      else
        helo helodomain
      end
    rescue Net::ProtocolError
      if @esmtp
        @esmtp = false
        @error_occured = false
        retry
      end
      raise
    end
  end

  def starttls
    getok('STARTTLS')
  end

  def quit
    begin
      getok('QUIT')
    rescue EOFError, OpenSSL::SSL::SSLError
    end
  end
end

options = {
  :address              => "mail.domain.net",
  :port                 => 466,
  :domain               => 'mail.domain.net',
  :user_name            => '[email protected]',
  :password             => 'Secret!',
  :authentication       => 'login',
  :use_ssl => true  }

Mail.defaults do
  delivery_method :smtp, options
end

mail = Mail.new do
  from '[email protected]'
  to '[email protected]'
  subject 'This is a test email'
  body File.read('body.txt')
end

puts mail.to_s
mail.deliver!

for some reason the use_ssl in the orig monkey patch doesn't make it in, and couple that with VERSION being undefined in Net::SMTP. So I changed that out, and forced use_ssl to be true, and was able to send email..

like image 139
Doon Avatar answered Oct 08 '22 23:10

Doon