Postfix SMTP サーバには SMTP プロトコルの特定の場面でメールを拒否したり受け付けるためのメカニズムがいくつか組み込まれています。バージョン 2.1 の時点で、Postfix はポリシーの決定を Postfix の外で動く外部サーバに委譲することができるようになりました。
このポリシー委譲メカニズムを使うと、このドキュメントの最後に示すような、たった十数行程度の Perl で単純な greylist ポリシーを実装できます。ポリシー委譲のほかの例としては、http://spf.pobox.com/ にある Meng Wong による SPF ポリシーサーバがあります。どちらのポリシーの例も Postfix ソースコードの examples/smtpd-policy ディレクトリに含まれています。
ポリシー委譲は Postfix にポリシーを追加するのに現在好まれている方法です。数行の Perl で新しい機能を開発するのは、同じことを C コードでやろうとするよりもずっと簡単です。パフォーマンスの違いは非常に要求の厳しい環境以外では目立たないでしょう。アクティブなシステムでは、ポリシーデーモンプロセスは入ってくるSMTP接続数が $max_use に達するまで複数回使われます。
このドキュメントは以下の話題をカバーしています:
Postfix ポリシー委譲プロトコルは本当にシンプルです。クライアントの要求は name=value 属性が改行で分割された並びであり、空行で終わります。サーバの応答は name=value 属性1つであり、これも空行で終わります。
Postfix SMTP サーバが SMTPD アクセスポリシー委譲の要求で送る全ての属性の例を示します:
Postfixバージョン2.1以降: request=smtpd_access_policy protocol_state=RCPT protocol_name=SMTP helo_name=some.domain.tld queue_id=8045F2AB23 sender=foo@bar.tld recipient=bar@foo.tld recipient_count=0 client_address=1.2.3.4 client_name=another.domain.tld reverse_client_name=another.domain.tld instance=123.456.7 Postfixバージョン2.2以降: sasl_method=plain sasl_username=you sasl_sender= size=12345 ccert_subject=solaris9.porcupine.org ccert_issuer=Wietse+20Venema ccert_fingerprint=C2:9D:F4:87:71:73:73:D9:18:E7:C2:F3:C1:DA:6E:04 Postfixバージョン2.3以降: encryption_protocol=TLSv1/SSLv3 encryption_cipher=DHE-RSA-AES256-SHA encryption_keysize=256 etrn_domain= [empty line]
注意:
"request" 属性は必須です。この例では、要求タイプは "smtpd_access_policy" です。
属性は順不同です。ポリシーサーバは気にしない属性を無視すべきです。
同じ属性名が2度以上送られると、サーバは最初の値を保持するかも しれませんし、最後の属性値を保持するかもしれません。
属性値が得られないと、クライアントはその属性を送らないか、 値を空にして ("name=") その属性を送る、もしくは数値属性の場合はゼロ値 ("name=0") を送ります。
"recipient" 属性が使えるのは "RCPT TO" の場面、および "DATA" と "END-OF-MESSAGE" の場面でPostfixが現在のメッセージについて1つの 受信者だけを受けた場合のみです。
"recipient_count" 属性 (Postfix 2.3以降) は "DATA" および "END-OF-MESSAGE" の場面でのみゼロ以外の値になります。これはPostfixが 現在のメッセージに対して受け付けた受信者数を示します。
クライアントアドレスは 1.2.3.4 という形のドットで区切られたIPv4の 4つの数字または 1:2:3::4:5:6 のような形のIPv6アドレスです。
反転 client_name および検証済み client_name 情報の違いに関する 議論は、postconf(5) ドキュメントの reject_unknown_client_hostname の項を 参照してください。
属性名に "=" や null、改行を含んだり、属性値に null や改行を 含んではいけません。
"instance" 属性値は同じメッセージ配送に関する別の要求を関連 づけるのに使われます。
"size" 属性値にはクライアントが MAIL FROM コマンドで指定した メッセージのサイズ (指定されていなければゼロ) が入ります。Postfix 2.2 以降では、クライアントが END-OF-DATA コマンドを送った時に実際のメッセージ サイズになります。
"sasl_*" 属性 (Postfix 2.2以降) はクライアントがSASLで認証された 方法に関する情報を示します。SASL認証がない場合、これらの属性は空です。
"ccsert_*" 属性 (Postfix 2.2以降) はクライアントがTLSで認証された方法に関する情報を示します。証明書の認証がない場合、これらの属性は空です。Postfix 2.2.11で、これらの属性値は xtext としてエンコードされるように なりました: いくつかの文字は +XX で置き換えられます。ここで XX は 文字の値を表す16進2桁の数値です。
"encryption_*" 属性 (Postfix 2.3以降) は接続が暗号化されている 方法に関する情報を示します。平文接続では、protocol および cipher 属性は 空に、keysize はゼロになります。
"etrn_domain" 属性は ETRN の場面でのみ定義され、ETRN コマンド パラメータを示します。
以下は SMTPD ポリシー委譲要求に特有です:
プロトコル名は ESMTP または SMTP です。
プロトコル状態は CONNECT, EHLO, HELO, MAIL, RCPT, DATA, END-OF-MESSAGE, VRFY または ETRN です; これらはPostfix SMTPサーバが OK/REJECT/HOLD/他を決定するSMTPプロトコルの場面です。
ポリシーサーバはPostfix SMTPD access(5) テーブルで許されるいずれかの action で応答します。例:
action=defer_if_permit Service temporarily unavailable [empty line]
これにより、Postfix SMTP サーバが要求を恒久的に拒否する理由が見つからない場合は、 Postfix SMTP サーバは要求を 450 一時的エラーコードに "Service temporarily unavailable" という文を付けて拒否するようになります。
問題が起こった場合、ポリシーサーバは応答を返してはいけません。代わりにサーバは警告をログに記録して接続を切らなければいけません。Postfix はしばらくしてから要求を再試行します。
Postfix でポリシーを委譲するクライアントは TCP ソケットもしくは UNIX ドメインソケットに接続できます。例:
inet:127.0.0.1:9998 unix:/some/where/policy unix:private/policy
最初の例では、ポリシーサーバが 127.0.0.1 のポート 9998 で TCP ソケットを listen するように指定しています。2つ目の例では UNIX ドメインソケットの絶対パス名を指定しています。3番目の例では Postfix キューディレクトリからの相対パス名を指定しています; Postfix master デーモンによって呼ばれるポリシーサーバに対してはこれを使ってください。
"policy" という名前の UNIX ドメインソケットで listen し、Postfix spawn(8) デーモンの制御かで動くポリシーサーバを作るには、このようなものを使います:
1 /etc/postfix/master.cf: 2 policy unix - n n - 0 spawn 3 user=nobody argv=/some/where/policy-server 4 5 /etc/postfix/main.cf: 6 smtpd_recipient_restrictions = 7 ... 8 reject_unauth_destination 9 check_policy_service unix:private/policy 10 ... 11 policy_time_limit = 3600
注意:
2, 11行目: Postfix spawn(8) デーモンはデフォルトで1000秒後に子プロセスを kill します。これは SMTP クライアントが SMTP サーバプロセスに接続している間動き続けるポリシーデーモンには短すぎます。デフォルトの時間制限は main.cf で明示的に "policy_time_limit" を設定することで上書きされます。パラメータの名前は master.cf エントリの名前 ("policy") と "_time_limit" サフィックスをつなげたものです。
2行目: smtpd プロセス制限が default_process_limit を超えたときに "connection refused (接続が拒否されました)" や他の問題が出るを避けるため、デフォルトのプロセス制限 "-" ではなく "0" を指定します。
8, 9行目: "check_policy_service" は必ず "reject_unauth_destination" の「後に」指定してください。そうしないとシステムがオープンリレーになってしまいます。
Solaris の UNIX ドメインソケットは信頼して使うことができません。代わりに TCP ソケットを使ってください:
1 /etc/postfix/master.cf: 2 127.0.0.1:9998 inet n n n - 0 spawn 3 user=nobody argv=/some/where/policy-server 4 5 /etc/postfix/main.cf: 6 smtpd_recipient_restrictions = 7 ... 8 reject_unauth_destination 9 check_policy_service inet:127.0.0.1:9998 10 ... 11 127.0.0.1:9998_time_limit = 3600
クライアント側のポリシー委譲プロトコルを制御する他の設定パラメータ:
smtpd_policy_service_max_idle (デフォルト: 300s): Postfix SMTP サーバが使われていないポリシークライアント接続を閉じるまでの時間。
smtpd_policy_service_max_ttl (デフォルト: 1000s): Postfix SMTP サーバがアクティブなポリシークライアント接続を閉じるまでの時間。
smtpd_policy_service_timeout (デフォルト: 100s): ポリシーサーバへの接続、ポリシーサーバへの送信、またはポリシーサーバからの受信における時間制限。
Greylisting は http://www.greylisting.org/ にかかれているような、ジャンクメールに対する防衛方法です。このアイディアは有名になる1年以上前にpostfix-usersメーリングリストで議論されました。
Postfix ソースツリーにあるファイル examples/smtpd-policy/greylist.pl は単純化された greylist ポリシーサーバを実装します。このサーバは全ての (クライアント、送信者、受信者) の組み合わせに対するタイムスタンプを保存します。デフォルトでは、タイムスタンプが60秒以上経過するまでメールは受け付けられません。これはランダムに選択された送信者アドレスを持つジャンクメールや、ランダムに選択されたオープンプロキシを経由して送られるメールを止めます。また、頻繁に IP アドレスを変えるスパマーカらのジャンクメールも止めます。
examples/smtpd-policy/greylist.pl を /usr/libexec/postfix またはシステムの適した場所にコピーしてください。
greylist.pl Perl スクリプトには、treylist データベースファイルの場所と、メールが受け入れられるまでの遅延時間の長さを指定する必要があります。デフォルトの設定:
$database_name="/var/mta/greylist.db"; $greylist_delay=60;
/var/mta ディレクトリ (もしくはあなたが選択した場所) は "nobody" もしくはポリシーサービスに対して master.cf で設定したユーザ名が書き込める必要があります。
例:
# mkdir /var/mta # chown nobody /var/mta
注意: /tmp や /var/tmp のように誰でも書き込めるディレクトリに greylist データベースを作ったり、空きを使い果たしてしまう可能性のあるファイルシステムにデータベースを「作らないでください」。Postfix はメールキューやメールボックス保管庫用の "空きスペースがない" 状態でも生き残ることができますが、greylist データベースが壊れると生き残ることはできません。ファイルが壊れたら手でファイルを削除するまでメールを全く受信できなくなってしまいます。
greylist.pl Perl スクリプトは Postfix master デーモンの制御下で動かすことができます。例えば、"nobody" ユーザとしてスクリプトを走らせるには、Postfix プロセスのみがアクセス可能な UNIX ドメインソケットを使います:
1 /etc/postfix/master.cf: 2 policy unix - n n - 0 spawn 3 user=nobody argv=/usr/bin/perl /usr/libexec/postfix/greylist.pl 4 5 /etc/postfix/main.cf: 6 policy_time_limit = 3600
注意:
3行目: それぞれの要求や応答の冗長なログを取るには、"greylist.pl -v" を指定します。
2, 6行目: Postfix spawn(8) デーモンはデフォルトで1000秒後に子プロセスを kill します。これは SMTP クライアントが SMTP サーバプロセスに接続している間動き続けるポリシーデーモンには短すぎます。デフォルトの時間制限は main.cf で明示的に "policy_time_limit" を設定することで上書きされます。パラメータの名前は master.cf エントリの名前 ("policy") と "_time_limit" サフィックスをつなげたものです。
2行目: smtpd プロセス制限が default_process_limit を超えたときに "connection refused (接続が拒否されました)" や他の問題が出るを避けるため、デフォルトのプロセス制限 "-" ではなく "0" を指定します。
Solaris では、上の "ポリシークライアント/サーバ設定" セクションで詳細が述べられているように、unix: 形式のソケットではなく inet: 形式のソケットを使わなければいけません。
1 /etc/postfix/master.cf: 2 127.0.0.1:9998 inet n n n - 0 spawn 3 user=nobody argv=/usr/bin/perl /usr/libexec/postfix/greylist.pl 4 5 /etc/postfix/main.cf: 6 127.0.0.1:9998_time_limit = 3600
このサービスを呼び出すためには、 "check_policy_service inet:127.0.0.1:9998" と指定します。
頻繁に騙られる特定のドメインに対して greylisting を有効にするのは比較的安全です。サイバー空間/時間におけるある点での、よく騙られる MAIL FROM ドメインのリストは http://www.monkeys.com/anti-spam/filtering/sender-domain-validate.in に以前はありました。
1 /etc/postfix/main.cf: 2 smtpd_recipient_restrictions = 3 reject_unlisted_recipient 4 ... 5 reject_unauth_destination 6 check_sender_access hash:/etc/postfix/sender_access 7 ... 8 smtpd_restriction_classes = greylist 9 greylist = check_policy_service unix:private/policy 10 11 /etc/postfix/sender_access: 12 aol.com greylist 13 hotmail.com greylist 14 bigfoot.com greylist 15 ... etcetera ...
注意:
9行目: Solaris では、上の "例: greylist ポリシーサーバ" セクションで詳細が述べられているように、unix: 形式のソケットではなく inet: 形式のソケットを使わなければいけません。
6行目: "check_sender_access" は確実に "reject_unauth_destination" の「後に」指定してください。そうしないとシステムがオープンメールリレーになってしまいます。
3行目: Postfix 2.0 スナップショットリリースでは、"reject_unlisted_recipient" は "check_recipient_maps" と呼ばれていました。Postfix 2.1 は両方の形式を理解します。
3行目: greylist データベースは偽のアドレスですぐに汚染されてしまいます。知らない送信者や受信者を拒否するような他の制限で greylist 検索を保護することが役立ちます。
全てのメールに対して greylisting を有効にすると、一時的な送信者アドレスを使うメーリングリストは比較的急速に greylist データベースを汚染してしまうので、そのようなメーリングリストをまず間違いなく除外したくなるはずです。
1 /etc/postfix/main.cf: 2 smtpd_recipient_restrictions = 3 reject_unlisted_recipient 4 ... 5 reject_unauth_destination 6 check_sender_access hash:/etc/postfix/sender_access 7 check_policy_service unix:private/policy 8 ... 9 10 /etc/postfix/sender_access: 11 securityfocus.com OK 12 ...
注意:
7行目: Solaris では、上の "例: greylist ポリシーサーバ" セクションで詳細が述べられているように、unix: 形式のソケットではなく inet: 形式のソケットを使わなければいけません。
6-7行目: check_sender_access および check_policy_service は確実に reject_unauth_destination の「後に」指定してください。そうしないとシステムがオープンメールリレーになってしまいます。
3行目: greylist データベースは偽のアドレスですぐに汚染されてしまいます。greylist 検索に先だって知らない送信者や受信者を拒否するように制限しておくことが役立ちます。
greylist サーバはデータベースのエントリを削除しないため、greylist データベースは時間がたつと大きくなっていきます。放っておくと、greylist データベースは最終的にファイルシステムの空きを使い果たしてしまいます。
ステータスファイルサイズがある閾値を超えた際に単にそのファイルをリネームしたり削除しても悪影響はありません; Postfix は自動的に新しいファイルを作成します。最悪の場合、新しいメールは1時間程度遅れます。影響を知るには、週末の真夜中にそのファイルをリネームもしくは削除してください。
これは greylist ポリシーの例を実装した Perl サブルーチンです。 Postfix ソースの examples/smtpd-policy/greylist.pl として配布されている汎用のサンプルポリシーサーバの一部です。
# # greylist 状態データベースおよび greylist 時間間隔。/tmp や /var/tmp のように # 誰でも書き込めるディレクトリに greylist 状態データベースを「作っては # いけません」。greylist データベースを空きを使い果たしてしまうファイル # システムに「作ってはいけません」。 # $database_name="/var/mta/greylist.db"; $greylist_delay=60; # # デモ SMTPD access ポリシールーチン。結果は Postfix access テーブルの右側部分で # 指定されるのと全く同じ action です。要求属性は %attr ハッシュを通じて得られます。 # sub smtpd_access_policy { my($key, $time_stamp, $now); # 動的にデータベースを開きます。 open_database() unless $database_obj; # この client/sender/recipient に対応するタイムスタンプを検索します。 $key = lc $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"}; $time_stamp = read_database($key); $now = time(); # 新しい要求の場合、データベースにこの client/sender/recipient を加えます。 if ($time_stamp == 0) { $time_stamp = $now; update_database($key, $time_stamp); } # result は Postfix access(5) マップで許されるいずれかの action です。 # # メールにラベルを付けるには ``PREPEND headername: headertext'' を返します。 # # 成功の場合は ``OK'' ではなく ``DUNNO'' を返し、 # check_policy_service 制限に他の制限が続くようにします。 # # 失敗の場合は ``DEFER_IF_PERMIT optional text...'' を返し、 # 他の access 制限でもブロックできるようにします。 # syslog $syslog_priority, "request age %d", $now - $time_stamp if $verbose; if ($now - $time_stamp > $greylist_delay) { return "dunno"; } else { return "defer_if_permit Service temporarily unavailable"; } }