はじめに
本稿ではRubyを使ってシンプルなPOP3サーバを作成します。
POP3は、いわゆる「メールの受信」のときに使われるプロトコル(通信規約)です。本稿では、このPOP3でサービスを提供するサーバの作成を通じて、以下のことを学びます。
- ネットワークプログラミングの基礎
- POP3の仕組み
- Rubyによるネットワークプログラミング
- RubyによるUNIXシステムプログラミング
POPdの概要
本稿で作成するPOP3サーバ(POPd)は、イントラネットなどの信頼できるネットワークを前提として作成します。そのため、セキュリティへの配慮は最低限にとどめ、できるだけシンプルな構造を心掛けます。ただし「最低限の配慮」として、パスワードが平文で流れないようにするため、POP3の中でも特にAPOPという認証方法を用います。
また、速度やメモリ容量に関してもあまり配慮しません。せいぜい数人から数十人が日常的に使える程度の速度が出ればよいものとします。
そして最後に、メールボックス(メールをためておくデータベース)には、Maildirという形式を採用します。Maildirは元はqmailというメール配送システムで使われていたメールボックス形式で、シンプルでありながら高い信頼性を誇ります。
私がこのプログラムを書いたのは、既存のPOPサーバでAPOPとMaildirを使う設定をするのが非常に面倒だったことが原因でした。そのあたりの設定を決め打ちにすることで設定と実装を簡潔にして、導入を簡単にしていることがこのプログラムの最大の特色です。そのようなスクリプト言語らしい手軽さと実用性を感じていただければと思います。
対象読者
本稿は次のような読者を対象にしています。
- Rubyプログラミングを一通り理解している
- Linuxの基本的なコマンドが使える
Rubyをまったく使ったことのない読者は対象としません。最低でも、自分でクラスを定義できるようになっておいてください。
また、Linuxのコマンドラインを使った経験があり、cdやls、cp、mvなどの基本コマンドが一通り使えることを前提とします。
必要な環境
本稿では以下の環境を前提とします。
- Ruby 1.8以上
- LinuxなどのUNIX系オペレーティングシステム
Rubyは安定版1.8系の、できるだけ最新のバージョンを使ってください。原稿執筆時点での最新バージョンは1.8.5です。また、筆者は以下の環境で動作を確認しました。
- Ruby 1.8.5、安定版最新スナップショット、開発版最新スナップショット
- Linux 2.6.8 / x86-64
- Cygwin 1.5 / Windows XP Professional / x86
インターネットサーバプログラミングの基礎
この節ではネットワークプログラミング、特にインターネットサーバを作成するときに必要な知識を解説します。ごく初歩的な内容なので、ネットワークプログラミングの経験がある方は読み飛ばしてください。
インターネット
ネットワーク通信とは、(通常は)複数のコンピュータで動いている、複数のプロセスが通信することです。1つのコンピュータで動いているプロセス同士でもネットワーク通信はできますが、典型的な状況とは言えません。
ちなみに、ネットワークの話をするときは「コンピュータ」のことをホスト(host)や、ノード(node)と言います。本稿では「ホスト」で統一します。
さて、実はネットワークにもいろいろ種類があるのですが、現在はネットワークと言えば普通、「インターネット」を指します。本稿で話すPOP3もインターネット上で使われるプロトコルです。
ではインターネットとは何でしょうか。インターネットとは、ホスト同士がインターネットプロトコル(IP:Internet Protocol)というプロトコル(通信規約)を使って通信するネットワークのことです。
IPの最大の特徴は、パケット(packet)を基本とするプロトコルであることです。パケットというのは、データの切れ端のことです。最近は携帯メールについて話をする際、「パケ代が……」と言ったりしますが、この「パケ」がパケットのパケです。インターネットに参加しているホストは、このパケットを投げあって通信します。
パケットを投げる、と言っても、ただ投げるだけでは、どのホストからどのホストへ向けて投げたパケットなのか分かりません。そこでIPのパケットには、投げたホストと宛先のホスト、それぞれのIPアドレス(IP address)が書き込まれています。IPアドレスは4バイトのバイト列で、通常はバイトごとにピリオドで区切って、例えば「192.168.1.1」のように表記します。ちなみに、この「4バイト」というサイズが足りないということで開発されたのがIPv6(Internet Protocol version 6)です。それに対して、現在最も広く普及しているIPはバージョン4、すなわちIPv4(Internet Protocol version 4)です。
さて、IPアドレスが分かればホストが特定できますが、IPアドレスはただの数値ですから、人間にとっては不便なことこのうえありません。そこで、IPアドレス(数値)とは別にホストに名前を付けることもできます。その名前がドメイン名(domain name)です。皆さんおなじみの「www.example.com」などがドメイン名です。インターネットに参加しているホストは、DNS(Domain Name System)というシステムを使ってドメイン名をIPアドレスに変換できます。
TCPとソケット
いま説明したように、IPでやりとりできるのはパケットだけです。ところが困ったことに、IPではそのパケットの届く順序が決まっていません。それどころか、パケットがちゃんと届くかどうかも分かりません。何か事故が起きると投げたパケットがいきなり消失する場合もあるのです。このようなネットワークでは安心して通信ができません。例えば、ウェブからダウンロードしようとしたHTMLや画像のデータがところどころ抜けていたり、順序がバラバラになっていたりしたら非常に困るでしょう。
そこで使われるのがTCP(Transmission Control Protocol)です。TCPはIPのパケットを使ってさらに仕掛けを凝らすことで、ファイルの読み書きのように、順序の明確な入出力を行うことができます。しかも、通信エラーを検出する仕組みを用意して、データが確実に届くことを保証してくれます。
さらに、TCPでは1つのホストでも複数のプロセスが同時に通信できるよう、ポート番号(port number)を導入します。例えば、www.example.comというホスト1つでも、1番ポート、2番ポート、3番ポート……で同時平行的に通信できるわけです。逆に言うと、TCPを使って通信するときは、通信したい相手のIPアドレスとポート番号を、両方指定する必要があるということです。
TCP通信のための経路は、UNIX上ではソケット(socket)というもので表現されます。ソケットは開いたファイルのようなもので、ファイルと同じくreadシステムコールやwriteシステムコールで読み書きができます。Rubyレベルでは、ソケットはTCPSocket
オブジェクトやTCPServer
オブジェクトで表現されており、File
オブジェクトと同じようにread
メソッドやwrite
メソッドを使って入出力することができます。
サーバとクライアント
いったんTCPの通信が始まったら、TCPを使って通信するプロセスは対等に扱われます。つまり、どちらのプロセスからも同じように読んだり書いたりできます。しかし、TCPによる接続が完了するまでは立場に違いがあります。
TCPでは、接続を待つ側と、接続しにいく側がはっきり区別されます。前者のことをTCPサーバ(TCP server)、後者のことをTCPクライアント(TCP client)と言います。
TCPのサーバ・クライアントと同じように、TCPを利用するより上位のサービスにおいても、サーバとクライアントが区別されることが多々あります。例えば、POP3サービスでもサーバとクライアントの区別があります。メールを提供する側がこれから作るPOP3サーバであり、メールを受信する側がPOP3クライアントです。
一般には、上位のサービス(例えばPOP3)のサーバであるからと言ってTCPレベルでもサーバであるとは限りません。しかし、少なくともPOP3に限って言えば、POP3のサーバは同時にTCPサーバでもあります。つまりPOP3サーバはTCP接続を待ち受ける側にならなければいけません。
デーモン
次にデーモン(daemon)について説明します。
サーバにはいつクライアントから接続要求が来るか分かりませんから、24時間365日起動して接続を待ち続ける必要があります。これ自体は簡単です。accept()というシステムコールを使えばTCP接続が完了するまで待つことができるので、こちらは単にaccept()を呼んでおけばいいわけです。
ただ、サーバで問題になるのが端末とのつながりです。サーバも誰かに起動してもらわなければいけません。しかし、その場合、サーバを起動した「誰か」がログアウトすると、その端末から起動されたプロセスがすべて停止してしまいます。この仕組みは、誰にも使われていないプロセスをためこまないという点から見れば便利ですが、サーバにとっては大問題です。サーバを起動した人が永久にログアウトできなくなってしまうからです。
そこでデーモンが使われます。デーモンとは、端末から切り離されたプロセスのことです。デーモンになったプロセスは、そのプロセスを起動した人がログアウトしても停止されません。
このような事情から、インターネットサーバを作るときはデーモンになる機能がほぼ必須になります。もちろん、Rubyプログラムでもデーモンになることができますから、これから作成するPOP3サーバにも自力でデーモンになる機能を実装します。
inetd
デーモンに関しては前項の通りですが、自力でデーモンになるのはやはり多少面倒です。
それに、サーバといっても忙しいプログラムばかりではありません。ずっと接続を待っているだけのような、暇なサーバもたくさんあります。そのような、ただ接続を待つだけのプロセスをずっと動かしておくのはメモリの無駄遣いではないでしょうか。
そこで次のキーワード、inetdが登場します。inetdとは、複数のサーバプログラムの身代わりとなって、TCP接続が来るのをまとめて待っていてくれるサーバです。inetdにTCP接続要求が来ると、その時点で初めて実際のサーバプログラムを起動し、TCP接続を引き渡してくれます。
inetdから起動されたプログラムは標準入出力がソケットにつながっていて、標準入出力を相手にしてやりとりするだけでネットワーク通信が行えます。Rubyで言うなら、STDINから入力して、STDOUTに出力すればよいのです。
また、inetdから起動されたプログラムは自動的にデーモンになるので、自力でデーモンになる必要がありません。この点もinetdを使う大きなメリットです。
このように、inetdを使えると便利な点があるので、これから作成するPOP3サーバ(POPd)でもinetdに対応することにします。