第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)
MUA(送信者) メールクライアント submission 587番,認証 MSA 投稿受付 内部キューへ MTA(送信側) Postfix SMTP 25番 サーバー間中継 MTA(受信側) Postfix ローカル配送 MDA Dovecot lda 書込み メールボックス Maildir / mbox IMAP/POP3 MRA 取得窓口 IMAP/POP3 MUA(受信者) メールクライアント
メール配送の全体像。送信側は、MUA(メールクライアント)→MSA(投稿受付)→MTA(送信側)→MTA(受信側)→MDA→メールボックスと、SMTPで押し出す形(プッシュ)で配送されます。受信側は逆に、MUAがMRAを介してIMAP/POP3でメールボックスから取りに行く形(プル)で取得します。

近年はスパム対策として、多くの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サービス名(ソケットのパスやポート名)
typeinet(TCP/IPソケット)またはunix(Unixドメインソケット)
private他のPostfixコンポーネント以外からのアクセスを禁止するか
unpriv非特権ユーザー(postfixユーザーなど)として実行するか
chrootchroot環境(/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サーバーなど)を指定
オープンリレーに注意: mynetworksの範囲を誤って広く設定してしまうと、第三者が自社のメールサーバーを踏み台にしてスパムを送信できる「オープンリレー」状態になり、送信元IPがブラックリストに登録されるなど深刻な問題を招きます。設定後は外部から中継を試みて拒否されることを必ず確認しましょう。

上記以外にも、main.cfには実務上重要なパラメータが数多くあります。

パラメータ役割
myoriginローカルで生成されるメール(システム通知など)の送信元ドメインとして使われる値。既定は$myhostname
mydestinationこのサーバー自身が最終目的地として受け取る(ローカル配送する)ドメインの一覧
relay_domains自ネットワーク外からの中継要求のうち、受け付けてよい宛先ドメインの一覧(セカンダリMXとして機能する場合などに指定)
inet_protocols使用するIPプロトコル(ipv4/ipv6/all)
message_size_limit1通あたりのメールサイズの上限(バイト単位)
mailbox_size_limit1つのメールボックスファイルの上限サイズ
home_mailboxローカル配送時に使うメールボックスの形式・場所(例:"Maildir/"を指定するとMaildir形式で配送)
smtpd_bannerSMTP接続時にクライアントへ最初に返すバナー文字列
disable_vrfy_commandSMTPのVRFYコマンド(ユーザーの存在確認)を無効化するかどうか
main.cf の追加例
myorigin = $mydomain
inet_protocols = ipv4
message_size_limit = 26214400        # 約25MB
home_mailbox = Maildir/
smtpd_banner = $myhostname ESMTP
disable_vrfy_command = yes
なぜVRFYを無効化するのか: SMTPのVRFYコマンドは、本来「そのメールアドレスが実在するかどうか」を問い合わせるための機能ですが、スパム業者やアカウント調査を狙う攻撃者に、有効なメールアドレスの一覧を効率的に洗い出す手段として悪用されることがあります。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 -nmain.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_restrictionsHELO/EHLOコマンドを受け取った時点
smtpd_sender_restrictionsMAIL FROMコマンドを受け取った時点
smtpd_recipient_restrictionsRCPT TOコマンドを受け取った時点(最も重要でよく使われる)
smtpd_relay_restrictionsRCPT TOの評価と合わせて、中継可否を判定する
smtpd_data_restrictionsDATAコマンドを受け取った時点

各restrictionパラメータには、次のような条件をカンマ区切りで並べ、上から順に評価します(いずれかの条件でREJECTが確定すれば、それ以降は評価されずセッションが拒否されます)。

条件意味
permit_mynetworksmynetworksに含まれる送信元であれば許可する
permit_sasl_authenticatedSASL認証済みのクライアントであれば許可する
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互換ラッパー)
mailqPostfix独自のpostqueue -pと同等の出力を返す
newaliasesPostfix独自のpostalias /etc/aliasesと同等の処理を行う

これらのコマンドは実体としては/usr/sbin/sendmailなどに配置された、Postfix用に書き換えられた互換プログラムです。アプリケーション側は自分がPostfixとやり取りしているかSendmailとやり取りしているかを意識する必要がなく、既存のシステムやスクリプトをそのまま流用できるという移行のしやすさが、Postfixが広く普及した理由の一つでもあります。

送信ドメイン認証(SPF・DKIM・DMARC)

迷惑メール対策として、送信元のなりすましを防ぐ「送信ドメイン認証」の仕組みが広く使われています。いずれもDNSのTXTレコードとして公開する点が共通しており、DNSの知識とつながる部分です。

技術仕組み
SPFそのドメインからのメールを送ってよいIPアドレスをTXTレコードで公開し、受信側が送信元IPと照合する
DKIMメールヘッダに電子署名を付与し、受信側が公開鍵(DNSのTXTレコード)で検証することで改ざんの有無を確認する
DMARCSPF/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は、それ以外からの送信を「不正」として明示的に拒否する
迷惑メール対策の基礎知識: SPF・DKIM・DMARCは単体でも一定の効果がありますが、3つを組み合わせることで、なりすましメールの検知精度が大きく向上します。自社ドメインを騙ったフィッシングメール対策としても、DMARCポリシーの設定が推奨されています。
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の設定漏れがあった場合に正規のメールまで届かなくなるリスクがあります。

受信メール到着 SPFチェック 送信元ドメインのTXTレコード (v=spf1…)を取得 接続元IPと照合 → pass / fail DKIMチェック DKIM-Signatureヘッダの 署名を検証 公開鍵はselector._domainkeyのTXTから取得 → pass / fail DMARCチェック SPF/DKIMのアライメント (Fromドメインとの一致)を確認 ポリシー(p=)を適用 配送 p=none(合格) 隔離 p=quarantine 拒否 p=reject 集計レポート送信 rua=mailto:report@…
SPF・DKIM・DMARCの検証フロー。受信サーバーはSPF(送信元IPとTXTレコードの照合)とDKIM(DNS上の公開鍵による署名検証)をそれぞれ確認したうえで、DMARCがFromドメインとのアライメントを判定し、ポリシー(p=)に従って配送・隔離・拒否を決定し、あわせて集計レポートを送信者に送ります。

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要求されたコマンドが正常に完了した
354DATAコマンドを受理し、本文の送信を開始してよい
421サービスが利用できない(一時的。接続がまもなく切断される)
450要求されたアクションが実行されなかった(一時的、メールボックス利用不可など)
550要求されたアクションが実行されなかった(恒久的、宛先不在など)
552ストレージ容量超過によりメールが拒否された

4xx台は一時的エラー(Postfixは自動的に再試行する)、5xx台は恒久的エラー(再試行せずバウンスする)という原則は、DSNコードの分類とも対応しています。ログに残るこれらの数字を見比べることで、問題が自サーバー側にあるのか、相手サーバー側にあるのかを素早く切り分けられます。

メールキューの詳細

Postfixは、送信・中継しようとしたメールを複数の種類のキューで管理します。

キュー意味
incoming受信したばかりで、まだ処理が始まっていないメール
active現在まさに配送処理中のメール
deferred一時的な配送失敗(4xxエラーなど)により再送待ちになっているメール
holdpostsuper -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接続を確立できます。

確認クイズ

Q1. サーバー間でメールを転送する役割を持つソフトウェアの分類はどれですか?

解説: MTA(Mail Transfer Agent)がサーバー間のメール転送を担当します。PostfixやSendmailが代表例です。

Q2. MUAからのメール投稿を587番ポートで受け付け、認証を必須にする役割を担うのはどれですか?

解説: MSA(Mail Submission Agent)は、MUAからの投稿を587番ポートで受け付け、認証や投稿ポリシーのチェックを行います。

Q3. 設定ファイル/etc/aliasesで、宛先欄に絶対パスを書いた場合の動作はどれですか?

解説: 宛先欄に絶対パスを書くと、そのファイルへメール本文がそのまま追記されます。プログラムを実行したい場合はパイプ記法(|コマンド)を使います。

Q4. 設定ファイル/etc/aliasesを編集した後、変更を反映させるために実行する必要があるコマンドはどれですか?

解説: Postfixはハッシュ化されたバイナリDB(aliases.db)を実際に参照しているため、テキストファイルを編集しただけでは反映されません。newaliases(postalias /etc/aliasesと同等)でDBを再生成する必要があります。

Q5. ~/.forwardで、転送しつつ自分の元のメールボックスにもコピーを残したい場合に追加する記法はどれですか?

解説: バックスラッシュ付きのユーザー名は、エイリアスとして再展開せずローカル配送を強制する特殊な記法で、これを追加することで転送と同時に自分のメールボックスにもコピーを残せます。

Q6. virtual_alias_domainsとvirtual_mailbox_domainsの最大の違いはどれですか?

解説: virtual_alias_domains/mapsはエイリアス転送のみを行い実体のメールボックスを持ちません。virtual_mailbox_domainsは実際にメールボックスとして配送・保存する仮想ドメインを指定します。

Q7. main.cfのうち、コンパイル時の既定値と異なる(管理者が変更した)設定だけを表示するコマンドはどれですか?

解説: postconf -nは既定値と異なる設定のみを表示し、postconf -dは全パラメータの既定値を表示します。postconf -eは設定の書き換えに使います。

Q8. postmapで使えるルックアップテーブルの形式のうち、キーをPerl互換正規表現として扱うものはどれですか?

解説: pcre:はPerl互換正規表現(PCRE)でパターンマッチする形式です。hash:は完全一致検索用の伝統的なDBMハッシュ形式です。

Q9. master.cfでsmtps(465番ポート)を有効化する際、接続開始時点から即座にTLSで包む方式であることを示す設定はどれですか?

解説: smtpd_tls_wrappermode=yesは、STARTTLSのように途中から昇格するのではなく、接続開始時点から即座にTLSで通信を包む方式(smtps)を示します。submission(587番)ではsmtpd_tls_security_level=encryptでSTARTTLSを必須化します。

Q10. smtpd_sasl_type = dovecot と設定する目的はどれですか?

解説: smtpd_sasl_type = dovecotにより、PostfixはDovecotが提供する認証ソケットへ認証要求を委譲し、認証ロジックを1箇所に集約できます。

Q11. 一時的な配送失敗により再送待ちになっているメールが入るPostfixのキューはどれですか?

解説: deferredキューは、一時的な配送失敗(4xxエラーなど)により再送待ちになっているメールが入るキューです。holdは管理者が意図的に保留したメールが入ります。

Q12. postsuper -hコマンドの動作として正しいものはどれですか?

解説: postsuper -hは指定したメールをhold状態にして配送を一時停止します。削除はpostsuper -d、再送の強制はpostqueue -fです。

Q13. Postfixのmaster.cfで定義される各デーモンのうち、キューマネージャとしてどのメールをいつ配送するかをスケジューリングする中枢はどれですか?

解説: qmgrはキューマネージャで、どのメールをいつ配送するかをスケジューリングする中枢的なデーモンです。cleanupは受信メールの前処理、pickupはローカル投稿の取り込みを担当します。

Q14. master.cfの書式(service type private unpriv chroot wakeup maxproc command)のうち、chrootフィールドが示すものはどれですか?

解説: chrootフィールドは、そのデーモンをchroot環境に閉じ込めて実行するかを示します。同時プロセス数の上限はmaxproc、定期起動の間隔はwakeupフィールドです。

Q15. disable_vrfy_command = yes と設定する主な目的はどれですか?

解説: VRFYコマンドはメールアドレスの実在確認に使われますが、攻撃者に悪用されるリスクがあるため、disable_vrfy_command = yesで無効化するのが定石です。

Q16. smtpd_recipient_restrictionsが評価されるタイミングはどれですか?

解説: smtpd_recipient_restrictionsはRCPT TOコマンドを受け取った時点で評価される、最も重要でよく使われる制限リストです。

Q17. smtpd_recipient_restrictionsの条件リストで、reject_unauth_destinationが果たす役割はどれですか?

解説: reject_unauth_destinationは、無関係な宛先への中継要求を拒否する、オープンリレー防止の要となる条件です。

Q18. restrictionの条件リストの評価順序について正しいものはどれですか?

解説: 条件は上から順に評価され、最初に確定した結果で判定が決まるため、緩い条件を厳しい条件より先に書くと意図しない許可につながる危険があります。

Q19. mailqコマンドがPostfix環境でも使える理由はどれですか?

解説: Postfixは、多くのアプリケーションがsendmail/mailq/newaliasesを直接呼び出す慣習に対応するため、Sendmail互換のコマンド群を提供しています。

Q20. ログファイル/var/log/mail.logの1行に含まれるstatus=sentが意味することはどれですか?

解説: status=sentは配送が正常に完了したことを示します。恒久的失敗はbounced、一時的失敗による再送待ちはdeferredで示されます。

Q21. DSNコードの先頭が5.x.xである場合に意味することはどれですか?

解説: DSNコードの先頭が5.x.xは恒久的エラー、2.x.xは成功、4.x.xは一時的エラーを表します。

Q22. SMTP応答コード550が示す内容はどれですか?

解説: 550は恒久的エラーを示す応答コードで、宛先不在などの理由でメールが拒否されたことを表します。354はDATAコマンド受理、220はサービス準備完了を示します。

Q23. telnetで25番ポートに手動接続してSMTPセッションを行う際、DATAコマンド送信後に本文の終わりを示す方法はどれですか?

解説: DATAコマンドの後、本文の終わりは単独行のピリオドだけで示します。これを忘れるとサーバーが本文の続きを待ち続けてしまいます。

Q24. openssl s_client -starttls smtp -connect mail.example.com:587 の -starttls smtp オプションの役割はどれですか?

解説: -starttls smtpは、平文接続の後にSTARTTLSコマンドでTLSへ昇格させてから通信を継続するオプションです。接続開始時点からのTLSが必要な465番ポート(smtps)では、このオプションなしで直接接続します。