zorioの日記 このページをアンテナに追加 RSSフィード

2006-04-16

[]ActionMailerでGMailSMTPサーバを使いたい

ActionMailerのSMTPサーバGMailを使いたい。

だって宅鯖なんだもの。


問題は、GMailがTLS経由でのSMTP接続を要求すること。

Rubyのメジャーバージョンが上がるとnet/popやnet/smtpでTLSが使えるようになるみたいだが、いつになるか分からない。

まだ作業の途中だがメモしておく。


参考:

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/2789

http://bloghome.lovepeers.org/daymemo2/?date=20040802#p02

http://d.hatena.ne.jp/drawnboy/20051113/1131822348


接続のための設定

$ tail config/envirionment.rb
ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.server_settings = {
  :address => "smtp.gmail.com",
  :port => "587",
  :domain => "localhost.localdomain",
  :authentication => :plain,
  :user_name => "hogehoge",
  :password => "password"
}

net/smtpの変更

Ruby On Railsのpluginの機能を使ってnet/smtpの挙動を変える。

今回は動くかどうか試すだけなので、決め打ちでTLSを使うようにした。

$ cat vendor/plugins/action_mailer_tls/init.rb
require_dependency 'smtp_tls'
$ cat vendor/plugins/action_mailer_tls/lib/smtp_tls.rb
require "openssl"
require "net/smtp"

Net::SMTP.class_eval do
  private
  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

    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_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
end

実行結果

対話環境から実行してみた。

$ ruby script/console
Loading development environment.
 >> ProductMailer.deliver_added
 -> "220 mx.gmail.com ESMTP 12sm68450nzn\r\n"
 <- "EHLO localhost.localdomain\r\n"
 -> "250-mx.gmail.com at your service\r\n"
 -> "250-SIZE 20971520\r\n"
 -> "250-8BITMIME\r\n"
 -> "250-STARTTLS\r\n"
 -> "250 ENHANCEDSTATUSCODES\r\n"
 <- "STARTTLS\r\n"
 -> "220 2.0.0 Ready to start TLS\r\n"
 <- "EHLO localhost.localdomain\r\n"
 -> "250-mx.gmail.com at your service\r\n"
 -> "250-SIZE 20971520\r\n"
 -> "250-8BITMIME\r\n"
 -> "250-AUTH LOGIN PLAIN\r\n"
 -> "250 ENHANCEDSTATUSCODES\r\n"
 <- "AUTH PLAIN xxxxxxxxxxxxxxxxxxxx\r\n"
 -> "235 2.7.0 Accepted\r\n"
 <- "MAIL FROM:<>\r\n"
 -> "250 2.1.0 OK\r\n"
 <- "RCPT TO:<hogehoge@example.com>\r\n"
 -> "250 2.1.5 OK\r\n"
 <- "DATA\r\n"
 -> "354 Go ahead\r\n"
writing message from String
wrote 276 bytes
 -> "250 2.0.0 OK 1145201897 12sm68450nzn\r\n"
<- "QUIT\r\n"
EOFError: end of file reached
        from /usr/lib/ruby/1.8/net/protocol.rb:133:in `sysread'
        from /usr/lib/ruby/1.8/net/protocol.rb:133:in `rbuf_fill'
        from /usr/lib/ruby/1.8/timeout.rb:56:in `timeout'
        from /usr/lib/ruby/1.8/timeout.rb:76:in `timeout'
        from /usr/lib/ruby/1.8/net/protocol.rb:132:in `rbuf_fill'
        from /usr/lib/ruby/1.8/net/protocol.rb:116:in `readuntil'
        from /usr/lib/ruby/1.8/net/protocol.rb:126:in `readline'
        from /usr/lib/ruby/1.8/net/smtp.rb:664:in `recv_response'
        from /usr/lib/ruby/1.8/net/smtp.rb:651:in `getok'
        from /usr/lib/ruby/1.8/net/smtp.rb:686:in `critical'
        from /usr/lib/ruby/1.8/net/smtp.rb:649:in `getok'
        from /usr/lib/ruby/1.8/net/smtp.rb:639:in `quit'
        from /usr/lib/ruby/1.8/net/smtp.rb:426:in `do_finish'
        from /usr/lib/ruby/1.8/net/smtp.rb:381:in `start'
        from /usr/lib/ruby/1.8/net/smtp.rb:316:in `start'
        from /usr/lib/ruby/gems/1.8/gems/actionmailer-1.2.1/lib/action_mailer/base.rb:447:in `perform_delivery_smtp'
        from /usr/lib/ruby/gems/1.8/gems/actionmailer-1.2.1/lib/action_mailer/base.rb:333:in `deliver!'
        from /usr/lib/ruby/gems/1.8/gems/actionmailer-1.2.1/lib/action_mailer/base.rb:227:in `method_missing'
        from (irb):1>>

メールは送信出来たが、最後にEOFErrorが発生した。

試しにsylpheedで同様にメール送信を試してみたが、QUITを送るとコネクションが切断されるらしく、sylpheedプロトコルエラーと怒られた。

どうも、GMailはQUITを送ると応答を返さずにコネクションを切断するようだ。

どうするのが正しいのか分からんので、とりあえず保留。


追記

やっぱりGMailが悪いみたい。

http://www.puni.net/~mimori/rfc/rfc2821b.txt

RFC2821の4.1.1.10で、

The receiver MUST NOT intentionally close the transmission 
channel until it receives and replies to a QUIT command 
(even if there was an error).

と書いてある。

SylpheedでもGMailに合わせた対応を入れていた。

http://tmtm.org/cgi-bin/w3ml/sylpheed-jp/msg/2885

あと、2chのWanderlustスレでも似たような事で困ってる人がいた。