smtpd – サンプル SMTP サーバ

目的:SMTP サーバを実装するクラスを提供する
利用できるバージョン:2.1 以上

smtpd モジュールは SMTP (簡易メール転送プロトコル) サーバを構築するためのクラスを提供します。それは smtplib が使用するプロトコルのサーバ側のプログラムです。

SMTP サーバ

SMTPServer は全てのサンプルサーバのベースクラスになります。それはクライアントとの通信を制御してデータを受信します。そして、データを完全に受信すると、そのメッセージ操作をオーバライドする便利なフックを提供します。

コンストラクタの引数はクライアントからの接続を listen するローカルアドレスとプロキシのリモートアドレスです。 process_message() メソッドは派生したサブクラスでオーバーライドされるフックとして提供されます。そのメソッドはメッセージを完全に受信したときに呼び出されて、次の引数を受け取ります。

peer

クライアントのアドレス、IP アドレスと入力ポート番号のタプルです。

mailfrom

メッセージエンベロープから取り出された “from” の情報です。メッセージが配信されるときにクライアントからサーバへ渡されます。この情報は必ずしもメールヘッダの From と一致するというわけではありません。

rcpttos

メッセージエンベロープの受取人のリストです。繰り返しますが、この情報も必ずしもメールヘッダの To と一致するわけではありません。例えば BCC の場合が一致しません。

data

RFC 2822 完全準拠のメッセージ本文です。

process_message() のデフォルト実装は NotImplementedError を発生させるので、 SMTPServer を使用して実行するには、サブクラスとそのメソッドの実装を作成する必要があります。最初のサンプルは受信したメッセージに関する情報を表示するサーバです。

import smtpd
import asyncore

class CustomSMTPServer(smtpd.SMTPServer):
    
    def process_message(self, peer, mailfrom, rcpttos, data):
        print 'Receiving message from:', peer
        print 'Message addressed from:', mailfrom
        print 'Message addressed to  :', rcpttos
        print 'Message length        :', len(data)
        return

server = CustomSMTPServer(('127.0.0.1', 1025), None)

asyncore.loop()

SMTPServerasyncore を使用するので、そのサーバを実行するために asyncore.loop() を呼び出します。

ここでデータを送信するクライアントが必要です。 smtplib ページから適応するサンプルを使用して、ローカルホストの 1025 番ポートで実行したテストサーバへデータを送信するクライアントを設定します。

import smtplib
import email.utils
from email.mime.text import MIMEText

# メッセージを作成する
msg = MIMEText('This is the body of the message.')
msg['To'] = email.utils.formataddr(('Recipient', 'recipient@example.com'))
msg['From'] = email.utils.formataddr(('Author', 'author@example.com'))
msg['Subject'] = 'Simple test message'

server = smtplib.SMTP('127.0.0.1', 1025)
server.set_debuglevel(True) # show communication with the server
try:
    server.sendmail('author@example.com', ['recipient@example.com'], msg.as_string())
finally:
    server.quit()

あるターミナルで smtpd_custom.py を、別のターミナルで smtpd_senddata.py を実行すると次のようになります。

$ python smtpd_senddata.py
send: 'ehlo farnsworth.local\r\n'
reply: '502 Error: command "EHLO" not implemented\r\n'
reply: retcode (502); Msg: Error: command "EHLO" not implemented
send: 'helo farnsworth.local\r\n'
reply: '250 farnsworth.local\r\n'
reply: retcode (250); Msg: farnsworth.local
send: 'mail FROM:<author@example.com>\r\n'
reply: '250 Ok\r\n'
reply: retcode (250); Msg: Ok
send: 'rcpt TO:<recipient@example.com>\r\n'
reply: '250 Ok\r\n'
reply: retcode (250); Msg: Ok
send: 'data\r\n'
reply: '354 End data with <CR><LF>.<CR><LF>\r\n'
reply: retcode (354); Msg: End data with <CR><LF>.<CR><LF>
data: (354, 'End data with <CR><LF>.<CR><LF>')
send: 'Content-Type: text/plain; charset="us-ascii"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 7bit\r\nTo: Recipient <recipient@example.com>\r\nFrom: Author <author@example.com>\r\nSubject: Simple test message\r\n\r\nThis is the body of the message.\r\n.\r\n'
reply: '250 Ok\r\n'
reply: retcode (250); Msg: Ok
data: (250, 'Ok')
send: 'quit\r\n'
reply: '221 Bye\r\n'
reply: retcode (221); Msg: Bye

$ python smtpd_custom.py
Receiving message from: ('127.0.0.1', 58541)
Message addressed from: author@example.com
Message addressed to  : ['recipient@example.com']
Message length        : 229

入力メッセージのポート番号は毎回変わります。 rcpttos 引数は値のリストで mailfrom は文字列であることに注意してください。

Note

サーバを停止するには Ctrl-C を入力してください。

DebuggingServer

上述したサンプルは process_message() に渡された引数を表示しますが smtpdDebuggingServer という、さらに完璧なデバッグのために特別に設計されたサーバも提供します。それは入力メッセージを標準出力へ全て表示してから処理を停止します(実際のメールサーバへプロキシのようにメッセージは送信しません)。

import smtpd
import asyncore

server = smtpd.DebuggingServer(('127.0.0.1', 1025), None)

asyncore.loop()

先ほど使用した smtpd_senddata.py クライアントプログラムを使用すると DebuggingServer の出力は次のようになります。

$ python smtpd_debug.py
---------- MESSAGE FOLLOWS ----------
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
To: Recipient <recipient@example.com>
From: Author <author@example.com>
Subject: Simple test message
X-Peer: 127.0.0.1

This is the body of the message.
------------ END MESSAGE ------------

PureProxy

PureProxy クラスは真っ直ぐで単純なプロキシサーバを実装します。入力メッセージはコンストラクタの引数として渡された上流のサーバへ転送されます。

Warning

標準ライブラリドキュメントには “このプログラムの実行はオープンリレーについて理解する良い機会なので慎重に行ってください” とあります。

プロキシサーバの設定はまさにデバッグサーバと同じぐらい簡単です。

import smtpd
import asyncore

server = smtpd.PureProxy(('127.0.0.1', 1025), ('mail', 25))

asyncore.loop()

このサンプルは何も出力しないのでプロキシサーバが動作しているかを調べるためにメールサーバのログを見る必要があります。

Oct 19 19:16:34 homer sendmail[6785]: m9JNGXJb006785: from=<author@example.com>, size=248, class=0, nrcpts=1, msgid=<200810192316.m9JNGXJb006785@homer.example.com>, proto=ESMTP, daemon=MTA, relay=[192.168.1.17]

MailmanProxy

さらに smtpdMailman のフロントエンドとして動作する特別なプロキシを提供します。それはローカルの Mailman 設定がそのアドレスを認識する場合、直接的に操作されます。一方、そのメッセージはプロキシへ配送されます。

See also

smtpd
本モジュールの標準ライブラリドキュメント
smtplib
クライアントインタフェースを提供する
email
メールメッセージを構文解析する
asyncore
非同期サーバを実装するベースモジュール
RFC 2822
メールメッセージフォーマットを定義する
GNU Mailman mailing list software
メールを扱う Python ソフトウェアの優れたサンプル
Bookmark and Share