GmailはSPFがないとメールを受け取ってくれない問題に対処

このサーバーで運用しているMailman 3のメーリングリスト参加者からどうも一部の会員にメールが届いていないのではないかという苦情。Mailman 3のバウンスログが/var/log/mailman3/bounce.logにあるのでチェックしてみると確かにぞろぞろとバウンスされたという記録が並んでいる。しかし、妙なことにその大半がgmail.comかhotmail.comのアドレスだ。これは、gmail.comがこのサーバーからのメール受け取りを拒否してるのではないか?しかし返ってくるエラーメールについてはMailman 3が自動で破棄してしまうため、実際にどう拒否されてるのか分からない。そこでサーバーから手動で自分のgmail.comなアドレスに向けてテストメールを送ってみると、その理由が判明した。

Reason: 550 5.7.26 This message does not pass authentication checks (SPF and DKIM both

つまるところ発信者認証であるSPFとDKIMのどちらでも発信元が認証できないとはなっから受け取ってくれないという挙動をgmail.comが始めたということらしい。SPFは発信元の情報をDNSのTXTレコードから検証してIPアドレスの比較を行って確認する仕組みで、DKIMはサーバーの公開鍵をこれまたDNSのTXTレコードから取得して、メールがその鍵で署名されているかを検証して確認する仕組みだ。後者はPostfixにさらに小細工をして通過するメールがすべてDKIMで署名されるようにする必要があってとても面倒だ。そこでSPFで認証するために必要な情報をDNSのTXTレコードに登録することにする。幸いなことにこのサーバーが利用しているダイナミックDNSはTXTレコードの書き換えに対応しているので、とりあえずSPFで解決することは可能なようだ。

さて、このサーバーは接続しているプロバイダーが非固定IPからのOutbound Port 25 Blockingを行っているために、SMTPサーバーが直接外部にメールを送信することができない。そこでASAHI-NETのメールサーバーをリレーサーバーにしてすべてのメールをASAHI-NET経由で送信している。なので、SPFレコードに書く内容もこのリレーサーバーが正しく認証されるようにしなければならない。

さて、gmail.comにSPFなしで送ったメールのエラー情報を見ると検証しようとした送信元IPアドレスがこのサーバーのFQDNからのSPF情報で検証できなかったとご丁寧に検証対象になったIPアドレスが記載されている。そこでこのIPアドレスを逆引きしてドメイン名を確認するとhsmtpd-dty.xpsmail.jpというFQDNのようだ。ここから順引きするとAレコードが5つ表示されて、そのうちの一つがエラー情報に書かれていたIPアドレスと一致していたので、ASAHI-NETのSMTPサーバーはAレコードの多重化でラウンドロビンしているタイプのようだ。

今度はこのhsmtpd-dty.xpsmail.jpのSPFレコードを調べてみよう。

dig hsmtpd-dty.xpsmail.jp TXT

これでTXTレコードからSPFの情報を取得できる。詳細はこうだ。

v=spf1 include:spf.xpsmail.jp ~all

どうやらSPFの情報は別のDNSレコードで一括して管理しているようだ。メールサーバーのIPアドレスはいつ変更されるか分からないので、それをカバーできるようなSPFレコードを別に用意してあるということだろう。実際、このspf.xpsmail.jpのTXTレコードを取得してみるとSPFにAレコードの参照がずらずらと書いてある。

ここまで分かれば、あとはこのSPFレコードをこのサーバーのTXTレコードに書き込んで取得できるようにしてあげれば問題ないはずだ。というわけで、このSPFをダイナミックDNSのTXTレコード更新ツールを使って書き込んでからキャッシュされた情報が消えるタイミングであるTTLに書かれた秒数が経過するのをじっと我慢して待つ。そして、おもむろに先ほどやった手動のgmail.comへのメール送信をやってみると、あにはからんや、無事にメールが届いたのでこの対処法で完了である。

やれやれ。(まだhotmail.comにも無事に届くかは確認できていないw)

Mailman3のFedora認証プラグインを無効にする

突然、Mailman3からエラーメールが送られてきた。

[Django] ERROR (EXTERNAL IP): Internal Server Error: /mailman3/accounts/fedora/login/

こんなタイトルのメールだ。どうやらよく読むとFedora認証プラグインを使って認証しようとしたけれど認証に必要な設定がされていないからエラーになったということのようだ。前からPostoriusの画面にFedoraのアイコンが出てるのは気になっていたが、設定しないと機能しないのならこんなもの出しておかれても困るよなあ。

というわけでこれを消す。修正するのは以下のファイル。

/usr/share/mailman3-web/settings.py

この中にINSTALLED_APPS = (で始まるセクションがある。この中のFedora認証プラグインを有効にしている行をコメントアウトする。

django_mailman3.lib.auth.fedora

これでFedora認証プラグインが無効になるので間違って押してエラーを出すこともなくなるはず。

USB-GbEドングルのドライバロード時のエラー

先日、Linuxカーネルがapt経由で更新されてから、logwatchで報告されるレポートに謎のエラーが報告されるようになった。

platform regulatory.0: Direct firmware load for regulatory.db failed with error -2

とりあえずdmesgで表示されるブート時のログを眺めてみるとどうやらax88179のドライバーをカーネルがロードする時にこのエラーが出ているようだ。

このax88179は先日入れ替えたUSB-GbEドングルのTP-Link UE305に搭載されているチップなのだが、このチップのドライバーがカーネルにロードされる時にregulatory.dbなるファイルを読もうとして失敗しているというログのようだ。

このregulatory.dbというのが何かというと、コミュニティベースで各国の通信規制の内容をまとめたデータベースのようで、LinuxではCRDAという仕組みでWiFiアダプターなどを使用する時に国別の使用可能なチャンネルなどの規制をダイナミックに適用するための仕組みの様だ。なぜ有線LANであるax88179でこれを読み込もうとするのかはよく分からないし、実際、これの読み込みに失敗しても通信自体は何の問題もなく行えているのだが、それでも気持ち悪いので対策しよう。

で、解決方法は簡単。CRDAはDebianではcrdaというパッケージで提供されているのだが、これがインストールされていないので追加でインストールする。

apt install crda

全く不要なiwなどのWiFi用のパッケージも追加されるがまあ気にしないでおこう。インストールが完了したら再起動してブートログ中のax88179周りのログを確認すると、無事にエラーを出さずにregulatory.dbの読み込みに成功していた。

platform regulatory.0: firmware: direct-loading firmware regulatory.db

これで再起動するたびに気持ち悪いエラーが報告されずに済む。

Debian busterからbullseyeへのアップグレード

先日、ObsoleteになるPython2.7に依存したmailman2をmailman3へと移行したのは、busterで動いていたこのサーバーをbullseyeへアップグレードするためだ。ファイルシステム全体のバックアップもできるようになったので、念のためファイルシステムを一度全部dumpした後、Debianを最新に更新することにした。

busterからbullseyeへのアップグレードはいろんなところに情報があるので割愛。まあ、いつも通り/etc/apt/sources.listをbullseyeに書き換えて、apt updateからのapt full-upgradeをするだけだ。

途中、mailman3のパッケージの更新時にデータベース設定の対話的ダイアログが出てきた。本来ならすでに設定済みのmailman3ではこれを聞いてはいけないはずだが、最初のダイアログで自分でやるから勝手にすんな、を選んでやれば設定をぶち壊されずに済む。あと、mailman3-webも設定を新しいデフォルトに置き換えようとするが、すでに設定済みのローカルファイルを残すように指示してあげる必要がある。

すべてのパッケージが更新されたら再起動して、サービスがちゃんと動いているかを確かめると、あらら、いろいろ動いていないじゃないの。具体的にどんなサービスが動いていないかは以下のコマンドで見ることができる。

systemctl list-units –type=service

サービス起動に失敗したサービスは赤字で表示される。

このうち、uwsgiのサービスが動いていない理由は、どうやらsystemctlの古いユニットファイルが残っていて、そのユニットファイルに基づいて起動しようとして失敗しているようだ。このような問題はrcステートになっている設定だけが残骸で残っているパッケージを総パージしてあげればよい。

apt purge $(dpkg -l | awk ‘/^rc/ { print $2 }’)

これで大丈夫かと思ったら、なんとmailman3が動いていない。いろいろと調べてみるとどうも/var/lib/mailman3/data/内にlist:listな所有権でないファイルがパッケージの更新によって出現してしまったからのようだ。というわけで、このディレクトリ内の所有権が間違ってるファイルをlist:listにchownしてあげればサービスが起動する。

あと、sslまわりの証明書を用意しないと文句を言って起動しないdovecotはこのサーバーでは必要ないので自動起動しないように設定した。

サービスがなぜ起動しないのかについては、実際に

systemctl start サービス名

でサービスを起動してみると、エラーメッセージがちゃんと表示されるので、それを見ると原因追及がはかどる。

これで必要なサービスを一通りちゃんと起動できるようにしたら、実際にそれらのサービスが外部から見て正しく動いているかどうかを確認して、ようやくDebianのbullseyeへのアップグレードは完了だ。

minissdpdの再設定

いつの間にやらminissdpdというデーモンがDebian busterなこのサーバーで動いていた。というか、動いていなかった。というのも先日、USB接続のNICを交換した後、どうやら立ち上がっていなかったようなのだ。

このデーモン、いったい何をするものなのかというとどうやらLAN内のUPnPなサービスのアナウンスを自動的にキャッシュしてUPnPなサービスのディスカバリーを高速化するためのものらしい。

で、これが動いていないと何が問題かというとbusterからbullseyeへのアップグレードを行ったときに、minissdpdのパッケージが更新された後、サービスの起動に失敗してapt full-upgradeの進行がその時点で中断してしまうからだ。

で、このデーモン、やっかいなことにパッケージインストール時の対話的なダイアログを使ってその時点でのネットワークインターフェースを/etc/default/minissdpdに書き込んでしまう。これがデーモン起動時のデフォルト設定として使われ、それ以外にはユーザーが変更可能な設定ファイル等をおかないというはなはだ時代錯誤な仕組みになっているのだ。

というわけで、NICが変更されるたびにパッケージの再設定が必要になる。

dpkg-reconfigure minissdpd

デーモンを自動起動するかどうか、有効にするインターフェース名、IPv6を使用するかどうかを答えれば再設定される。しかし、UPnPというユーザー側がわざわざ設定しなくても自動で接続できるサービスのためのツールが、いちいち手動でインターフェースを明示的に再設定しないと動かないというのは本末転倒だよな。

余談だが、これを行えばパッケージのアップグレードは進行するようになるものの、このデーモン、ネットワークが有効になるのを待たずに起動しようとするという、systemctlのお作法でちゃんと依存関係を設定してないバカなデーモンなのだ。結果、USBデバイスが有効になるまでインターフェースが起動しないUSBなNICではスタートアップ時の起動に失敗する。

このminissdpdはどうやらtransmission-daemonの依存上流で入っているものの、transmission-daemon自体はこれがサービスで動いていなくても問題なく動くらしいので、さっきの対話的設定でスタートアップ時に起動しないように設定することにした。さらば。

dumpによるDebianサーバーのバックアップ

さて、LVMに未使用スペースを作りたかったのは、LVMでスナップショットを取りたかったからだ。

近々やらなければならないbusterからbullseyeへのアップグレード。いつもならえいやっと適当にやってしまってトラブってから対処を考えるのだが、今回はMailman 3へのアップグレードという難事業をやった後なので万が一の事態でサーバー再構築となることだけは避けたい。普段から再構築できるようにデータだけは定期的にバックアップを取っているのだが、今回は全ファイルシステムの完全なバックアップを取ってからやることにした。

いろいろとDebianで使えるバックアップツールを調べたのだが、どうにも決定打となるものがない。フルバックアップしてもいちいちファイル単位で戻すことになるのであれば、データだけバックアップしてサーバーを再構築するのと変わらないからだ。どうせフルバックアップするならリストアも機械的に行えばバックアップ時の状態が再現されることが望ましい。となると、古くから使い古されたdumpを使うしか方法がないようだ。dumpでファイルシステム単位でフルバックアップを取れば、Liveシステム等から内蔵ストレージをパーティションしてリストアすれば元の状態に戻すことができるからだ。

とはいえdumpには致命的な弱点がある。それはシステムがオンラインの時にバックアップができないということだ。dumpはもともとテープドライブにバックアップすることを目的としたツールで、ファイルシステムをブロックデバイスとして読み込んでテープのようなブロックデバイスにそのまま書き出すツールだ。なので、処理中にファイルシステムに変更が起きたときに正しくバックアップを行うことができないのだ。だからバックアップするときには必ずシングルユーザーモードにシステムを落としてから行うこととされている。いやいや、バックアップするたびにシステムを落としてオフラインでコンソールで作業とかありえないだろ。 😛

ところが、LVMを使ってパーティション管理をしている場合にはこれを回避する裏技がある。それはスナップショットだ。LVMの利点としてファイルシステムを任意の時点でスナップショットを取ってその状態を固定することができる。スナップショットはリードオンリーのファイルシステムとなるが、一度固定されたスナップショットはオンライン状態でも変更されることはないので、dumpでも安全に書き出すことができるというわけだ。スナップショットは作成時にそれ以降の元になるファイルシステムに生じた変更を記録するワークエリアが必要だ。そのため、LVMに空き領域がないと作ることができないのだ。

というわけで、LVMのスナップショットとdumpを組み合わせたフルバックアップ環境を整えることにした。まあ、dumpそのものはテープ時代以降あまり使われなくなったので、もはやDebianでは標準でインストールされるツールではない。そこでまずはdumpを追加する。

apt install dump

バックアップ時には必ず復元時に元通りのパーティション構成にできるようにパーティション情報を書き出しておこう。それには一連のコマンドの出力を記録しておけばいい。

fdisk -l
pvdisplay
vgdisplay
lvdisplay

バックアップしたいディスクはMBRディスクなので、最初にMBR領域を書き出しておく。このサーバーのメインストレージは/dev/sdbなのでddでその先頭領域をファイルにイメージで書き出す。

dd if=/dev/sdb of=****.img bs=512 count=1

さらにLVMで管理されていない/bootパーティションが/dev/sdb1に存在するので、これをdumpしておく。/bootはカーネル等をアップデートしない限り書き換わることはないので、オンラインでも気にしないでdumpしていい。

dump -0 -f ***.dump /dev/sdb1

残りのLVM上に作られたパーティションはそれぞれスナップショットを作成してからdumpする。便宜上、ボリューム名に_snapをつけた名前でスナップショットを作成する。

lvcreate -s -L 1G -n volume_snap /dev/volue_group/volume

-Lオプションの引数がスナップショットが確保するワークエリアのサイズだ。100%FREEのように指定すると未使用スペースに対する割合で指定できるようだが、今回は別にスナップショット後の変更をそれほど長く保持する必要がないので便宜的に1GBを割り当てている。このスナップショットをdumpする。

dump -0 -f ***.dump /dev/volume_group/volume_snap

最後に必要なくなったスナップショットを削除する。というのもスナップショットを作成したファイルシステムはスナップショットのワークエリアにその後の変更が記録される。そして、このワークエリアを使い切ってしまうと元のファイルシステムがそれ以上書き込むことができなくなってしまうのだ。当然、/varなどはその状態になったらログの書き出しすらできなくなってしまうので、サーバーが正しく動作できなくなってしまう。だから、最後にかならず忘れずにスナップショットを削除しておく。

lvremove -y /dev/volume_group/volue_snap

かならず消していいか確認をしてくるので、-yでyesと自動で答えるようにしているが、間違ってスナップショット元のファイルシステムをふっとばさないように注意が必要だ。

これをバックアップが必要なLVM内のボリュームについて順に行えば完全なバックアップが取れるというわけだ。

とはいえ、bullseyeのアップグレードで問題が起きないことを切に願う。

LVMのボリューム容量再配分

Mailmanを3にアップグレードした結果、思わぬ副作用があった。というのも、Mailman 3はデータをすべて/var/lib/mailmanに置くので、/varの容量が想定外に逼迫してしまったのだ。しかも、今後メーリングリストでメールがやり取りされるたびにメールがアーカイブされて追加で容量を消費していくとなるとこれは早急に/varの容量を増やしておく必要がある。

前回、ルートの容量が不足した時にはリモートから作業する必要があったので容量が有り余っている/homeをアンマウントすることができず、swapを一時的にオフにしてそこから容量を捻出するという荒業を行ったが、今回は手元にサーバーがあるのでコンソールからrootでログインして/homeをアンマウントして捻出しようと思う。

まあ、手順としては前回とほぼ同じなので省略。作業としては、/homeのアンマウント→/homeのext4ファイルシステムのfsck→resize2fsによる/homeのext4ファイルシステムの縮小→lvreduceによる/homeの論理ボリュームの縮小でLVMに未使用スペースを確保する。もともと/homeには40GB弱を割り当てていて、常時使用率が1%なので思い切って20GBまで縮小してしまおう。最後にもう一度/homeをマウントしてデータが破壊されていないことを確認する。

LVMに未使用スペースを確保できたら、今度は/varの拡張だ。ext4ファイルシステムの拡張はマウントしたままでも実行できるので、lvextendで/varの論理ボリュームを拡張したら、続けてresize2fsで/varのext4ファイルシステムを拡張すればいい。もともと4GB弱だった/varを一気に3倍近い10GBまで拡張しておいた。これで当分容量不足を心配する必要はないだろう。

え?なぜ捻出した20GB弱を全部新しくボリュームに割り当てないのかって?それは、この未使用スペースを別の目的に利用する計画があるからさ。その計画はまた後程。

Debian RTL8111GがBusterで使えるようになってた

本サーバーのオンボードのイーサネットコントローラーはRTL8111Gだ。しかも2ポートもついていてどちらもGigabit Ethernet対応だ。にもかかわらず、長らくLinux上ではRealtekの8111x系のコントローラーのドライバーがプロプライエタリなために使い物にならず、現に本サーバーでも最初に稼働させるときにも使えず、泣く泣くUSB2.0接続のGbEアダプターを増設してネットワークに接続していた。この状態はDebian 10 Busterにアップグレードした時も変わらなかったので、相変わらずUSBアダプターで接続する状態が続いていたのだが、Debian 11 Bullseyeの状況を調べてみると、どうやらどこかの時点でLinux kernelに8111x系のドライバーがちゃんと取り込まれたらしいということが判明。しかも、BusterでもそのバージョンのKernelがすでに使われているというじゃないの。もし本当ならUSB2.0の480Mbpsで頭打ちになるアダプター経由でなくオンボードの8111Gで接続してフルスピードを活用したいということで、試しにオンボードのLAN端子にイーサネットケーブルを接続してインターフェースをアクティブにしてあげると、なんと確かにちゃんとつながるじゃないか。 🙂

というわけで、動作することが分かったのでオンボード側のインターフェースでイーサネット接続するように設定と配線を変更。とはいえ、いついかなる理由でエンバグしてネットワークがダウンするとも限らないので、USB接続のアダプターもそのまま残し、別のIPアドレスを割り当てておいて、いざというときにはケーブルさえ接続すればサーバーのメンテぐらいはできるようにしておいた。

これでちょっとはネットワークのレスポンスがよくなるといいんだけど。

Debian Mailman 2から3へのアップグレード フロントエンド編

Mailman 3は管理用ウェブフロントエンドにPostoriusというPython3とDjangoで稼働するサービスを利用している。Djangoのフレームワークが会員登録やログイン認証などの必要なインターフェースを提供してくれているわけだ。今時のユーザー登録をするとメールでアクティベーションURLが送られてくるというシステムなので、まずはPostoriusからメールが正しく送れることを確認しておく必要がある。

実は、この作業、一度でもMailman Coreからメールを送っておかないとメールの送信に必要なフォルダやデータベースが作成されないので、メーリングリストの移行とテスト送信を済ませてからでないとできないのだ。

実際にメールが送れるかどうかのテストは以下の通り。

/usr/share/mailman3-web/manage.py sendtestemail MYADDRESS@MYDOMAIN.COM

これでPostriusからのメールが受信できれば問題ない。

次にサイト全体を管理する権限を持つスーパーユーザーを作成する。

/usr/share/mailman3-web/manage.py createsuperuser

対話的なインターフェースでスーパーユーザーのユーザー名、メールアドレス、パスワードを設定する。ここまでで準備は完了。ここからはブラウザを使って設定を続行する。

まずはPostoriusにブラウザでアクセスする。

http://FQDN.COM/mailman3/

作成したスーパーユーザーのアカウントでログインすると設定したメールアドレスにアクティベーションするためのメールが送られてくるので指示に従ってアクティベーションすると晴れてスーパーユーザーが有効になってメーリングリストの設定等を変更できるようになる。

ここまででメーリングリスト運用に必要なすべての準備が整ってはいるのだが、最後にお化粧をする必要がある。というのも、Postriusのデフォルトのドメイン名がexample.comになっているので、格好悪いからだ。

Postriusにスーパーユーザーでログインした状態でツールバーのDomainsを選び、説明内のリンクからドメインを追加登録する。するとドメインがexample.comと新しく登録したドメインから選べるようになるので、新しく登録した正しいドメインを選ぶ。

ここで、元のexample.comを削除すればいいように思えるが、実はそれが大きな罠である。というのも、ここに登録されたドメインには順番にSITE_IDという内部識別IDが割り振られており、しかもmailman3-webは設定の中でSITE_ID = 1を絶対に利用するようにハードコードされており、このSITE_ID = 1がexample.comなのだ。というわけで、これをオーバーライドしておかないとexample.comを削除したとたんに、Postriusが動作不能になってしまうのだ。

というわけで、/etc/mailman3/mailman-web.pyに以下の記述を足して設定をオーバーライドしておく。

SITE_ID = 2

追加したドメインは自動的にSITE_ID = 2になっているはずなので、これでexample.comでなく新しいドメインが削除できない必須ドメインになった。

この状態でもう一度ドメイン一覧に戻り、example.comを削除すればよい。

以上でMailman 2から3へのアップグレード作業は全終了である。

Debian Mailman 2から3へのアップグレード リスト移行編

さて、基本的な設定が完了したら実際にMailman 2で利用していたメーリングリストを3にインポートする作業を行う。

ここからの作業もroot権限で行う必要があるためにsudo bashでrootのシェルを立ち上げて作業する。

まずは移行するメーリングリストをMailman 3に作成する。Mailman 3ではメーリングリスト名は投稿するアドレスそのものなのでFQDNまで含めて指定する必要があることに注意。

mailman create LISTNAME@FQDN.COM

次にMailman 2のメーリングリストの設定をインポートする。

mailman import21 LISTNAME@FQDN.COM /var/lib/mailman/lists/LISTNAME/config.pck

その後、Mailman 2のメーリングリストのアーカイブをインポートする。mailman3-webのDjangoプロジェクトのディレクトリに移動する。

cd /usr/share/mailman3-web

Python2と3の混在環境ではPython3を明示して実行する必要があるので以下のコマンドでインポート作業を実行する。

python3 manage.py hyperkitty_import -l LISTNAME@FQDN.COM /var/lib/mailman/archives/private/LISTNAME.mbox/LISTNAME.mbox

多少のエラーは出るかもしれないが気にしない。インポートが完了したらインデックスを作成する。

python3 manage.py update_index_one_list LISTNAME@FQDN.COM

最後にダイジェストを保管するディレクトリの所有者がrootで作成されてしまっているので所有者をMailman 3のユーザーIDであるlistに変更する。ディレクトリ名がLISTNAMEとFQDNの間を@ではなく.でつないでいることに注意。

chown list:list /var/lib/mailman3/lists/ LISTNAME.FQDN.COM

以上で移行作業は完了。この時点で正しく配送されているかどうかを実際にメーリングリストにメールを投げてみて確認しよう。うまくいけばMailman Coreは問題なく動いている。あとは管理フロントエンドの設定を残すのみ。