2013/01/16

OpenSMTPDの試運転と設定/OpenBSD 5.2

今までアップグレードで騙し騙し使っていたOpenBSDなホストが年末にハードウェア障害で退役しました。Pentium !!!の多分800MHzぐらいのマシンで、多分10年以上働いてもらっていたマシンです。

晩年はPCルータとして活躍していたので、急遽別のマシンにOpenBSD 5.2を入れました。で、よい機会なのでOpenSMTPDがどのくらい出来ているか、実際にSMTPサーバを構築して、実用的な設定で試してみました。
[追記] 安定版での記事を書きました → 安定版OpenSMTPD 5.3で仮想メールボックスの試運転/CentOS 6

OpenSMTPDて何や?

OpenSMTPDは、the OpenBSD Projectの一部として現在も実装が進められているフリーでセキュアでシンプルな感じのSMTPサーバ実装です。まだstable releaseではありませんが、ポータブル版としてFreeBSD, NetBSD, DragonflyBSD, MacOS X, そしてLinuxでも動くソースコードが配布されています。

OpenBSD発プロジェクトの成果が事実上の標準になった例としてはOpenSSHが最も有名でしょう。

OpenSSH登場以前は、確か北欧のどっかの会社からsshクライアントを買う必要があったような、別のクライアントを使うと鍵の互換性がなくてログインできない事があるとか、なんか面倒な感じやった印象があります。現在ではssh = OpenSSHと言っても差し支えないぐらい標準です。

SMTPサーバの実装としては、歴史と伝統・信頼と実績のSendmail, その対抗馬として登場し現在では最も選択されるであろうPostfix、DJB好きな人とか高速MTAの土台用(?によく使われるけどずっと放置されてて野良パッチが沢山あるゾンビ化したqmailという所ですが、完成したOpenSMTPDがこれらを駆逐するのか、うまいこと共存するのか、MeTA1と同じ運命をたどるのかはわかりません。

下準備と試運転の内容

インストールは不要です。OpenBSDには最初から/usr/sbin/smtpdとして入っています。マシンが余っていたら他のBSDかMacかLinuxでポータブル版の試運転して書きます。
# uname -a ⏎
OpenBSD mta.example.jp 5.2 GENERIC.MP#339 i386

# ls -laF /usr/sbin/smtpd ⏎
-r-xr-xr-x  1 root  bin  230260 Aug  2 00:49 /usr/sbin/smtpd*

Sendmailを止めておく

OpenBSD 5.2でも標準でsendmailが動いているので、OpenSMTPDの試運転をする前にsendmailを止めておく必要があります。
# /etc/rc.d/sendmail stop ⏎
sendmail(ok)

昔ながらの方法でもよい
# kill -TERM `head -1 /var/run/sendmail.pid`

試運転の内容

ある程度実用的な運用ができるかどうかの試運転なので、下記項目の設定を行いました。
  1. バーチャルドメイン対応とMaildir/への配送
  2. LAN内からのメールはリレーする
  3. 特定のドメイン宛メールはGmailのSMTPサーバへ認証してリレーする
  4. 特定のホストからのSMTP接続は拒否する
  5. 仮想メールボックスへの配送(1/23追記)

設定ファイルを作る

/etc/mail/smtpd.confが設定ファイルです。このファイルとaliasesだけで最低限のMTAは構成できるのですが、sendmailと同じディレクトリなので、外部DBとかでファイル数が増えると散らかりそうなので、今回の試運転では/etc/smtpdってディレクトリを作ってそこにいれました。
# mkdir /etc/smtpd ⏎

設定ファイルとaliasesをコピーして、リンクを作る。
# mv /etc/mail/smtpd.conf /etc/smtpd/ ⏎
# cp /etc/mail/aliases* /etc/smtpd/ ⏎
# ln -s /etc/smtpd/smtpd.conf /etc/mail/smtpd.conf
/usr/sbin/smtpdは設定ファイルの位置がソースコードに直接書いてあるので、/etc/mail/smtpd.confとしてリンクを作っておきます。

で、完成した設定ファイルが下記になります。

設定内容(最初の方)

マクロ

/etc/pf.confと同じ感じで、if_int = "vr0"という形式でマクロを定義して、設定ファイル内で$if_intとして参照します。"$if_int"というように""で囲まれるとマクロは展開されませんので要注意です。

listen on

listen onでLISTENするインターフェイスを指定します。listen on 127.0.0.1とか、listen on 192.0.2.25 port 587とか、別の書き方もできます。

map

map マップ名 source dbまたはplain "ファイルへの絶対パス"
ここではaliases(sendmailのaliasesと同じ、流用できる)と、GmailとSendGridへリレーする時のSMTP認証情報を記録したauthinfo, バーチャルドメイン対応表であるvirtusrs, リレー許可するホスト一覧のrelayhosts の四つを定義しています。

マップの名前は何でもよいのですが、予約名と被る場合(例えばバーチャルドメインの定義をvirtualにする)場合は、"virtual"と、""に入れて書く必要があります。被らない名前を付けたほうが無難っぽいです。

設定内容(ローカル宛配送)

# local宛(*@mta.example.jp)は~/Maildirへ配送する
accept for local alias aliases deliver to maildir
デフォルトのsmtpd.confにはdeliver to mboxと書いていて、/var/mail/ユーザ名 に配送されますが、今回は近代的なMaildir/への配送をする為に、末尾を上記のようにmaildirとしました。

配送先ユーザの$HOMEにMaildir/が無ければ勝手に作って配送されます。配送先ユーザの権限でメールが作成されるので、ログインアカウントを持たないユーザへの配送は、別のMDAを使ってうまいことやる必要があります(実験未了)。

for localはローカル(*@mta.example.jp, hostnameコマンドで得られるホスト名)に存在するユーザ宛、alias aliasesはaliases.dbに定義されたエイリアス宛という意味になります。

for 何とかで宛先を表す、電車の行き先にfor Kyotoとか書いてるのと同じですね。

設定内容(バーチャルドメイン対応)

accept from all for virtual map "virtusers" deliver to maildir
virtusers.dbに定義したバーチャルドメインなメールアドレスで受けとったメールは、各ユーザの~/Maildirへ配送されます。

/etc/smtpd/virtusers

virtusersは下記のような形式で記述します。
kyoto.azumakuniyuki.org         OK
kyoto.example.jp                TRUE
example.co.jp                   NEKO

neko@kyoto.azumakuniyuki.org    mikeneko
cat@kyoto.example.jp            kijitora
tama@example.co.jp              ekicho
@example.co.jp                  blackhole
miichan@kyoto.example.jp        mikeneko@example.net
sendmailのvirtuser-domainsとvirtusertable(.db)が結合したようなフォーマットですが、同一ではありません。

virtusers: 受けとるドメイン

最初の三行で、このホストが受けとるドメインを定義します。このエントリがないといくら下のメールアドレスとユーザの対応を書いてもメールは受けとりません。

左辺値にメールを受けとるドメイン名、右辺値はなんでもよいっぽいです。FALSEって書いても受信しましたので、右辺値がNEKOでもCATでも、おそらく左辺値が一致さえすればよいです、多分。

virtuser: メールアドレスの定義

後半の四行で、受けとるべきメールアドレスと配送先となるローカルユーザの対応を書きます。このあたりはsendmailのvirtusertableと同じですが、%0,%1とかは使えないようです。

cat@kyoto.example.jp宛メールが/home/kijitora/Maildir/へ配送される、という流れです。miichan@kyoto.example.jpはローカル宛ではなく別ホストのアドレスへ転送する例です。これはsendmailのvirtusertableと同じ書き方ができます。

makemap virtusers

virtusersファイルを書き終わったらmakemapしますが、使用するのはOpenSMTPD用のmakemapプログラムで、/usr/libexec/smtpdの中にあります。
# /usr/libexec/smtpd/makemap virtusers ⏎
# ls -alF ./virtusers* ⏎
-rw-r--r--  1 root  wheel     191 Jan 10 05:16 virtusers
-rw-r--r--  1 root  wheel  131072 Jan 10 05:16 virtusers.db

設定内容(リレーの設定)

特定ドメイン宛のリレー先を定義(GmailとSendGrid)

特定ドメイン宛のリレー先定義は、sendmailのmailertableのような役割です。作ったsmtpd.confには三つほどリレー先を定義しています。
accept for domain "azumakuniyuki.org" relay via "tls+auth://smtp.sendgrid.net:587" auth "authinfo"
accept for domain "*.example.com" relay via "tls+auth://smtp.gmail.com:587" auth "authinfo"
accept for domain "example.org" relay via "smtp://relay.example.jp"
一つ目は、宛先メールアドレスが*@azumakuniyuki.orgであった場合、SendGridのサーバ smtp.sendgrid.netの587番ポートへ繋いで、SMTP-AUTHの認証を経てリレーする、です。

二つ目も同じで、宛先メールアドレスが*@*.example.com(全てのサブドメインに一致)であったら、Gmailのサーバ smtp.gmail.comの587番へ認証を通してリレーする、となります。

スキーマは"tls+auth"以外に"smtps+auth"とか"ssl+auth"とか、いくつかあって/usr/src/usr.sbin/smtpd/util.c の584行目あたりに書いてあります。この書き方は最近変更になったもので、以前は relay via smtp.gmail.com port 587 のような書き方でしたので注意が必要です。

/etc/smtpd/authinfo

リレーする際の認証で必要なユーザ名とパスワードは、先に定義したauthinfoというマップに書いておきます。
smtp.sendgrid.net               username:sendgridpassword
smtp.gmail.com                  username@gmail.com:gmailpassword
認証情報のファイルはsendmailに倣ってauthinfo.dbとしてますが、別に何でもいいです。smtpd.confのマニュアルにはsecret.dbで例示されています。ファイルを書いたらmakemapします。パスワードが書いているので、パーミッションを厳しくしておきます。
# chown root._smtpd ./authinfo ⏎
# chmod 640 ./authinfo ⏎
# /usr/libexec/smtpd/makemap authinfo ⏎
# ls -alF ./authinfo* ⏎
-rw-r-----  1 root  _smtpd     157 Jan 12 21:15 authinfo
-rw-r-----  1 root  _smtpd  131072 Jan 12 21:14 authinfo.db
三つ目は、単純なSMTPでのリレーで、*@example.org宛メールがrelay.example.jpの25番ポート経由でリレーされる、となります。これはsendmailのmailertableによく書くアレと同じです、たぶん。

LAN内のホストからリレーを許可

sendmailのaccess.dbやrelay-domains, PostfixのmynetworksにIPアドレスを列挙してリレーを許可するあれです。
accept from map "relayhosts" for all relay
accept from "192.0.2.0/24" for all relayという感じでsmtpd.confに直接書いても同じですが、別ファイルにしたほうが管理しやすいので、/etc/smtpd/relay-domainsというファイルを作りました。

/etc/smtpd/relay-domains

192.0.2.0/24
smtpd.confでのマップ名はrelayhostsですが、ファイル名はrelay-domainsです。sendmailの設定ファイルを流用できるかと思ってそのまま持ってきたので、relay-domainsです。

このファイルはsource plainで定義しているマップなので、makemapしませんし、左辺値しか存在しないファイルとなります。つまり、一致するエントリさえあれば良いわけです。

sendmailのそれと違って、ちゃんとしたCIDRでの表記ができます。逆に192.0.2というsendmailでの文字列一致的な表記では意図した通りにリレーが許可されません。

このホストからのリレーを許可

accept for all relay
省略せずに書くと、accept from local for all relay になります。このホスト(mta.example.jp)からはどこ宛でもリレーする設定です。これは最初から入っている設定なので、そのまま残してあります。
accept for all relay as "@example.jp"
as "@ドメイン"と書くと、sendmailのgenericsのような書換が行われます。例えば
# hostname | mail -s 'test' neko@example.com
のようにメールを送ると、送られるメールの発信者はroot@mta.example.jpとなりますが、これをroot@example.jpとしたい場合には有効です。

/usr/sbin/smtpdの起動

一通り必要なファイルの作成とmakemapで準備が出来たら、文法チェックしてデバッグモードで起動して試験して、問題なさそうならデーモンとして起動します。
# pwd ⏎
/etc/smtpd
# ls -laF ⏎
total 1040
drwxr-xr-x   3 root  wheel      512 Jan 10 17:14 ./
drwxr-xr-x  31 root  wheel     2560 Jan 10 16:44 ../
-rw-r--r--   1 root  wheel     1787 Jan 10 23:35 aliases
-rw-r--r--   1 root  wheel   131072 Jan 10 23:35 aliases.db
-rw-r-----   1 root  _smtpd     157 Jan 12 21:15 authinfo
-rw-r-----   1 root  _smtpd  131072 Jan 12 21:14 authinfo.db
-rw-r--r--   1 root  wheel       14 Jan 13 02:07 relay-domains
-rw-r--r--   1 root  wheel     2485 Jan 15 20:06 smtpd.conf
-rw-r--r--   1 root  wheel      191 Jan 10 05:16 virtusers
-rw-r--r--   1 root  wheel   131072 Jan 10 05:16 virtusers.db

文法チェックとデバッグモードで起動

smtpd.confに文法エラーがあると起動しませんので、起動前に文法チェックしておきます。
# /usr/sbin/smtpd -n ⏎
configuration OK
親切にOKと返してくれます。エラーがあれば直すべき行番号がでます。sendmailはわりと前のほうで止めていますので、まずはデバッグモードで起動して試験します。
# /usr/sbin/smtpd -vd ⏎
using "fs" queue backend
using "ramqueue" scheduler backend
startup [debug mode]
parent_send_config: configuring smtp
...
smtpd: scanning offline queue...
smtpd: offline scanning done
-d(デバッグ?)と-v(冗長)で起動すると標準出力にログがいっぱいでます。この状態でtelnet localhost 25したり、mailコマンドで配送したり、いろいろテストします。

デーモンで起動

明示的にaccept from all for all relayと書かない限りオープンリレーにはなりません(たぶん)ので、-vdで起動して問題なさそうならデーモンになってもらいます。
# /usr/sbin/smtpd ⏎
using "fs" queue backend
using "ramqueue" scheduler backend

# ps ax | grep smtpd ⏎
12821 ??  Ss      0:00.01 smtpd: [priv] (smtpd)
28359 ??  I       0:00.02 smtpd: scheduler (smtpd)
13930 ??  I       0:00.01 smtpd: queue (smtpd)
24948 ??  I       0:00.02 smtpd: mail transfer agent (smtpd)
 6144 ??  I       0:00.01 smtpd: mail filter agent (smtpd)
26499 ??  I       0:00.01 smtpd: mail delivery agent (smtpd)
22507 ??  I       0:00.01 smtpd: lookup agent (smtpd)
20805 ??  I       0:00.01 smtpd: control (smtpd)
29025 ??  I       0:00.04 smtpd: smtp server (smtpd)
29729 p0  R+/0    0:00.00 grep smtpd (zsh)
sendmailのような一枚岩プロセスではない、役割毎に分離されたプロセスが動いています。各プロセスは専用のユーザ"_smtpd"で動いています。

/usr/sbin/smtpctl

よく実行しそうな操作のために制御用コマンドsmtpctlがあります。とりあえずはキューの表示をやってみました。 show qでもshow queでも動きます。
# /usr/sbin/smtpctl show queue ⏎
MTA|c4329a9c2f084a00|-|a@mta.example.jp|b@example.org|1358237589|345600|1|190 secrets lookup failed
MTA|df1113571737bcdd|-|a@mta.example.jp|b@azumakuniyuki.org|1358237940|345600|1|150 Can not connect to MX
MDA|87272534cc71c1fe|-|a@example.jp|neko@example.co.jp|1358237568|345600|2|"mkdir cur failed: Permission denied"
各項目が``|''で区切られているので、他のMTAで実行したmailqコマンドの出力と比べると機械的に処理しやすそうです。

上記では実験で失敗したメールキューが残っていますが、メールキューの正しい捨て方が不明なので、/var/spool/smtpd の中にあるファイルをrmしました。smtpctl remove IDでキューが捨てられそうな感じでしたが、実行するとシグナル11が飛んで行ってsmtpdが終了したので...

他、smtpctl stopでプロセスの停止が出来ますが、restartやstartは(まだ?)ありません。たぶん /etc/rc.d/smtpd でやるのが正しい作法になると思います。

かんそう

わりと使える?

シンプルかつ堅牢な感じです。/usr/src/usr.sbin/smtpdのソースコードもべらぼうな量でもないです。wc -l *.{c,h}で20000行ちょいでした。

設定ファイルの構成も単純明快で、小規模なリレーサーバとしてならもう使ってもよさそうですし、mboxやMaildir/への配送も出来るので、dovecotでも立てればメールボックスの提供も出来そうです。

またTLS/SMTPSの設定も(今回はやってませんが)容易にできそうです。動作するかどうかの観点からはほぼ充分にちゃんと動く、です。

バウンス

bouncehammerってものを開発しているので、このあたりは個人的に重要項目です。

既定の動作でちゃ〜んとSMTPセッション中にエラーを返してきたので安心しました。エラーメッセージがどれも 530 5.0.0 Recipient rejected: で、まだエラー毎のD.S.N.とメッセージは実装してないのかなぁという感じではありますが。

もしsendmailを置き換えるなら

/etc/mailer.confの編集をして、/etc/rc.conf.localにsendmail_flags="NO"とsmtpd_flags=""ですぐにでも置き換え可能です。このあたりはsmtpd.conf(5)に書いてあります。

SASLv2はまだっぽい

しかし、クライアントが587番に繋いでSMTP-AUTHしてメールを投函するのに、SASLv2を使うという一般的な構成がまだ出来なさそうで、/usr/src/usr.sbin/smtpdのソースコードにはそれっぽいコードがありませんでした。

なので、どうしてもOpenSMTPD使いたいしSubmissionも対応したいって場合は、submitだけsendmailが受け付けて、それ以外のMTAの仕事はOpenSMTPDがやるとかになります、たぶん。

UNIXユーザでないMaildirへの配送

実験したのは/homeにディレクトリが存在するユーザへのMaildir/配送ですが、今どきのメールサーバはログイン用アカウントなし・メールボックスだけある、という構成が殆どでしょう。

/var/mailbox/example.co.jp/tama/Maildir に配送する為にprocmailやmaildropのMDAを用意して、うまいこと設定したら、そのあたりはできそうな気がします。

2013/01/23追記

Milterはまだ

grep -i milter /usr/src/usr.sbin/smtpd/* が何もないので未対応ですが、実装計画があるって話も出ているようです。実際にゲートウェイでのウィルス検査やろうとしたらclamav-milter使う事が多いので、実用的なMTAに於てmilterは必要でしょう。

他の設定値

smtpdやsmtpd.confのmanを見る限り、キューの有効期限や受けとるメールの最大値の設定はありますが、プロセス数や並列数等のパフォーマンス関係の設定値は記述がありませんでした。

smtpd.confの文法もちょいちょい変更が入っているので、この記事を含めWebの情報が役に立たなくなる変更があるかもしれません。インストールしたホストで見るmanが確実な情報源でしょう。

他の実験

メールボックスだけのユーザ宛配送とか、spamdの後ろで動かすとか、パイプでプログラムに渡すとか、root宛だけ/var/mail/rootに配送するとか、ゴミメールを/dev/nullに捨てるとか、いろいろ。

これから

未だstableではないので、まだまだsendmailやpostfixの代替にはならないかなって印象です。来年あたりでstableになって、本番環境で活躍するのは平成26年とか27年あたりになって導入されるかなぁって感じです。

あるいは、小さく軽量なので、高速配信MTAとして活躍するのかなぁとか、そんな気もします。

2 件のコメント:

  1. maildropをMDAにして、仮想メールボックス(バーチャルメールボックス)へ配送する話を書きました。
    http://blog.azumakuniyuki.org/2013/01/virtual-mailbox-with-maildrop-and.html

    返信削除
  2. 安定版のOpenSMTPD 5.3.1での試運転記事書きました >> http://blog.azumakuniyuki.org/2013/05/virtual-mailbox-with-opensmtpd-531-on.html

    返信削除