Sendmail

電子メールと聞いたときに多くの人が思い浮かべるのは、メールを読み書きするときにユーザーとの対話を行うプログラムです。これはメールクライアント、正確には Mail User Agent (MUA) と呼ばれます。しかし電子メールにはもう一つ重要な部分があります。それはメールを送信者から受信者へ実際に転送するソフトウェアであり、Mail Transfer Agent (MTA) と呼ばれます。インターネットを使う最初の MTA にして現在最もよく使われている MTA でもあるのが sendmail です。

sendmail が最初に作られたときインターネットは正式には存在していませんでしたが、インターネットはその後とてつもない成功を収めました。1981 年にはインターネットは数百のホストをつないだ学術的な実験に過ぎず、それ以上のものになるのかは全く分からない状況でした。しかし 2011 年 1 月の時点でインターネットのホストは 8 億を超えます1。このような状況の中で、sendmail はインターネット上で最もよく使われる SMTP 実装の地位を保っています。

ワンス・アポン・ア・タイム...

後に sendmail として知られるようになるプログラムの最初のバージョンは 1980 年に書かれました。最初の一歩は異なるネットワーク間でメッセージを送りあうための簡単なハックに過ぎません。そのころインターネットは開発中でしたが動作はしていませんでした。実はたくさんのネットワークが同時期に提案されており、その間にはこれといった合意も存在していませんでした。米国では Arpanet が利用されており、そのアップグレードとしてインターネットが設計されていました。一方ヨーロッパは OSI (Open System Interconnect) に注力しており、一時は OSI が優勢になったこともありました。両方とも電話会社からリースした回線を利用しており、その速度は米国では 56 Kbps でした。

そのころ接続されたコンピューターと人の数でおそらく最も成功を収めていたネットワークは UUCP ネットワークです。UUCP は中央で行われる管理が一切存在しないという点が変わっており、ある意味では最初の P2P ネットワークとも言えます。通信はダイヤルアップの電話回線であり、速度は最速でも 9600 bps でした。世界一高速なネットワーク (3 Mbps) は Xerox の Ethernet をベースとしたものであり、XNS (Xerox Network Systems) と呼ばれるプロトコルを使っていました。ただしこのネットワークはローカルにインストールした環境と外部の通信ができませんでした。

当時の環境は今と大きく異なります。コンピューターは多様であり、8 ビットを 1 バイトとすることさえ決まっていませんでした。例えば PDP-10 (36 ビットが 1 ワード、9 ビットが 1 バイト)、PDP-11 (16 ビットが 1 ワード、8 ビットが 1 バイト)、CDC 6000 シリーズ (60 ビットが 1 ワード、6 ビットが 1 文字)、IBM 360 (32 ビットが 1 ワード、8 ビットが 1 バイト) があり、他にも XDS 940, ICL 470, Sigma 7 がありました。最先端のプラットフォームは当時ベル研究所が開発していた Unix でした。Unix ベースのマシンは 16 ビットのアドレス空間を持っており、多くの Unix マシンは PDP-11 で、Data General 8/32 や VAX-11/780 がちょうど生まれた頃でした。スレッドは存在せず、動的なプロセスという概念さえまだ新しいものでした (Unix にはプロセスがありましたが、IBM の OS/360 のような「本物の」システムにはプロセスがありませんでした) し、ファイルのロックは Unix カーネルで実装されていませんでした (ただしファイルシステムのリンクを使ったトリックは可能でした)。

ネットワークは存在しているというだけで、一般的には低速でした (たいていは 9600-baud TTY 回線で、大金持ちは Ethernet を持っていましたが、ローカルでしか使えませんでした)。現在の私たちが親しんでいるソケットインターフェースが発明されるのは何年も後のことです。公開鍵暗号も発明されておらず、現在あるようなネットワークセキュリティは不可能でした。

ネットワーク電子メールは Unix で既に存在していましたが、ハックによって作られていました。当時よく使われていたユーザーエージェントは /bin/mail コマンド (現在では binmail あるいは v7mail と呼ばれることもあります) でしたが、他のユーザーエージェントを使っている場所もありました。例えば Berkeley で使われた Mail はメッセージを個別のアイテムとして扱うことができ、ただの cat プログラムではありませんでした。ユーザーエージェントはどれも /usr/spool/mail を直接読んで (そしてたいていは書き込んで!) おり、メッセージの保存方法についての抽象化はありませんでした。

メッセージをネットワークに送るかローカルに送るかの判断は、アドレスが感嘆符を含む (UUCP) かコロンを含むか (BerkNET) を調べるという単純なものでした。Arpanet を使う場合には完全に別のメールプログラムを使う必要があり、他のネットワークとの通信はできず、ローカルのメールさえ別の場所に別のフォーマットで保存されました。

事態をより面白くするのが、メッセージのフォーマットが標準化されていなかったことです。唯一慣習として決まっていたのが、メッセージの先頭にヘッダーフィールドのブロックがあり、各ヘッダーフィールドは一行ごとに書かれ、フィールドの名前と値がコロンで分かれているということでした。それ以外に標準規格らしいものはほとんど存在せず、ヘッダーフィールドの名前やフィールドのシンタックスについて決まっていることはありませんでした。例えば Subject の代わりに Subj を使っていたり、Date フィールドのシンタックスが統一されていなかったり、From フィールドがフルネームを認識しなかったりしており、ドキュメントも曖昧で、実際に使われているものと異なっていることもありました。例えば RFC 733 (Arpanet のメッセージのためのフォーマット) は実際に使われていたものと少しだけ違っており、ときにはその違いが重大な意味を持ちました。またメッセージの転送方式については正式なドキュメントが全く存在しませんでした (転送方式に触れている RFC はありましたが、きちんとした定義を与えていません)。結果としてメッセージシステムはなんだか聖職じみた機関になっていました。

1979 年、INGRES Relational Database Management (私の勤務先) が DARPA から資金を獲得し、それに伴い職場の PDP-11 が 9600 bps の Arpanet に接続されました。当時の計算機科学科で唯一利用可能だったネットワークがこの Arpanet であり、誰もが私たちのマシンを通じて Arpanet にアクセスしたがりました。しかしマシンは既に満杯で、部局の誰でも利用できるログインポートは二つしか用意できませんでした。そのため接続は常に利用中となり、取り合いがしょっちゅう起こりました。ただここで私が気付いたのが、人々はリモートログインやファイル転送をしているのではなく、電子メールを行っているということでした。

この状況を受けて sendmail (最初は delivermail と呼ばれました) が作成され、カオスを一つの場所にまとめることになりました。MUA (mail user agent、またの名をメールクライアント) は delivermail を呼ぶだけで済み、アドホックな (たいていは互換性のない) 基準に沿って何をすべきかを考える必要はありません。delivermail/sendmail はローカルのメールの保存と転送については何も気に掛けず、メールを他のプログラムに渡すこと以外は何も行いませんでした (これは SMTP が追加されたときに変わることになります。後で見ます)。つまりこのプログラムは様々なメールシステムをつなげる糊に過ぎず、完全なメールシステムではありませんでした。

sendmail の開発中に Arpanet はインターネットに姿を変えました。変化は根本的であり、低レベルのパケットからアプリケーションプロトコルまで全てが変わりました。こういった変化は瞬時に起きたわけではなく、sendmail は文字通り標準規格と並行して開発され、ときには標準規格に影響を与えることもありました。もう一つ注目すべき事実が、「(私たちの知るところの) ネットワーク」のスケールが数百ホストから数百万ホストに成長してもなお sendmail が生き残っていることです。

もう一つのネットワーク

もう一つの完全に異なるメール標準規格が当時提案されていたことにも言及しておくべきでしょう。それは X.400 と呼ばれ、ISO/OSI (International Standards Organization/Open System Interconnect) の一部でした。X.400 はバイナルプロトコルであり、メッセージは ASN.1 (Abstract Syntax Notation 1) を使ってエンコードされます。ASN.1 は LDAP のようなインターネットプロトコルにおいて現在でも利用されています。LDAP は X.500 を単純化したものであり、X.500 は X.400 が利用していたディレクトリサービスです。sendmail は X.400 との直接の互換性は持っていませんが、そのころにはゲートウェイサービスが存在していました。X.400 は最初こそ当時の商用ベンダーで多く採用されましたが、市場を勝ち取ったのはインターネットメールと SMTP でした。

設計原則

sendmail を開発において私はいくつかの設計原則を固守しました。全ては次の文章に要約できます: 「可能な限り少ないことをせよ」これは当時行われていた他の取り組みの一部と全く対照的です。非常に大きな目標を持ち、大規模な実装が必要になっていた取り組みがいくつかありました。

一人のプログラマーの限界を受け入れる

私にとって sendmail は空いた時間に進めるもので、給与も無いプロジェクトでした。そもそも sendmail は U.C. Berkeley の人々が Arpanet メールをもっと便利に利用できるようにするための簡単な調整だったからです。鍵となるのはメールを既存のネットワークに振り分ける処理であり、ネットワーク内ではスタンドアローンのメールプログラムが他のネットワークの存在に気付くことなく動作します。既存のソフトウェアの相当な部分を書き換えるというのは一人のパートタイムプログラマーには無理な話です。設計は新しく書くコードだけではなく既存のコードに必要な変更も最小化しなければなりません。他の設計原則の多くはこの制約から導かれます。後になって分かったことですが、仮に大きなチームが利用可能だったとしても、ほとんどの場合においてはこうした設計を用いるべきでした。

ユーザーエージェントを再設計しない

ほとんどのユーザーはメールの読み書きに使うメールユーザーエージェント (MUA) のことを「メールシステム」だと思っています。しかし電子メールを送信者から受信者のもとに送り届けるメール転送エージェント (MTA) と MUA は全く異なります。sendmail が書かれた当時、多くの実装がこの二つを部分的にであれ混同しており、たいていは同時に開発されていました。私には両方に同時に取り組むだけの時間が無かったので、sendmail はユーザーインターフェースの問題を完全に放棄しました。MUA が行うべき唯一の変更は、独自の転送処理の代わりに sendmail を呼ぶようにするというだけです。そのころユーザーエージェントはいくつか存在しており、メールのやり取りについては一過言ある人が多かったので、二つに同時に手を出すというのは不可能でした。現在では MUA と MTA の分離は常識として受け入れられていますが、当時の標準的なやり方からは大きく離れていました。

ローカルのメール保存場所を再設計しない

メールを受信したときに、受信者が読むまでの間それをローカルのどこに保存するかは標準化されていませんでした。/usr/mail, /var/mail, /var/spool/mail といった中央の場所に保存するシステムもあれば、受信者のホームディレクトリ (.mail ファイル) に保存するシステムもありました。多くのシステムはメッセージの最初を“From ”で始まる行で始めていました (とてつもなく悪い設計ですが、当時はそうなっていました) が、Arpanet に集中していたシステムではメッセージ同士を四つの control-A 文字からなる行で分割していました。メール受信箱をロックして衝突を避けているところもありましたが、ロックの慣習は異なっていました (ファイルロックのプリミティブは存在しませんでした)。そのためローカルのメール保存場所をブラックボックスにするのが唯一の解決方法でした。

ほぼ全てのシステムにおいて、ローカルのメール受信箱の管理は /bin/mail プログラムによって行われていました。(極めて単純な) ユーザーインターフェース、メールの転送、保存処理を一つにしたのがこのプログラムです。sendmail を利用するには、/bin/mail のメールの転送部分を切り出して sendmail の呼び出しと取り換える必要がありました。このために -d フラグが追加され、これを使うと sendmail を使わない転送を強制できます。後になって物理的なメール受信箱にメッセージを転送するコードは取り出されて mail.local というプログラムになり、現在の /bin/mail の役割はメール送信のための最小公倍数的な処理だけです。

Sendmail が世界に合わせる、逆ではない

UUCP や BerkNET といったプロトコルは既に独自のプログラムと (時に風変わりな) コマンドライン構造を持っていました。また sendmail と同時期に活発に開発されるということもありました。そのためそういったプログラムを再実装する (例えば標準的な呼び出し規約を使うよう改変する) ことは大変な作業でした。sendmail が世界に合わせるべきであり、世界を sendmail に合わせるようにしてはいけないという原則はここから生まれました。

変更を最小限にする

sendmail の開発の最初から最後まで、私はどうしても触れなければならない部分以外には全く手を付けませんでした。時間がなかったというのに加えて、当時の Berkeley では正式なコードの所有者というものをあまり考えず、「最後にそのプログラムに触れた人が、そのプログラムについて質問するべき人である」(あるいはもっと単純に「いじったら、お前の物だ」) という考え方があったためです。今見れば混沌とした考え方に思えるかもしれませんが、誰も Unix にフルタイムで割り当てられていなかった Berkeley ではとても上手く行っていました。個人は興味を持ったシステムの一部に取り組み、どうしてもという事情がない限り他のコードベースには触らなかったのです。

早くから信頼性を考える

sendmail 以前のメールシステム (転送システムの多くを含む) は信頼性について真剣に考えていませんでした。例えば 4.2 BSD 以前の Unix にはネイティブのファイルロックがありませんでした (ただし一時的なファイルを作ってそれをロックファイルにリンクすればシミュレートは可能でした。ロックファイルが既に存在していればリンクの作成が失敗します) が、ときには複数のプログラムが同じデータファイルにロック方法の合意なしに (例えばロックファイルに異なる名前を使ったり、そもそもロックを行わずに) 書き込んでしまうこともあったので、メールが失われることも珍しくありませんでした。これに対して sendmail ではメールの消失が絶対に起きないアプローチを取りました (私が以前にデータベースに取り組んでいたのが関係しているかもしれません。データベースの世界においてデータの消失は大罪です)。

その他

初期のバージョンでできなかったこともたくさんあります。私はこのメールシステムを再設計したり完全に一般的な解決法を作成したりはせず、新しい機能を必要性が生じたときに追加していきました。非常に初期のバージョンでは設定を行うのにソースコードとコンパイラが必要だったほどです (この仕様はすぐに変更されましたが)。一般的に言って、まずは動作させるところまで素早く持っていき、後は修正が必要とされて問題が深く理解されたときに動作しているコードを直していく、というのが sendmail のやり方です。

開発フェーズ

長く開発の続くソフトウェアの多くと同じように、sendmail もいくつかのフェーズに分かれて開発されました。それぞれのフェーズには異なる基本テーマと感触があります。

ウェーブ 1: delivermail

最初の sendmail は delivermail という名前でした。非常に単純で、ほんの少しの機能しか持ちません。その唯一の役目はメールを他のプログラムに渡すことであり、SMTP のサポートはなくネットワークに接続することもありません。ネットワークがキューを実装することから delivermail にキューは必要なく、プログラムにはクロスバースイッチがあるだけです。さらに delivermail にネットワークプロトコルのサポートが含まれないことから、デーモンとして実行する必要はありません。メッセージが送信されるたびに呼び出され、次の処理を行うプログラムにそれを渡し、終了するだけです。それからネットワークに応じてヘッダーを書き換えることもしないので、delivermail によって転送されたメッセージには返信できないことが普通でした。この状況はとてもひどく、メールのアドレスについてだけ書かれた本があるほどです (“!%@:: A Directory of Electronic Mail Addressing & Networks” [AF94] という分かりやすい名前が付いています)。

delivermail の全ての設定はコンパイルされ、アドレスに含まれる特殊文字にのみ依存していました。特殊文字には優先順位があり、ホストの設定の一例を示すと、まずアドレス内の @ を検索し、マッチしたらアドレス全体を Arpanet のリレーホストに送信します。マッチしなかった場合には次に ; を検索し、マッチしたらホスト名とユーザー名があるならそれを付けて BerkNET に送信します。それでもマッチしなければ ! を検索し、マッチしたら UUCP のリレーに送信します。以上のどれでもなければローカルへの送信となります。この設定においては次のようにアドレスが解釈されます:

入力 {ネットワーク, ホスト, ユーザー} に送信される
foo@bar {Arpanet, bar, foo}
foo:bar {Berknet, foo, bar}
foo!bar!baz {Uucp, foo, bar!baz}
foo!bar@baz {Arpanet, baz, foo!bar}

アドレスの分離文字が持つ結合の強さが異なり、曖昧なヒューリスティックを使わざるを得なくなっていることに注意してください。例えば最後の例は {Uucp, foo, bar@baz} とパースされても不思議ではありません。

設定がコンパイルされていたのにはいくつか理由があります。まず、16 ビットアドレス空間と限られたメモリにおいては実行時に設定を読み込むというのはコストが大きすぎました。次に、当時のシステムは徹底的にカスタマイズされるものだったので、ライブラリのローカルバージョンを作るためだけにコードを再コンパイルするというのは普通のことでした (Unix 6th Edition には共有ライブラリはありません)。

delivermail は 4.0 BSD と 4.1 BSD に付属して配布され、予想以上の成功をおさめました。複数のネットワークアーキテクチャを持っていたのは Berkeley だけではなかったのです。そしてさらなる作業が必要なことが判明しました。

ウェーブ 2: sendmail 3, 4, 5

バージョン 1 とバージョン 2 は delivermail という名前で配布されました。バージョン 3 の作業は 1981 年 3 月に開始され、このバージョンから sendmail という名前になりました。そのころ 16 ビットの PDP-11 が今だに広く使われていましたが 32 ビットの VAX-11 も広まりつつあり、アドレス空間が小さいことから生じていた初期の制約が緩和され始めていました。

sendmail の初期の目標は設定の実行時読み込み、ネットワーク間のメール転送の互換性を保つためのメッセージの改変、そして転送の判断のための高機能な言語でした。利用されたテクニックはアドレスを (文字列ではなくトークンに基づいて) テキストとして書き換えるというもので、当時のエキスパートシステムで使われていた仕組みと同じです。またコメント文字列 (カッコに囲まれたもの) を抽出・保存するためのアドホックなコードがあり、プログラムによる改変が終わった後にコメントを入れ直すことができました。さらにヘッダーフィールドを追加したり拡張したりできることも重要でした (例えば Date フィールドを追加する、 From ヘッダーにフルネームを入れられるようにするなど)。

SMTP の開発は 1981 年 11 月に開始されました。U.C. Berkeley の Computer Science Research Group (CSRG) は DARPA との契約を勝ち取り、Unix ベースのプラットフォームを作成することになりました。このプラットフォームは DARPA が出資する研究を助けるためのものであり、プロジェクト間の共有を簡単にするのが目標でした。TCP/IP スタックに関する基礎的な作業はそのころ既に終わっていましたが、ソケットインターフェースの詳細はまた揺れ動いていました。そして Telnet や FTP といった基本的なアプリケーションプロトコルは完了していましたが、SMTP の実装はまだでした。というより実のところ、SMTP プロトコルはまだ策定されていませんでした。メールの送信のための Mail Transfer Protocol (MTP) という (とても創造的な名前の) プロトコルについての議論は白熱し、MTP はどんどん複雑になりました。これに失望した人々によって SMTP (Simple Mail Transfer Protocol) のドラフトが書かれ (そして 1982 年 8 月 に正式に公開されました) が、これには多かれ少なかれ権力者による専断2 という側面がありました。私の役目は正式には INGRES Relational Database Management System でしたが、当時の Berkeley の中ではメールシステムを一番良く知っている人物だったので、SMTP の実装を担当することになりました。

私が最初に考えたのは個別の SMTP メーラーを作成し、それに独自のキューとデーモンを持たせるということでした。このサブシステムを sendmail と連携させれば転送ができるようになります。しかし SMTP の機能の一部が問題になりました。例えば EXPN コマンドと VRFY コマンドにはパース、エイリアス、ローカルアドレス検証を行うモジュールへのアクセスが必要でした。またアドレスが不明な場合には RCPT コマンドがすぐに返ることが重要であり、メッセージを受け取っておいて後で送信失敗メッセージを送るのは望ましくないと当時の私は考えていました。この考えには先見の明があったことが判明しているのですが、後から作られた MTA でもこのことを認識していないものが存在し、後方散乱 (backscatter) スパム問題を悪化させてしまっています。以上のような問題により、STMP を sendmail の中に取り込む決断が下されました。

sendmail 3 は 4.1a BSD と 4.1c BSD (ベータバージョン) に付属し、sendmail 4 は 4.2 BSD に、sendmail 5 は 4.3 BSD に付属しました。

ウェーブ 3: 混沌とした年月

私が Berkeley を離れてスタートアップに移動すると、sendmail に割ける時間はすぐに減少しました。しかしインターネットは爆発的に大きくなっており、sendmail は新しい (そして今までよりも大きな) 様々な環境で使われていました。多くの Unix システムのベンダー (特に Sun, DEC, IBM) は独自の sendmail を作成していましたが、相互互換性があるものは一つとして存在しませんでした。オープンソースのバージョンを作ろうとした試みもいくつかあり、その中でも IDA sendmail と KJS は特筆に値します。

IDA sendmail はリンシェーピン大学で作成されました。IDA には大規模環境においてインストールと管理を簡単にするための拡張機能が含まれ、全く新しい設定システムも付いていました。重要な新機能の一つが dbm(3) データベースを取り込んだことで、これによって非常に動的なサイトもサポートできるようになっています。新機能は設定ファイルの新しいシンタックスを使っており、例えば外部シンタックスとアドレスとの間の相互マッピング (例えば john@example.com ではなく john_doe@example.com をとしてメールを送信するなど) やルーティングといった機能においてこのシンタックスが使われています。

King James Sendmail (KJS, Paul Vixie が作成) は雨後の筍のように現れていた sendmail の亜種を全て統一することを目標としていましたが、残念ながら目標とされた効果が発揮されるような牽引力を持つことはできませんでした。それからこの時代には新しい技術がすさまじいスピードで開発され、KJS もこの影響を受けました。例えば Sun がディスクレスクラスターを作ったことで YP ディレクトリサービス (後の NIS) と NFS (Network File System) が追加されましたが、このうち YP は sendmail から見える必要がありました。エイリアスをローカルではなく YP に保存していたためです。

ウェーブ 4: sendmail 8

数年後、私は Berkeley にスタッフメンバーとして戻りました。私の仕事は計算機科学科が関わる研究の共有インフラのインストールとサポートを行うグループの管理であり、そのためには研究グループごとにバラバラで大部分がアドホックな環境をまともな方法で統一しなければなりませんでした。インターネットの黎明期と同様に異なる研究グループが全く異なるプラットフォームを使っており、中には非常に古いものもありました。一般的に言って全ての研究グループが独自のシステムを持っており、きちんと管理されているシステムも一部ありましたが、ほとんどは「いつか支払わなければならない維持費3」を抱えていました。

電子メールもたいてい同じように壊れていました。メールアドレスは person@host.berkley.edu という形をしており、host が研究室または共有サーバーにあるワークステーションの名前でした (キャンパスには内部サブドメインさえありませんでした)。また @berkeley.edu という形のアドレスを持っている人もいました。私たちの目標は全てのアドレスを (cs.berkeley.edu という) 内部サブドメインに切り替え、統一されたメールシステムを構築することでした。この目標を達成するための一番簡単な方法は、学科全体で使えるような sendmail の新しいバージョンを作ることでした。

私は名の知れていた sendmail の亜種を調べるところから作業を始めました。新しいコードベースを使おうとしたのではなく、他の人が便利だと思った機能を理解したかったからです。ここで見つかった様々なアイデアは融合したりさらに一般化されたりして sendmail 8 に組み込まれました。例えば sendmail のいくつかのバージョンには dbm(3) や NIS といった外部データベースにアクセスする機能がありましたが、sendmail 8 はこういった機能を統合し、複数のデータベース (あるいはデータベースですらない変形データ) を扱うための「マップ」という仕組みが追加されました。

sendmail 8 にはマクロプロセッサ m4(1) を使った新しい設定パッケージが追加され、手続き的だった sendmail 5 の設定パッケージよりも宣言的になりました。つまり sendmail 5 では基本的に管理者が設定ファイルの全てを手で書く必要があり、使えるのは m4 における include だけだったのに対して、sendmail 8 の設定ファイルではどの機能やメーラーが必要なのかを宣言すれば済むようになっています。このファイルに基づいて m4 が実際に使われる設定ファイルを作成します。

sendmail 8 で行われた改善については 17.7 節 で触れます。

ウェーブ 5: ビジネスの時間

インターネットが成長し sendmail のユーザー数が爆発的に増えた結果、サポートが問題になりました。初期には私が立ち上げたボランティアグループ (Sendmail Consortium あるいは sendmail.org といった名前で呼ばれました) が電子メールやニュースグループを通した無料のサポートを提供していました。しかし 1990 の後半にはボランティアベースのサポートが成り立たない程にインストール数が増えてしまいました。ビジネスに精通した友人と共に私は Sendmail, Inc4 を創業し、コードを支えるリソースを手に入れることを期待しました。

初期の商用プロダクトの大部分は設定ツールや管理ツールでしたが、その後オープンソース MTA の sendmail にはビジネスの世界が必要とする機能が多数追加されました。例えば TLS (接続の暗号化) や SMTP 認証、DoS からの保護といったセキュリティ対策、そして一番重要なものとしてメールフィルタリングプラグイン (後で触れる milter インターフェース) があります。

執筆時点では商用プロダクトはさらに広がり、電子メールベースの大規模なアプリケーションスイートを含むまでになっています。そのほとんど全ては、創業から数年のうちに sendmail に追加された機能を元に構築されています。

sendmail 6 と sendmail 7 はどうなった?

sendmail 6 は sendmail 8 のベータバージョンでした。正式にはリリースされませんでしたが、かなり広く使われていました。sendmail 7 は存在せず、sendmail はバージョン 8 に飛びました。これは 1993 年 6 月に 4.4 BSD がリリースされたときに BSD ディストリビューション向けのソースファイルが全てバージョン 8 になったためです。

設計判断

正しい設計判断もありますし、最初は正しくても環境が変わるにつれてそうでもなくなったものもあります。あるいは最初から周りと噛み合っておらず、ずっとそのままというものもあります。

設定ファイルのシンタックス

設定ファイルのシンタックスはいくつかの問題によって形作られました。まず、アプリケーション全体が 16 ビットアドレス空間に収まる必要があったので、パーサーを小さくしなければなりませんでした。次に、初期の設定はとても短かかった (一ページ以下) ので、シンタックスが多少奇妙であっても理解可能でした。しかし時が経つにつれ操作の設定が C のコードから設定ファイルに移動し、設定ファイルは肥大を始めました。その結果 sendmail の設定ファイルは難解であるという評判が付いて回るようになりました。特に人々をイラつかせたのが、タブ文字をシンタックスに含めた点です。これは make といった当時の他のシステムをから受け継いでしまった間違いです。この問題はウィンドウシステム (コピーとペーストのときにタブ文字が無視される) が利用可能になるとさらにはっきり表れるようになりました。

今考えれば、設定ファイルが大きくなって 32 ビットマシンが広まっていったときであれば、シンタックスを上手く変更できたかもしれません。そうしようと思ったことはあったのですが、「たくさんの」インストールベースを破壊しなくないと思って結局しませんでした (当時は数百台といったところでしょう)。これは間違いでした。私はインストールベースが今後どれだけ巨大になるか、そしてシンタックスを早く変えておけばどれだけの時間が節約されるかを見誤りました。また標準規格が安定した段階で、一般的な部分の多くを C コードベースに戻すことで設定を単純化することもできたでしょう。

当時関心を持っていたのが、どれだけ多くの機能を設定ファイルに移動できるかでした。sendmail は SMTP の規格が作られるのと同時に開発されていたので、操作に関する判断を設定ファイルに移動させれば設計の変更を素早く (たいていは 24 時間以内に) 反映させることができたのです。提案された設計の変更をすぐに実際に動作させて実験できたので、これによって SMTP という標準規格は改善されたと私は信じています。ただ設定ファイルは理解しにくくなりました。

規則の書き換え

sendmail の開発で難しかった判断の一つが、ネットワーク越しの転送で必要になるアドレスの書き換えを行うときに、どうすれば受け取り側のネットワークの規則に違反しないで済むかということでした。この書き換えではメタ文字の変更 (例えば BerkNET はコロンを分離文字に使いますが、SMTP アドレスではコロンは不正な文字です)、アドレス要素の並べ替え、要素の追加や削除などが行われます。例えば次に示すような書き換えが必要になります:

From To
a:foo a.foo@berkeley.edu
a!b!c b!c@a.uucp
<@a.net,@b.org:user@c.com> <@b.org:user@c.com>

正規表現はワードの境界やクオートなどをサポートしないので良い選択肢ではありませんでした。また処理を行うための正確な正規表現を書くのがほぼ不可能であり、分かりやすさなどは望むべくもないことがすぐに判明しました。中でも問題だったのが、正規表現のメタ文字に ., *, +, [, ] といった電子メールのアドレスに現れうる文字が含まれる点です。設定ファイルでエスケープすることもできましたが、複雑で分かりにくく、美しくもないと判断されました (これはベル研究所の UPAS という Unix Eighth Edition 向けのメーラーで試されましたが、流行ることはありませんでした5)。

メタ文字をたくさん用意して (正規表現の) 特殊な意味を表現すると通常の文字を表現するのにエスケープが必要になってしまうので、「エスケープ文字」を一つ用意してそれと通常の文字を組み合わせることでワイルドカード (任意の一文字など) を表現するという方法が取られました。伝統的な Unix のやり方ではエスケープ文字にバックスラッシュを使いますが、バックスラッシュはアドレスのシンタックスでクオートとして使用済みだったので、未使用の $ を使うことになりました。

初期の判断で正しくなかったものの一つは、皮肉なことに、空白の取り扱いという些細な問題です。空白は他の入力と同様の分離文字なので、トークンの間で自由に利用可能でした。しかしオリジナルの設定ファイルは空白を使っておらず、パターンがかなり理解しにくくなってしまいました。次の (同じ意味を持つ) 二つのパターンを比べてみてください:

$+ + $* @ $+ . $={mydomain}
$++$*@$+.$={mydomain}

書き換えを使ったパース

「sendmail でアドレスを改変するときに書き換え規則を使うのはやめて、伝統的な文法ベースのパース技法を使ってアドレスをパースしてはどうか」という提案があったことがあります。標準規格が文法を使ってアドレスを定義していることを考えれば、一見これは良さそうな提案に思えます。それでも書き換え規則を使っている一番の理由は、ヘッダーフィールドからアドレスを読むことがあるためです (エンベロープを持たないネットワークからのメールを受け取ったときに、送信者のエンベロープをヘッダーから取り出す場合など)。そのようなアドレスでは先読みの量が多くなるので、YACC などの伝統的なスキャナーが実装している LALR(1) ではパースが難しくなります。例えば allman@foo.bar.baz.com <eric@example.com> というアドレスでは、スキャナーやパーサーによる先読みが必要になります。なぜなら、最初の allman@... がアドレスでないことは < を読むまで分からないからです。LALR(1) パーサーはトークンを一つしか先読みしないので、この処理はスキャナーで行われなければなりませんが、もしそうするとスキャナが非常に複雑になってしまいます。書き換え規則を使えば任意の位置にバックトラックできる (好きなだけ先読みできる) ので、これが適しています。

書き換え規則を使う二つ目の理由は、間違っている入力を発見・修正するのが比較的簡単なことです。最後の理由は、書き換えは必要となるよりもずっと強力なので、コードを再利用した方が賢明です。

書き換え規則には一つ見慣れない点があります。パターンマッチングを行うときには入力とパターンの両方をトークンで表した方が便利なので、入力のアドレスとパターンに同じスキャナーが適用されます。そのためスキャナーは入力ごとに異なる文字種を使って呼べるようになっています。

SMTP とキューの sendmail への埋め込み

外側への (クライアントからの) SMTP を実装する「明らかな」方法は、UUCP と同じように外部のメーラーとして作成してしまうことです。しかしこうすると疑問がいくつか生じます。例えば、キューは sendmail と SMTP クライアントモジュールのどちらで行われるべきでしょうか?もし sendmail で行うのであれば、メッセージのコピーを一つずつ受取人に送信する (つまり単一の接続を使って複数の RCPT コマンドを送信する相乗り (piggybacking) を使わない) か、そうでなければもっと高機能な逆方向の路を使うことになります。Unix の終了コードでは不可能な受取人ごとの状態を確認するためです。一方でキューをクライアントモジュールで行うと、不必要な複製が起こることになります。特に、当時は XNS のような他のネットワークがまだ競争相手として残っていました。加えて、キューを sendmail に含めるとリソースの枯渇といった一時的な障害にエレガントな方法で対処できます。

SMTP を受信するサーバーでは別の種類の問題があります。そのころの私は VRFYEXPN コマンドを正確に実装するのが重要だと感じており、そのためにはエイリアスの情報へのアクセスが必要でした。ここでもサーバーの SMTP モジュールと sendmail の間でコマンドラインと終了コードよりも高機能なプロトコルを使ったやり取りが必要になります ――実際には SMTP と似たプロトコルとなりました。

現在の私はキューを sendmail のコアに残しておくことに今まで以上に賛成していますが、SMTP 本体の実装とキューの実装の両方を他のプロセスに移した方が良かったと思っています。この理由の一つはセキュリティの向上です: サーバーサイドがポート 25 の接続を開いてしまえば、その後は root 権限は必要ありません。TLS や DKIM といったモダンな拡張による署名はクライアントサイドを複雑にする (秘密鍵は非特権ユーザーからアクセスできてはいけないため) ものの、厳密に言えば root へのアクセスは必要となりません。ただしそうしたとしてもセキュリティの問題が全て解決するわけではなく、クライアントの SMTP が秘密鍵を読める非 root ユーザーによって実行されていたとしても、そのユーザーが特殊な権限を持っている以上、他のシステムと直接通信できてはいけません。こうした問題は少しの作業で解決できます。

キューの実装

sendmail は当時よく使われていた方法を使ってキューファイルを保存しています。実は使われているフォーマットは当時の lpr サブシステムとほぼ同じです。各ジョブが二つのファイルを持ち、一つが制御用のデータを、もう一つが実際のデータを保存します。制御ファイルはフラットなテキストファイルで、各行の最初の文字がその行の意味を表します。

sendmail がキューを処理するときは、制御ファイルを全て読み、必要な情報をメモリに格納し、それからそのリストをソートします。この仕組みはキューに溜まるメッセージの数が比較的少なければ上手く行きますが、メッセージの数が 10,000 通ほどになると遅くなって使いものにならなくなります。特にディレクトリ内の数が多くなりすぎてファイルシステムによって間接ブロックが作られるようになると深刻な影響が生じ、パフォーマンスが数桁分も落ちることがあります。複数のキューディレクトリを認識するよう sendmail を変更すれば問題を和らげることもできますが、それもハックに過ぎません。

もう一つの実装方法として、全ての制御ファイルを一つのデータベースに保存するというのが考えられます。しかし sendmail は広く利用可能なデータベースパッケージが登場する前に書き始められたので、この方法は取られていません。最初に公開された dbm(3) にはいくつか欠点があり、スペースを再配置できなかったり、ハッシュされる全てのキーが一つのページ (512 バイト) に収まる必要があったり、ロックがなかったりしました。信頼性の高いデータベースパッケージが登場するのは何年も後です。

あるいは、キューの状態を常にメモリに保持しておくデーモンを用意するという実装も考えられます。さらにログを書いておけばリカバリも可能になるでしょう。ただ当時やり取りされるメールの量がそれほど多くなかったこと、ほとんどのマシンでメモリが枯渇していたこと、バックグラウンドプロセスのコストが比較的高いこと、処理を行うプロセスの実装が複雑になってしまうことなどの理由により、この実装の利点と欠点は釣り合わないと判断されました。

もう一つの設計判断が、メッセージヘッダーを保存するのにデータファイルではなくてキューの制御ファイルを使うというものです。ヘッダーは送信先ごとに大きく書き換える必要があり (そして送信先が複数あるために何度も書き換えることになる場合も多く)、ヘッダーをパースするコストは高く思えたので、パースされたフォーマットで保存しておけば時間の節約になるだろうという考えを元にした判断でした。しかし今になって考えると、これが良い判断だったとは言えません。ここでの問題は、メッセージの本体を Unix 標準のフォーマット (LF 終端) で保存し、受信したときのフォーマット (LF, CR, CRLF, LFCR) を無視したときにおこる問題と似ています。つまり、電子メールの世界が発達して規格が整備されるにつれ書き換えの必要性はなくなり、ちょっとした書き換えであってもエラーのリスクが生じるようになったということです。

不完全な入力の受理と修正

sendmail が作られた時代にはプロトコルがいくつもあり、残念なことに標準規格が定まっていたプロトコルはほとんど存在していなかったので、私は不完全なメッセージも可能な限り修正するように心がけました。これは RFC 793 で言及されている「頑健性の原則6」 (別名ポステルの法則) にも合致します。すべきことが明らかな修正もあれば、必ず行わなければならない修正もあります。例えば UUCP メッセージを Arpanet に送るときには、UUCP のアドレスを Arpanet のアドレスに変換する必要があります。また reply コマンドを正しく動作させるには、プラットフォーム固有の行終端に変換が必要です。もっと分かりにくいなものもあります。例えば受信したメッセージにインターネット規格が要求する From ヘッダーフィールドが含まれていなかった場合、From フィールドが欠けたメッセージを送信するべきでしょうか? それともメッセージを拒否するべきでしょうか? 私は相互運用性を一番に考えていたので、sendmail は From ヘッダーフィールドを追加してメッセージをパッチするようになっています。しかし、これによって他の壊れたメールシステムが修正されることも引退することもなく生き残るようになってしまったと言われることがあります。

当時はこの判断も正しかったと信じていますが、現在では問題があります。高い相互運用性はメールの流れを止めないために重要でした。もし不完全なメッセージを拒否するようにしていたら、当時のメッセージはほとんどが拒否されていたでしょう。もしメッセージを修正することなく送信していたら、返信できない、あるいは差出人さえ分からないメッセージが送られることになったでしょう ――そのようなメッセージは他のメーラーによって拒否されるだけです。

現在では標準規格が整備され、ほとんどの部分において規格は正確で完全です。ほとんどのメッセージが拒否されるという仮定はもはや当てはまりません。しかしそれでも、規格に沿っていないメッセージを送信するメールソフトウェアが未だに存在しています。そしてこういったソフトウェアがインターネットで動作するソフトウェアにおいて余計な問題を山ほど引き起こすのです。

設定ファイルと m4 の利用

開発当初は sendmail の設定ファイルによく変更を加えており、個人的に様々なマシンをサポートしていました。そういった設定ファイルの大部分はマシンが異なっても同じだったので、設定ファイルを生成するツールが必要になりました。マクロプロセッサ m4 は当時の Unix にも付属しており、プログラミング言語のフロントエンドとして設計されていました。一番重要だったのが、C 言語における #include に似た include 機能です。オリジナルの設定ファイルはこの機能と多少のマクロ展開だけを使っています。

IDA sendmail も m4 を使いますが、使い方が大きく異なります。今思えば、こういったプロトタイプをもっとよく調べるべきだったと感じています。賢いアイデアがいくつも含まれており、特にクオートの処理には目を見張るものがあります。

m4 を使った設定ファイルは sendmail 6 で完全に書き直され、宣言的な記法でコンパクトに書かれるようになりました。この設定ファイルは m4 の機能をさらに利用していましたが、この結果 GNU m4 がセマンティクスをほんの少し変更したときに問題が起こりました。

当初の計画では、m4 を使った設定ファイルは 80/20 ルールに従うつもりでした。つまり設定はシンプルにして、20 % の作業で 80 % のケースをカバーできるようにするのが目標でした。しかしこれはすぐに頓挫しました。理由は二つあります。一つ目のあまり重要でない理由は、少なくとも最初のうちは大半のケースを処理するのが比較的簡単に見えたことです。sendmail とそれを取り巻く世界が拡大するにつれ、それが格段に困難になりました。特にずっと後になってから現れる TLS 暗号化と SMTP 認証といった機能の追加が設定を複雑にしました。

もう一つの重要な方の理由は、生の設定ファイルがほとんどの人にとって難解すぎたことです。つまり、生の .cf ファイルは理論上は編集可能であるものの解読はほぼ不可能であり、アセンブリコードも同然でした。ユーザーにとっての「ソースコード」は .mc ファイルに保存された m4 スクリプトだったわけです。

もう一つ重要だったのが、生の設定ファイルのフォーマットがプログラミング言語であった点です。手続き的なコード (ruleset) やサブルーチン呼び出し、変数の展開、ループが利用可能でした (ただし goto はありませんでした)。奇妙なシンタックスをしていましたが、少なくとも概念上は sedawk との共通点を多く持っていました。m4 フォーマットは宣言的であり、低レベルの生の言語を触ることも可能ですが、そういった詳細はユーザーから見えないようになっています。

この判断が正しかったのかどうかはよく分かりません。複雑なシステムにはドメイン固有言語 (Domain Specific Language, DSL) を作った方が便利であるというのが当時から変わらない私の意見です。しかしその DSL を設定方法としてエンドユーザーにまで公開すると、システムを設定するためにプログラミングが必要になってしまいます。DSL を使えば大いなる力が手に入りますが、それにはコストも伴います。

その他の懸案事項

アーキテクチャおよび開発に関連して、注目に値する点がいくつかあります。

インターネットスケールのシステムの最適化

多くのインターネットベースのシステムにおいて、クライアントとサーバーは緊張関係にあります。クライアントにとって都合のいい戦略はサーバーにとって迷惑なものであることが多く、逆も然りです。例えばサーバーは処理のコストを最小化するためにあらゆることをクライアントに押し付けようとしますが、クライアントも逆方向に同じことを考えています。あるいはサーバーはスパム処理の間も接続を保持してメッセージを拒否するときのコストを低減させようとします (現代のメールシステムではメールを拒否することの方が多いからです) が、クライアントからすれば速く接続を切って次の処理に進むのが望ましいです。使われているシステム、つまりインターネット全体を考えれば、最適解は双方が必要とするもののバランスを取ることです。

クライアントとサーバーの片方だけを優遇する MTA もありますが、これはインストール数が比較的少ないからできているだけです。そのシステムがインターネットにおいて大きな部分を占めるようになれば、クライアントとサーバー両方の負荷のバランスを取ってインターネット全体を最適化するよう設計せざるを得ません。ここで自体を複雑にするのが、何らかの目的にのみ特化した MTA が常に存在する点です (例えば送信のことだけを考える大規模メーリングシステムなど)。

接続の両端が関係するシステムを設計するときには、えこひいきを避けることが大切です。これはクライアントと「サービス」の間に生じる非対称性とは全く対照的であることに注目してください。例えばウェブサーバーとウェブクライアントは、通常は異なるグループが開発します。

milter

sendmail に追加された機能の中で最も重要なものの一つが milter (mail filter) インターフェースです。milter を使うとオフボードの (別のプロセスで実行される) メール処理プラグインを起動できます。この機能は元々スパム対策のために設計されました。milter プロトコルはサーバーの SMTP プロトコルと同期的に動作します。sendmail は SMTP コマンドをクライアントから受信するたびに milter を起動し、そのコマンドの情報を渡します。milter はコマンドを受理するか拒否するかを選択でき、拒否した場合には SMTP コマンドを実行するプロトコルのフェーズが終了します。milter はコールバックとしてモデル化されており、SMTP コマンドがやって来るたびに対応するサブルーチンが呼ばれます。milter はスレッド化されており、接続を表すポインタを各ルーチンに渡すことで状態の受け渡しを行っています。

理論上は、milter は sendmail のアドレス空間にロード可能なモジュールとして動作するはずでしたが、こうできない理由が三つありました。まず第一に、セキュリティの問題があまりにも重要すぎました。sendmail が非 root のユーザー ID で実行されていたとしても、そのユーザーは全てのメッセージの状態にアクセスできます。これと同様に milter の作者が sendmail の内部状態を読むのを止めることはできません。

第二に、sendmail と milter の間にファイアウォールが必要だったというのがあります。milter がクラッシュしたときに責任の所在を明確にし、可能ならばメールの処理が続くようにしたかったためです。第三に、milter の作者からすれば、スタンドアローンのプロセスを考える方が sendmail 全体を考えるよりも簡単です。

milter がスパム対策以外にも有用であることがすぐに明らかになりました。実際 milter.org7 には、スパム対策、ウイルス対策、アーカイブ、コンテンツ監視、ログ、トラフィック整理などのカテゴリに、営利企業やオープンソースプロジェクトによって提供されたたくさんの milter が掲載されています。またメーラーの postfix8 は同じインターフェースを使って milter をサポートしています。milter は sendmail が成し遂げた大きな成功の一つです。

リリーススケジュール

「素早く頻繁にリリース」と「安定なシステムをリリース」という二つの流儀の間には激しい議論があります。sendmail はその歴史の中で両方の流儀を使ってきました。多いときには一日に何度もリリースを行うこともあり、そのとき私は変更毎にリリースを行うべきだと考えていました。これはソース管理システムのソースツリーをパブリックに公開することと似ています。個人的に私はソースツリーをパブリックにするよりはリリースを頻繁に行う方が好きでしたが、この理由の一部は私がソース管理に今ではあまり良くないと考えられている方法を使っていたためでした。つまり、私は大きな変更を行うときにはコードを書きながら動かないスナップショットを何度もチェックインしていました。ツリーが公開されるならばこういったスナップショット用にブランチを使いますが、そうしたとしてもそのブランチは世界中に公開されるので、混乱の元になるでしょう。またリリースには番号が付くので、変更を追跡したりやバグ報告を読むのが楽なります。このためにはリリースを簡単に生成できなくてはいけませんが、常にそうであったわけではありません。

sendmail がさらにクリティカルなプロダクション環境で使われるようになると、このリリース方法が問題になりました。開発者でない人はテスト用の変更と実環境用の変更の区別が付かないのです。リリースに「アルファ」や「ベータ」などのラベルを付ければ問題が緩和されますが、解決されるわけではありません。こういった理由もあり、sendmail は成熟するにしたがって頻度の低い大きなリリースへと移っていきました。これが特に重要となるのが、sendmail を使う営利企業が一番新しくて一番機能の多いバージョンと安定したバージョンの両方をどうしても使いたいと望んだ場合です。

オープンソース開発者が必要とするものと商用プロダクトが必要とするものの間の差は決して埋まることがありません。初期バージョンでも頻繁にリリースしていくことのメリットは大きいです。特筆すべきなのが、たくさんの勇敢な (そして時には愚かな) テスターが、通常の開発システムにおいてはまず望めない方法でシステムに負荷をかけてくれることです。しかしプロジェクトは成功すると「プロダクト」になりがちであり、たとえそれがオープンソースで無料であったとしても、プロジェクトとは異なるものが必要になってきます。

セキュリティ

sendmail のセキュリティはこれまでに何度も大きく変更されています。私たちが原因で変更が必要になったこともありますが、そうでなかったこともあります。「セキュリティ」の概念が私たちとは関係ないところで変化したためです。初期のインターネットのユーザーは数千人程度で、そのほとんどはアカデミアや研究機関に属していました。様々な点で今よりも親切で紳士的なインターネットがそこにはありました。ネットワークは共有を奨励し、ファイアウォールは存在しませんでした (ファイアウォールは初期のインターネットに存在しなかった概念の一つです)。現在のインターネットはそのころよりも危険で、非友好的で、スパマーとクラッカーに満ち満ちています。交戦地帯などと呼ばれることも増え、民間人の負傷者も生まれるようになりました。

ネットワークサーバーをセキュアに書くのは難しく、プロトコルが少しでも複雑になるとさらに難しくなります。ほとんど全てのプログラムに小さな問題が少なくとも一つは存在しており、広く使われている TCP/IP の実装さえ攻撃に遭いました。実装に高レベルの言語を使っても万能薬にはならず、その言語の脆弱性が追加されるだけでした。「出所に関わらずどんな入力も信用するな」という標語を心に留めなければなりません。信用してはいけない入力には DNS サーバーや milter からの二次的な入力も含まれます。初期のネットワークソフトウェアの多くと同じように、初期バージョンの sendmail は他者を信用しすぎでした。

初期バージョンの sendmail が持っていた最も重大な問題は、root 権限で実行されることです。root 権限が使われるのは SMTP のリスニングソケットを開くとき、ユーザーの転送情報を読み込むとき、ユーザーの受信箱やホームディレクトリにファイルをコピーするときなどです。しかし現在では受信箱の名前はシステムユーザーと切り離されており、root 権限が必要なのは SMTP のリスニングソケットを開くときだけです。そのため今の sendmail には接続を処理する前に root 権限を手放す機能があり、これが使える環境では root 権限による問題がなくなります。またユーザーの受信箱への直接のコピーを行わないシステムでは chroot した環境で sendmail を実行することも可能で、これによってさらに権限を隔離できます。

残念なことに、sendmail はセキュリティが弱いという評判を得てしまいました。ただし批判は sendmail と何ら関係ない問題から始まっています。例えばあるシステム管理者は自分で /etc ディレクトリを書き込み可能に設定したにもかかわらず、sendmail が /etc/passwd を書き換えたと批判しました。このような事例を受けてセキュリティを大幅に厳しくする処置が取られ、sendmail がアクセスするファイルとディレクトリの所有者およびモードの明示的なチェックなどが追加されました。ここで追加されたチェックはとても厳しかったので、チェックを (選択的に) 無効化するためのオプション DontBlameSendmail (sendmail を責めません) を追加しなければなりませんでした。

プログラムのアドレス空間の保護とは関係しないセキュリティの側面もあります。例えばスパムの台頭によりアドレスの収集が問題となりました。SMTP の VRFT コマンドと EXPN コマンドはそれぞれ個人のアドレスとメーリングリストの要素を検証しますが、この二つのコマンドはスパマーによる悪用があまりにも多かったので、現代の多くのシステムでは完全に無効化されています。これは残念な状況と言えます。少なくとも VRFY コマンドについては、スパム対策エージェントが送信アドレスの検証するときに使うことがあるためです。

同様に、以前はデスクトップの問題とみなされていたウイルス保護対策も、その重要性が増した結果、商用グレードの MTA にはウイルスチェックがあるべきだという考えが一般的になりました。その他の現代的な設定におけるセキュリティ関連の機能の例としては、重要データの強制的な暗号化、データ消失保護、HIPAA といった規制の施行などがあります。

sendmail が初期から重要視している原則の一つが信頼性です。つまり全てのメッセージは送り届けられるか、そうでなければ送信者に送り返されるということです。しかしジョージョブ (攻撃者がメッセージの返信アドレスを偽造するもので、セキュリティの問題だとみなされました) の台頭により多くのシステムがメッセージのバウンスをやめてしまいました。こうしても SMTP 接続が開いている間に障害を検出できれば、サーバーはコマンドを失敗させることで問題を報告できます。しかし SMTP 接続を閉じてしまえば、間違ったアドレスのメッセージはエラーを出すことなく消失してしまいます。公平のために言っておくと、現代の常識的なメールのほとんどは一度のホップで届くので、エラーは検出されます。しかし原理的には、世界は信頼性ではなくてセキュリティを選んだということになります。

sendmail の進化

ソフトウェアを取り巻く環境は速いペースで変化しており、ソフトウェアは環境に適応するよう進化しなければ生き残ることはできません。新しいハードウェア技術の登場はオペレーティングシステムを変化させ、さらにそれがライブラリやフレームワークを変化させ、最後にはアプリケーションを変化させます。またアプリケーションが成功を収めると、さらに扱いづらい環境で使われることになります。この節では sendmail の進化の中で生じた重要な変化をいくつか見ていきます。

詳細な設定

初期の sendmail の設定は非常に簡潔でした。例えばオプションやマクロの名前は全て一文字でした。これには理由が三つあります。第一に、簡潔にすることでパースがとても単純になります (16 ビット環境では重要です)。第二に、オプションの数が少なかったので、省略形の名前を思いつくのは難しくありませんでした。第三に、一文字でオプションを表すという慣習はコマンドラインのフラグで使われていました。

同様に、書き換えルールセットには名前ではなく番号が付いていました。ルールセットの数が少ないなら耐えられるでしょうが、その数が大きくなるにつれて整理しやすい名前を付けることが求められました。

sendmail が運用される環境が複雑になり 16 ビットの環境が引退すると、設定を行うための高機能な言語の必要性が明らかになりました。幸いにもこういった変更は後方互換性を保ったまま行うことができ、これによって設定ファイルが格段に理解しやすくなりました。

他のサブシステムとの結び付き: より進んだ統合

sendmail が書かれたころ、メールシステムは他のオペレーティングシステムとほぼ切り離されていました。統合が必要なサービスは少しだけで、例えば /etc/passwd/etc/hosts といったファイルぐらいでした。サービススイッチは発明されておらず、ディレクトリサービスは存在せず、小さな設定は手動で管理されていました。

この状況はすぐに変化しました。最初に追加されたのは DNS です。システムホストを検索するための抽象化 (gethostbyname) が IP アドレスの検索に利用できましたが、電子メールは MX などの他のクエリを使わなければなりませんでした。IDA sendmail では dbm(3) を使った外部データベースの検索機能が追加され、sendmail 8 では他のデータベースタイプも利用可能な一般的なマッピングサービスへと拡張されました。例えば外部データベースや内部での変形を使うことができ、アドレスのデクオートといった書き換えでは不可能な処理も行えます。

現在の電子メールシステムはたくさんの外部サービスに依存しており、そういったサービスは一般的に電子メールのためだけに開発されたわけではありません。そのため sendmail のコードは抽象化が進みました。また「可動部分」が追加されるにつれ、メールシステムをメンテナンス可能にするのがより難しくなりました。

敵対的な世界と向き合う

sendmail が開発されたころの世界は、現在の基準からすると全く異質です。初期のネットワークのユーザーはほとんどが研究者であり、基本的に温和でした (学問的な対立で荒れることも時にはありましたが)。sendmail は生まれたときの世界を反映し、ユーザーエラーがあったとしてもメールを可能な限り高い信頼性で配送することを重要視していました。

今の世界はもっと敵対的です。電子メールのほとんどは悪意を持ったものであり、MTA の役割はメールの配達ではなく必要ないメールをはじくことになりました。現在の MTA においてはフィルタリングが一番優先度の高い機能でしょう。これによって sendmail は変更を余儀なくされました。

例えば問題をなるべく早く検出するために、受信する SMTP コマンドのパラメータをチェックするルールセットがいくつも追加されました。メッセージのエンベロープを呼んだときにメッセージを捨てるのはメッセージ全体を読んでから捨てるよりもずっと高速であり、ましてメッセージを受理して配送処理を始めた後で捨てたのでは非常に高く付いてしまいます。初期のフィルタリングはメッセージを受理し、フィルタプログラムに渡し、パスしたメッセージを別の sendmail のインスタンスに送信するというやり方で基本的に行われていました (いわゆる「サンドイッチ」構成です)。しかし現在ではこのやり方は無駄が大きすぎます。

同様に、最初は TCP/IP 接続を普通に使っているだけだった sendmail は、より洗練された使い方をするようになりました。例えばコマンドを受理する前に以降のネットワーク入力を「のぞき見」して次のコマンドがあるかを確かめたりします。これによって複数のネットワークタイプに対応できるように追加された抽象化の一部が破られます。現在の sendmail を使って例えば XNS や DECnet といったネットワークに接続するのにはかなりの作業が必要になるでしょう。TCP/IP の知識がコードの至る所に組み込まれているからです。

敵対的な世界に対抗するための設定機能が多く追加されました。アクセステーブル、Realtime Blackhole Lists、アドレス収集の防御策、DoS 攻撃からの保護、スパムフィルタなどです。これによってメールシステムの設定作業は格段に複雑になりましたが、現在の世界に対応するために欠かすことは絶対にできません。

新しい技術の導入

長年の間にたくさんの新しい標準規格が表れ、sandmail に大きな変更が必要になってきました。例えば TLS (暗号化) の追加によってコードの大部分が変更されました。また SMTP パイプラインの実装ではデッドロックを避けるために低レベルの TCP/IP ストリームのコードをいじる必要がありましたし、サブミッションポート (587) では複数の受信ポートの一覧を取得する機能が必要で、さらに受信ポートに応じて動作を切り替えなければなりませんでした。

標準規格ではなく環境からの重圧もありました。例えば milter インターフェースの追加されたのはスパムがあったからです。milter に書面の規格は存在しませんが、当時としては大きな新技術でした。

いずれのケースにおいても、変更によってメールシステムは改善されました。セキュリティの向上、パフォーマンスの向上、新機能があったからです。しかし変更にはコストが伴います。ほとんどのケースにおいてコードベースと設定ファイルの両方が複雑になりました。

今やるとしたら?

後から見れば何もかも明らかであり、今であれば違う方法を取るであろうことはたくさんあります。当時には予測不可能だった (つまりスパムが電子メールの考え方をどれだけ変えたか、現代のツールセットがどのような形をしているか、など) こともあれば、大いに予測のついたものもあります。また私が sendmail を書く中で電子メールや TCP/IP、あるいはプログラミングについて様々なことを学んだから、というのが理由でもあります。誰であれコードを書くと成長するものです。

しかし同じやり方でやるであろうことも多くあり、そのうちいくつかは一般的に言われていることと矛盾します。

違うやり方でやるであろうこと

sendmail に関する私の最大の過ちはおそらく、このプログラムがどれほど重要になるかを初期に認識しなかった点です。世界を正しい方向に向かわせるチャンスが何回かあったのですが、私はそうしませんでした。さらに言えば、私が世界に損害を与えたことさえありました。例えば不完全な入力に対して厳しくなるべき頃合いになっても sendmail はそうしませんでした。同様に設定ファイルのシンタックスには修正が必要なことは、稼働する sendmail が数百程度のかなり初期の頃から認識していたのですが、インストールしてくれたユーザーに迷惑をかけたくないという理由で修正は行われませんでした。今思えば、長期的な結果のために早いうちに一時的な痛みを取るべきだったでしょう。

バージョン 7 の受信箱のシンタックス

この例の一つが、バージョン 7 の sendmail が使った受信箱内のメッセージを分割する方法です。メッセージの切れ目は From_ で表されました (ここで _ は ASCII 空白文字 0x20 です)。受信したメッセージに From_ という語が含まれた場合には、ローカルの受信箱を管理するソフトウェアがそれを >Form_ に変更します。これを改善して From_ の後に空行を要求していたシステムも一部ありましたが、どこででも使えると信頼することはできません。このシンタックスのせいで、現在でも >From という文字列が電子メールとは明らかに無関係な予期せぬ場所で表れています (ですが電子メールによる処理が途中であったのは明らかなわけです)。今考えれば、BSD メールシステムなら新しいシンタックスを使うよう変更できたでしょう。手厳しい批判を受けたでしょうが、世界をトラブルから救えたはずです。

設定ファイルのシンタックスと内容

設定ファイルのシンタックスに関する私の最大の過ちはおそらく、書き換え規則のパターンと書き換え結果を分けるのにタブ文字 (HT, 0x09) を使ったことです。当時の私は make を真似たのですが、make の著者 Stuart Feldman がこの選択を自身最大の過ちだと考えていることを後で知りました。スクリーン上で設定ファイルを見るだけでは分かりにくいのに加えて、タブ文字は多くのウィンドウシステムにおけるコピペで失われてしまいます。

書き換え規則というアイデアは正しかった信じています (後述) が、設定ファイルの全体的な構成は変更するでしょう。例えば、私は設定の階層 (SMTP リスナーポートごとにオプションを変える機能) が必要になったことがありません。設定ファイルが設計された当時には「標準的な」フォーマットというのは存在しませんでしたが、現在であれば Apache スタイルの設定ファイルを使うでしょう。このスタイルは簡潔で、分かりやすく、表現能力も高いです。あるいは Lua などの言語を埋め込むのも悪くないかもしれません。

sendmail が開発された当時はアドレス空間が小さく、プロトコルは流動的でした。そのため設定ファイルに何でも詰め込んでしますのが良さそうに思えたのですが、今の基準で考えればこれは誤りです。MTA が使えるアドレス空間は大量にあり、標準規格はしっかりと定まっています。また「設定ファイル」の一部は実際にはコードになっており、リリース毎に更新が必要なほどでした。.mc の設定ファイルを使えばその必要もなくなりますが、ソフトウェアを更新するたびに設定を再ビルドしなければいけないというのは面倒です。簡単な解決法としては、sendmail が読む設定ファイルを二つに分け、一つはソフトウェアのリリース毎にインストールされる隠されたファイル、もう一つをローカルの設定のためにユーザーに公開するファイルとすることが考えられます。

ツールの利用

現在では利用可能な新しいツールがたくさんあります。例えばソフトウェアを構成・ビルドするツールなどです。必要なときに必要なツールを使えば非常に役立ちますが、使うツールが「やり過ぎ」であり、システムが必要以上に分かりにくくなってしまうこともあります。例えば strtok(3) で事足りるなら絶対に yacc(1) を使うべきでありません。一方で車輪の再発名も良いアイデアではありません。例えば autoconf には不満な点もいくつかありますが、私は現在でもまず間違いなく autoconf を使うでしょう。

後方互換性

後知恵を持っていて、sendmail がどれだけ広く利用されるようになったかを知っていたとしたら、開発の初期に既存のインストールを破壊することにそれほど躊躇しないでしょう。今あるものがまるっきり壊れているのであれば、それは修正するべきであり、引き継いでいくべきではありません。とは言うものの、それでもメッセージのフォーマットの厳密なチェックは行わないでしょう。安全な方法で簡単に無視あるいは修正できる問題もあるからです。例えば Message-Id: ヘッダーフィールドを持っていないメッセージがあれば付けてやるでしょう。一方で From: ヘッダーフィールドが無いメッセージについては、エンベロープからの情報を使って生成することはせずに拒否するでしょう。

内部の抽象化

内部の抽象化には二度と行わないであろうものもありますし、追加したいものもあります。例えばヌル文字終端の文字列は使わずに長さと値のペアを使うでしょう。これによって標準 C ライブラリ関数の多くが使いにくくなったとしてもです。こうすることによるセキュリティの向上だけを考えたとしても、やる意味があります。C で例外処理を作ろうとはしないでしょうが、コードベース全体で統一されたステータスコードは作ることになるでしょう。nullfalse あるいはエラーを表す負数を返すようにはしません。

受信箱の名前と Unix のユーザー ID を切り離す抽象化は間違いなく追加するでしょう。最初の sendmail では Unix ユーザーにしかメッセージを送らないというモデルを作りましたが、現在このモデルを使っているシステムはほとんどありません。またこのモデルを使っていたとしても、電子メールを受け取ってはいけないシステムアカウントが存在します。

同じやり方でやるであろうこと

もちろん、上手く行ったこともありました...

syslog

sendmail から生まれ成功したサイドプロジェクトの一つが syslog です。sendmail が書かれたころ、プログラムはログに専用のファイルを使っており、それがファイルシステム中に散らばっていました。当時 syslog を書くのには困難が伴いました (UDP が存在しなかったので、mpx ファイルと呼ばれるものを使いました) が、それに十分見合う価値がありました。ただ、一つだけ変更したい部分があります: ログメッセージをマシンでパースしやすくするよう注意を払うでしょう ――つまり、私はログモニタリングの登場を予測できなかったのです。

書き換え規則

書き換え規則には苦しめられましたが、使うことはやめないでしょう (ただ今ほど様々なものに使わないでしょうが)。タブ文字を使ったのは明らかな間違いでしたが、電子メールアドレスのシンタックスと ASCII という制限を考えると、何らかのエスケープ文字がおそらく必要です9。全体的に見て、「パターンを置き換える」という考え方は強力で、柔軟性も高いです。

必要でないツールを避ける

既存のツールをもっと使うだろうと先ほど言いましたが、私は現在利用可能なランタイムライブラリの多くを使う気にはなれません。あまりにも多くのライブラリが肥大化しており、危険だと私は考えています。ライブラリを採用するときには、再利用のメリットと単純な問題を解くために強力すぎるツールを使ってしまうことの問題のバランスを取らなければなりません。私が利用を避けているツールを一つあげると、XML です。XML を少なくとも設定言語として採用することはないでしょう。利用目的に対してシンタックスがごてごてしすぎていると私は思っています。XML が適した利用場所は存在しますが、それ以外の場所で使われすぎています。

コードを C で書く

Java や C++ といったより高機能な実装言語を勧められたことがあります。C にはよく知られた問題点がありますが、それでもコードを実装するとなれば私はこの言語を使います。理由の一部は個人的なものです: つまり、私は C を Java や C++ よりもはるかに詳しく知っています。しかしそうでない理由として、私はほとんどのオブジェクト指向言語がメモリの確保について気にも留めていないことに失望しているというのがあります。メモリの確保にはパフォーマンスに関して注意を要する点が多くあり、一般化が難しいこともあります。sendmail の内部では必要な場合にはオブジェクト指向の考え方を取り入れています (例えばマップクラスの実装など) が、完全にオブジェクト指向にしてしまうのは無駄が多く、必要のない制約であるというのが私の意見です。

最後に

sendmail という MTA が生まれたとき世界は大きな変革の真っただ中であり、まるで西部開拓時代のような時代でした。電子メールはアドホックであり、メールの標準規格はまだ定式化されていません。それから 31 年の間に、最初は失敗せずに動作することだけだった「電子メールの問題」は、巨大なメッセージと膨大な負荷をさばくことになり、さらにはスパムやウイルスからシステムを守ること、そして最終的には電子メールベースのアプリケーション用のプラットフォームとして使われることになりました。sendmail はリスクをとにかく忌避する企業さえ採用するほどのプログラムに進化し、その間には電子メールも人と人とのテキストを使った通話手段からマルチメディアベースのミッションクリティカルなインフラへと進化しました。

この成功の理由にははっきりしない部分もあります。変化の激しい世界で生き残り、さらに成功さえ収めるプログラムを数人のパートタイム開発者が書くというのは、伝統的な開発手法を使っては不可能です。sendmail がどのように成功を収めたかについて、いくらかの洞察を提供できたことを願います。


  1. http://ftp.isc.org/www/survey/reports/2011/01/[return]

  2. 訳注: fiat[return]

  3. 訳注: deffered maintenance[return]

  4. http://www.sendmail.com[return]

  5. http://doc.cat-v.org/bell_labs/upas_mail_system/upas.pdf[return]

  6. 「自分のすることには厳格に、他人のすることには寛容に」[return]

  7. http://milter.org[return]

  8. http://postfix.org[return]

  9. 設定に Unicode を使うと利用者数が伸びないのではないかと私は思っています。[return]

広告