🙆

メール転送エージェント qmail を読む

2021/07/09に公開

qmail とは

qmail - Wikipedia

qmail(キューメイル)は、ダニエル・バーンスタインにより開発されたメール転送エージェント (MTA)。Unix系オペレーティングシステム (OS) で動作する。現在の最新バージョンは1998年6月15日にリリースされた1.03。

MTA つまり Sendmail とか Postfix の仲間です。

MTA としてはだいぶ小さいのがウリのようなので読んでみます。

qmail は複数のプロセスが協調して動作します。そう、モジュールじゃなくてプロセスです。いまどきでいうマイクロサービスみたいな感じです。

qmail の動作 に図解されたものがあるので図を見ると分かりやすいです。この qmail-ナントカという 1 つ 1 つがプロセスです。

起動処理

なにはともあれ起動処理から見ていきます。

/var/qmail/rc

qmail を起動するには /var/qmail/rc を実行します。 /var/qmail/rc は以下のとおり。

#!/bin/sh

# Using splogger to send the log through syslog.
# Using qmail-local to deliver messages to ~/Mailbox by default.

exec env - PATH="/var/qmail/bin:$PATH" \
qmail-start ./Mailbox splogger qmail

env - することで既存の環境変数をクリアします。 env - については どさにっき - デーモンさんの起こしかた も参照するとよいでしょう。

exec することでシェルと同じプロセスとして起動し、 kill できるようになります ( ref. UNIXの部屋 コマンド検索:exec (*BSD/Linux) )

あとは qmail-start./Mailbox splogger qmail を渡します。

qmail-start

qmail-start を読みます。 main からざっと見ていきます。

void main(argc,argv)
int argc;
char **argv;
{
  if (chdir("/") == -1) die();
  umask(077);
  if (prot_gid(auto_gidq) == -1) die();

  if (fd_copy(2,0) == -1) die();
  if (fd_copy(3,0) == -1) die();
  if (fd_copy(4,0) == -1) die();
  if (fd_copy(5,0) == -1) die();
  if (fd_copy(6,0) == -1) die();

  if (argv[1]) {
    qlargs[1] = argv[1];
    ++argv;
  }

  if (argv[1]) {
    if (pipe(pi0) == -1) die();
    switch(fork()) {
      case -1:
        die();
      case 0:
        if (prot_gid(auto_gidn) == -1) die();
        if (prot_uid(auto_uidl) == -1) die();
        close(pi0[1]);
        if (fd_move(0,pi0[0]) == -1) die();
        close23456();
        execvp(argv[1],argv + 1);
        die();
    }
    close(pi0[0]);
    if (fd_move(1,pi0[1]) == -1) die();
  }

ここでは子プロセスを起動するための準備をしています。ファイルディスクリプタを閉じたりパイプを作ったりするのは親子プロセスをプログラムするときの定石です ( ref. Unix Programming Frequently Asked Questions - 1. Process Control とか パイプによるプロセス間通信 [Linux] - Web/DB プログラミング徹底解説 図があります )

fd_copy() はファイルディスクリプタをコピーしていますが、ようするに使わないファイルディスクリプタを無効にしているようです。後に子プロセスを起動するときに ( すでに開いているファイルディスクリプタがあれば ) ファイルディスクリプタを継承させないようにしているのだと思います( ref. ファイル・ディスクリプタ ) 0, 1, 2 はそれぞれ標準入力、出力、エラーですが 3 以降はなんでしょう。

argv には子プロセスへの引数として扱われます。

qlargs は qmail-start.c 冒頭で定義されています。 qmail-lspawn への引数です。

char *(qsargs[]) = { "qmail-send", 0 };
char *(qcargs[]) = { "qmail-clean", 0 };
char *(qlargs[]) = { "qmail-lspawn", "./Mailbox", 0 };
char *(qrargs[]) = { "qmail-rspawn", 0 };

fork() が 0 を返すのは子プロセスのほうなので、 case 0: の中は子プロセスの処理です。

execvp(argv[1],argv + 1); でさらにプロセスを起動しています。ここに到達する時点では argv"splogger qmail" が入っています。つまり execvp()splogger を起動し、 splogger の引数として argv + 1 以降の配列を渡しています。

以降は同じように qmail-send, qmail-clean, qmail-rspawn を起動しています。

Discussion