西山和広です。

# 一度security / ruby-lang.orgに送りましたが、既に公開されている脆弱性に
# ついての話なので、ruby-devでいいのではないかということのようなので
# ruby-devに送り直しています。


http://sylpheed.sraoss.jp/diary/?date=20080926#p01
をみて、net/pop.rbもsylpheedと同じような対応を
すればいいのではないかと思ったのですが、どうでしょうか?

1.8ではNet::POP3Command#apopで
      if /[^!-~]/n =~ @apop_stamp || /@/ !~ @apop_stamp
        raise POPAuthenticationError, 'Invalid APOP server; cannot login'
      end
のようなチェックになると思います。
(1.9ではnをはずすだけ?)

以下はtest/net/imap/test_imap.rbを参考にして作成したテストです。

require 'net/pop'
require 'test/unit'
require 'digest/md5'

class TestPOP < Test::Unit::TestCase
  def setup
    @users = {'user' => 'pass' }
    @ok_user = 'user'
    @stamp_base = "#{$$}.#{Time.now.to_i}@localhost"
  end

  def test_pop_auth_ok
    pop_test(false) do |pop|
      assert_instance_of Net::POP3, pop
      assert_nothing_raised do
        pop.start(@ok_user, @users[@ok_user])
      end
    end
  end

  def test_pop_auth_ng
    pop_test(false) do |pop|
      assert_instance_of Net::POP3, pop
      assert_raise Net::POPAuthenticationError do
        pop.start(@ok_user, 'bad password')
      end
    end
  end

  def test_apop_ok
    pop_test(@stamp_base) do |pop|
      assert_instance_of Net::APOP, pop
      assert_nothing_raised do
        pop.start(@ok_user, @users[@ok_user])
      end
    end
  end

  def test_apop_ng
    pop_test(@stamp_base) do |pop|
      assert_instance_of Net::APOP, pop
      assert_raise Net::POPAuthenticationError do
        pop.start(@ok_user, 'bad password')
      end
    end
  end

  def test_apop_invalid
    pop_test("\x80"+@stamp_base) do |pop|
      assert_instance_of Net::APOP, pop
      assert_raise Net::POPAuthenticationError do
        pop.start(@ok_user, @users[@ok_user])
      end
    end
  end

  def test_apop_invalid_at
    pop_test(@stamp_base.sub('@', '.')) do |pop|
      assert_instance_of Net::APOP, pop
      assert_raise Net::POPAuthenticationError do
        pop.start(@ok_user, @users[@ok_user])
      end
    end
  end

  def pop_test(apop=false)
    host = 'localhost'
    server = TCPServer.new(host, 0)
    port = server.addr[1]
    thread = Thread.start do
      sock = server.accept
      begin
        pop_server_loop(sock, apop)
      ensure
        sock.close
      end
    end
    begin
      pop = Net::POP3::APOP(apop).new(host, port)
      #pop.set_debug_output $stderr
      yield pop
    ensure
      begin
        pop.finish
      rescue IOError
        raise unless $!.message == "POP session not yet started"
      end
    end
  ensure
    server.close
    thread.value
  end

  def pop_server_loop(sock, apop)
    if apop
      sock.print "+OK ready <#{apop}>\r\n"
    else
      sock.print "+OK ready\r\n"
    end
    user = nil
    while line = sock.gets
      case line
      when /^USER (.+)\r\n/
        user = $1
        if @users.key?(user)
          sock.print "+OK\r\n"
        else
          sock.print "-ERR unknown user\r\n"
        end
      when /^PASS (.+)\r\n/
        if @users[user] == $1
          sock.print "+OK\r\n"
        else
          sock.print "-ERR invalid password\r\n"
        end
      when /^APOP (.+) (.+)\r\n/
        user = $1
        if apop && Digest::MD5.hexdigest("<#{apop}>#{@users[user]}") == $2
          sock.print "+OK\r\n"
        else
          sock.print "-ERR authentication failed\r\n"
        end
      when /^QUIT/
        sock.print "+OK bye\r\n"
        return
      else
        sock.print "-ERR command not recognized\r\n"
        return
      end
    end
  end
end

__END__
module Net
  class POP3Command
    def apop(account, password)
      raise POPAuthenticationError, 'not APOP server; cannot login' \
                                                      unless @apop_stamp
      if /[^!-~]/n =~ @apop_stamp || /@/ !~ @apop_stamp
        raise POPAuthenticationError, 'Invalid APOP server; cannot login'
      end
      check_response_auth(critical {
        get_response('APOP %s %s',
                     account,
                     Digest::MD5.hexdigest(@apop_stamp + password))
      })
    end
  end
end


-- 
|ZnZ(ゼット エヌ ゼット)
|西山和広(Kazuhiro NISHIYAMA)