第21章 Postfixによるメール配送の基本
LPIC-2 211.1 相当
電子メールのやり取りは、複数の役割を持つソフトウェアが連携して成り立っています。ここでは送信・中継・配送の基盤であるPostfixを中心に、エイリアス・仮想ドメイン・TLS/SASL・メールキューの運用までを扱います。受信側のDovecotの詳細設定・Sieveによる振り分けは、別章「メール配送フィルタとメールボックスアクセス」で扱います。
メール配送の全体像
1通のメールが送信者から受信者に届くまでの流れ
[送信者MUA]
| SMTP submission(587番、認証あり)
v
[MSA](投稿受付。多くの場合MTAと同一ソフトウェアの別プロセス/ポート)
| 内部的にキューへ
v
[MTA](Postfix)
| SMTP(25番、サーバー間中継、インターネット経由)
v
[受信側MTA](Postfix)
| ローカル配送
v
[MDA](Dovecotのlmtp/lda等)
| Maildir/mbox形式で書き込み
v
[メールボックス]
^
| IMAP/POP3
[MRA](Dovecot)
^
| IMAP/POP3
[受信者MUA]
| 略称 | 役割 | 代表的なソフトウェア/ポート |
|---|---|---|
| MUA(Mail User Agent) | ユーザーが実際に使うメールクライアント | Thunderbird、Outlookなど |
| MSA(Mail Submission Agent) | MUAからのメール投稿を受け付ける窓口。認証を必須にし、投稿ポリシーをチェックする | 587番ポート(Submission) |
| MTA(Mail Transfer Agent) | サーバー間でメールを転送・中継する | Postfix、Sendmail/25番ポート |
| MDA(Mail Delivery Agent) | 受信したメールを、実際のメールボックス(Maildir/mbox)に書き込む | Dovecotのlda/lmtp |
| MRA(Mail Retrieval Agent) | ユーザーがメールボックスの中身を取得・閲覧するための窓口 | Dovecot/IMAP(143)・POP3(110) |
近年はスパム対策として、多くのISPがポート25での直接の外部送信をブロックしており、メールクライアントの送信設定は587番ポート+認証(MSA経由)を使うのが一般的です。Postfixでは、MTAとMSAは別のソフトウェアではなく、同じpostfixデーモンがmaster.cfの設定でポートごとに挙動を切り替えて両方の役割を担うのが一般的です。
SMTPの通信自体は、テキストベースの単純なコマンドのやり取りで成り立っています。telnetやopenssl s_clientで手動で接続し、動作を確認することもできます。
SMTPの通信の流れ(概念)
EHLO client.example.com # 挨拶とサーバーの対応機能の問い合わせ
MAIL FROM:<alice@example.com> # 送信元を宣言
RCPT TO:<bob@example.org> # 宛先を宣言
DATA # 本文の送信開始
Subject: test
本文...
. # 単独のピリオドで本文の終わりを示す
QUIT
Postfixのアーキテクチャ — masterプロセスと各デーモン
Postfixは、1つの巨大なプログラムがすべての処理を行うのではなく、masterという管理プロセスが、役割ごとに分割された小さなデーモン群を必要に応じて起動・監視するという設計を採っています。この設計により、あるデーモンが異常終了しても他のデーモンやシステム全体への影響を局所化でき、各デーモンに必要最小限の権限だけを与える(最小権限の原則)ことができます。
| デーモン | 役割 |
|---|---|
smtpd | 外部からのSMTP接続を受け付ける(受信側) |
cleanup | 受信したメールのヘッダーを正規化し、キューに登録する前の前処理を行う |
qmgr | キューマネージャ。どのメールをいつ配送するかをスケジューリングする中枢 |
local | ローカルのUnixアカウント宛のメールを配送する |
smtp | 外部のサーバーへSMTPでメールを送信する(送信側) |
pickup | ローカルのメールキュー投入用ディレクトリ(sendmailコマンド経由の投稿など)を監視し、キューへ取り込む |
bounce | 配送に失敗したメールについて、送信者へエラー通知(バウンスメール)を生成する |
これらのデーモンの起動条件・権限はmaster.cfで定義されています。
/etc/postfix/master.cf の書式
# service type private unpriv chroot wakeup maxproc command + args
smtp inet n - y - - smtpd
pickup unix n - y 60 1 pickup
qmgr unix n - n 300 1 qmgr
| フィールド | 意味 |
|---|---|
| service | サービス名(ソケットのパスやポート名) |
| type | inet(TCP/IPソケット)またはunix(Unixドメインソケット) |
| private | 他のPostfixコンポーネント以外からのアクセスを禁止するか |
| unpriv | 非特権ユーザー(postfixユーザーなど)として実行するか |
| chroot | chroot環境(/var/spool/postfix以下)に閉じ込めて実行するか |
| wakeup | デーモンを定期的に起動する間隔(秒)。"-"は起動不要(イベント駆動) |
| maxproc | 同時に起動できるプロセス数の上限。"-"は既定値を使用 |
| command | 実際に起動する実行ファイル名と引数 |
この行のこの値に注目: smtp inet n - y - - smtpdという行から、smtpdはTCP/IPソケット(inet)で待ち受け、chroot環境(y)の中で動作することが読み取れます。一方qmgrはchrootがnになっており、キューディレクトリなどシステムの広い範囲にアクセスする必要があるためchroot化されていません。このように、外部からの直接の攻撃対象になりやすいsmtpdのようなデーモンほど厳しく権限を絞り、内部処理を担うqmgrのようなデーモンには必要な範囲でアクセス権を残すという設計思想が読み取れます。
Postfixの基本設定
Postfixの主な設定ファイルは /etc/postfix/main.cf です。
/etc/postfix/main.cf の主な項目
myhostname = mail.example.com
mydomain = example.com
mydestination = $myhostname, $mydomain, localhost
inet_interfaces = all
mynetworks = 127.0.0.0/8, 192.168.1.0/24
relayhost =
| 主なパラメータ | 役割 |
|---|---|
mynetworks | 認証なしで中継(リレー)を許可するネットワーク範囲。オープンリレー化を防ぐため、必要最小限に絞ることが重要 |
relayhost | すべての外向きメールを経由させる上位のスマートホスト(プロバイダのSMTPサーバーなど)を指定 |
上記以外にも、main.cfには実務上重要なパラメータが数多くあります。
| パラメータ | 役割 |
|---|---|
myorigin | ローカルで生成されるメール(システム通知など)の送信元ドメインとして使われる値。既定は$myhostname |
mydestination | このサーバー自身が最終目的地として受け取る(ローカル配送する)ドメインの一覧 |
relay_domains | 自ネットワーク外からの中継要求のうち、受け付けてよい宛先ドメインの一覧(セカンダリMXとして機能する場合などに指定) |
inet_protocols | 使用するIPプロトコル(ipv4/ipv6/all) |
message_size_limit | 1通あたりのメールサイズの上限(バイト単位) |
mailbox_size_limit | 1つのメールボックスファイルの上限サイズ |
home_mailbox | ローカル配送時に使うメールボックスの形式・場所(例:"Maildir/"を指定するとMaildir形式で配送) |
smtpd_banner | SMTP接続時にクライアントへ最初に返すバナー文字列 |
disable_vrfy_command | SMTPのVRFYコマンド(ユーザーの存在確認)を無効化するかどうか |
main.cf の追加例
myorigin = $mydomain
inet_protocols = ipv4
message_size_limit = 26214400 # 約25MB
home_mailbox = Maildir/
smtpd_banner = $myhostname ESMTP
disable_vrfy_command = yes
disable_vrfy_command = yesで無効化しておくのが実務上の定石です。
/etc/aliasesの書式と用途
/etc/aliasesは、システム管理者が管理する、あるユーザー宛のメールを別の宛先へ振り替えるための対応表です。書式はエイリアス名: 宛先です。
/etc/aliases の例
root: admin@example.com # ローカルユーザーへの転送
postmaster: alice, bob # 複数宛先(カンマ区切り)
bulk-archive: /var/log/mail-archive.txt # 絶対パスを書くと、そのファイルへメール本文を追記する
autoresponder: "|/usr/local/bin/autoreply.sh" # パイプ記法で外部プログラムに渡す(コマンドはダブルクォートで囲むのが慣例)
announce-list: :include:/etc/postfix/lists/announce.txt # 外部ファイルの各行を宛先リストとして展開
root: admin@example.comは定番の設定です。rootアカウントはcronのエラー通知やシステムの重要メッセージの宛先になりますが、rootに直接ログインしてメールを確認する運用は一般的ではありません。このエイリアスを設定しておかないと、重要な通知が誰にも読まれないまま放置されてしまいます。
/etc/aliasesを編集しただけでは、変更はすぐには反映されません。Postfixは高速な検索のために、このテキストファイルをハッシュ化したバイナリDB(/etc/aliases.db)を実際には参照しているためです。編集後は必ずnewaliases(内部的にはpostalias /etc/aliasesと同等)を実行してDBを再生成する必要があります。
$ sudo vi /etc/aliases
$ sudo newaliases # /etc/aliases.dbを再生成
/etc/aliases: 28 aliases, longest 22 bytes, 267 bytes total
この行のこの値に注目: newaliases実行後のN aliasesという件数が、/etc/aliasesに定義された行の数と一致していれば、意図した内容が正しくDBに反映されています。件数が想定より少ない場合、コメント行やコロンの記述ミスで一部の定義が読み飛ばされている可能性があります。
~/.forwardによるユーザー独自の転送設定
/etc/aliasesが管理者による設定であるのに対し、~/.forwardは各ユーザー自身が自分のホームディレクトリに置くことで、管理者の手を借りずに自分宛メールの転送先を設定できる仕組みです。書式は1行に1つの宛先で、/etc/aliasesと同様にメールアドレス・パイプ・ファイルパスが指定できます。
~/.forward の例(他ドメインのアドレスへ転送)
alice@another-domain.example.com
ただし、このように転送先だけを書くと、届いたメールは転送先にのみ送られ、元のメールボックスには残りません。自分の元のメールボックスにもコピーを残したい場合は、次のようにバックスラッシュ付きの自分のユーザー名を追加の行として書きます。
~/.forward(転送しつつ、自分宛のコピーも残す)
alice@another-domain.example.com
\alice
\aliceのバックスラッシュは「これ以上エイリアスとして再展開せず、ローカルのメールボックスへ強制的に配送する」ことを意味する特殊な記法です。バックスラッシュを付けずに単にaliceとだけ書くと、無限ループやエイリアスの再解釈による予期しない挙動を招く可能性があります。
仮想ドメイン
1台のPostfixサーバーで複数のドメイン宛のメールを扱いたい場合、「仮想ドメイン」の仕組みを使います。仮想ドメインには、実体を持たない「エイリアス転送のみ」のものと、実際にメールボックスを持つものの2種類があります。
| パラメータ | 意味 |
|---|---|
virtual_alias_domains | エイリアス転送のみを行うドメインの一覧(このサーバー上に実体のメールボックスを持たない) |
virtual_alias_maps | 仮想エイリアスドメイン内の各アドレスを、実際の宛先へどう転送するかのルックアップテーブル |
virtual_mailbox_domains | 実際にメールボックスとして配送・保存するドメインの一覧 |
/etc/postfix/virtual の書式
alice@example-alias.com alice@realdomain.com
sales@example-alias.com alice@realdomain.com, bob@realdomain.com
@example-alias.com catchall@realdomain.com # @だけを左辺にすると、他に一致しなかった全アドレスの受け皿(キャッチオール)になる
$ sudo postmap /etc/postfix/virtual # テキストファイルからハッシュDB(virtual.db)を生成(正常時は無出力)
main.cf での指定
virtual_alias_domains = example-alias.com
virtual_alias_maps = hash:/etc/postfix/virtual
virtual_alias_domains/virtual_alias_mapsとvirtual_mailbox_domainsの最大の違いは、「エイリアス転送か実配送か」です。前者はあくまで別の実在アドレスへの振り替えにすぎず、そのドメイン専用のメールボックスは存在しません。後者は、Unixのシステムアカウントを作らずに、独立した「仮想メールボックス」としてメールを実際に保存する仕組みで、virtual_mailbox_baseやvirtual_mailbox_mapsと組み合わせ、大量の仮想ユーザー・複数ドメインを1台でホストする大規模環境で使われます。
Postfixの設定コマンド
| コマンド | 用途 |
|---|---|
postconf -n | main.cfのうち、コンパイル時の既定値と異なる(管理者が明示的に変更した)設定だけを表示する。トラブル報告時などによく使われる |
postconf -d | 全パラメータの既定値を表示する |
postconf -e 'parameter = value' | main.cfを直接エディタで開かずに、コマンドラインから安全にパラメータを書き換える |
postmap | テキスト形式のルックアップテーブルを、指定した形式のインデックス付きデータベースに変換する |
$ postconf -n
myhostname = mail.example.com
mydestination = $myhostname, $mydomain, localhost
smtpd_tls_security_level = may
$ postconf -e 'smtpd_tls_security_level = encrypt' # 正常時は無出力(main.cfが直接書き換えられる)
$ sudo systemctl reload postfix # 正常時は無出力
postmapで使えるルックアップテーブルの形式には複数の種類があり、用途に応じて選びます。
| 形式 | 特徴 |
|---|---|
hash: | 伝統的なDBMハッシュ形式。完全一致検索に最適化されており高速 |
btree: | B-木構造のデータベース。hashとほぼ同等の速度で、順序付きアクセスにもやや適する |
regexp: | キー自体をPOSIX正規表現として扱い、パターンマッチで検索する |
pcre: | Perl互換正規表現(PCRE)でパターンマッチする。regexpよりも高機能な表現が可能 |
$ postmap -q "alice@example.com" hash:/etc/postfix/virtual # 特定キーの検索結果をテストする
alice@realdomain.com
この行のこの値に注目: 該当するキーが見つかった場合、その変換後の値(この例では実際の転送先アドレス)が1行だけ表示されます。キーが存在しない場合は何も出力されず、終了コードだけが非0になるため、スクリプトから存在確認したい場合は$?を確認します。
PostfixのTLS設定
main.cf でのTLS基本設定
smtpd_tls_cert_file = /etc/ssl/certs/mail.crt
smtpd_tls_key_file = /etc/ssl/private/mail.key
smtpd_tls_security_level = may # STARTTLSを提供するが強制はしない(encryptにすると平文接続自体を拒否する)
smtp_tls_security_level = may # 送信側(他サーバーへ中継する際)のTLS方針
受信側のsmtpd_tls_security_levelと、送信側(他サーバーへの中継時)のsmtp_tls_security_levelは別のパラメータです。送信側をいきなりencryptにすると、相手サーバーがTLSに対応していない場合に配送そのものが失敗してしまうため、通常はmay(TLSが使えれば使う)のまま運用することが多くなります。
MUAからの投稿(587番のsubmission)や、接続開始直後から暗号化するsmtps(465番)を有効にするには、master.cfで該当エントリを有効化します。多くのディストリビューションでは既定でコメントアウトされているため、コメントを外して調整します。
/etc/postfix/master.cf の該当部分(全文)
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_reject_unlisted_recipient=no
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
smtps inet n - y - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
submission(587番)はSTARTTLSで平文接続から暗号化へ昇格する方式で、smtpd_tls_security_level=encryptにより暗号化を必須にしています。smtps(465番)は接続開始時点から即座にTLSで包む方式で、smtpd_tls_wrappermode=yesがそれを示します。両方ともsmtpd_sasl_auth_enable=yesでSASL認証を必須にし、smtpd_relay_restrictions=permit_sasl_authenticated,rejectで認証済みユーザー以外の中継を拒否しています。
SASL認証
MUAからの投稿を認証するために、PostfixはSASL(Simple Authentication and Security Layer)を使います。Postfix自身に認証機能を実装させる代わりに、同じサーバー上で稼働するDovecotの認証機能を間借りする構成が広く使われています。
main.cf でのSASL設定
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_type = dovecotとsmtpd_sasl_pathにより、PostfixはDovecotが提供する認証ソケット(Dovecot側のconf.d/10-master.confで定義される、Postfix用の認証ソケット)に認証要求を委譲します。認証ロジックをDovecot側に一元化することで、ユーザーの認証方式(PAM、LDAP、専用DBなど)を変更した際にも、Postfix側の設定を変えずに済むという利点があります。
制限リスト(restriction)による受信制御と評価順序
Postfixのsmtpdは、SMTPセッションの各段階(接続・HELO・MAIL FROM・RCPT TO・DATA)ごとに、それぞれ独立したチェックリストを適用できます。これらはrestriction(制限リスト)と呼ばれ、SMTPプロトコルが進行する順序と同じ順序で評価されます。
| パラメータ | 評価されるタイミング |
|---|---|
smtpd_client_restrictions | クライアントが接続してきた直後(HELOより前) |
smtpd_helo_restrictions | HELO/EHLOコマンドを受け取った時点 |
smtpd_sender_restrictions | MAIL FROMコマンドを受け取った時点 |
smtpd_recipient_restrictions | RCPT TOコマンドを受け取った時点(最も重要でよく使われる) |
smtpd_relay_restrictions | RCPT TOの評価と合わせて、中継可否を判定する |
smtpd_data_restrictions | DATAコマンドを受け取った時点 |
各restrictionパラメータには、次のような条件をカンマ区切りで並べ、上から順に評価します(いずれかの条件でREJECTが確定すれば、それ以降は評価されずセッションが拒否されます)。
| 条件 | 意味 |
|---|---|
permit_mynetworks | mynetworksに含まれる送信元であれば許可する |
permit_sasl_authenticated | SASL認証済みのクライアントであれば許可する |
reject_unauth_destination | 自サーバーが最終目的地でなく、かつ中継が許可されていない宛先を拒否する(オープンリレー防止の要) |
reject_unknown_sender_domain | 送信者アドレスのドメインがDNSで解決できない場合に拒否する |
reject_rbl_client | 指定したRBL(リアルタイムブラックリスト)に登録されているクライアントを拒否する |
check_client_access | 指定したルックアップテーブルを参照し、個別のクライアントごとに許可・拒否を判定する |
reject_non_fqdn_recipient | 宛先アドレスが完全修飾ドメイン名(FQDN)の形式でない場合に拒否する |
main.cf の設定例
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination,
reject_unknown_sender_domain,
reject_rbl_client zen.spamhaus.org,
permit
この行のこの値に注目: 条件は上から順に評価され、最初に確定した結果(permitまたはreject)でその時点の判定が決まります。この例では、まず自ネットワークからの接続(permit_mynetworks)とSASL認証済みのクライアント(permit_sasl_authenticated)を優先的に許可し、それ以外についてはreject_unauth_destinationで無関係な宛先への中継を拒否、reject_unknown_sender_domainで送信元ドメインが実在しないメールを拒否、reject_rbl_clientでスパム送信元として既知のIPアドレスを拒否した上で、最後に残った接続をpermitで許可しています。順序を誤ると、本来拒否すべき条件が評価される前に緩い条件でpermitが確定し、意図しない中継を許してしまう危険があります。
sendmail互換コマンドが使える理由
Postfixは、歴史的に広く使われてきたSendmailというMTAとの互換性を意識して設計されています。多くのUnix系アプリケーション(cronのエラー通知や各種システムスクリプトなど)は、内部でメールを送信する際に/usr/sbin/sendmailというコマンドを直接呼び出すという慣習に依存しているため、Postfixはこの呼び出し方法をそのまま受け付けられるよう、Sendmail互換のコマンド群を提供しています。
| コマンド | Postfixでの実体 |
|---|---|
sendmail | 標準入力からメールを受け取り、Postfixのpickupキューへ投入する(実体はPostfixのsendmail互換ラッパー) |
mailq | Postfix独自のpostqueue -pと同等の出力を返す |
newaliases | Postfix独自のpostalias /etc/aliasesと同等の処理を行う |
これらのコマンドは実体としては/usr/sbin/sendmailなどに配置された、Postfix用に書き換えられた互換プログラムです。アプリケーション側は自分がPostfixとやり取りしているかSendmailとやり取りしているかを意識する必要がなく、既存のシステムやスクリプトをそのまま流用できるという移行のしやすさが、Postfixが広く普及した理由の一つでもあります。
送信ドメイン認証(SPF・DKIM・DMARC)
迷惑メール対策として、送信元のなりすましを防ぐ「送信ドメイン認証」の仕組みが広く使われています。いずれもDNSのTXTレコードとして公開する点が共通しており、DNSの知識とつながる部分です。
| 技術 | 仕組み |
|---|---|
| SPF | そのドメインからのメールを送ってよいIPアドレスをTXTレコードで公開し、受信側が送信元IPと照合する |
| DKIM | メールヘッダに電子署名を付与し、受信側が公開鍵(DNSのTXTレコード)で検証することで改ざんの有無を確認する |
| DMARC | SPF/DKIMの検証結果に基づき、不合格だったメールをどう扱うか(隔離・拒否など)のポリシーを定義し、結果を送信者にレポートさせる |
SPFレコードの例(DNSのTXTレコード)
example.com. IN TXT "v=spf1 mx a ip4:192.0.2.10 -all"
# mxレコードとaレコードで示されるホスト、および192.0.2.10からの送信のみ許可
# -allは、それ以外からの送信を「不正」として明示的に拒否する
DMARCレコードの例(_dmarc.example.com のTXTレコード)
_dmarc.example.com. IN TXT "v=DMARC1; p=quarantine; rua=mailto:report@example.com"
# p=quarantineは、認証に失敗したメールを迷惑メールフォルダに隔離するよう受信側に指示する
# ruaで指定したアドレスに、集計レポートが定期的に送られてくる
DMARCのポリシー(p=)は、影響を確認しながら段階的に強めるのが実務上のセオリーです。まず p=none(何もしないが監視レポートは受け取る)で正規メールが誤って弾かれていないかを確認し、問題なければ p=quarantine(隔離)、最終的に p=reject(拒否)へと引き上げていきます。いきなりrejectにしてしまうと、SPF/DKIMの設定漏れがあった場合に正規のメールまで届かなくなるリスクがあります。
mail.logの読み方とSMTP応答コード
Postfixの動作記録は/var/log/mail.log(ディストリビューションによっては/var/log/maillog)に出力されます。トラブルシューティングの基本は、このログの1行1行が何を示しているかを正確に読み取ることです。
/var/log/mail.log の1行の例
Aug 21 10:15:32 mail postfix/smtp[12345]: 3F2A1B4C5D6E: to=<bob@example.org>, relay=mx.example.org[203.0.113.5]:25, delay=1.2, delays=0.1/0.02/0.5/0.58, dsn=2.0.0, status=sent (250 2.0.0 OK)
この行のこの値に注目: 先頭は日時とホスト名、postfix/smtp[12345]がこのログを出力したデーモン名とプロセスID、3F2A1B4C5D6EがキューID(1通のメールを追跡するための識別子)です。to=は宛先、relay=は実際に配送を試みた相手サーバー、delay=は処理全体にかかった秒数、dsn=はDSN(Delivery Status Notification)コード、status=がその配送試行の結果です。
| status | 意味 |
|---|---|
sent | 正常に配送(または次のホップへの中継)が完了した |
bounced | 恒久的な失敗により配送を断念し、送信者へエラー通知(バウンス)を返した |
deferred | 一時的な失敗により、再送のためdeferredキューへ移された |
DSNコードは3つの数字(クラス.サブジェクト.詳細)からなり、先頭の数字がおおまかな性質を示します。
| DSNコードの先頭 | 意味 |
|---|---|
2.x.x | 成功。メールは正常に配送された |
4.x.x | 一時的エラー。時間をおいて再試行すれば成功する可能性がある |
5.x.x | 恒久的エラー。再試行しても成功しない(宛先不在など) |
DSNコードとあわせて記録される、SMTPプロトコル自体の応答コード(3桁の数字)も、原因の切り分けに直結する重要な情報です。
| コード | 意味 |
|---|---|
| 220 | サービス準備完了(接続直後の初期応答) |
| 250 | 要求されたコマンドが正常に完了した |
| 354 | DATAコマンドを受理し、本文の送信を開始してよい |
| 421 | サービスが利用できない(一時的。接続がまもなく切断される) |
| 450 | 要求されたアクションが実行されなかった(一時的、メールボックス利用不可など) |
| 550 | 要求されたアクションが実行されなかった(恒久的、宛先不在など) |
| 552 | ストレージ容量超過によりメールが拒否された |
4xx台は一時的エラー(Postfixは自動的に再試行する)、5xx台は恒久的エラー(再試行せずバウンスする)という原則は、DSNコードの分類とも対応しています。ログに残るこれらの数字を見比べることで、問題が自サーバー側にあるのか、相手サーバー側にあるのかを素早く切り分けられます。
メールキューの詳細
Postfixは、送信・中継しようとしたメールを複数の種類のキューで管理します。
| キュー | 意味 |
|---|---|
| incoming | 受信したばかりで、まだ処理が始まっていないメール |
| active | 現在まさに配送処理中のメール |
| deferred | 一時的な配送失敗(4xxエラーなど)により再送待ちになっているメール |
| hold | postsuper -hなどにより、管理者が意図的に配送を保留したメール |
| corrupt | データが破損しており、正常に処理できないメール |
メールはincomingに入り、activeで配送が試行されます。成功すればキューから消え、一時的な失敗であればdeferredに移り、一定間隔で再びactiveに戻されて再試行されます。
$ mailq
-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------
3F2A1B4C5D6E 2048 Mon Aug 21 10:00:00 alice@example.com
(host mx.example.org[203.0.113.5] said: 450 4.2.1 mailbox temporarily unavailable)
bob@example.org
$ postqueue -p # mailqと同等の出力(Postfix標準のコマンド)
-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------
3F2A1B4C5D6E 2048 Mon Aug 21 10:00:00 alice@example.com
(host mx.example.org[203.0.113.5] said: 450 4.2.1 mailbox temporarily unavailable)
bob@example.org
この行のこの値に注目:1行目がキューID・サイズ・到着時刻・送信者、丸括弧内がそのメールが今どんなエラーで止まっているかの理由、その次の行が宛先です。この例では、相手先メールサーバーからの一時的なエラー(450、メールボックス一時利用不可)によりdeferred状態になっていることが読み取れます。
| コマンド | 用途 |
|---|---|
postqueue -f | キュー全体の再送を、通常のリトライ間隔を待たずに強制的にトリガーする |
postqueue -i <ID> | 指定した1通だけを対象に再送を試みる |
postcat -q <ID> | 指定したキューIDのメールの中身(ヘッダーと本文)をそのまま表示する |
postsuper -d <ID> | 指定したキューIDのメールを削除する |
postsuper -d ALL | キュー内の全メールを削除する(配送不能メールの一括整理などに使用。実行には十分注意) |
postsuper -h <ID> | 指定したメールをhold状態にし、配送を一時停止する |
postsuper -H <ID> | hold状態を解除し、通常のキュー処理に戻す |
$ postcat -q 3F2A1B4C5D6E
*** ENVELOPE RECORDS deferred/3/3F2A1B4C5D6E ***
message_size: 1024 120 1 0 1024
sender: alice@example.com
*** MESSAGE CONTENTS deferred/3/3F2A1B4C5D6E ***
Received: from mail.example.com (localhost [127.0.0.1])
Subject: Monthly report
$ sudo postsuper -h 3F2A1B4C5D6E # 内容を確認するまで配送を止めておきたい場合
postsuper: 3F2A1B4C5D6E: placed on hold
postsuper: Placed on hold: 1 message
$ sudo postsuper -H 3F2A1B4C5D6E # 確認が済んだので保留を解除
postsuper: 3F2A1B4C5D6E: released from hold
postsuper: Released from hold: 1 message
この行のこの値に注目: postcatはENVELOPE RECORDS(送信者・宛先などのメタ情報)とMESSAGE CONTENTS(実際のヘッダー・本文)の2部構成で表示され、問題のあるメールの中身を配送前に確認できます。postsuper -h/-H実行後のplaced on hold/released from holdで、保留・解除の操作が正しく反映されたことを確認できます。
再送待ちのメールを無期限に保持し続けるわけではありません。maximal_queue_lifetime(既定5日)を過ぎると、Postfixはそのメールを配送不能と判断し、送信者へバウンス(配送失敗)通知を送ります。バウンス通知そのものが送れない場合の再送期限はbounce_queue_lifetimeで別途管理されています。
特定の宛先へのメールだけが送れない場合は、ログに記録されるSMTPの応答コード(4xxは一時的エラーで再送される、5xxは恒久的エラーで再送されない)を確認すると、原因が自分側にあるのか相手側にあるのかを切り分けやすくなります。
$ tail -f /var/log/mail.log
Aug 21 10:00:00 mail postfix/smtp[8821]: 3F2A1B4C5D6E: to=<bob@example.org>, relay=mx.example.org[203.0.113.5]:25, delay=0.4, status=deferred (host mx.example.org[203.0.113.5] said: 450 4.2.1 mailbox temporarily unavailable)
(新着ログが記録されるたびに追記され続ける。Ctrl+Cで終了)
この行のこの値に注目: status=以降が配送結果で、sentなら成功、deferredなら一時的な保留、bouncedなら恒久的な失敗です。relay=で実際にどの相手先サーバーへ接続を試みたかが分かるため、宛先側の問題か自サーバー側の問題かを切り分ける材料になります。
オープンリレーを避ける設定
Postfixを構築する際に特に注意すべきなのが、オープンリレー(誰でも自由に第三者宛のメールを中継送信できてしまう状態)にしないことです。オープンリレーになっていると、スパム業者に踏み台として悪用され、自サーバーのIPアドレスがブラックリストに登録されて正規のメールまで届かなくなるという深刻な事態を招きます。
/etc/postfix/main.cf の一部
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination
permit_mynetworksで信頼できる自ネットワークからの中継のみを許可し、外部からの送信はpermit_sasl_authenticatedによるSMTP認証を経たユーザーに限定するのが基本方針です。新規構築時には、必ず外部の第三者ドメイン宛にテストメールを中継できてしまわないか、設定直後に確認することが推奨されます。
telnet・openssl s_clientによる手動SMTPセッションの実演
SMTPはテキストベースの単純なプロトコルであるため、専用のメールクライアントを使わなくても、telnetやopenssl s_clientで直接接続し、コマンドを手入力しながらサーバーの応答を1つずつ確認できます。トラブルシューティングや動作検証の際、非常に有用なスキルです。
$ telnet mail.example.com 25
Trying 192.0.2.10...
Connected to mail.example.com.
Escape character is '^]'.
220 mail.example.com ESMTP Postfix
> EHLO client.example.com
250-mail.example.com
250-PIPELINING
250-SIZE 26214400
250-STARTTLS
250 8BITMIME
> MAIL FROM:<alice@example.com>
250 2.1.0 Ok
> RCPT TO:<bob@example.org>
250 2.1.5 Ok
> DATA
354 End data with <CR><LF>.<CR><LF>
> Subject: test mail
>
> This is a test.
> .
250 2.0.0 Ok: queued as 3F2A1B4C5D6E
> QUIT
221 2.0.0 Bye
この行のこの値に注目: EHLOへの応答にある250-STARTTLSは、このサーバーがSTARTTLSによる暗号化への昇格に対応していることを示します。DATAの後、本文の終わりは単独行のピリオド(.)だけで示す必要があり、うっかりピリオドを送り忘れるとサーバーが延々と本文の続きを待ち続けてしまいます。25番ポートは平文のtelnetでも到達できますが、submission(587番)やsmtps(465番)はSTARTTLSまたは最初からのTLSが必須になっていることが多く、telnetでは途中までしか確認できません。
TLSで暗号化されたセッションを手動で確認したい場合は、openssl s_clientを使います。
$ openssl s_client -starttls smtp -connect mail.example.com:587 -crlf
CONNECTED(00000003)
...(証明書チェーンの情報)...
250 8BITMIME
> EHLO client.example.com
250-mail.example.com
250 AUTH PLAIN LOGIN
> QUIT
221 2.0.0 Bye
-starttls smtpは、平文で接続を開始した後、SMTPのSTARTTLSコマンドを内部的に発行してTLSへ昇格させてから、以降のやり取りを暗号化した状態で継続するオプションです。-crlfは、入力した行の末尾に正しい改行コード(CR+LF)を付与するオプションで、これを付けないと一部のサーバーで正しくコマンドとして認識されないことがあります。465番ポート(smtps)のように接続開始時点から即座にTLSで包まれている場合は、-starttls smtpを付けずに-connect mail.example.com:465だけで直接TLS接続を確立できます。