目次


洗練されたPerl: PerlでIMAPを使う 第2回

Maildirの調査とifrom.plスクリプトおよびMail::IMAPClientによるトンネリング

Comments

この記事は「PerlでIMAPを使う 第1回」の続編であり、ifrom.plツールについて再び説明します。この記事を読む前に前回の記事を読んで、ifrom.iplの具体的なメカニズムを理解してください。この記事では新しいトピックとして、Maildirメール格納形式とトンネリング(ポート転送とも呼ばれます)を扱います。

ifrom.plの拡張は、IMAPユーザーとしての私自身のニーズに合わせたものなので、皆さんのお役にも立つと思います。

トンネリング(ポート転送とも呼ばれる)

ネットワーク接続のトンネリング(「ポート転送」とも呼ばれますが、この2つは技術的に正確に同じものではありません)は、コンピューティング環境では一般的な技法です。接続のトンネル化とは、1つまたは複数の宛先(必ずしもリモート・マシンとは限りません)にリダイレクトすることです。トンネリング・アプリケーションは通常、転送するデータの内容を関知しません。トンネルを使用するプログラムは、トンネルを使用していることを関知しません。一般的なUNIXトンネリング・アプリケーションとしては、stunnelとOpenSSHがよく知られています。(もちろん、OpenSSHには他の用途もありますが、トンネリングに最適です。)ネットワーク・ポートを使用できるプログラムであれば、トンネリングされたネットワーク・ポートも使用できます。地球の裏側とでも隣の家のように話すことができる電話接続のようなものと考えてください。

OpenSSH以外のSSHのバージョンもトンネリングをサポートしていますが、OpenSSHは人気が高く、自由に利用できるので、この記事の説明と例ではOpenSSHを使用します。

制限された環境では、トンネリングは非常に便利です。たとえば、ファイアウォールの設定によって制限されているため、外部のIMAPサーバーに接続できないとします。OpenSSHをポート転送モードで実行すると、外部IMAPサーバーのポート143(IMAP)をローカル・マシンのポートXYZに転送することができます。IMAPサーバーそのものにログインする必要はありません。SSH接続を受け入れてIMAPサーバーに接続できる中間ジャンプ点があればよいのです。もちろん、IMAPサーバーに直接SSH接続できれば、中間ジャンプ点がなくなるので、接続が高速になり、信頼性が高くなります。

IMAPサーバーから自分のマシンへトンネルして、接続プロセスを逆転することもできます。

トンネリングのもうひとつの用途は、接続の暗号化です。OpenSSHでトンネリングすると、IMAPサーバーがセキュア接続を提供していない場合、OpenSSHの暗号化機能を使用して、IMAPトラフィックを暗号化することができます。すでにIMAPサーバーがセキュア接続を提供している場合でも暗号化できます。その場合、接続を2回暗号化することになります。

トンネリングを提供するために、ifrom.plにTUNNELオプションを追加してみました。これは驚くほど簡単です。実際、10か月になる私の娘の方が、半分の時間で、もっと上手に書けるくらいです(彼女が先にキーボードを食べたりしなければ、ですが)。ifrom.plに-tunnelオプションを追加して、そのオプションが表示されたら、system()で実行するだけです。

リスト1. ifrom.plのTUNNELオプションの使用
# use -tunnel like so with OpenSSH:
# ifrom.pl -tunnel "ssh REMOTEHOST.COM -N -T -n -L 2002:127.0.0.1:143 &"
#          [other options to follow...]
# This forwards the REMOTE port 143 on REMOTEHOST.COM to the LOCAL
# port 2002.See the OpenSSH documentation for details and more
# examples.There is no intermediate jump point.
if ($config->TUNNEL())
{
 system($config->TUNNEL());
}

その後は、ifrom.plの機能を代わりに実行します。すなわち、IMAPサーバーに接続して、メールをチェックします。これがトンネリングの便利なところです。つまり、肝心な部分はすべてデータ転送層にあるので、トンネリングを使用するプログラムは、通常の処理以外は何もする必要がありません。私の場合、ifrom.plに少し手を加える必要がありましたが、プログラムの主要ロジックには手を触れていません。

Maildirサポート

Maildirは、メール格納形式です。ユーザーのメールを格納するために、Courier IMAPやqmailで使用されています。Maildirは、1つのディレクトリーとcur、new、およびtmpというサブディレクトリーで構成されています。他のサブディレクトリーがあっても無視されます。ここでは、Maildirディレクトリーを単純に「Maildir」と呼ぶことにします。

先ほど述べた3つのサブディレクトリーのうち、ここで直接取り上げるのはnewだけです。Maildirは、各サブディレクトリーに1つのメッセージを1つのファイルとして格納します。ファイルには一意なファイル名が付けられます。

newサブディレクトリーには、Maildirへのすべての新着メッセージが格納されます。新着メッセージの扱いは、Maildirを使用する特定のメール配信エージェント(MDA)やその他のプログラムによって異なります。たとえば、Courier IMAPは、新着メッセージをMaildirのnewサブディレクトリーから移動することによって、開封済みメッセージとしてマークします。

まず、私はifrom.plに-maildirスイッチを追加しました。これは、IMAP接続を試みる代わりにMaildirロジックを実行するように指示するスカラーにすぎません。

Maildir処理は、glob()コールによって行われます。つまり、"Maildir/new/*"のワイルドカード・マッチングのためのローカル・シェルの機能が使用されます。opendir()readdir()を使用することもできましたが、glob()の方がはるかに簡単です。

glob()コールで見つかったファイルごとに、送信者と件名を取得して印刷します。次に、-dumpまたは-printスイッチが指定されていた場合は、該当するファイルを1つずつ印刷することによって、要求されたメッセージ全体を印刷します。ファイルの順序は、シェルのglob()関数によって決まります。日付によるソートはしません。シェルのMaildirファイルのソートで十分だからです(qmailによってファイルが配信されるときに、すでに日付順にソートされています)。他のメール配信エージェントでは、日付ソートが必要になるかもしれません。

リスト2. ifrom.plのMaildirサポート
if ($config->MAILDIR())
{
 my $count = 0;
 foreach my $file (glob($config->MAILDIR() . '/new/*'))
 {
  $count++;
  open M, "<$file";
  my $address = 'UNKNOWN';
  my $subject = 'UNKNOWN';
  while (<M>)
  {
   $address = $1 if m/^From: (.*)/;	# the sender of the message
   $subject = $1 if m/^Subject: (.*)/;	# the subject of the message
   last if $_ eq "\n";
  }
  printf "%5d %-35.35s %s\n", $count, $address, $subject;
  if ($config->DUMP || grep {$_ == $count} @{$config->PRINT})
  {
   close M;
   open M, "<$file";
   print MARKER();
   print foreach <M>;
   print MARKER();
  }
 }
}

メールのインポート

メール・サーバー管理者として、私はユーザーのメールをいわゆるmbox形式(メッセージが入っている1つの大きなファイル)からIMAPサーバーに移行しなければなりませんでした。ifrom.plには、必要なIMAPロジックがすでに含まれているので、mboxファイルからのメールの読み取りを処理するために、-importスイッチを追加し、Mail::Boxモジュールを使用しました。

これは効率の悪い方法です。すべてのメッセージをネットワーク接続経由で移動しなければならないからです。多数のユーザーのメールを一括してインポートする場合は、サイトに応じた、より高度な方法を検討した方がよいかもしれません。たとえば、メールの格納にMaildirを使用しているサイトでmbox形式から移行する場合は、safecatなどのmbox-to-Maildirコンバーターを使用してください(safecatには他にもさまざまな用途があります)。mbox-to-Maildir変換の詳細については、safecatのWebサイトを参照してください(参考文献を参照)。

-mailboxauto_mailboxパラメーター(MAILBOX_AUTO定数に格納されます)は、ファイル名に基づいてメールボックス名を付けるようにifrom.plに指示します。私は-importスイッチの値に対してbasename()関数とPREFIXパラメーターを使用しています。auto_mailboxが指定されなかった場合は、-mailboxが新しいメールボックスの名前を付けます。したがって、-import A/B/C/FILE -mailbox auto_mailboxは、"FILE"という名前のIMAPフォルダーを作成して、データを格納します。一方、-import A/B/C/FILE -mailbox XYZ は、"XYZ"という名前のフォルダーを作成して、データを格納します。

さらに、"XYZ.msf"という名前のファイルがあった場合(XYZは何でもかまいませんが、.msfはMozilla mboxインデックス・ファイル名拡張子です)、"XYZ"だけがファイル名として使用されます。これを応用して、find DIRECTORY -name "*.msf" -exec ifrom.pl -import {} ... \; というような命令を実行することができます。これは、"find"の結果を1つずつifrom.plに渡します。Mozillaインデックス・ファイルがすべて検索されて、ifrom.plが該当するメールボックスを1つずつインポートします。

PREFIXは、IMAPサーバーのプレフィックスをifrom.plに指示します。プレフィックスは、実際には、namespace()関数でIMAPサーバーから取得できますが、これを取得するロジックは、ifrom.plのように単純なスクリプトにとって、あまりに複雑でした。IMAPプレフィックスまたはIMAPセパレーターが必要な場合は、namespace()で取得できます。一例として、一般的なUW IMAPのプレフィックスとセパレーターは""と"/"なので、メールボックスは"a/b/c"という形式になります(ファイル名とディレクトリー名の区別がないことに注意してください)。Courier IMAPサーバーのプレフィックスとセパレーターは"INBOX."と"."なので、メールボックスは"INBOX.a.b.c"という形式になります(フラット構造に注目してください。Courierのフォルダーはすべて、トップ・レベルのMaildir直下にあり、サブディレクトリーはありません)。

ifrom.plの-dryrunオプションは、非常に便利です。これによって、実際のインポートを実行することなく、インポート時にifrom.plが実行する処理をすべて確認することができます。

リスト3. メールのインポート
elsif ($config->IMPORT)
{
 eval { require Mail::Box::Manager; };
 die "You need to install the Mail::Box module, exiting" if $!;
 my $file = $config->IMPORT;
 if ($file =~ m/^(.*)\.msf$/i)
 {
  $file = $1;
  print "MSF file detected, using $file as the file name\n";
 }
 die "Can't access import file $file" unless -r $file;
 if ($config->MAILBOX eq MAILBOX_AUTO)
 {
  $box = $config->PREFIX . basename($file);
 }
 my $mgr    = Mail::Box::Manager->new;
 my $folder = $mgr->open(folder => $file);
 my $i;
 if ($config->DRYRUN)
 {
  print "Skipping folder check and creation because a dry run was requested\n";
 }
 else
 {
  if ($imap->select($box))
  {
   print "Selected folder successfully, ready to import.\n"
    if $config->VERBOSE;
  }
  else
  {
   print "Could not select folder $box, trying to create it...\n";
   $imap->create($box)
    or die "Could not create import folder: $@\n";
  }
 }
 # Iterate over the messages.
 foreach ($folder->messages)
 {
  printf "Appending to mailbox %s, message %d: ID %s, %d lines\n",
   $box, ++$i, $_->messageId, $_->nrLines;
  if ($config->DRYRUN)
  {
   print "Skipping import because a dry run was requested\n";
  }
  else
  {
   $imap->append($box, $_->string);
  }
 }
}

その他の改良点とメモ

短い参考情報として、ヘルプ・セクションを書いてみました。ifrom.pl -helpで表示できます。

"\n===\n\n"ディバイダーを頻繁に使用するので、MARKER()定数を追加しました。

Maildirについては、-backup-delete_mailbox_reallyスイッチなど、IMAPにあるようなファンシー・コードは追加しませんでした。Maildirはローカルな機能であり、Maildir内のデータにアクセスできるのはローカル・マウントしたファイルシステムからだけで、バックアップやメールボックスの削除は単純なシェル操作に過ぎないからです。たとえば、Maildirをバックアップするには、次の例のようにします。

リスト4. ローカルMaildirのバックアップ
rsync -avP --delete MaildirLocation Destination
# so, for example, to back up /var/qmail/maildirs/tzz
# to /home/tzz/backups
rsync -avP --delete /var/qmail/maildirs/tzz /home/tzz/backups

rsyncマニュアルを読んでください。ryncは、バックアップとディレクトリー同期のための優れたツールです。これを使っていない人は、現在使用可能な最良のUNIXツールの1つを見逃していることになります。

MaildirとIMAPのループのロジックは同じように見えるかもしれませんが、まったく同じわけではないので、マージはできません。もう1つ別のメール・ソースをifrom.plに追加したのであれば、すべてのループを1つにマージすることも意味があったかもしれません。しかし、2つのループをマージしてもプログラム・ロジックが分かりにくくなるだけで、メリットはないと考えました。

コマンド・ラインから番号だけで-printを呼び出せるようにしました。ifrom.pl -print 1 -print 2と指定する代わりに、ifrom.pl 1 2と指定するだけです。ユーザーにとって、はるかに使いやすくなったと思います。コードは非常にシンプルです。まず、$config->args()を呼び出すことを忘れないようにするだけです。

リスト5. -printスイッチのコマンド・ライン引数を取る
# all non-switch command-line arguments are implied -print requests
if (scalar @ARGV)
{
 $config->print($_) foreach @ARGV;
}

まとめ

ifrom.plは、私が今までに書いたツールの中でも最も便利なものの1つです。単純で高速であり、必要なことは何でもできます。この記事とifrom.plが皆さんのお役に立てば幸いです。また、IMAPとMaildirについて得た知識が将来役に立つことがあれば幸いです。

ifrom.plを自由に試してください。改良のヒントがあれば、教えてください。


ダウンロード可能なリソース


関連トピック

  • PerlでIMAPを使う」(developerWorks, 2003年6月)は、Mail::IMAPClient CPANモジュールとifromユーティリティーを使ってのIMAPアクセスについて紹介しています。
  • CPANモジュールのアーカイブである、CPANを見てください。
  • Mail::IMAPClientについて、Mail::IMAPClientのマニュアルのページを調べてください。
  • IMAP4rev1RFC 2060は、今日のバージョンのIMAPプロトコルが定義されているドキュメントです。これは必読です。
  • TAoUPつまりThe Art of Unix Programmingは、Eric Raymond著による素晴らしい本です。これを読むと、なぜUNIX環境で-tunnelオプションが、あれほど単純でありながら強力なのかが理解できるでしょう。
  • safecatは、mboxesをMaildirsに変換するために使われる、素晴らしいツールです。
  • Stunnelというプログラムを使うと、SSL(Secure Sockets Layer)内部で、任意のTCP接続を暗号化できるようになります。UNIX版とWindows版の両方があります。
  • OpenSSHは、ネットワーク接続ツールのSSHプロトコル・スイートの無料版です。(パスワードを含めて)全トラフィックを暗号化し、盗聴、接続ハイジャック、その他のネットワーク・レベル攻撃を、実質的にほとんど無くすことができます。
  • developerWorksのLinuxゾーンにはLinux開発者のための資料が豊富に用意されています。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux, Open source
ArticleID=259481
ArticleTitle=洗練されたPerl: PerlでIMAPを使う 第2回
publish-date=05192005