バーチャルドメイン(1)

この文書は、Thomas Pircher 氏 <tehpeh@gmx.net> が、http://www.tty1.net/virtual_domains_en.html で公開している、"Virtual Domains with Exim + Courier-IMAP + MySQL" Version 0.6 (2003年3月17日) の和訳です。完全な逐語訳ではなく、適宜注釈や書き換えを含んでいます。

Exim + Courier-IMAP + MySQL によるバーチャルドメイン

まず、exim4-config ファイルを提供してくれた James Harr 氏、MySQL関係 (ENCRYPT() 関数の使用やコンパイルについてのノウハウ) を提案してくれた David Buxton 氏、そして Python のスクリプトを提供してくれた Michael Fischer v. Mollard 氏に感謝します。

はじめに

この文書では、EximCourier IMAPMySQL データベースで「バーチャルドメイン」(Virtual Domain) を構築する方法について説明します。 バーチャルドメインとは、複数のドメイン宛のメールを1台のホストで受信したり送信したりするシステムです。 DNSサーバーでのMXレコードさえ適切に設定されていれば、どんなドメインのメールでも扱えるようになります。 バーチャルドメイン環境では、すべてのユーザー情報・ドメイン情報は、SQLデータベースに保存されます。 この文書では、上記のそれぞれのプログラム自体について一から詳しく説明はしません。 すでにこれらのプログラムが正しく動作し、私たちの目的を達成できるよう調整(つまり、EximにMySQLサポートを組み込んでコンパイルする、など)されていることを前提とします。

このガイドが誰かの役に立てば、と思っています。ご意見・批判など歓迎します。いただいたご意見は、次期バージョンに役立てるつもりです。

準備

exim と Courier IMAP は、mysql 対応を含んでコンパイルしなければなりません (または、お好みで $DISTRIBUTOR に mysql プラグインがインストールされてなければなりません)。

MySQLの準備

まず、maildbという名のデータベースが存在し、ユーザー mail でアクセス可能な状態にします。

create database maildb;
grant select,insert,update,delete on maildb.* to mail@localhost identified by 'secret';
flush privileges;

このデータベース中に、usersという名のテーブルを作成します:

use maildb;
CREATE TABLE users (
  id char(128) DEFAULT '' NOT NULL,
  crypt char(128) DEFAULT '' NOT NULL,
  clear char(128) DEFAULT '' NOT NULL,
  name char(128) DEFAULT '' NOT NULL,
  uid int(10) unsigned DEFAULT '65534' NOT NULL,
  gid int(10) unsigned DEFAULT '65534' NOT NULL,
  home char(255) DEFAULT '' NOT NULL,
  maildir char(255) DEFAULT '' NOT NULL,
  quota char(255) DEFAULT '' NOT NULL,
  KEY id (id(128))
);

ちなみに、これは Courier-IMAP が推奨するテーブル構造です。それぞれのフィールドが何を意味するかはきわめて直感的でわかりやすいでしょうが、もしわからなければ、(Courier-IMAPに付属の) ファイル authmysqlrc を参照すれば詳しく書いてあります。とりあえずこの例では、ユーザーのホームディレクトリはシステムユーザー mail に属するものとし、2つのフィールド uidgid はそれに即したものとしておきます (Debianだと、uidとgidはそれぞれ8と8です)。

テスト用のドメインを用意する

さて、ここからの例では、バーチャルドメインのメールは /usr/local/vdomains/ に保存されるものとします。たとえば、example.comドメインのユーザーのホームディレクトリは /usr/local/vdomains/example.com/users/username/ になります。それぞれのユーザーのホームディレクトリには、Maildir というサブフォルダが必要です。このサブフォルダは、Courier-IMAP に付属の maildirmake というコマンドで作成する必要があります (または、シェルから "mkdir -m 0700 Maildir; mkdir -m 0700 Maildir/{cur,new,tmp}" でもOK)。 話を単純にするために、とりあえずすべてのユーザーのメールフォルダが、システムユーザー mail に所属するものとして、exim と courier imap (どちらもユーザーmailで動かす) が書き込めるようにしておきます。まぁ、すべてのユーザーがIMAPサーバー経由でメールボックスを操作する限りはこれで大丈夫でしょう。
最後に、このドメインをテーブル domains に書き込みます。domainsテーブルは、次のように定義します:

CREATE TABLE domains (
userid char(128) NOT NULL default '',
KEY userid (userid)
);
INSERT INTO domains (userid) VALUES ("example.com");

テストユーザーを作る

ユーザー (ここでは kasperl@example.com) を作りますが、そのためにはホームディレクトリを作り、データベースに登録しなければなりません:

INSERT INTO users (id, crypt, clear, name, home, maildir) VALUES (
"kasperl@example.com", "jnk.l3QByZvdk", "lrepsak", "Kasperl",
"/usr/local/vdomains/example.com/users/kasperl",
"/usr/local/vdomains/example.com/users/kasperl/Maildir/");

ちなみに、cryptフィールドの値は、次のようなperlスクリプトで用意します:

#!/usr/bin/perl

if( $#ARGV != 0 )
{
print "usage: vcrypt password\n";
exit 1;
}
my $salt = join '', ('.', '/', 0..9,'A'..'Z', 'a'..'z')[rand 64, rand 64];
print crypt($ARGV[0], $salt) ."\n";

もっとも、パスワードは MySQL の ENCRYPT() 関数で作ることができるのですが、OSが暗号化ユーティリティを用意しているような環境でしか使えません (たとえば WinNT 用の MySQL などでは使えない技です)。

Courier-IMAP を準備する

Courier-IMAPサーバーのインストールが終わったところから解説します。authdaemon の設定ファイル (Debian だと /etc/coueier/authdaemonrc) 中の authmodulelist の項目に authmysql モジュールを追加します。(注: もし MySQL 4 を使っているのであれば、'configure' ファイル中のすべての mysql_connect を mysql_real_connect に換える必要があります)。
次に、authmysqlrc を編集します。MYSQL_SERVER, MYSQL_USERNAME, MYSQL_PASSWORD, MYSQL_DATABASE の4つの項目を適切に設定してください。ここでは簡単のため、次のように設定します。

MYSQL_SERVER            localhost
MYSQL_USERNAME mail
MYSQL_PASSWORD secret
MYSQL_DATABASE maildb
MYSQL_USER_TABLE users
MYSQL_CRYPT_PWFIELD crypt
MYSQL_LOGIN_FIELD id
MYSQL_HOME_FIELD home
MYSQL_NAME_FIELD name

このファイルにはユーザー mail 用のMySQLのパスワードをプレインテキストで書き込むので、root しか開けないようにするのが最良だと思います。

IMAPアカウントを試す

IMAPサーバーに telnet して、上で作成したアカウントを試してみます:

telnet my.server.com imap
* OK Courier-IMAP ready. Copyright 1998-2002 Double Precision, Inc.
1 login kasperl@example.com lrepsak
1 OK LOGIN Ok.
5 select INBOX
* FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
* OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
* 0 EXISTS
* 0 RECENT
* OK [UIDVALIDITY 1021381622] Ok
5 OK [READ-WRITE] Ok
6 logout
* BYE Courier-IMAP server shutting down
6 OK LOGOUT completed

まぁ悪くないでしょう。Kasperl は、IMAPサーバーで認証を受け、自分自身のメールボックスからメールを読んだり書き込んだりできるようになりました。

exim の設定

exim の Maildir サポート

If you want to use Maildir as the default mailbox style, you have to change the local_delivery-transport. (This transport must not have the same name on your machine, it is the one which is called for local deliveries by a director with "driver = localuser".) Here is an example (from a default Debian config file, modified)

local_delivery:
  driver = appendfile
  group = mail
  mode = 0660
  mode_fail_narrower = false
  envelope_to_add = true
  return_path_add = true
  directory = /home/${local_part}/Maildir/
  maildir_format

This configuration makes exim store the mails for system users in the Maildir directory within their homedirectory. Add a second transport, address_directory, which will be used to store redirected mails (redirected by a users .forward file, or, as in our case, by a entry in the database):

address_directory:
  driver = appendfile
  no_from_hack
  prefix = ""
  suffix = ""
  maildir_format

Now set up the appropriate directors (in the directors section) by adding a "directory_transport = address_directory". (I added it everywhere I found a line like "directory_transport = ..."). Doing so, every filename (in the systemwide alias-file, or in the users .forward-file or in the database) ending with a slash is trod as a Maildir.
Note: as with exim-4.14 and later the pending slash may not necessary, but it won't hurt to append it anyway.

SQL support for exim

In order to set up a connection to the database, define the variable "mysql_servers" at the top of exim.conf:

hide mysql_servers = localhost/maildb/mail/secret

The hide statement is necessary to protect the mysql-password from being printed when a user calls exim with the -bP option. Now every domain exim accept mails for, must be inserted in the local_domains list. This can be simply done with a sql-statement:

local_domains = localhost:my.server.com:\
mysql;SELECT userid FROM domains WHERE userid='$key';

In order to handle incoming mail, a new director has to be defined. Let us call it mysql_aliases:

mysql_aliases:
driver = aliasfile
file_transport = address_file
directory_transport = address_directory
pipe_transport = address_pipe
search_type = mysql
query = "SELECT maildir FROM users where id = '$local_part@$domain';"
user = mail
group = mail

Testing exim

A sample test session may look:

telnet localhost smtp
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 my.server.com ESMTP Exim 3.35 #1 Wed, 05 Jun 2002 06:48:31 +0200
mail from: xx@yy.zz
250 <xx@yy.zz> is syntactically correct
rcpt to: kasperl@example.com
250 <kasperl@example.com> verified
data
354 Enter message, ending with "." on a line by itself
Subject: Test

1, 2, 3 test
.

250 OK id=17FSnP-0000Cl-00
quit
221 my.server.com closing connection
Connection closed by foreign host.

Should this test not be passed, read the output of "exim -d2 -bt kasperl@example.com" and check the mysql-log. In exims FAQ there is a section with various debug-techniques, which I found very useful.

Some notes about exim4

The configuration of exim4 is quite similar to the above example. Here is an example of the router for virtual domains:

virtual_user:
  driver = redirect
  allow_fail
  allow_defer
  data = ${lookup mysql{ SELECT maildir FROM users \
  WHERE id='${local_part}@${domain}' }}
  directory_transport = address_directory

If you use /etc/aliases with unqualified usernames, let's say:

root: kasperl@example.com
then not only mails to root@local.domain.of.exim will be delivered to kasperl, but every mail which has root as local part. The option check_local_user doesn't look at the domain, so you must add a condition to the system_aliases router:
  condition = ${if {eq{$domain}{$primary_hostname}} {yes}{no}}
will do the job.

Some considerations

Sample Config Files

Here are the official sites of exim, Courier IMAP and MySQL.
Jani Reinikainen wrote a very pretty Exim, Amavis, Qpopper with TLS+MySQL Auth Mini How-To.
The Document Virtual Mail Router Design explains how to set up a virtual Domain with exim, with LDAP authentication.
Useful patches for exim:
If you plan to run a webmail-service, have a look at squirrelmail, a feature-rich webmail-application, wich retreives the data from a IMAP-server. No need to worry about MySQL-Integration.
The asr-manpages are very helpful for many problems a sysadmin can encounter in his life.

Copying

Copyright (c)   2003   Thomas Pircher <tehpeh@gmx.net>
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation.

  01.Apr.2003   email to the webmaster