アウトルックをロックアウト

back


はじめに

あれほどいったのに、まだわからんのくわあぁ! というほど世間は Outlook およびその親戚に満ちている。 田中・徳永研では新山が睨みをきかせているせいか、 今のところ研究室内で Outlook を使っているユーザはいない。 けれども Windows はいつもことあるごとに Outlook を インストールさせようとしてくるし、Office を入れたときや ノートパソコンが持ちこまれたときにうっかり Outlook が起動する可能性は あるのだ。そんなことで研究室がウイルスの発信元になるのはごめんである (まあ Outlook 自体、ウイルスみたいなものだが)。 そのため、政治的な対策だけでなく技術的な (レッシグ的にいえば、コードによる) 制限を加える必要がある。 しかし、世間一般からは相変わらず Outlook を使ったメールが山のように 送られてくるので、Outlook のメールを完全に中継拒否することはできない。 そこで、

ようにしたい。 どうすればできるのか、というのがこの文章のテーマである。


ブレイクダウン

さて、田中・徳永研ではメールサーバに qmail + tcpserver を 使っている。研究室内はプライベートネットワークであり、 外部に送るメールは必ずメールサーバを経由しなければならない (直接外の SMTP は叩けないようになっている)。ゲートウェイ上の メールサーバは内→外および外→内のメールのどちらも中継するようになっている。 この状況で、上の要求をより細かく書くと以下のようになる:

これをどのように実現すればよいのか。qmail にパッチをあてることは、 できるだけしたくない。djb のソースに手を入れずに、 qmail + tcpserver の構成でどこに手を加えればこの機能が実現できるのか?

今回目をつけたのは qmail-queue にラッパをかぶせることである。 qmail-queue は標準入力からメッセージを読み込み qmail のキューに 溜めるもので、ゲートウェイ上のメールサーバでは 研究室に出入りするすべてのメールが必ず qmail-queue を通るはず だからだ。それに qmail-queue は引数もなにもないし、単純な インターフェイスを持っていて置きかえやすいように作られている。 実際、QMQP を使った配送など、qmail-queue を置き換えるケースもある。

そこで qmail-queue に一段ラッパをかぶせて、そこでヘッダの検査を 行うようにすればよい。そしてヘッダ中に「X-Mailer: Microsoft*」を 含むメッセージは、受けとりを拒否すればよいのだ。 qmail-queue のマニュアルをみるとこれは終了状態 31 を 返せばよい。この場合 qmail-smtpd はエラー 554 を返して 受けとり拒否された旨を表示するため、メールはもとのユーザに バウンスされる。検査する部分はヘッダだけでよいため、 ラッパが記憶する部分はメールのヘッダだけでよい (これは たいした分量にはならないはずだ)。ヘッダの検査に通ったあとは qmail-queue を起動し、そこに今まで読みこんだヘッダと メールの本文 (標準入力) を流してやればよい。そして qmail-queue の 終了状態と同じ終了状態で exit するのである。

では、次に「内→外に向かうメールだけを検査の象にする」には どうするか。ここで tcpserver の rule を使う。 ゲートウェイ上の tcpserver には、 内側のホストから SMTP 接続を受たときには特定の環境変数 (この場合は CHECKHEADER とした) を設定させるようにしておく。 そして qmail-queue のラッパには、 CHECKHEADER が 設定されているときだけ ヘッダを検査するようにさせる。qmail-queuetcpserver から呼び出される qmail-smtpd の 子孫として実行されるので、まだ環境変数は継承されているはずだ。 これによって、内→外の Outlook メールだけをはじくことができる。

しかしまだ問題がある。外からきた Outlook のメールが いったん研究室内に配送され、転送されてふたたび外に出るときは どうすればよいのか? このような場合には出してあげるのが筋というものである。 それには、メールの Delivered-To: ヘッダを見ればよい。 qmail では、一度あるアドレスに配送されたメールには必ずこのヘッダが 入る。そのため、ヘッダの検査は次のような規則で行う:

  1. ヘッダのどこかに 「X-Mailer: Microsoft*」が含まれていたら、 配送しない。
  2. 1. のケースでも、 ヘッダのどこかに 「Delivered-To: *@cl.cs.titech.ac.jp」が 含まれていたら、配送する。

そこで今回は上で述べたような機能をもち、 しかもなるべく一般的に使えるラッパ checkheader を作った。


checkheader の設計

checkheader がチェックするのはヘッダだけだが、 問題は上の規則をどうやって指定するかということである。 コード中に埋めこむのはやりたくない。それに 「X-Mailer: Microsoft*」のようにパターンも使いたい。 どうすればいいか?

ここで思いついたのが、multilog の規則を利用する方法である。 multilog では、どの行をログに残しどの行を残さないかを -'pattern', +'pattern' で指定できる。 ログのある行が 「+'pattern'」 で指定されたパターンにマッチすればその行は記録され、 「-'pattern'」 で指定されたパターンにマッチすればその行は記録されない。 そしてこれらのパターンはコマンドライン引数で与えられ、 左から右に走査される。 パターンは右に現れるものが一番優先順位の高い規則になる。 また、パターン中にはワイルドカード * が使える。

同じことをメールのヘッダに対してもおこなえばよい。 ただし、この場合フィルタリングするのはメールのヘッダ 1行ではなく メッセージ全体なので、パターンの適用範囲を 1行ではなく メールのヘッダ全体にひろげる。このことから、checkheader の 動作を次のように設計した:

  1. メールのヘッダをまず全部読む。
  2. コマンドラインを走査する:
    1. -'pattern' が指定された 場合、このパターンにマッチするヘッダが 存在すれば、このメールは拒否される。
    2. +'pattern' が指定された 場合、拒否されたメール中に このパターンにマッチするヘッダが 存在すれば、このメールは受けとられる。
  3. メールの可否によって動作を変える。

この仕様なら、先の規則を実現するには 「-'x-mailer: microsoft*' +'delivered-to:*@cl.cs.titech.ac.jp'」 のようなコマンドラインを与えてやればよい。 qmail-queue のラッパを、 「checkheader を exec するシェルスクリプト」にしておけば、 お好みの規則を加えることも簡単。

実際の実装では、 標準入力からヘッダを読み込んで処理するのに、qmail についてきた 関数 headerbody を使った。これはメールを読み込み、 それぞれの場所 (ヘッダ、ヘッダ終了、本体) に応じたコールバック関数を 呼びだす。これを使うと、めんどうなテキスト処理はほとんど必要なくなる。 ほんとうはヘッダ名の書式にはばらつきがあるため token822hfield を使う必要があるのだが、 この用途では単純な行単位のパターンマッチで十分なのでパスする。 ただし multilog に含まれていた match() を拡張し、 大文字小文字を区別しないようにした。

あとはヘッダを一時的に保存するバッファ (stralloc 100個分 - ヘッダが 100行以上続くことはふつうあるまい) を確保して、 ヘッダを読み込み終わったところで受けとり検査をすれば OK。 拒否なら exit(31); し、受けとるなら qmail-queue を fork/exec して子プロセスにヘッダを渡す。 すべて終わったら qmail-queue の終了を待ち、 自分も同じ終了状態で exit するようにした。

checkheader 書式:

checkheader qmail-queueのパス パターン1 パターン2 ...
qmail-queueのパス
通常は /var/qmail/bin/qmail-queue を指定する。
パターン
以下の 2種類のどちらかを指定する。パターン文字列中には 任意の長さの文字列を表すワイルドカードとして * が使える:
  • -'pattern' (このパターンにマッチするヘッダが含まれている メールを拒否する)
  • +'pattern': (このパターンにマッチするヘッダが含まれている メールを受けとる)

終了状態は、qmail-queue に準じる。 パターン中の大文字小文字は区別しない。


checkheader の使いかた

先の規則にしたがって実際に Outlook をしめだす方法は次のとおり。

  1. まず純粋な qmail-1.03.tar.gz をとってきて、 これに checkheader-0.1.patch.gz をあてる。この状態で make すると checkheader ができるので、 これを /var/qmail/bin に手動でインストールする。
  2. つぎにゲートウェイ上の /service/smtpd/tcp.smtp 等を編集して、 内側からの SMTP 接続時には環境変数 CHECKHEADER をセットするように する。うちではメールを外部に中継するため同時に RELAYCLIENT も セットしているので、以下のようになる:
           #
           192.168.208.:allow,RELAYCLIENT="",CHECKHEADER=""
           127.:allow,RELAYCLIENT="",CHECKHEADER=""
           :allow
           
  3. qmail-queue が実行されないように、 qmail、smtpd を止める。
  4. まず /var/qmail/bin 以下の qmail-queueqmail-queue.bin に リネームする。つぎに、ラッパとなるシェルスクリプトを 作ってこれを qmail-queue と名づけ、実行可能にする:
           #!/bin/sh
           exec /var/qmail/bin/checkheader /var/qmail/bin/qmail-queue \
             -'x-mailer: microsoft*' \
             +'delivered-to:*@cl.cs.titech.ac.jp'
    

    (パターンはお好みに応じてご変更ください)

  5. qmail、smtpd を再開する。
  6. うまく機能しているかどうかのテスト。 確かめるべきケースは以下のとおり:

    1. まず、通常のまともなメーラでメールを外部に送信し、 ただしく送信されるかどうかを確かめる。
    2. つぎに、Outlook を設定する。 SMTP サーバをそのゲートウェイに設定し、 嫌そうな顔をしながらメールを外部に送信する。 エラーになって送れないはずである。
    3. つぎに、外部に forward する設定のアドレスに対して、 外部の人から Outlook でメールを送ってもらう。 これができなければ、自分で SMTP を打ってもよい。 正しく転送されれば成功。

おわりに

ということで、これで研究室内で Outlook を使っても意味が なくなるし、Outlook-互換のウイルスを開いても 外には送られなくなる (しかし、最近はやりの SirCam はムリらしい)。 ざまーみろ Outlook!


Last modified: Wed Dec 19 16:48:16 2001
Yusuke Shinyama