Ruby で疑似メールサーバを作ってみた

suzukiRuby,SMTP

昨日、静岡まで富士山を見に行ってきた suzuki です。雲が晴れるまで、40分くらい待って、写真を撮ってきました。

さて、今回は Ruby で擬似的なメールサーバを紹介してみます。

大量のメール配信の負荷テストと同時にエラーメールの処理テストを行いたい時があり、作ってみたプログラムです。このプログラムは次のようなイメージで使います。

dummy-smtp
dummy-smtp

まず、メールサーバからメールを送信し、一定の割合で疑似メールサーバがエラーを返します。すると、そのエラーメールをメールサーバからエラーメール処理サーバへ渡すようなイメージになります。

プログラムは、こちら。
■dummy-smtpd.rb


#!/bin/env ruby

require 'socket'
require 'syslog'

# port number
port = 25

# randmax 100 means => about 1 / 100
randmax = 100

gs = TCPServer.open(port)
addr = gs.addr
addr.shift

syslog = Syslog.open('dummy-smtpd', Syslog::LOG_PID | Syslog::LOG_USER)
syslog.log(Syslog::LOG_INFO, sprintf("server is on %s", addr.join(":")))

while true
   Thread.start(gs.accept) do |s|

      # greeting
      s.write("220 welcome to dummy smtp server\n")

      # DATA command flag
      dataflg = false

      while s.gets
         # request data
         original_request = $_.chomp
         request = original_request.upcase

         if dataflg == true
            # in DATA stream
            if /^\.$/ =~ request
               s.write("250 OK\n")
               dataflg = false
            end
         else
            # normal request
            case request

            when 'DATA'
               dataflg = true
               s.write("354 OK\n")

            when /^RCPT TO:/
               if rand( randmax ) == 1
                  syslog.log(Syslog::LOG_INFO, original_request + ' :ERROR')
                  s.write("550 ERROR\n")
               else
                  syslog.log(Syslog::LOG_INFO, original_request + ' :OK')
                  s.write("250 OK\n")
               end

            when 'QUIT'
               s.write("221 OK\n")
               s.close

            else
               # ok ok ok !
               s.write("250 OK\n")
            end
         end
      end
   end
end

使い方はこんな感じです。

■疑似メールサーバ

  1. 事前に本来の MTA(Postfix / qmail / sendmail など)を停止しておく
  2. root で dummy-smtpd.rb を起動する
    # ruby dummy-smtpd.rb
  3. 動作テスト(太字部分を入力する)
    $ telnet localhost 25
    Trying 127.0.0.1...
    Connected to localhost (127.0.0.1).
    Escape character is '^]'.
    220 welcome to dummy smtp server
    EHLO localhost
    250 OK
    MAIL FROM: <hoge@example.com>
    250 OK
    RCPT TO: <hoge-1@example.com>
    250 OK
    DATA 354
    OK
    To: <hoge-1@example.com>
    From: <hoge@example.com>
    Subject: test 
    
    test
    .
    250 OK
    QUIT
    221 OK

見た感じ、普通の SMTP サーバのようですが、ソースコードを見れば分かる通り、実際には何もしていません。

また、上記の処理中、RCPT TO: のところでランダムに「550 ERROR」が返るようにしています。そのアドレスは、syslog にも記録しています。

大量配信のテスト後、syslog に記録されたエラーアドレスと、実際にエラーメール処理サーバで処理したアドレスを比較するなどして、エラー処理に漏れが無いかどうか、などをチェックしました。

もちろん、疑似メールサーバなど使わずに、Postfix / qmail / Sendmail などの MTA を使っても良いのですが、大量送信時の負荷(特にディスク負荷)がバカになりません。送信側のピークより前に、受信側のピークが来てしまうのです。

このプログラムではメールキューをディスクへ保存していませんので、低スペックなサーバでも比較的負荷に耐えられるようになります。

あまり使う機会は無いかも知れませんが、何かの参考になれば幸いです。

suzukiRuby,SMTP

Posted by suzuki