Table of Contents [Close]

FreeBSD で spam 対策

2008/12/20〜2009/02/27

ここ数年、spam メールが非常に増えた。
これはうちのサイトだけではなく、全世界的な傾向だ。 先日、世界的に spam を送信しているボットネットをコントロールしていたサイトが、 強制的にインターネットから排除された。 そのため、世界中に流れる spam メールが4割ぐらい減った。 実際、自サイトに届く spam メール数も今までの半分以下に激減した。
だが、別のサイトからボットネットをコントロールすることに成功したらしく、 効果は2週間ぐらいしか続かず、今は、また元通りの流量になってしまった。
(コンピュータを乗っ取られて)spam メールを送信してしまうサイト、 spam サイトから spam メールを受け取ってしまうサイトがあるかぎり、 自分のところに来る spam メールはなくならない。

自サイトでは、2005年ぐらいまでは、1日数通ぐらいでかわいいものだったが、 2006年初めから突然、大量の spam が届くようになった。
そこで、sendmail の spamlist をメールの From とかから手作業で集めていたが (当時はよくわからなかったので)、 あまり効果が上がらず、spam 拒否率は 60% ぐらいから徐々に落ち始め、 最終的には 30% を切るようになって、大量の spam が届くようになってしまった。
2007年ぐらいから、Received ヘッダなどを解析するツールを作り、 接続元の IP アドレスを収集し、DNS を逆引きし spam サイトのドメイン名などを見つけ、 拒否するドメインやネットワーク、IP アドレスなどを見つけるプログラムを作った。 これで sendmail.smaplist と sendmail.deny を作った。 これによって、30% に落ちていた拒否率が一気に 70% を越えるまで回復した。
しかし、その後、徐々に落ち始め最近では 50% ぐらいまで落ちてしまった。。。

現在、spam メール対策としてメールサーバ(MTA)にいくつかの対策をすることが薦められている。 この対策が無いと、将来的には自分のサイトからメールを送信しても、 まともにメールを受け取ってくれなくなる可能性がある。
ということで 自分のサーバにいろいろな対策を入れ、 spam メール拒否率アップとメールセキュリティ増強をしてみたいと思う。

SPF (Sender Policy Framework)

SPF は送信サイトがどのサーバからメールを送信するかを表明することで、 偽装したメールアドレスの spam メールを排除する手助けをするものだ。 たとえば、 「foo.org サイトからのメールは、foo.org 以外からは送信されません。だからそれ以外は拒否して。」 という表明をしておけば、 送信者 abc@foo.org のメールが bar.org から送信されたときには、 受け取ったメールサーバは「そのメールは信頼できないぞ」と判断することができる。

MTA の送信用の設定としては、DNS の TXT 情報としてこの情報を設定するだけだ (受信用の設定は送信メールアドレスから DNS をアクセスし SPF 情報を取得・判定し、 信頼できないメール受信を拒否するメールフィルタ(spf-milter)が必要なので後回し)。

2008/12/20
spf, setting DNS ...1
では DNS(bind9) の設定をしてみよう!
DNS の設定は設定ファイルを書き換えるだけだ。 ゾーン設定ファイル /etc/namedb/marimo.org.zone の末尾に以下を追加して、 bind をリロードしてみる

; SPF
sample.marimo.org. IN     TXT     "v=spf1 +mx ~all"
+mx は 「marimo.org からのメールのうち MX で指定したサーバからのメールは信頼してよい」、 ~all は 「それ以外のサーバからのメールは拒否すべきだと思う」という設定。
まず、DNS にちゃんと設定されているか確認。
% dig sample.marimo.org a txt +norec
    :
;; ANSWER SECTION:
sample.marimo.org.        1D IN TXT       "v=spf1 +mx ~all"
    :
どうやら設定はされているようだ。
ところで、これ、どうやって動作確認すればいいんだ?
あ!! そうだ、Gmail は SPF をちゃんと見ているらしいぞ。 自分の Gmail のアカウントにメールを出せばいいのか。
    :
Received-SPF: neutral (google.com: XXX.XXX.XXX.XXX is neither permitted nor denied by best guess record for domain of xxxxxxx@marimo.org)
    :
うーん SPF はちゃんと見てくれているようだが(SoftFail や Fail じゃない)、 結果は「neutral(中立:判断できかねる)」かぁ。 どうしたら「pass(安全)」になるんだ?

spf, setting DNS ...2
sample.marimo.org だけではなく、 marimo.org にも SPF を設定するのか? marimo.org.zone

                IN      TXT     "v=spf1 +mx ~all"
を追加で、リロード。Gmail に送信。
    :
Received-SPF: neutral (google.com: XXX.XXX.XXX.XXX is neither permitted nor denied by best guess record for domain of xxxxxxx@marimo.org)
    :
うー、なぜだぁ?

spf, setting DNS ...3
もしかして、、、テストだからと少し緩めに設定していたが、 -all にして 「marimo.org のメールで MX で指定したメールサーバ以外からのメールはすべて拒否して」 にしてみよう。

                IN      TXT     "v=spf1 +mx -all"
        (略)
; SPF
sample.marimo.org. IN     TXT     "v=spf1 +mx -all"
これで Gmail の結果は、、、
    :
Received-SPF: pass (google.com: domain of xxxxxxx@marimo.org designates XXX.XXX.XXX.XXX as permitted sender)
    :
すばらしい!! pass になった〜。

DKIM (DomainKeys Identified Mail)

DKIM(ディーキム) は以前は Yahoo! が提唱していた Domain-Key と Cisco の IIM が合わさった仕組み。 これから主流になると言われていいて、SPF よりも強固な認証をする。

簡単に言えば、送信したメールのヘッダに証明書を付けて送信する。 受け取った側はメールアドレスの DNS から鍵を取得して、証明書を検証する。 こうすることで、確実に送信元から送信されたメールであること (偽装されたメールでないこと)が確認できるわけだ。

2008/12/25
dkim, checking sendmail
DKIM は送信する際にメールヘッダを追加する必要があるので、 送信対応だけでもメールフィルタの組み込みが必要だ。 まずは、自分でコンパイルして使っている sendmail にメールフィルタを組み込むための MILTER が組み込まれているか確認してみる。

% sendmail -bt -d0.1 < /dev/null
Version 8.1X.X
 Compiled with: DNSMAP LOG MAP_REGEX MATCHGECOS MILTER MIME7TO8 MIME8TO7
	(略)
ここに MILTER があるので、sendmail を作り直す必要はないようだ。

dkim, compiling openssl0.9.8i
で、 DKIM のコンパイルには openssl 0.9.8 系が必要だそうだ。 自サーバの openssl は 0.9.7系だけど、 これを置き換えちゃうと、今までリンクしている他のソフトもリンクしなおさないといけないので、 置き換えは見送って並行利用にしよう。
OpenSSL から 0.9.8i をダウンロードして、コンパイル。 そして /usr/local/ssl8 にインストール。 これは何の問題もなく完了。

dkim, compiling dkim-milter ...1
次に DKIM のソース dkim-milter-2.7.2.tar.gz をダウンロードして、コンパイルの準備をする。
うーん なんで Build スクリプトなんだぁ?コンパイル環境を自動構築してくれないじゃないか。 configure とかにしてくれればいいんだが。。。
まず devtools/Site/site.config.m4 を新規に作成する。

% tar xvzf dkim-milter-2.7.2.tar.gz
% cd dkim-milter-2.7.2
% vi devtools/Site/site.config.m4
define(`confMTCCOPTS', `-pthread') define(`confMTLDOPTS', `-pthread') APPENDDEF(`confINCDIRS', `-I/usr/local/ssl8/include') APPENDDEF(`confLIBDIRS', `-L/usr/local/ssl8/lib')
でコンパイル。
% ./Build
      :
util.c:841: `addr' undeclared (first use in this function)
ほらぁ 何かエラー出たよぉ (;_;)。
とりあえず、(何か編集するかもしれないから) コンパイルエラーが出ている dkim-filter/util.c をバックアップして、 何とかせねば。
% cd dkim-filter/
% cp util.c util.c.org
% vi util.c
      :
util.c のコードを読んでみるっと、
        in_addr_t addr;
この in_addr_t 型が無いんだな。 確か sys/types.h にあったような、、、 お〜、うちのマシンには無い。。。
でも、sys/types.h
	struct in_addr a.s
s の型だな。となると、
    in_addr_t = u_int32_t
なわけだ。 実際、in_addr_t が宣言してある sys/types.h には
	typedef in_addr_t u_int32_t;
が書かれているらしい。
ええい util.c を書き直してしくれるわ!!
	u_int32_t addr;

dkim, compiling dkim-milter ...2
で、 再コンパイル。

% make clean
% rm -rf obj.FreeBSD.X.X-RELEASE.i386
% ./Build
      :
dkim-milter-2.7.2/obj.FreeBSD.X.X-RELEASE.i386/libdkim/libdkim.a(dkim.o): In function `dkim_process_set':
dkim.o(.text+0x9ce): undefined reference to `strtoull'
strtoull がない? なんだこの関数。String 系みたいだけど(検索...検索...)。 うーん、C99 以降に入った文字列を unsigned long long に変換する関数かぁ。 どうすっぺかなぁ。。。うちのサーバのコンパイラ gcc 2.95.2 (古っ!!) だし。 だいたい、こんな古い gcc のバージョンじゃ unsinged long long 自体ダメじゃねーか?

dkim, compiling gcc3.4.6 ...1
仕方ない gcc をバージョンアップするかぁ。 で、gcc 3 系を作って、それでもダメなら gcc 4 系を作るか。
ということで、gcc-3.4.6.tar.gz をダウンロードして展開。

% tar xvzf gcc-3.4.6.tar.gz
% mkdir build-gcc-3.4.6
% cd build-gcc-3.4.6
で、あ〜ゆ〜どりま〜 (このサイトの人も相当苦労しているようだが。。。) というサイトを参照させてもらって、以下を書く。
% vi config.bsd
../gcc-3.4.6/configure --prefix=/usr/local \ --enable-shared \ --with-gnu-ld \ --enable-threads \ --enable-languages=c \ --enable-threads=posix \ --enable-__cxa_atexit \ --build=i386-unknown-freebsdX.XR \ --host=i386-unknown-freebsdX.XR \ --enable-clocale=gnu
% sh ./config.bsd
で、コンパイル。
% make
    :
"Makefile", line 777: Need an operator
うー やっぱり。 make(gmake) が古いらしい。 最新の make-3.81.tar.gz をダウンロードしてコンパイルしてインストール。 こいつは、既存の make と置き換えちゃえ。

dkim, compiling gcc3.4.6 ...2
gcc のコンパイル継続。

% make
    :
../../gcc-3.4.6/gcc/intl.h:31: libintl.h: No such file or directory
くそぉ 今度は libintl.h が無いだと。 libintl ということは、 intl ディレクトリになんかあるんじゃねーか?
% cd intl
% less Makefile
: all-yes: libintl.a libintl.h config.intl :
ほー、all-yes ねぇ。
% make all-yes
    :
% ls libintl.h
libintl.h
おー できた。

dkim, compiling gcc3.4.6 ...3
コンパイルの続き。

% cd ..
% make
    :
make[1]: Entering directory `gcc'
../../gcc-3.4.6/gcc/intl.h:31: libintl.h: No such file or directory
あれ? -I オプションで intl ディレクトリを見ていないみたいだぞ。
この設定、何処に書くんだ?。。。 えーい、めんどくせ〜、Makefile 書き直しちゃえ。
INCLUDES-I../intl を追加してっと。
% vi gcc/Makefile
INCLUDES = -I. -I$(@D) -I$(srcdir) -I$(srcdir)/$(@D) \ -I$(srcdir)/../include -I../intl
これでどうだ?

dkim, compiling gcc3.4.6 ...4

% make
    :
SHELL=/bin/sh: Command not found.
export: Command not found.
if: Expression Syntax.
あぁ? 随分進んだのに、、、ここでエラーかよ。。。 って export とか ifsh のシンタックス。
確か、SHELL=/bin/sh になっているはずなのに。。。 どうやら、
make TARGETS=oneprocess SHELL="/bin/tcsh" CC="gcc" CFLAGS=" -g -O2
      :
SHELL="/bin/tcsh" がいけないらしい、 ってこれどこで設定しているんだよ!!
うー、わからん。 grep しても何も引っかからない。 となると、login shell から取ってきているのかなぁ。 面倒だ、Makefile 書き換えちゃえ。 machname.h のルールの $(SHELL)/bin/sh にしてしまう。
% vi gcc/fixinc/Makefile
machname.h: ../specs /bin/sh $(srcdir)/genfixes $@
にしてコンパイル継続!

dkim, compiling gcc3.4.6 ...5

% make
    :
'fixincl version 1.1'
if: Expression Syntax.
うー まだあったかぁ。 ここは gcc/fixinc/Makefile
install-bin : $(TARGETS)
        ./fixincl -v < /dev/null
        @if [ -f ../fixinc.sh ] ; then rm -f ../fixinc.sh || \
            mv -f ../fixinc.sh ../fixinc.sh.$$ || exit 1 ; else : ; fi
        @cp $(srcdir)/fixincl.sh ../fixinc.sh
        chmod 755 ../fixinc.sh
の部分だな。 fixinc.sh が、、、あれ?無いぞ!! えぇい、ソースからコピーして実行してしまえ〜。
% cp -p ../gcc-3.4.6/gcc/fixinc/fixincl.sh gcc/fixinc
% cd gcc/fixinc
% make install-bin
    :
%
おぉ、何か進展したみたいだぞ(よくわからんが)。

dkim, compiling gcc3.4.6 ...6
コンパイル続き。

% cd ../..
% make
    :
mp libgcc_s.so.1 && ln -s libgcc_s.so.1 libgcc_s.so
make[2]: Leaving directory `build-gcc-3.4.6/gcc'
make[1]: Leaving directory `build-gcc-3.4.6/gcc'
%
おぉ、コンパイルできた。gcc 3.4.6 をインストール。
# make install

2008/12/27
dkim, compiling dkim-milter ...3
よ〜し このコンパイラで dkim-milter のコンパイルの続き、と。

% cd ../dkim-milter-2.7.2
% vi devtools/Site/site.config.m4
define(`confCC', `/usr/local/bin/gcc') define(`confMTCCOPTS', `-pthread') define(`confMTLDOPTS', `-pthread') APPENDDEF(`confINCDIRS', `-I/usr/local/ssl8/include') APPENDDEF(`confLIBDIRS', `-L/usr/local/ssl8/lib')
% make : dkim.o(.text+0x9ce): undefined reference to `strtoull'
あれ? strtoull がまだ解決できない。 なぜだ?(ごそごそ...)
げ、 strtoull は C++ のライブラリにあるらしい(なんでまた、、、)。
でも、g++ のコンパイラを作るのは面倒らしい (できなさそうではないが、面倒くさいのでやりたくない)。
えーい、strtoull だけありゃいいんだろ!! こいつだけ持ってきてやる。
ということで、strtoull.c のソースを適当なところから探して、 これを libdkim に追加じゃ。
% vi libdkim/Makefile.m4
define(`bldSOURCES', `base64.c dkim.c dkim-cache.c dkim-canon.c dkim-keys.c dkim-policy.c dkim-tables.c dkim-test.c dkim-util.c rfc2822.c util.c vbr.c strtoull.c ')
にして再コンパイル。

dkim, compiling dkim-milter ...4

% make clean
% rm -rf obj.FreeBSD.X.X-RELEASE.i386
% make
    :
/usr/local/bin/gcc -O -I. -I../../include  -I/usr/local/ssl8/include  -pthread -DXP_MT
-c -o strtoull.o strtoull.c
strtoull.c:47:22: ansidecl.h: No such file or directory
strtoull.c:48:24: safe-ctype.h: No such file or directory
うー なんじゃこりゃ。 多分、safe-ctype.h はスレッドセーフな ctype.h だな。 ctype.h でなんとかならんかなぁ。
関連しそうな #include をコメントアウトして ctype.h#include
% vi libdkim/strtoull.c
/* #include "ansidecl.h" #include "safe-ctype.h" */ #include "ctype.h"
で再コンパイル

dkim, compiling dkim-milter ...5

% make
    :
dkim-milter-2.7.2/obj.FreeBSD.X.X-RELEASE.i386/libdkim/libdkim.a(strtoull.o): In function `strtoull':
strtoull.o(.text+0x1c): undefined reference to `ISSPACE'
strtoull.o(.text+0xd8): undefined reference to `ISDIGIT'
strtoull.o(.text+0xed): undefined reference to `ISALPHA'
strtoull.o(.text+0x101): undefined reference to `ISUPPER'
ぐぅ 多分、ISSPACEisspace() の代替マクロだ。
% vi libdkim/strtoull.c
(#include "ctype.h" の下に追加) #define ISSPACE(x) isspace(x) #define ISDIGIT(x) isdigit(x) #define ISALPHA(x) isalpha(x) #define ISUPPER(x) isupper(x)
を追加して、コンパイルを継続と。

dkim, compiling dkim-milter ...6

% make
    :
make[1]: Leaving directory `dkim-milter-2.7.2/obj.FreeBSD.X.X-RELEASE.i386/dkim-filter'
% ls obj.FreeBSD.X.X-RELEASE.i386/dkim-filter/dkim-filter
obj.FreeBSD.X.X-RELEASE.i386/dkim-filter/dkim-filter*
%
お! できちゃったよぉ。

2008/12/27
sendmail, compiling BerkleyDB-4.7.25
sendmail, compiling Cyrus-SASL-2.1.22
sendmail, compiling sendmail8.14
ちょっと気が変わって、sendmail を 8.13系から 8.14系にバージョンアップ (どうせなら最新の実装にしたほうが後々いいでしょ)。
ついでに、SMTP-AUTH ができるように Cyrus-SASL をインストール、、、 という前に、BerkleyDB を最新版に。
なんか、1つ機能追加しようと思うと、その前に準備するものがたくさん出てくる。 それに SASL は最初、パスワードファイルが作れなくてすごく困った。 でもなんとか解決。
とりあえず、コンパイル時の設定だけ。詳細は省略(SPF/DKIM には関係ないので)。

BerkleyDB-4.7.25 は dist ディレクトリ内で ./configure & make

Cyrus-SASL-2.1.22(configure のオプション)

./configure --prefix=/usr/local \
	--enable-sql=no \
	--enable-login=yes \
	--enable-ntlm \
	--with-dblib=berkeley \
	--with-dbpath=/etc/mail/sasldb2 \
	--with-bdb-libdir=/usr/local/BerkeleyDB.4.7/lib \
	--with-bdb-incdir=/usr/local/BerkeleyDB.4.7/include \
	--with-openssl=/usr/local/ssl \
	--with-plugindir=/usr/local/lib/sasl2

sendmail8.14(devtools/Site/site.config.m4)
dnl ### Changes for STARTTLS support
APPENDDEF(`confENVDEF',`-DSTARTTLS -DIDENTPROTO=0')
APPENDDEF(`confLIBS', `-lssl -lcrypto -lsasl2')
APPENDDEF(`confLIBDIRS', `-L/usr/local/ssl/lib -R/usr/local/ssl/lib')
APPENDDEF(`confINCDIRS', `-I/usr/local/ssl/include')

dnl ### SASL2 support
APPENDDEF(`confENVDEF',`-DSASL=2')
APPENDDEF(`confLIBS', `-lsasl2')
APPENDDEF(`confLIBDIRS', `-L/usr/local/lib -R/usr/local/lib')
APPENDDEF(`confINCDIRS', `-I/usr/local/include')

dnl ### Queue
APPENDDEF(`confENVDEF',`-DQUEUE')

結構、大変だったよぉ (でも、これで SASLv2 も SMTPS も(設定すれば)使えるようになった)。 ちなみに -DIDENTPROTO=0 は今は亡き IDENT を使わないようにするため。

2009/1/31
sendmail, making sendmail.cf by cf
久々に復活です。
もともと、sendmail.cf は FreeBSD であるがために CF というツールから作成していた。 しかし、sendmail 8.10.X 以降の機能を使うためには CF では作れなくなってしまった。 そこで、sendmail 付属の cf (名前が小文字のツール)に乗り換えないといけない。 そうしないと、ここでやろうとしているメールフィルタ(MILTER)の設定ができないのだ。

しかし!!ここでハマった。
cf の説明をしている Web ページを探しまくり、 自分のサーバに合う設定 sendmail.mc に記述。 sendmail.cf を作って動かしてみた。 そうすると、

と、まるでダメダメになってしまった。
これを解決するために、

と、O'REILLY のコウモリ本全部を買ってしまったぐらいだ(1万円オーバー)。
暇をみてちょこちょこやっていたら、1ヶ月も経ってしまったが、 やっとのこと「とりあえずローカル・外部への配信/受信ができる sendmail.mc」 を書くことができた。
重要なポイントは、 『結局のところ、デフォルトでかなりのことは設定されているから、 余計なことは sendmail.mc に書かない。』 ということだ。 sendmail に付いてくるサンプルから freebsd.mc をコピーしてきて、 必要なことだけ書き加えればいい。 オープンリレー対策も、配信エラー時の挙動も、 デーモンの設定も基本的には書かなくてもいいらしい。

ということで、自分の sendmail.mc を示す。
基 本は「spam 対策+バーチャルホスト+smtpfeed+メールヘッダからバージョン等を消す」。

外部からメールを出したり、自分からメールを出したりして正常に送受信できることを確認。 あと、適当に見つけたオープンリレーチェックサイトでチェックをして、 オープンリレーになっていないことを確認した。

2009/2/3
dkim, configuring fml
DKIM はメーリングリストと相性がよくない。
DKIM はメールヘッダを含めて署名するが、 メーリングリストハンドルソフト(うちは fml を使っている)は、 メンバに配布する際にメールヘッダを書き換えてしまう。 そのため、署名が意味をなさなくなってしまう。 そこで、メーリングリストでは以下のような設定が必要だ。

これで、メーリングリストサーバで新たな署名をして発信する。 受け取った側の DKIM フィルタは From ヘッダのドメインの署名検証が失敗すると、 次に Sender ヘッダのドメインの署名検証をしようとするので、 これでうまくいくはずである。

ということで、fml の設定を変更する。 fml の設定ファイル cf に以下を追加して、
% makefml config メーリングリスト
して config.ph を生成しなおす。
# DKIM
&DELETE_FIELD('DKIM-Signature');
&DELETE_FIELD('Authentication-Results');
&DELETE_FIELD('X-DKIM');
&DEFINE_FIELD_FORCED('Sender', $MAINTAINER);
最初の DELETE_FIELD で DKIM 関係のヘッダを削除。 DEFINE_FIELD_FORCED で Sender ヘッダを追加している。
実際のメーリングリストのヘッダで以下のように Sender ヘッダが追加されていることを確認した。
    :
Subject: [Mailing-list:01234] Test
Sender: admin@xxxxxxxxxx
    :

dkim, making key pair
DKIM はデジタル署名するので、キーペアが必要だ。
dkim-filter には dkim-genkey というツール(中身は shall script)が付いているので、 簡単に作れる。
が、、、

# /usr/bin/dkim-genkey -d marimo.org -s alpha -D /etc/mail/dkim
grep: alpha.public: No such file or directory
#
エラーだ。うーん、、、公開鍵のファイルが見つからない、ということか。。。
あ、openssl が 0.7 系じゃだめなんじゃないか? じゃぁ、dkim-genkey を書き換えちゃえ。
vi /usr/bin/dkim-genkey して、、、 openssl/usr/local/ssl8/bin/openssl のフルパスに書き換えて、っと。
# /usr/bin/dkim-genkey -d marimo.org -s alpha -D /etc/mail/dkim
#
おぉ、できた。
ちなみにオプションは あとは、DNS に設定する公開鍵がちゃんとできているかだが、、、
# cat /etc/mail/dkim/alpha.txt
alpha._domainkey IN TXT "v=DKIM1; g=*; k=rsa; p=MIGfMA0GC...(略)...
...(略)...YXFwIDAQAB" ; ----- DKIM alpha for marimo.org
#
ということで、うまくできているようだ。

dkim, setting public key to DNS
DNS(bind) に生成された公開鍵(/etc/mail/dkim/alpha.txt)を設定する。
zone ファイルの末尾にでも公開鍵を追加する。 そして、Serial を上げて、bind を再起動する。
設定を確認する。

# dig @127.0.0.1 alpha._domainkey.marimo.org TXT
    :
;; ANSWER SECTION:
alpha._domainkey.marimo.org.  1D IN TXT  "v=DKIM1; g=*; k=rsa; p=MIGfM..(略)..FwIDAQAB"
    :
よし、登録された。

spam 受信対策

2009/2/9
sendmail, setting greetpause
sendmail 8.13 からは spam 対策用に Greetpause という機能が付いたらしい。
これは送信元が 25 番ポートに接続してきたとき、

220 marimo.org ESMTP Mon, 9 Feb 2009 22:17:16 +0900 (JST)
という Greeting Message を返す。 RFC 的には、送信元はこの 220 を待ってから、次の手順に進まなくてはならない。 だが、大量の spam を送信しているところは、効率化を求めるあまり、 この 220 を待たずに次の手順に進んでくるところがある。
そこで、この Greeting Message をすぐに返さないでおいて、 これを無視して次の手順を踏んだやつをエラーにして拒否することができるのが、 Greetpause だ。これを設定してみる。
まず、sendmail.mc に Greetpause の設定を追加する。
   :
dnl -- Greeting pause --
FEATURE(`greet_pause',`10000')dnl
   :
まぁ、だいたい FEATURE が並んでいるところの最後あたりに追加すればいい。 この設定は 25 番ポートに接続してきたら10秒後(10000ミリ秒)に Greeting Message を返す設定だ。 sendmail.cf を作って sendmail を再起動する。
% telnet localhost 25
として10秒後に Greeting Message が表示されればOKだ。
まぁ、真面目に手順を踏んでくる spam サイトには、この設定の効果は無いのだが。

で、自分達が送信するときとか、 自分の別のメールアドレスがあるサイトなんかは、これをやっていると送信に若干時間がかかってしまう。 そこで、そういうサイトは特別扱いにしてやる。
access ファイルに
   :
GreetPause:127.0.0.1			0
   :
GreetPause:nifty.com			0
GreetPause:google.com			0
   :
と先頭に"GreetPause:"を付けて、続いて待つ秒数を0に設定すれば、 これらのサイトは 0 秒で Greeting Message が返される。
ついでに、spam ばっかり送ってくるところは、じっくり待たせてやろう。 以下は、うちのサイトに spam を大量に送ってくるサイトだ。 ほかのサイトとは桁が違う。そして、まともなメールは1通もない。
GreetPause:ttnet.net.tr		45000
GreetPause:tpnet.pl			45000
GreetPause:rr.com			45000
GreetPause:t-dialin.net		45000
GreetPause:prod-infinitum.com.mx	45000
GreetPause:163data.com.cn		45000
GreetPause:rima-tde.net		45000
GreetPause:verizon.net		45000
GreetPause:veloxzone.com.br		45000
GreetPause:charter.com		45000
GreetPause:arcor-ip.net		45000
GreetPause:speedy.net.pe		45000
GreetPause:gaoland.net		45000
GreetPause:mtu-net.ru			45000
GreetPause:airtelbroadband.in		45000
GreetPause:proxad.net			45000
GreetPause:t-ipconnect.de		45000
GreetPause:telecomitalia.it		45000
これらのサイトはたとえまともな手順を踏んできても、 Connect 拒否リストにも入っているから、 45秒じっくり待たされて最後に拒否られる。 これらの spam サイトからいろいろなサイトに spam を送っていると、 うちのサイトに接続したとたんに処理が止まってしまうのだ。ふふふ。

ただし、GreetPause の設定はあまり待たせるようにすると、 待たせている間、大量に sendmail プロセスが溜まってしまう。 CPU/メモリに余裕が無い限り、あまり無謀な設定は難しいようだ。 ちなみに、RFC には「5分は待て」と書かれているようなので、 5分以上待たせるのはまずそうだ。
まずは、これでどれだけ拒否率が上がるか試してみよう。

2009/2/10
sendmail, reject un-resolve site
どうも、Greetpause は効果が薄いみたい。残念。
ということで、次なる手段は「DNS の逆引きできないサイトを拒否する」。
いままで、長年、spam を受け取ってきて(今まで受け取った spam は全部保存してある)、 わかったことは「spam サイトの半分は DNS の逆引きが設定されていない」ということ。 実際、spam の発信が多い中国などは、DNS の逆引きを設定していないところが多いらしい。 だから、DNS の逆引きが設定されていないところからのメールを拒否してしまえば、 spam の半分を拒否できることになる。
まぁ、この手法には賛否両論があるようで 「DNS の逆引きの設定は RFC として義務化されていないのに、 単に逆引きできないからといって拒否するのは安易過ぎる」 とか 「DNS のトラフィックが増える」 とか 「Web サーバからのメール送信とかは逆引き設定していない場合もある」 だとか。
でもねぇ〜 俺にしてみりゃねぇ、

ということで、逆引き設定は義務じゃないが、お行儀の悪いサイトは信用できない。 だから、うちのサイトは拒否する!!

で、設定だが、簡単。。。
sendmail.mc の MAILER の設定の後(つまりファイルの最後)に以下を追加する。
    :
LOCAL_RULESETS
SLocal_check_relay
R$*	$: $&{client_resolve}
RTEMP	$#error $@ 4.7.1 $: "450 Access denied. Cannot resolve PTR record for " $&{client_addr}
RFAIL	$#error $@ 4.7.1 $: "450 Access denied. IP name lookup failed " $&{client_name}
(注)左端のスペースはタブ1個
あとは、sendmail.cf を生成し置き換え sendmail の再起動。
ちなみに上記は2つの拒否ルールが設定されている。
RTEMP どこの DNS にも登録されていない
RFAIL 担当すべき DNS が答えを返さない
こういうところは拒否する。
ちなみに、もう1つ、
RFORGED	$#error $@ 4.7.1 $: "450 Access denied. IP name possibly forged " $&{client_name}
というのがある。 これは「正引きと逆引きの答えが一致しない」ときに拒否する。 しかし、これは設定しちゃいけない。絶対に。
逆引きできないサイトの受信を拒否するのは「逆引き設定がないサイトは信用できない」 という信用問題の話で、設定しようと思えばできるものだが、 正引きと逆引きが一致しないサイトは、普通の運用上存在してしまう。 たとえば、 ちなみに、ウチのサイトはホスティングじゃないが、 上位プロバイダが逆引きを設定しているし、 そもそも、バーチャルドメイン設定しているから、 どっちにも該当するし、特にバーチャルドメインの問題はどうにもならない。 これが理由で拒否されるのはおかしいでしょ。 ということで、うちのサイトでもこれは設定しない。

翌日の結果
maillog を確認してみると、
% grep PTR /var/log/maillog
Feb 11 02:33:56 sample sm-mta[17153]: ruleset=check_relay, arg1=[65.48.194.43], arg2=65.48.194.43, relay=[65.48.194.43], reject=450 4.7.1 Access denied. Cannot resolve PTR record for 65.48.194.43
Feb 11 03:43:49 sample sm-mta[17408]: ruleset=check_relay, arg1=[189.118.138.1], arg2=189.118.138.1, relay=[189.118.138.1], reject=450 4.7.1 Access denied. Cannot resolve PTR record for 189.118.138.1
    :
% grep "lookup failed" /var/log/maillog
Feb 11 05:23:40 sample sm-mta[17614]: ruleset=check_relay, arg1=[211.41.245.160], arg2=211.41.245.160, relay=[211.41.245.160], reject=450 4.7.1 Access denied. IP name lookup failed [211.41.245.160]
Feb 11 05:37:57 sample sm-mta[17695]: ruleset=check_relay, arg1=[203.234.91.253], arg2=203.234.91.253, relay=[203.234.91.253], reject=450 4.7.1 Access denied. IP name lookup failed [203.234.91.253]
    :
%
とちゃんと拒否できている。 設定後約10時間で61件拒否。 もちろん、これをすり抜けてきた spam もあるが、 まぁ、1日100通以上は減らせそうだ。

sendmail, delete sendmail version in submit.cf
今日、自分宛のメールのヘッダを見て気がついた。
ぬぉぉ〜 Received ヘッダに「8.14.X/8.14.X/Submit」ってバージョンばっちり出てんじゃん。 くそぉ、サーバローカルからの送信とか、.forwardprocmail の送信で MSP 経由の場合がダメなのか。確かに submit.cf ではバージョン消してないなぁ。
ということで、submit.cf にバージョンを消す設定追加(confRECEIVED_HEADER)。

よし、これで消えた。

DKIM 再び

2009/2/11
dkim, setting & run
そろそろ、もともとの目的の DKIM に戻ろう。
dkim のインストールやキーの生成、DNS の設定は終わっている。 あとは、dkim-filter コマンドを起動するだけだ。
その前に、準備。 署名を行うサイトを指定するファイルを作る。 ここにあるサーバから送られたメールだけ署名される。

# vi /etc/mail/dkim/ilist
127.0.0.1 200.100.200.150/24
あとは、各ファイル・ディレクトリのパーミッションを変更しておく。
# chown -R smmsp:mail /etc/mail/dkim
# chmod 640 /etc/mail/dkim/*
だいたい、こんな感じで指定すればいいのかな?
# /usr/libexec/dkim-filter -l -D -h -p inet:8891@localhost -P /var/run/dkim-filter.pid -d marimo.org,sample.org -k /etc/mail/dkim/alpha.private -s alpha
おお、行き当たりばったりでコンパイルしたけど、動いているみたいじゃん。
オプションについて説明しておく。
-lsyslog に記録する
-Dサブドメインからのメールも署名する
-h経路ヘッダに DKIM の処理が入ったことを挿入する
-psendmail と通信するソケットを指定(inetはポート、localはソケットファイル)
-Pdkim-filterはデーモンになるので、そのプロセスIDを記録するファイル名
-d署名するドメイン名
-kプライベートキーのファイル
-sセレクタ名
-i署名対象サーバ
さて、あとは、sendmail が dkim-filter を使うように設定すればいい。 sendmail.mcMAILER の前に以下を追加
INPUT_MAIL_FILTER(`dkim-filter',`S=inet:8891@localhost,F=T,T=R:4m')dnl 
sendmail.cf を作って、sendmail を再起動。 DKIM をチェックしてくれるメールアドレス( autorespond+dkim@dk.elandsys.com )にメール送信。 ぬぉぉ〜検証結果の返信メール。
    :
DKIM Signature validation: not available
    :
失敗だ。というか、こっちが出したメールに署名がない。なんで?
ポリシが足りないのかなぁ。 DNS に以下を追加。
_policy._domainkey IN TXT "t=y; o=~"
チェックのメールを送付。。。やっぱりダメだ。署名されない。 うーん、うーん、あ、-i ilist オプション忘れてた。起動しなおし。
# /usr/libexec/dkim-filter -l -D -h -p inet:8891@localhost -P /var/run/dkim-filter.pid -d marimo.org,sample.org -k /etc/mail/dkim/alpha.private -s alpha -i /etc/mail/dkim/ilist
再度、チェックメールを送付。
    :
X-DKIM: Sendmail DKIM Filter v2.7.2 marimo.org n1B987vM020126
DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=marimo.org; s=alpha; t=1234343287; bh=g40XxM9hSj...(略).../uO8/+jA5/q0q7w =
    :
おぉ、署名された。で、結果メールは
    :
DKIM Signature validation: pass
    :
やったね!!

あとは、OS 起動時に自動起動するようにしないといけない。 まず、起動する shell script をテキトーに書く。
# vi /etc/mail/startdkim.sh
#!/bin/sh /usr/libexec/dkim-filter -l -D -h \ -p inet:8891@localhost \ -P /var/run/dkim-filter.pid \ -d marimo.org,sample.org \ -k /etc/mail/dkim/alpha.private \ -s alpha \ -i /etc/mail/dkim/ilist
# chmod 700 /etc/mail/startdkim.sh
あとは、sendmail を起動する前に dkim-filter を起動するように書く。
# vi /etc/rc
: # DKIM Filter if [ -x /etc/mail/startdkim.sh ]; then echo -n ' dkim-filter' /etc/mail/startdkim.sh fi case ${sendmail_enable} in :
これで DKIM は完成。

sendmail, repair setting(sasl2)
いろいろいじっていたら、/var/log/messages

    :
Feb 11 15:07:30 sample sm-mta[19323]: OTP unavailable because can't read/write key database /etc/opiekeys: No such file or directory
    :
というメッセージが大量に出ていた。なんだこれ?
いろいろ調べたら、sendmail にリンクした cyrus-sasl2 の OTP(One Time Password)のプラグインが出しているらしい。 つぅか、OTP なんて使わない。 どうやら、libotp 関係を消してしまえばいいらしい。
# rm /usr/local/lib/sasl2/libotp*
でいいらしい、が、まぁ、念のため、自分は .bak を付けて退避しておいた。 これで sendmail を再起動したら、、、 おぉ、messages に変なメッセージが出なくなった。

2009/2/12
dkim, delete version
DKIM で署名されたヘッダを見ると、

    :
X-DKIM: Sendmail DKIM Filter v2.7.2 marimo.org n1B987vM020126
DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=marimo.org; s=alpha; t=1234343287; bh=g40XxM9hSj...(略).../uO8/+jA5/q0q7w =
    :
と、dkim-filter のバージョンが丸見えだ。 これはセキュリティ的にまずい。
ということで、起動オプションから -h オプションを取り除いた。 これで、
    :
DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=marimo.org; s=alpha; t=1234431230; bh=g40XxM9hSj...(略)...25gOGVcgT+EeIkQ=
    :
と X-DKIM ヘッダが付かなくなった。

dkim, delete duplicate headers
ヘッダを見ていて、もう1つおかしいことに気がついた。
自分宛のメールは、別のサーバに転送しているのだが、 転送先で受け取ったメールのいくつかで、 DKIM のヘッダ(X-DKIM, DKIM-Signature)が 2つずつ付いてきているものがあった。 これはおかしい。

いろいろ考えてわかった。
二重についているメールは、サーバ自身から送信されたものだった。 もちろん、これには署名は付く(ilist で設定しているから)。 そして自分宛のメールを転送するときには procmail + bsfilter で spam を外部に転送しないようにしているのだ。
つまり、こういうことだ。

サーバ →送信→ sendmail(署名) → ローカルユーザ(私)
    → .forward による転送 → procmail+bsfilter
        →送信→ sendmail(署名) →転送先
2回目の署名が検証されるべき署名だから、最初の署名は余計なのだ。
そして、自サーバ送信以外のメールはもともと署名されないので、 上記の場合だけ対応すればいい。
あぁ、そういえば、DKIM を導入する際の注意事項として 「フォワーダーとメーリングリストは注意が必要」 とか書いてあったなぁ。

ということで、.forwardprocmail に転送する前に、 DKIM のヘッダを取っちゃえばいいんだな。
ちょいとこんなのを書いて、っと。
deletedkim.pl:

まぁ、テキトーに作ったので、ダサダサだけど、自分専用にはこれで十分。
あとは、~/.forwardprocmail の手前にこいつを挟んでやればよさそうだ。

実際に送信してみて、maillog を確認すると、
Feb 12 18:56:41 sample sm-mta[28680]: n1C9uewv028679: to="|/usr/local/bin/deletedkim.pl|IFS=' ' && exec /usr/local/bin/procmail -f- || exit 75",
...(略)...
mailer=prog, pri=60986, dsn=2.0.0, stat=Sent
とりあえず、ちゃんと送れたようだが、、、あれ?受け取れない。
どうも deletedkim.pl の起動に失敗しているようだが、 送信側のサーバにはどこにもエラーログが残っていない。。。 .forward じゃなくて、bsfilter を動かしている .procmailrc に書くようにしてみよう。
% cat ~/.procmailrc
:0 fw
| /usr/local/bin/bsfilter -j kakasi --pipe --insert-flag --insert-probability

:0
* ^X-Spam-Flag: Yes
spam/.

:0 fw
| /usr/local/bin/deletedkim.pl

:0
! xxxxxxxx@to.myforward.com
%
お、これなら送れる。

sendmail, result of new reject rule
やはり、逆引きできないサイトからのメールを拒否するのは絶大な効果がある。

spam 拒否ルール拒否率
アクセスファイルのみ50%前後
+Greetpause70.2%
+逆引き不可サイトの拒否88.7%
spam の9割近くを拒否できた(spam は1日400〜800通ぐらい来る)。 もちろん、spam 以外は1通も欠けることなく届いている(と思う ;-)。
複数の spam が同じサイトから送信されているので、 サイト単位の拒否率を計算すると92.1%にもなる。 つまり、spam 発信サイトの9割を拒否できている。 おかげで、メーラで受信する spam は激減した。
ちなみに、access ファイルから DNS 未登録な IP アドレスを削除したら、 エントリが 93073 から 36388 まで減った(6割減)。 これもいい効果が出ている。

巷では S25R とかいう spam 対策で「高い効果がでている」とかを言っているところもあるが、 逆引きできないサイトの拒否よりナンセンスと思う。 S25R とは、FQDN で「spam サイトっぽい名前のサイト」を片っ端から拒否する、というものだ。 言うなれば、郵便局に葉書を持っていったら 「あなたの名前は鈴木さんですね。あなたは違法な広告メールを出す人の名前に似ているので、 発送を受け付けません」 と門前払いをするようなものだ。
まぁ、ホワイトリストの自動化とかいろいろやっているようだが、 コンセプトとしてやってはいけないと思う。
私のサイトは spam サイトではなく、まっとうなサイトだが、 逆引きすると上位プロバイダが IP アドレス入りの FQDN を返してしまう。 S25R 方式では私のサイトは逆引きも含めて1つも落ち度がなくても拒否対象だ。
さらに「逆引きをチェックするために DNS のトラフィックが増える」とか言っているようだが、 もともとトラフィックを増やしているのは spam であるわけだし、 今後、セキュリティを考えれば、相手が信頼できるかどうかを調べるトラフィックは必要なだけ使われるべきだ。 何の検証もせずに名前で判断しておいて 「S25R 方式は無駄なトラフィックがない」 というのは、面倒な処理を手抜きした場当たり的対処でしかない。
まぁ、いろいろ意見があるだろうが、少なくともうちは S25R なんてのは採用しない。

SPF 再び

2009/2/13
spf, compiling & install
SPF は送信側としての設定はしてあるが、 SPF 対応したサーバからのメールを受信するフィルターは、まだ導入していない。 ということで、SPF Filter を導入する。
まず、ソースをダウンロードするが、どうやら、 SID Mail Filter(sid-milter) で SenderID と SPF の両方を処理できるみたいだ。ということで、 sid-milter-1.0.0.tar.gz からダウンロードして、ソースを展開してコンパイル。 何も考えず Build スクリプトを流せばいいらしい。

% ./Build
    :
cc -O -I. -I../../libar  -I../../libmarid  -I../../sendmail   -I../../include  -DUSE_ARLIB   -D_THREAD_SAFE -DXP_MT   -c -o util.o util.c
util.c: In function `sid_inet_ntoa':
util.c:139: syntax error before `addr'
util.c:143: `addr' undeclared (first use in this function)
util.c:143: (Each undeclared identifier is reported only once
util.c:143: for each function it appears in.)
make[1]: *** [util.o] Error 1
    :
%
あれまぁ dkim-milter と同じ util.c でエラーだわ。
ということは、同じように util.c を直してコンパイル。
% ./Build
    :
% ls obj.FreeBSD.X.X-RELEASE.i386/sid-filter/sid-filter
obj.FreeBSD.X.X-RELEASE.i386/sid-filter/sid-filter*
%
お、今度は簡単にできた。
じゃ、インストールっと。
# make install
    :
Making install in:
/usr/local/src/sid-milter-1.0.0/sid-filter
Configuration: pfx=, os=FreeBSD, rel=X.X-RELEASE, rbase=X, rroot=X.X-RELEASE, arch=i386, sfx=, variant=optimized
Making in /usr/local/src/sid-milter-1.0.0/obj.FreeBSD.X.X-RELEASE.i386/sid-filter
make[1]: Entering directory `/usr/local/src/sid-milter-1.0.0/obj.FreeBSD.X.X-RELEASE.i386/sid-filter'
install -c -o bin -g bin -m 555 sid-filter /usr/bin
    :
#
ありゃ、sid-filter/usr/bin に入っちゃった。 /usr/libexec がいいのに。。。移動だ、移動。
# mv /usr/bin/sid-filter /usr/libexec/
# ls -l /usr/libexec/sid-filter
-r-xr-xr-x  1 bin  bin  154753 Feb 13 21:32 /usr/libexec/sid-filter
#
よし、いい感じ。 これでコンパイル&インストール完了。

2009/2/14
spf, setting & run
sid-filter を動かす。

# /usr/libexec/sid-filter -l -p inet:8892@localhost -P /var/run/sid-filter.pid -r 0
ちゃんと動いているようだ。 オプションは dkim-filter と似ている。
-lsyslog に記録する
-psendmail と通信するソケットを指定(inetはポート、localはソケットファイル)
-Psid-filterはデーモンになるので、そのプロセスIDを記録するファイル名
-r 検証結果によるアクション
0:すべてのメールを受信する
1:SenderIDとSPFの認証結果の両方が「fail」の場合受信拒否
2:SenderIDかSPFのどちらかの認証結果が「fail」の場合受信拒否する
3:SenderIDかSPFのどちらかの認証結果が「pass」ではない場合受信拒否する
4:SenderIDかSPFの両方の認証結果が「pass」ではない場合受信拒否する
まぁ、まずは、-r 0 で全部受け取る、と。
ついでに、起動スクリプトを書く。
# vi /etc/mail/startsid.sh
#!/bin/sh /usr/libexec/sid-filter -l \ -p inet:8892@localhost \ -P /var/run/sid-filter.pid \ -r 0
# chmod 700 /etc/mail/startsid.sh
次に、sendmail.mc を書き換えて受信時に sid-filter を通るようにする。 dkim-filter の INPUT_MAIL_FILTER の後に同じように書く。
dnl -- SPF/Sender-ID --
INPUT_MAIL_FILTER(`sid-filter',`S=inet:8892@localhost,F=T,T=R:4m')dnl
そして、sendmail.cf を生成して、sendmail を再起動。
さて、うまく検証してくれるか Gmail からメールを送信してヘッダを確認。
Authentication-Results: marimo.org; sender-id=pass header.from=xxxxxxxx@gmail.com; spf=pass smtp.mfrom=xxxxxxxx@gmail.com
よし!! SenderID と SPF の Authentication-Results ヘッダが付いた。
あとは、OS 起動時に sid-filter が起動するように DKIM の後に書いておく。
# vi /etc/rc
: # SPF/Sender-ID Filter if [ -x /etc/mail/startsid.sh ]; then echo -n ' sid-filter' /etc/mail/startsid.sh fi :
これで、SPF はおしまい。

DKIM ADSP

dkim, dkim ADSP
最近は DKIM ADSP(Author Domain Signing Practice) という受信側に動作をお願いする機能もあるらしい。
ということで、 DKIM ADSP Wizard で、自ドメインの DNS エントリを生成して、DNS に登録する(Serial を上げてね)。

; DKIM ADSP
_asp._domainkey	IN	TXT	"dkim=all"
named を再起動する。 autorespond+dkim@dk.elandsys.com へテストメールを送ってみる。
DKIM Author Domain Signing Practices: no DNS record for _adsp._domainkey.marimo.org
ん? _asp じゃなくて _adsp なの? DNS 書き直し。
_adsp._domainkey	IN	TXT	"dkim=all"
ためしに dig で引いてみる。
# dig @127.0.0.1 _adsp._domainkey.marimo.org TXT
    :
;; ANSWER SECTION:
_adsp._domainkey.marimo.org.  1D IN TXT  "dkim=all"
    :
よし、ちゃんと登録された。 再度、autorespond+dkim@dk.elandsys.com へテストメールを送ってみるが、、、ダメだ。 dig で引けるのに、なんでダメなんだろう? うーん、DNS のキャッシュがクリアされるまではダメなのかな?

では、sa-test@sendmail.netに出してみよう。
    :
Authentication System:       DomainKeys Identified Mail
   Result:                   DKIM signature confirmed GOOD
    :
Authentication System:       Domain Keys
   Result:                   (no result present)
    :
Authentication System:       Sender ID           
   Result:                   SID data confirmed GOOD
    :
Authentication System:       Sender Permitted From (SPF)
   Result:                   SPF data confirmed GOOD
実装していない Domain Key 以外は全部うまくいっているようだ。 ま、これでいいか。

DomainKey

dk, compiling & install
と思ったけど、どうせだから DomainKey も入れちゃえ〜。
dk-milter-1.0.1.tar.gz を取ってきて、展開。 site.config.m4 を書いてコンパイル。

% vi devtools/Site/site.config.m4
define(`confCC', `/usr/local/bin/gcc') define(`confMTCCOPTS', `-pthread') define(`confMTLDOPTS', `-pthread') APPENDDEF(`confINCDIRS', `-I/usr/local/BerkeleyDB.4.7/include ') APPENDDEF(`confLIBDIRS', `-L/usr/local/BerkeleyDB.4.7/lib ') APPENDDEF(`confLIBS', `-ldb ') APPENDDEF(`confENVDEF',`-DSTARTTLS') APPENDDEF(`confLIBS', `-lssl -lcrypto') APPENDDEF(`confLIBDIRS', `-L/usr/local/ssl8/lib -R/usr/local/ssl8/lib') APPENDDEF(`confINCDIRS', `-I/usr/local/ssl8/include')
% ./Build : % ls obj.FreeBSD.X.X-RELEASE.i386/dk-filter/dk-filter obj.FreeBSD.X.X-RELEASE.i386/dk-filter/dk-filter*
おや!全然、無事にできちゃった。
で、インストール
# make install
    :
/usr/local/src/dk-milter-1.0.1/dk-filter
Configuration: pfx=, os=FreeBSD, rel=X.X-RELEASE, rbase=X, rroot=X.X-RELEASE, arch=i386, sfx=, variant=optimized
Making in /usr/local/src/dk-milter-1.0.1/obj.FreeBSD.X.X-RELEASE.i386/dk-filter
make[1]: Entering directory `/usr/local/src/dk-milter-1.0.1/obj.FreeBSD.X.X-RELEASE.i386/dk-filter'
install -c -o bin -g bin -m 555 dk-filter /usr/bin
くそぉ、なんで /usr/bin に行くんだよ。移動、移動。
# mv /usr/bin/dk-filter /usr/libexec/

dk, making key pair
DomainKey の署名のためのキーペアを作る。

% ./dk-filter/gentxt.csh omega marimo.org
grep: omega.public: No such file or directory
omega._domainkey IN TXT "k=rsa; t=y; p=" ; ----- DomainKey omega for marimo.org
%
うー、またこれだ。 同じように openssl/usr/local/ssl8/bin/openssl に変更。
% ./dk-filter/gentxt.csh omega marimo.org
omega._domainkey IN TXT "k=rsa; t=y; p=MFwwDQYJKoZIhvcNAQEBBQAD...(略)...qsEfKqUCAwEAAQ==" ; ----- DomainKey omega for marimo.org
%
げ、うまく動いたけど、DNS の設定例が標準出力かよ。
うまくコピー&ペーストで、この公開鍵を DNS の zone に設定。 dig で設定内容を確認。
# dig @127.0.0.1 omega._domainkey.marimo.org TXT
    :
;; ANSWER SECTION:
omega._domainkey.marimo.org.  1D IN TXT  "k=rsa; t=y; p=MFwwDQYJKoZIhvcNAQEBBQAD...(略)...qsEfKqUCAwEAAQ=="
    :
#
秘密鍵を /etc/mail/dk に置いてパーミッション設定。
# mkdir /etc/mail/dk
# chown smmsp:mail /etc/mail/dk
# chmod 755 /etc/mail/dk
# cp omega.* /etc/mail/dk
# chown smmsp:mail /etc/mail/dk/*
# chmod 640 /etc/mail/dk/*
# ls -l /etc/mail/dk
total 2
-rw-r-----  1 smmsp  mail  497 Feb 14 18:35 omega.private
-rw-r-----  1 smmsp  mail  182 Feb 14 18:35 omega.public
#
これでキーペアの設定は終わり。

dk, setting & run
最後に DomainKey を動かす。
まずは、署名するドメインのリストを作るが、 これは DKIM と同じなのでシンボリックリンクにしてしまおう。

# cd /etc/mail/dk
# ln -s ../dkim/ilist
あとは、dk-filter を起動するスクリプトを書いて、実行する。
# vi /etc/mail/startdk.sh
#!/bin/sh ILIST=/etc/mail/dk/ilist SOCKETSPEC="inet:8893@localhost" SELECTOR="omega" PRIVATEKEY="/etc/mail/dk/omega.private" DOMAINS="marimo.org,sample.net" BADSIGN="reject" DNSERR="accept" INTERR="accept" NOSIGN="accept" SIGNMISS="reject" PIDFILE="/var/run/dk-filter.pid" /usr/libexec/dk-filter -l -D \ -p $SOCKETSPEC \ -P $PIDFILE \ -C badsignature=$BADSIGN,dnserror=$DNSERR,internal=$INTERR,nosignature=$NOSIGN,signaturemissing=$SIGNMISS \ -d $DOMAINS \ -s $PRIVATEKEY \ -S $SELECTOR \ -i $ILIST
# /etc/mail/startdk.sh
とりあえず、これで動いたようだ。 プロセスを見ると、3種類のフィルタが動いているのがわかる。
# ps -ax | grep filter
28157  ??  Ss     0:11.95 /usr/libexec/dkim-filter -l -D -p inet:8891@localhost
43105  ??  Ss     0:01.19 /usr/libexec/sid-filter -l -p inet:8892@localhost -P
46696  ??  Ss     0:00.25 /usr/libexec/dk-filter -l -D -p inet:8893@localhost -
コマンドラインオプションは、dkim-filter とほぼ同じだ。 -C は検証結果によるアクションを指定している。
検証結果意味
passDomainKey の認証に成功
dnserror認証キーが取得できない
internal処理中にエラーが発生した
nosignatureDomainKey が付いていない
signaturemissing全てのメールに署名する事になっているのに DomainKey がない
badsignatureDomainKey の認証エラー
これらのときに、同アクションするかを決めている。
accept受信する
discard廃棄する
tempfail4xxで受信拒否
reject5xxで受信拒否
最後に、sendmail.mc を書き直す。sid-filter の後に追加する。
dnl -- DomainKey --
INPUT_MAIL_FILTER(`dk-filter',`S=inet:8893@localhost,F=T,T=R:4m')dnl
では、sa-test@sendmail.netに出してみよう。
    :
Authentication System:       DomainKeys Identified Mail
   Result:                   DKIM signature confirmed GOOD
    :
Authentication System:       Domain Keys         
   Result:                   DK signature confirmed GOOD
    :
Authentication System:       Sender ID           
   Result:                   SID data confirmed GOOD
    :
Authentication System:       Sender Permitted From (SPF)
   Result:                   SPF data confirmed GOOD
    :
やったね!パーフェクトだ!

あ、あと fml の設定も変えないと。
cf に
&DELETE_FIELD('DomainKey-Signature');
を追加して、二重に署名されないようにしないと。

DKIM, DomainKey, SPF, SenderID まとめ

dkim&DomainKey&sid&SenderID, tune-up options
受け取った spam の中に dkim で fail するものが出てきた。
ということは、今は accept しているが、reject したらより spam 拒否率が増えるということ。 なので、ここは dkim, DomainKey, SenderID+SPF のいずれかで fail になったら、 受信を拒否するようにしてみる(署名がないメールなどは対象外)。
filter を起動するスクリプトを変更する。

/etc/mail/startdkim.sh:

#!/bin/sh

SOCKETSPEC="inet:8891@localhost"
SELECTOR="alpha"
PRIVATEKEY="/etc/mail/dkim/alpha.private"
DOMAINS="marimo.org,sample.org"
PIDFILE="/var/run/dk-filter.pid"
ILIST="/etc/mail/dk/ilist"
HDRLIST="Return-Path,Received,Comments,Keywords,Bcc,Resent-Bcc"
# Action
BADSIGN="reject"
DNSERR="accept"
INTERR="accept"
NOSIGN="accept"

/usr/libexec/dkim-filter -l -D \
	-p $SOCKETSPEC \
	-P $PIDFILE \
	-C badsignature=$BADSIGN,dnserror=$DNSERR,internal=$INTERR,nosignature=$NOSIGN \
	-d $DOMAINS \
	-k $PRIVATEKEY \
	-s $SELECTOR \
	-i $ILIST \
	-o $HDRLIST

/etc/mail/startdk.sh:
#!/bin/sh

SOCKETSPEC="inet:8893@localhost"
SELECTOR="omega"
PRIVATEKEY="/etc/mail/dk/omega.private"
DOMAINS="marimo.org,sample.org"
PIDFILE="/var/run/dk-filter.pid"
ILIST="/etc/mail/dk/ilist"
HDRLIST="Return-Path,Received,Comments,Keywords,Bcc,Resent-Bcc"
# Action
BADSIGN="reject"
DNSERR="accept"
INTERR="accept"
NOSIGN="accept"
SIGNMISS="reject"

/usr/libexec/dk-filter -l -D \
	-p $SOCKETSPEC \
	-P $PIDFILE \
	-C badsignature=$BADSIGN,dnserror=$DNSERR,internal=$INTERR,nosignature=$NOSIGN,signaturemissing=$SIGNMISS \
	-d $DOMAINS \
	-s $PRIVATEKEY \
	-S $SELECTOR \
	-i $ILIST \
	-o $HDRLIST

/etc/mail/startsid.sh:
#!/bin/sh

SOCKETSPEC="inet:8892@localhost"
PIDFILE="/var/run/sid-filter.pid"
PEERLIST="/etc/mail/sid/peerlist"
# Action
# 0:accept all, 1:reject SID&&SPF failed
# 2:reject SID||SPF failed, 3:reject SID||SPF not passed
# 4:reject SID&&SPF not passed
REJECTMODE=1

/usr/libexec/sid-filter -l \
	-p $SOCKETSPEC \
	-a $PEERLIST \
	-P $PIDFILE \
	-r $REJECTMODE
dkim-filterdk-filter は署名の検証が fail なら拒否。 ただし、DNS が引けないなどシステム的な問題がある場合は受け取る。
sid-filter は SPF と SenterID の両方が fail のときに拒否するようにした。 ただし、peerlist に記載されている IP アドレスからはチェックしないようにした。 そして、それ(peerlist)は dkim や DomainKey でいう ilist と同じだ (なので、/etc/mail/dkim/ilist へのシンボリックリンク)。

これで起動しなおせば、うまくいくはず。

うーん、外部のプロバイダで自サイトに自動転送設定している。
メール→ [My ISP] →自動転送→ [自サーバ]
そこにメールを出す(つまり、自分のメールアドレスに戻ってくる)。 外部からメールを受け取れるかのチェックに便利なのだ。
    __________(1)__________
   ↓                      ↑
[My ISP] →(2)自動転送→ [自サーバ]
しかし、こうして自サーバで受け取ったメールは SPF も SenderID も fail になってしまう。
Authentication-Results: marimo.org; sender-id=fail (NotPermitted) header.from=xxxxxxxx@marimo.org; spf=fail (NotPermitted) smtp.mfrom=xxxxxxxx@marimo.org
REJECTMODE=1 だと自動転送で返ってくるメールを自サイトが受け取ってくれない。 自分のメールなのに。。。
「SPF は直前のサイトしか見ない。 自ドメインのメールが、そのプロバイダから送られてくる」 から、 sid-filter は「メールの From が詐称されている」と勘違いするわけで、、、
いろいろ調べたところ、 「SPF/SenderID は転送に弱く(誤認識する)、メーリングリストに強い」 ということで、SPF/SenderID は転送では、 自分みたいな場合の対応は難しいらしい。 転送しているプロバイダで、Resent-From に送信者、From に転送者のメールアドレスに書き直す、 SRS(Sender Rewriting Scheme) をプロバイダがやってくれるわけない。

本来は、SPF/SenderID と DKIM/DomainKey の両方で fail なものを spam とみなすのが正らしい。 けど、別々のフィルタでやっているから両方の結果を付き合わせるのはどうすりゃいいんだ?
とりあえず、REJECTMODE=0 に戻しておく。

2009/2/15
spf, resolve foward problem...1
プロバイダから自動転送で返ってくるメールが SPF/SenderID で fail になる問題だが、 ちょっとズルして回避する方法を思いついた。
SPF は

Resent-Sender → Resent-From → Sender → From
の順にチェックする。 本当であれば、プロバイダが自動転送するときに、 Sender にでも私のプロバイダのメールアドレスを入れてから転送してくれればいいんだが、 そうもしてくれない。
そこで、自分に返ってくるのが確実なそのプロバイダ宛のメールは、 送信時にあらかじめ Sender を付けて送信してしまう。
To: xxxxxxxx@myprovider.jp
Sender: xxxxxxxx@myprovider.jp
これで送信して返ってきたメールでは、
Authentication-Results: marimo.org; sender-id=pass header.sender=xxxxxxxx@myprovider.jp; spf=pass smtp.mfrom=xxxxxxxx@myprovider.jp
と、SPF/SenderID 共に pass になった。 いいのか?こんなので。 (^_^;)
ということで、プロバイダの自アドレス宛はメーラのテンプレートを使って、 常に Sender を書くようにすれば、うまくすり抜けられそうだ。

2009/2/16
spf, resolve foward problem...2
SPF と転送の問題はいろいろ調べてみた。
そして結局のところ、SRS が全面的に採用されない限り 「SPF の転送問題は事実上解決しない」 ということがわかった。 だめじゃん!SPF。
あとは「SPF と DKIM の両方を失敗したものだけ拒否する」 という方法ぐらいしかない。 これを実現する方法は思いついた。 regex-milter だ。 これを SPF/DomainKey/DKIM の後に入れて、 Authentication-Results ヘッダの結果を regexp でチェックして、 ここで初めて Reject する。これなら実現できそうだ。
だが、たかだかこれだけのために、もう1つ MILTER を入れるのはどうもしっくりこない。 だいたい、SPF/DomainKey/DKIM の3つの MILTER を入れたおかげで、 メールの送受信が2テンポぐらい遅くなった。 これ以上入れるのは結構悩むところだ。
ということで、regex-milter保留

dkim, adsp
以前、DKIM ADSP を設定したが、その結果が得られなかった。
再度、autorespond+dkim@dk.elandsys.com にテストメールを送ってみると、

DKIM Signature validation: pass
DKIM Author Domain Signing Practices: "dkim=all"
    :
Authentication-Results: ns1.qubic.net; dkim=pass
	(1024-bit key; insecure key) header.i=@marimo.org;
	dkim-adsp=pass (insecure policy)
    :
と、しっかり ADSP の設定が反映されていた (ちなみに insecure key とか言われているのは、多分、自己署名の鍵だから)。
やったね!!

Mail Security

2009/2/17
sendmail, using submission port
最近は OP25B(Outbound Port 25 Block)とか言って、 メールのリレーにも使われている 25 番ポートでメール発信をさせないようにしているところが多い。 その代わり Submission Port として 587 番でメール発信できるようにする。
sendmail はデフォルトで Submission Port でも待ち受けているはず。

% telnet localhost 587
Trying 127.0.0.1...
Connected to localhost.marimo.org.
Escape character is '^]'.
220 sample.marimo.org ESMTP Tue, 17 Feb 2009 20:22:52 +0900 (JST)
   :
おぉ、普通につながる。
今まで、PC と自サーバとは LAN 内とはいえ、ssh port fowarder でつないでいた。 また、sendmail は LAN 内からのアクセスは許可しているから、 これで ssh 経由でなくてもつながるはずだ。
ということで、メーラの設定を ssh 経由の localhost:25 から、 自サーバの 587 番に直接つなぐ。 そして、メール送信すると、ほぉ、普通に送信できる。 じゃ、POP3 も LAN 内で認証あるから ssh 経由(暗号化)にする必要ないから、 メーラのそっちの設定も変えちゃって、、、
いぇ〜い、PortFowarder が必要なくなった。

2009/2/18
sendmail, using SMTP-AUTH
元もとの SMTP には認証機能がないために、下手をすると誰でも送信できてしまうところが欠点だ。
で、これを解決するのが SMTP-AUTH (SMTP認証) だ。 うちもこれを導入する。
まず、うちの sendmail は SMTP-AUTH(SASLv2) 付きでコンパイルしてあるので、 ちゃんと使えるのか確認する。

# sendmail -bs -Am
220 marimo.org ESMTP Tue, 17 Feb 2009 22:41:32 +0900 (JST)
ehlo marimo.org
250-sample.marimo.org Hello root@localhost, pleased to meet you
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-8BITMIME
250-SIZE
250-DSN
250-ETRN
250-AUTH DIGEST-MD5 CRAM-MD5
250-DELIVERBY
250 HELP
quit
221 2.0.0 marimo.org closing connection
#
ということで、AUTH コマンドがちゃんと使えることが確認できた。
次に、SASLv2(Cyrus-SASL2) でパスワードを設定する。 これが SMTP-AUTH 時の認証になる。 Cyrus-SASL2 をコンパイルしたときに util ディレクトリに saslpasswd2 コマンドができているはずで、これを使う。
# saslpasswd2 -c -u marimo.org test
Password:
Again (for verification):
# sasldblistusers2
test@marimo.org: userPassword
#
sasldblistusers2 はパスワード登録の確認だ。

次に sendmail.mc に以下を追加する。 define が並んでいる最後にでも追加すればいい。
    :
dnl -- SMTP-AUTH --
define(`confAUTH_MECHANISMS', `DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl
TRUST_AUTH_MECH(`DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl
define(`confDEF_AUTH_INFO', `/etc/mail/auth-info')dnl
    :
ここでは、テストができるように PLAIN を指定している。 だが、PLAIN はネットワークを生パスワードが流れるので、 テストが終わったら消すこと。 sendmail.cf を作り直して、sendmail を再起動する。
PLAIN のパスワードは SMTP 上では Base64 しないといけないので、 簡易 Base64 コマンドの ed64.c をコンパイルしておく(cc ed64.c -o ed64 とか)。 パスワードは
ユーザ名\000メールアカウント名\000パスワード
を Base64 したものを使う。
% ed64 -e 'test\000test@marimo.org\000hogehoge'
dGVzdAB0ZXN0QG1hcmltby5vcmcAaG9nZWhvZ2U=
% telnet localhost 587
Trying 127.0.0.1...
Connected to localhost.marimo.org.
Escape character is '^]'.
220 sample.marimo.org ESMTP Wed, 18 Feb 2009 21:31:01 +0900 (JST)
ehlo localhost
250-sample.marimo.org Hello localhost.marimo.org [127.0.0.1], pleased to meet you
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-8BITMIME
250-SIZE
250-DSN
250-AUTH DIGEST-MD5 CRAM-MD5 LOGIN PLAIN
250-DELIVERBY
250 HELP
AUTH PLAIN dGVzdAB0ZXN0QG1hcmltby5vcmcAaG9nZWhvZ2U=
235 2.0.0 OK Authenticated
quit
221 2.0.0 sample.marimo.org closing connection
Connection closed by foreign host.
%
ということで、ちゃんと認証されることを確認できた。
ということで、sendmail.mcPLAIN をはずして、 sendmail.cf を作り直して再起動。
    :
define(`confAUTH_MECHANISMS', `DIGEST-MD5 CRAM-MD5 LOGIN')dnl
TRUST_AUTH_MECH(`DIGEST-MD5 CRAM-MD5 LOGIN')dnl
    :
で、telnetPLAIN が抜けていることを確認する。
% telnet localhost 587
    :(略)
250-8BITMIME
250-SIZE
250-DSN
250-AUTH DIGEST-MD5 CRAM-MD5 LOGIN
250-DELIVERBY
250 HELP
あとは、メーラの設定で SMTP-AUTH の設定をして、 ちゃんと送信できるかを確認する、と。
おぉ、できた。

ちなみに、access ファイルに
XXX.XXX.XXX.XXX   RELAY
と書かれているマシンからの送信は認証しても、しなくても送信できちゃう (access ファイルの設定が優先)。
実際、メーラで SMTP-AUTH の設定をはずしても送信できることを確認。 なるほどぉ、これは便利だ。
なんだかんだ sendmail は評判悪いけど、よくできているじゃん。

2009/2/19
sendmail, using STARTTLS(SMTP over SSL)
SMTP は暗号化通信をしないため、通信を盗まれると内容などが漏洩する可能性がある。 特に SMTP-AUTH で PLAIN なんてしていると、パスワードがダダ漏れだ。
そこで、SMTP を SSL で暗号化通信してしまうのが SMTPS だ。 うちはクライアントとメールサーバの通信は LAN 内なので、あえて暗号化はいらないかもしれないが、 念のため、これも使えるようにしておこう。

まず、sendmail が対応しているか確認する。

% /usr/sbin/sendmail -bt -d0.1 < /dev/null
Version 8.14.X
 Compiled with: DNSMAP LOG MAP_REGEX MATCHGECOS MILTER MIME7TO8 MIME8TO7
                NAMED_BIND NETINET NETUNIX NEWDB NIS PIPELINING SASLv2 SCANF
                STARTTLS USERDB XDEBUG
    :
うん、対応している。
じゃぁ、telnet でつないでみる。
% telnet localhost 587
Trying 127.0.0.1...
Connected to localhost.marimo.org.
Escape character is '^]'.
220 sample.marimo.org ESMTP Wed, 18 Feb 2009 21:31:01 +0900 (JST)
ehlo localhost
250-sample.marimo.org Hello localhost.marimo.org [127.0.0.1], pleased to meet you
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-8BITMIME
250-SIZE
250-DSN
250-AUTH DIGEST-MD5 CRAM-MD5 LOGIN PLAIN
250-DELIVERBY
250 HELP
quit
221 2.0.0 sample.marimo.org closing connection
Connection closed by foreign host.
%
あれ? STARTTLS が出てこないぞ? うーん、どうしてだ? ログレベルを上げてみよう。
% /usr/sbin/sendmail -O LogLevel=14 -bs -Am
    :
% less /var/log/maillog
    :
Feb 18 20:01:15 sample sendmail[80411]: STARTTLS: ServerCertFile missing
    :
あぉ、証明書がないと、 起動時のコマンドリストにも出てこないんだ。

じゃぁ、改めまして証明書を作っていこう(自己署名のね ;-)。 sendmail の openssl は 0.9.7系をリンクしているから、 証明書も念のため 0.9.7系で作っておく。 保管場所は /etc/mail/certs だ。
以下は長くなるが、まぁ、仕方ない。
# mkdir /etc/mail/certs
# cd /etc/mail/certs
# openssl genrsa -out smtls.key 1024
Generating RSA private key, 1024 bit long modulus
............................++++++
.....................................................................................................++++++
e is 65537 (0x10001)
# openssl req -new -key smtls.key -out smtls.csr -days 3650
Using configuration from /etc/ssl/openssl.cnf
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Kanagawa
Locality Name (eg, city) []:Yokohama
Organization Name (eg, company) [Internet Widgits Pty Ltd]:MARIMO
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []:XXXX.MARIMO
Email Address []:postmaster@marimo.org

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
# openssl ca -in smtls.csr -out smtls.crt
Using configuration from /etc/ssl/openssl.cnf
Enter pass phrase for /etc/ssl/private/cakey.pem:
Check that the request matches the signature
Signature ok
Certificate Details:
...
 
Certificate is to be certified until Feb 19 19:55:28 2019 GMT (3650 days)
Sign the certificate? [y/n]:y
Certificate 1 signed. Commit? [y/n]:y
# ls
cacert.pem      smtls.crt       smtls.key
# chmod 600 *
これで証明書と鍵ができた。 Apache の SSL で使っている証明書と共有してもいいと思う(更新が1回で済むから便利だね)。 ただ、証明書関係のファイルのパーミッションで Group Write が付いていたりすると、 sendmail は STARTTLS を使わせてくれないので、パーミッションには気をつけよう。
あとは、sendmail.mc に以下を追加する。
    :
dnl -- SMTP over SSL ---
define(`confCACERT_PATH', `/etc/mail/certs')dnl
define(`confCACERT', `/etc/mail/certs/cacert.pem')dnl
define(`confSERVER_CERT', `/etc/mail/certs/smtls.crt')dnl
define(`confSERVER_KEY', `/etc/mail/certs/smtls.key')dnl
    :
これで、sendmail.cf を作り直して、sendmail を再起動。
STARTTLS が使えるか確認する。
% telnet localhost smtp
Trying 127.0.0.1...
Connected to localhost.marimo.org.
Escape character is '^]'.
220 sample.marimo.org ESMTP Thu, 19 Feb 2009 22:33:53 +0900 (JST)
ehlo localhost
250-sample.marimo.org Hello localhost.marimo.org [127.0.0.1], pleased to meet you
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-8BITMIME
250-SIZE
250-DSN
250-ETRN
250-AUTH DIGEST-MD5 CRAM-MD5 LOGIN
250-STARTTLS
250-DELIVERBY
250 HELP
quit
221 2.0.0 sample.marimo.org closing connection
Connection closed by foreign host.
%
ということで、STARTTLS が使えるようになった。 わーい。
最後に、メーラで SMTP over SSL(SMTPS) が使えるか確認する。
自分のメーラの Becky! だと、 一般的にはメールボックスの設定での [基本設定] で、
□ SMTPS
にチェックを入れるだけだが、 自分のサーバは自己署名だから、これだけでは送信時に、
サーバ証明書の検証に失敗しました。
     :
というエラーになる。 当たり前だ、信頼されている CA が発行したものじゃないからね。
なので、メールボックスの設定の [詳細] の [SSL/TLS] の
□ 証明書を検証しない
にチェックを入れる。「自己署名だけど勘弁してね」ということだ。
これで、正常に送信できるようになった。
ふぅ。

Tune-up

2009/2/20
sendmail, tune-up, etc ...1
メールサーバのカスタマイズも終盤にさしかかってきた。
まず、sendmail.cfsubmit.cf の Received ヘッダの設定に差があるので、 新しい設定の submit.cf の設定を sendmail.cf に持っていく。
sendmail.mc の設定を変更する。

define(`confRECEIVED_HEADER',`$?sfrom $s $.$?_($?s$|from $.$_)
        $.$?{auth_type}(authenticated$?{auth_ssf} bits=${auth_ssf}$.)
        $.by $j (MTA)$?r with $r$. id $i$?{tls_version}
        (version=${tls_version} cipher=${cipher} bits=${cipher_bits} verify=${verify})$.$?u
        for $u; $|;
        $.$b')dnl
これで、認証や暗号化の情報がヘッダに載るはずだ。 ためしに送信してみよう。
    :
Received: from [XXX.XXX.XXX.XXX] by sample.marimo.org
 with ESMTP id n1KCwuCM002797 (version=TLSv1/SSLv3 cipher=RC4-MD5 bits=128 verify=NO) for <xxxxxxxx@myprovider.jp>;
    :
ととりあえず載った。

以前に DNS の逆引きができないサイトを reject するようにして、 spam の拒否率が 90% 近くになったが、数日すると拒否率が少しずつ下がっているようだ。 spammer も手を変え品を変え送信しようとしてくるのだろう。
ということで、 こちらも更にチューニングして spam に対応しよう。 sendmail.mc に以下を追加した。
dnl -- Timeout --
define(`confTO_COMMAND',`5m')dnl
define(`confTO_DATABLOCK',`5m')dnl
define(`confTO_IDENT',`0s')dnl
define(`confTO_RESOLVER_RETRANS',`3s')dnl
define(`confTO_RESOLVER_RETRY',`2')dnl
dnl -- Resouce --
define(`confBAD_RCPT_THROTTLE',`3')dnl
define(`confCONNECTION_RATE_THROTTLE',`10')dnl
define(`confMAX_DAEMON_CHILDREN', `10')dnl
define(`confDOUBLE_BOUNCE_ADDRESS', `')dnl
それぞれの設定を説明しておく。
confTO_COMMAND SMTP のコマンドの入力が5分以上止まるとエラーで切断される。
confTO_DATABLOCK メールボディの送信が5分以上止まるとエラーで切断される。
confTO_IDENT 今では無用の長物の IDENT(RFC1413) を使わないようにする。 コンパイル時に -DIDENTPROTO=0 で止めてあるけど念のため。
confTO_RESOLVER_RETRANS DNS の解決が3秒以内に終わらなければリトライ。
confTO_RESOLVER_RETRY DNS の解決のリトライは2回まで。
confBAD_RCPT_THROTTLE Unknown User を4回以上出すと接続を保留される。
confCONNECTION_RATE_THROTTLE 1秒間に10接続以上は接続させない。
confMAX_DAEMON_CHILDREN sendmail プロセスの最大数。 制限をかけないと、DoS 攻撃などで無限にプロセスが増えてしまう。
confDOUBLE_BOUNCE_ADDRESS バウンスメール(エラーを送信者に伝えるメール)がエラーを起こしたら、 捨ててしまう(そんなものはいらないので)。
これで様子をみよう。

2009/2/21
sendmail, tune-up, etc ...2
チューンナップは意外に効いていそう。
もう少し、チューニングをしてみよう。
まず、自サイトから spam サイトに誤ってメールが送信されないように、 access リストに To: タグで抑制する。

    :
To:acn.gr				ERROR: Mail to acn.gr is not allowed
To:acsalaska.net			ERROR: Mail to acsalaska.net is not allowed
To:actaccess.net			ERROR: Mail to actaccess.net is not allowed
    :

次は、sendmail.mc の設定追加だ。
define(`confTO_CONNECT', `2m')dnl
define(`confCHECKPOINT_INTERVAL', 10)dnl
define(`confMIN_QUEUE_AGE', `15m')dnl
define(`confTO_QUEUEWARN', `5h')dnl
define(`confTO_QUEUERETURN', `4d')dnl
define(`confMAX_HOP', `25')dnl
define(`confCOLON_OK_IN_ADDR', `False')dnl
define(`confOPERATORS', `.:@!^/[]+')dnl
define(`confNO_RCPT_ACTION', `add-to-undisclosed')dnl
それぞれの設定を説明しておく。
confTO_CONNECT SMTP セッションのタイムアウトを2分にする。
confCHECKPOINT_INTERVAL 大量送信中に失敗したときの再送ポイントを10通ごとに作る。
confMIN_QUEUE_AGE 送信に失敗したときに再送するまでの間隔を15分にする。
confTO_QUEUEWARN 5時間経って送信に失敗したときに管理者へメールを送る。
confTO_QUEUERETURN 4日経っても送信に失敗するときは送信を諦める。
confMAX_HOP メールの経由は25ホップまで。
confCOLON_OK_IN_ADDR コロンを含むメールアドレスを拒否。
confOPERATORS メールアドレスのオペレータから % を除くことで、 ソースルーティングを拒否する。
confNO_RCPT_ACTION 宛先がない場合(Bcc:のみの指定など)は undisclosed を宛先に追加する。
まぁ、デフォルト値のままのもあるけど、 設定ポイントを作っておくだけでもメリットはあると思う。

あと、Greetpause の時間を見直す。
デフォルトの遅延時間(sendmail.cfに設定する値)を10秒→35秒、 spam サイトへの遅延時間(accessファイルに設定する値)を45秒→65秒 に延ばした。 これは統計的にこのくらい待たせれば spam サイトは諦めるらしい、ということだ。

最後に、sendmail のポート設定を設定しなおす。
何も設定しないと、sendmail.cf には以下のようなポートの設定が行われる。
O DaemonPortOptions=Name=MTA
O DaemonPortOptions=Port=587, Name=MSA, M=E
1行目は外部からメールを受信するための MTA としての設定で、 デフォルトの SMTP ポート(25)を使用する。
2行目はメールの発信用の MSA としての設定で Submission ポート(587)を使い、 ETRN コマンドを使用させない設定となる。
これを変更する。 sendmail.mc に以下を追加する。
dnl -- Daemon ports --
FEATURE(`no_default_msa')dnl
DAEMON_OPTIONS(`Port=smtp, Name=MTA, M=A')dnl
DAEMON_OPTIONS(`Port=587, Name=MSA, M=Ea')dnl
FEATURE(`no_default_msa') でデフォルトのポート設定をキャンセルする。 そして、外部からの受信用の接続 SMTP ポートでは認証なし、 発信用のポートは Submission ポートで認証が必要、とする。
あとは、これで認証付きでメーラから送信できるか確認しておしまい。

SpamAssassin導入

2009/2/22〜23
spamassassin, installing & setting ...1
チューンナップしたおかげで、再び spam の拒否率は98.4%、 サイト単位の拒否率は、なんと99.5%までになった。
ただ、これもたまたまで、ずっと維持できるかはわからない。 ということで、スパムフィルタの SpamAssassin+spamas-milter を導入しようと思う。 もう、regex-milter なんてセコイ対策は無しだ。 ベイジアンフィルタは bsfilter で好成績を叩き出すのは経験済みなので、 これで 100% の拒否率を達成できるだろう。

ということで、まずは、SpamAssassin をインストール。
まず、ソースファイル Mail-SpamAssassin-3.2.5.tar.gz をダウンロード。 場所は The Apache SpamAssassin Project の Donwload から。
で、インストール。

% cd Mail-SpamAssassin-3.2.5/
% perl Makefile.PL
    :
REQUIRED module missing: Digest::SHA1
REQUIRED module missing: HTML::Parser
REQUIRED module missing: Net::DNS
optional module missing: Mail::SPF
optional module missing: Mail::SPF::Query
optional module missing: IP::Country
optional module missing: Razor2
optional module missing: Net::Ident
optional module missing: IO::Socket::INET6
optional module missing: IO::Socket::SSL
optional module missing: Compress::Zlib
optional module missing: Mail::DomainKeys
optional module missing: Mail::DKIM
optional module missing: LWP::UserAgent
optional module missing: HTTP::Date
optional module missing: Archive::Tar
optional module missing: IO::Zlib
optional module missing: Encode::Detect
    :
%
うげげ かなり perl モジュールが足りないらしい。
Razor2 というのは SpamAssassin 内のモジュールだから、 それ以外を片っ端からオプションも含めて全部インストール。
# perl -MCPAN -e shell
cpan> o conf prerequisites_policy ask
    :
cpan> install Digest::SHA1
    :
cpan> quit
#
うーん、いくつかエラーになる。 そういうのはアーカイブをダウンロードしてきて、手動でインストールっと。
そして、SpamAssassin のインストールのやり直し。
% perl Makefile.PL
    :
% make
    :
# make install
    :
#
お、うまくいった。
動作確認。ソースファイルに spam に判断されるファイル sample-spam.txt、 spam じゃないファイル sample-nonspam.txt が含まれている。
% /usr/local/bin/spamassassin sample-spam.txt | grep '^X'
X-Spam-Flag: YES
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on sample.marimo.org
X-Spam-Level: **************************************************
X-Spam-Status: Yes, score=1000.0 required=5.0 tests=GTUBE,NO_RECEIVED,
XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
% /usr/local/bin/spamassassin sample-nonspam.txt | grep '^X'
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on sample.marimo.org
X-Spam-Level: *
X-Spam-Status: No, score=1.4 required=5.0 tests=SPF_FAIL,SPF_HELO_FAIL
Xav65S0P+Px4knaQcCIeCamQJ7uGcsw+CqMpNbxWYaTYmjAfkbKH1EuLC2VRwdmD
%
ということで、ちゃんと動いているようだ。
とりあえず、ルールを最新にする。
# /usr/local/bin/sa-update
error: gpg required but not found!
#
げ、今度は gpg かよぉ。 GnuPG からソースをダウンロードしてインストール。
ただし gpg をコンパイルしようとすると必要なライブラリが無いだけで configure すら通らない。 なので、ライブラリのインストールから始まって、いろいろ面倒なので、 パッケージからインストールできるなら、そちらをお勧めする。
# /usr/local/bin/sa-update
#
とうまくいった。 ただ、以下の手順でルールの一部を手作業で更新している。 これと同じように /usr/local/share/spamassassin を変更してしまうと、 sa-update を実行することで、 それらファイルも更新されてしまう可能性があるので、実行には注意が必要だ。

SpamAssassin は spam を判断するルールの他にベイジアンフィルタも使用できる。 ただし、ベイジアンフィルタは学習させないと効果は発揮できない。 これは bsfilter でも同じで、 bsfilter では自分が持っている spam を3万通ほど学習させている。
その前に、学習させる前に MTA で使用できるように SpamAssassin の設定を変更しなければならない。 まず、日本語の spam に対応したローカルルールを TLEC から取得する。 この user_prefs をサーバ全体のデフォルトルール local.cf に設定する。
デフォルトの設定は /etc/mail/spamassassin にある(OS によって少し違う)。
# cd /etc/mail/spamassassin/
# cp local.cf local.cf.org
# cp user_prefs local.cf
次に、学習結果を保存する場所を作る。 明示的に保存場所を指定しないと、ユーザのホームディレクトリに .spamassassin というディレクトリが作られて、その中にユーザ毎に学習結果が保存される (root で学習すると /root/.spamassassin/* とかに作られちゃう)。 インストールマニュアルにはパーミッション 666 で作れ、 と書いてあるけど、other はせめて書き込みできないようにしたい。
# mkdir -p /var/spool/spamassassin
# chmod 775 /var/spool/spamassassin
# chown root:daemon /var/spool/spamassassin
そして、local.cf の末尾に保存場所とかの設定を追加する。
# vi /etc/mail/spamassassin/local.cf
# LOCAL RULESET report_safe 0 use_bayes 1 bayes_auto_learn 1 bayes_path /var/spool/spamassassin/bayes bayes_file_mode 0664 auto_whitelist_path /var/spool/spamassassin/auto-whitelist auto_whitelist_mode 0664 trusted_networks XXX.YYY.ZZZ.AAA XXX.YYY.ZZZ.BBB bayes_ignore_header X-Spam-Flag bayes_ignore_header X-Spam-Status
設定を説明する。
report_safe 0 にしないと、spam と判定された場合、メールがレポートに変換されて届く。 まぁ、それも1つの方法だが、自分はヘッダに判断結果が残って、 メール本体はそのままがいい。
use_bayes ベイジアンフィルタを使う場合は 1
bayes_auto_learn 判定結果をもって、そのメールも自動的に学習する。
bayes_path 学習結果を保存する場所。 ディレクトリ(/var/spool/spamassassin/) + 保存されるファイル名のプレフィックス(bayes) で指定する。
bayes_file_mode 保存される学習ファイルのパーミッション
auto_whitelist_path ホワイトリストの自動保存場所とプレフィックス
auto_whitelist_mode ホワイトリストのパーミッション
trusted_networks 信用するネットワーク・IPアドレス
bayes_ignore_header 無理するヘッダ。他の spam フィルタで判断されたものを除外する。
では、実際に学習させる。
学習は sa-learn コマンドで行う。 なんでも、5000通も学習させると飽和する(それ以上はあまり変わらない)らしいので、 まずは7000通を学習させる ;-)。 時間がかかるので、1000通ずつ7回に分けて実行する (最初の1回は学習ファイルが無いので警告が出る)。
# sa-learn --spam --showdots --no-sync spamディレクトリ
.bayes: cannot open bayes databases /var/spool/spamassassin/bayes_* R/O: tie failed: Inappropriate file type or format
..................................................................................................................
Learned tokens from 1000 message(s) (1000 message(s) examined)
# sa-learn --spam --showdots --no-sync spamディレクトリ
    :(繰り返し)
# ls -l /var/spool/spamassassin/
total 6976
-rw-rw-r--  1 root  daemon  3330872 Feb 23 21:21 bayes_journal
-rw-rw-r--  1 root  daemon   663552 Feb 23 21:20 bayes_seen
-rw-rw-r--  1 root  daemon  5005312 Feb 23 21:20 bayes_toks
と学習ファイルが目的の場所にできている。 sa-learn のオプションは以下のとおり。
--spam ディレクトリにあるファイル(1メール1ファイル)を spam として学習させる。 正常メールとして学習させるのは --ham(スパムじゃなくてハム)。
--showdots 学習には時間がかかるが、デフォルトでは何も表示されないので、 ハングアップしたかと思う。 このオプションを付けると . が進行とともに表示される。
--no-sync 学習完了後にジャーナルファイルから DB へ反映すると、 時間がかかるので、このオプションを付けて DB の反映は後回しにする。
--no-sync オプションを付けて学習させたので、 最後にまとめて DB に反映させないといけない。
# sa-learn --sync
expired old bayes database entries in 60 seconds
132150 entries kept, 39033 deleted
token frequency: 1-occurrence tokens: 79.51%
token frequency: less than 8 occurrences: 15.71%
# ls -l /var/spool/spamassassin/
total 2968
-rw-rw-r--  1 root  daemon   663552 Feb 23 21:24 bayes_seen
-rw-rw-r--  1 root  daemon  2654208 Feb 23 21:24 bayes_toks
#
このように bayes_journal が消える。
さて、もう一度、spam のサンプルを読み込ませてみる。
うーん、簡単なものでも15秒ぐらいかかる。 思った以上に遅い。 これは、Milter のタイムアウトは考えないとダメかぁ。

2009/2/24
spamassassin, installing & setting ...2
sendmail のフィルタでは spamassassin コマンドをそのまま使用しない。
SpamAssassin は spamassassin をデーモンで動かす spamd があり、 それを動かすことで処理を軽量化する。
最初に、デーモンで動かす前に SpamAssassin の設定(/etc/mail/spanassassin/local.cf)が 正しく設定されているかを確認する。

% /usr/local/bin/spamassassin --lint
[54314] warn: config: failed to parse, now a plugin, skipping, in "/etc/mail/spamassassin/local.cf": ok_languages ja en
[54314] warn: config: failed to parse line, skipping, in "/etc/mail/spamassassin/local.cf": auto_whitelist_mode 0664
[54314] warn: lint: 1 issues detected, please rerun with debug enabled for more information
%
うー、skip されているとはいえ、警告が2件ある。
どうやら、ok_languages は SpamAssassin3.X からなくなったようだ。 auto_whitelist_mode がダメなのはわからんが、どちらもコメントアウトしておく。
さらに以下を local.cf に追加。スコアの付け直しだ。
# vi /etc/mail/spamassassin/local.cf
# Score cusmtamize # -- Invalid Date(3.308) score DATE_IN_FUTURE_12_24 1.0 # -- To: undisclosed-recipients(4.034) score UNDISC_RECIPS 1.0 # -- Same From: and To:(1.3) # score FROM_AND_TO_SAME_5 3.9 # -- Contains 'Dear Somebody'(-0.694) score DEAR_SOMEBODY 1.5 # -- How dear can you be if you don't know my name?(-0.694) score DEAR_FRIEND 1.5 # -- Subject looks like order info score ORDER_STATUS 3.0 # -- "$$$" (0.182) score CASHCASHCASH 0 # -- Subject has ?(0.7) score SUBJ_HAS_Q_MARK 0.3 # -- Frame wanted to load outside URL(0.031) score RELAYING_FRAME 3.0 # -- HTML-only mail, with no text version(1.665) score CTYPE_JUST_HTML 3.0
% /usr/local/bin/spamassassin --lint %
再度、--lint すると警告が無くなった。
次に、外部から spamd にアクセスされないように、 hosts.allow に書いておく(ついでに他の MILTER も書いておく)。
# vi /etc/hosts.allow
# SpamAssassin/spamd spamd: localhost : allow spamd: ALL : deny # DKIM milter dkim-filter: localhost : allow dkim-filter: ALL : deny # DomainKey milter dk-filter: localhost : allow dk-filter: ALL : deny # SPF/SenderID milter sid-filter: localhost : allow sid-filter: ALL : deny
spamd を root ではなく特定ユーザで動作させるようにする。 マニュアルでは「root で動かせ」と書いてあるが、それはご免だ。
spamd というユーザとグループを作り、 ベイジアンフィルタの DB があるディレクトリに spamd の書き込み権限を与える。
# vi /etc/group
spamd:*:803:spamd
# adduser
group=spamd shell=/usr/bin/false home=/var/spool/spamassassin username=spamd で作成
# chown -R spamd:spamd /var/spool/spamassassin
なお、FreeBSD でない場合は、グループの追加(groupadd)や ユーザの追加(useradd)だったりするので注意。
よし、spamd を起動するぞ。
# /usr/local/bin/spamd -d -r /var/run/spamd.pid -u spamd -g spamd -H
オプションは以下のとおり。
-d spamd をデーモンで動作させる。
-r プロセス番号を格納するファイル名。
-u 実行ユーザ名
-g 実行グループ名
-H -H オプションは引数を取る。 通常はログインユーザとは別のユーザの設定を読む指定をするが、 引数を空にすることで、どのユーザの設定も読まないようにしている。
ちゃんと起動されているか、プロセスとログを確認する。
# pstree -s spamd
-+= 00000 root (swapper)
 \-+= 00001 root /sbin/init --
   \-+= 55162 root perl: /usr/local/bin/spamd -d -r /var/run/spamd.pid -u spamd
     |--- 55167 spamd perl: spamd child (perl)
     \--- 55168 spamd perl: spamd child (perl)
# less /var/log/maillog
    :
Feb 24 21:18:29 sample spamd[54545]: spamd: server started on port 783/tcp (running version 3.2.5)
Feb 24 21:18:29 sample spamd[54545]: spamd: server pid: 54545
Feb 24 21:18:29 sample spamd[54545]: spamd: server successfully spawned child process, pid 54548
Feb 24 21:18:29 sample spamd[54545]: spamd: server successfully spawned child process, pid 54549
Feb 24 21:18:30 sample spamd[54545]: prefork: child states: II
おー、ちゃんと起動されているようだ。
動作確認をする。 spamd で spam 判定をするには、spamc コマンドにつっこんでやればいい。
% /usr/local/bin/spamc < /usr/local/src/Mail-SpamAssassin-3.2.5/sample-spam.txt | grep '^X'
X-Spam-Flag: YES
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on sample.marimo.org
X-Spam-Level: **************************************************
X-Spam-Status: Yes, score=999.9 required=13.0 tests=CONTENT_TYPE_PRESENT,GTUBE,
X-Spam-Report:
XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
% /usr/local/bin/spamc < /usr/local/src/Mail-SpamAssassin-3.2.5/sample-nonspam.txt | grep '^X'
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on sample.marimo.org
X-Spam-Level: **
X-Spam-Status: No, score=2.9 required=13.0 tests=ARIN,AWL,CONTENT_TYPE_PRESENT,
Xav65S0P+Px4knaQcCIeCamQJ7uGcsw+CqMpNbxWYaTYmjAfkbKH1EuLC2VRwdmD
%
ほー、問題なく動作しているようだ。処理も格段に速いぞ。
だが、spam のときは0.2秒ぐらいだが、 正常メールの場合は2〜3秒かかる。 巨大メールの処理を考えると、やはり MILTER のタイムアウトは要チェックだ。

最後に、OS の起動時に自動的に spamd が起動するようにする。
# cd /etc/mail
# vi startspamd.sh
#!/bin/sh PIDFILE=/var/run/spamd.pid USER=spamd GROUP=spamd /usr/local/bin/spamd -d -r $PIDFILE -u $USER -g $GROUP -H
# chmod 774 startspamd.sh
あとは /etc/rc ファイルの MILTER の起動前に追加。
# vi /etc/rc
# Spamd if [ -x /etc/mail/startspamd.sh ]; then echo -n ' spamd' /etc/mail/startspamd.sh fi
これで、SpamAssassin のインストールが完了した。 ふぅ。

2009/2/25
spamassassin, installing & setting ...3
もう少しだけ SpamAssassin の設定をいじる。
まず、auto_whitelist_path を設定してホワイトリストを自動学習させるようにしていたが、 どうも、From ヘッダに日本語がある場合にダメダメになるらしい。 そこで、/etc/mail/spamassassin/local.cf を書き換える。

auto_whitelist_path /var/spool/spamassassin/auto-whitelist
は削除し、代わりに
use_auto_whitelist 0
を入れて AutoWhiteList を使わないようにする。

自サイトからのメール、自分の出したメール、 信頼できるところからのメールなどは自前でホワイトリスト化する。 同じく /etc/mail/spamassassin/local.cf に追加する。

自サイト
trusted_networks 200.100.200.150/24
ホワイトリスト
# Whitelist
whitelist_from *@docomo.ne.jp
whitelist_from foo@bar.com
whitelist_from baz@boo.net
たとえば、ちゃんと spam フィルタリングをしている ISP にある自分のアカウントからの自動転送なんかは素通しでよいだろう。

spamd はパーミッションで少し優遇されないといけないらしい。 まぁ、こういうのは問題が起きてから対応するのでもいいんだが、 とりあえずやっておこう。
sendmail の Trusted User に spamd ユーザを追加する。
# vi /etc/mail/trusted-users
root daemon fml spamd
mail グループに spamd ユーザを追加する。
# vi /etc/group
mail:*:6:spamd
最後に、、、
spamd はメモリを相当食うらしい。
場合によっては 200MB とか食うこともあるらしい。 うちのサーバは非力だから、そんなでかいプロセスはちょいと厳しい。 まぁ、うちのサーバは今までの施策で、大量のメールを処理する必要はなくなったのだが、 念のため、ちょっと制限しておく。
spamd のオプションで spamd が fork するプロセス数を2に(--max-children=2)、 1プロセスが処理するコネクションを10に(--max-conn-per-child=10)制限する。
# vi /etc/mail/startspamd.sh
: OPTIONS="--max-children=2 --max-conn-per-child=10" /usr/local/bin/spamd -d -r $PIDFILE -u $USER -g $GROUP $OPTIONS -H
次回こそ spamass-milter の作業だ。

2009/2/26
spamass-milter, compiling & setting
以前、大量のメールをメーリングリスト宛に送りつけられたことがあった。 当然、NOT MEMBER で reject するんだが、大量に送りつけられたから、 fml のプロセスも大量に起動されてプロセスフルでサーバが落ちたことがあった。
今では、大半の spam の接続を拒否できているが、 どうも接続を拒否された反撃か、DoS 攻撃っぽいアクセスがあるらしい、、、 が、 こっちは、同時接続数もプロセス数も制限しているので、 もう、動作不能になることはない。
だいたい、こんな辺境サイトに一度に何百通も届くことはないのだ。

さて、spamass-milter をインストールしよう。
まず、ソースファイル Savannah から spamass-milter-0.3.1.tar.gz を取ってきて展開。コンパイルする。

% ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
    :
config.status: creating config.h
config.status: config.h is unchanged
config.status: executing depfiles commands
% make
    :
% ls -l spamass-milter
-rwxr-xr-x  1 sample  wheel  320002 Feb 25 23:38 spamass-milter*
%
うーん、やけに、素直にできた。
まぁ、単なる spamc のフロントエンドだからなぁ。
インストールする。make install すると /usr/sbin なんてところに入れられそうなので手で配置する。
# cp ./spamass-milter /usr/libexec/
# chown bin:bin /usr/libexec/spamass-milter
# ls -l /usr/libexec/spamass-milter
-r-xr-xr-x  1 bin  bin  320002 Feb 25 23:47 /usr/libexec/spamass-milter
うーん、でも、ほかのフィルターと名前を合わせたいから、名前を変える!!
# mv spamass-milter spamass-filter
じゃ、早速、起動してみよう。
# /usr/libexec/spamass-filter -f -p /var/run/spamass-filter.sock -P /var/run/spamass-filter.pid -m -i 127.0.0.1,200.100.200.150/24
# pstree -s spamass
-+= 00000 root (swapper)
 \-+= 00001 root /sbin/init --
   \--= 99189 root /usr/libexec/spamass-filter -f -p /var/run/spamass-filter.so
# ls -l /var/run/spamass*
-rw-r--r--  1 root  wheel  6 Feb 26 20:52 /var/run/spamass-filter.pid
srwxr-xr-x  1 root  wheel  0 Feb 26 20:52 /var/run/spamass-filter.sock
# less /var/log/maillog
    :
Feb 26 20:52:15 sample spamass-milter[99189]: spamass-milter 0.3.1 starting
ふーん、動いているみたい。
オプションの説明。
-f spamass-filter をデーモンで動作させる。
-p ソケットファイルのパス。 どうも、spamass-filter はポートは指定できないみたいだ。
-P プロセス番号を格納するファイル名。
-m spam と判定してもメールの本文、Content-type, Subject を変更しない。
-i 信頼するIPアドレス。 ここから送信されたメールはスルーする。
起動スクリプトを作る。
# vi /etc/mail/startspamass.sh
#!/bin/sh SOCKFILE=/var/run/spamass-filter.sock PIDFILE=/var/run/spamass-filter.pid OPTIONS="-m" #OPTIONS="-m -r 15" EXTRAFLAGS="-i 127.0.0.1,200.100.200.150/24" /usr/libexec/spamass-filter -f -p $SOCKFILE -P $PIDFILE $OPTIONS $EXTRAFLAGS
# chmod 774 /etc/mail/startspamass.sh # ls -l /etc/mail/startspamass.sh -rwxrwxr-- 1 root wheel 242 Feb 26 21:13 /etc/mail/startspamass.sh
ちなみに、コメントアウトしてある -r 15 は、 SpamAssassin のスコアで15点以上になったら spam とみなして、 メールを reject するオプション。 最初は、リジェクトしないで、 ちゃんと spam メールを spam と判断できるか、 安全メールを spam と誤審しないかを確認するためにしばらくは様子を見る。
実行してみる(もちろん起動しているspamass-filterを停止してね)。
# ./startspamass.sh
# pstree -s spamass
-+= 00000 root (swapper)
 \-+= 00001 root /sbin/init --
   \--= 99308 root /usr/libexec/spamass-filter -f -p /var/run/spamass-filter.so
最後に、OS 起動時に自動実行されるようにする。 spamd の後に追加する。
# vi /etc/rc
# SpamAssassin Filter if [ -x /etc/mail/startspamass.sh ]; then echo -n ' spamass-filter' /etc/mail/startspamass.sh fi
あとは sendmail と連結するだけ!!

2009/2/27
dk, repair
ちょっとずれるが、自分の出したメールを外部で受け取ったところ、 そのヘッダに、

Authentication-Results: sample.marimo.org; domainkeys=fail (testing)
と fail していた。
ちょっと調べたところ、 「dk-filter を(他のフィルタより)優先せよ」 ということらしいが、現状の sendmail の仕組みではどう順番を変えても対応できない(fail になる) ケースがあるらしい。 その解決方法としては -H オプションを付けることで、 署名対象のヘッダを明示的に提示することができる。
-H なし
DomainKey-Signature: a=rsa-sha1; s=omega; d=marimo.org; c=simple; q=dns; b=HJ9br3l...(略)...XOIShYsml6w==

-H あり
DomainKey-Signature: a=rsa-sha1; s=omega; d=marimo.org; c=simple; q=dns; h=dkim-signature:date:from:to:subject:message-id: mime-version:content-type:content-transfer-encoding:x-mailer; b=dQVG5HVtj...(略)...F1QyoTlG2wmg==
のようになる。 これで、検証側もどのヘッダで署名されているかがはっきりするので問題がなくなるそうだ。
<おまけ>
DomainKey の設定(DNS)から t=y を抜いてテストモードを卒業。

spamass-filter, setting sendmail.cf
本題に戻って、spamass-filter と sendmail をくっつける。
まず、sendmail.mc に以下を追加する。 追加する場所は、他のフィルター(DomainKey, DKIM, SPF/SenderID) のがいいと思う。

dnl -- SpamAssassin --
INPUT_MAIL_FILTER(`spamassassin', `S=local:/var/run/spamass-filter.sock, F=, T=C:15m;S:4m;R:4m;E:10m')dnl
define(`confMILTER_MACROS_CONNECT',`t, b, j, _, {daemon_name}, {if_name}, {if_addr}')dnl
define(`confMILTER_MACROS_HELO',`s, {tls_version}, {cipher}, {cipher_bits}, {cert_subject}, {cert_issuer}')dnl
1行目は他のフィルタと同じ。 後に続くフラグの説明をしておく。
F
R そのフィルタが使用不可の場合、そのメールの接続を拒否する(Reject)。
T そのフィルタが使用不可の場合、メールの接続を一時保留する(Temporary Error)。
なし そのフィルタが使用不可の場合、メールを素通しする。
T
C sendmail がフィルタに接続するときのタイムアウト(0 はシステムタイムアウトを使用)。
S sendmail がフィルタに情報を送信するときのタイムアウト。
R sendmail がフィルタから情報を受信するときのタイムアウト。
E 情報を受信した後、最終的な受信通知を受けるまでのタイムアウト。
とりあえず、FなしTT=C:15m;S:4m;R:4m;E:10m(マニュアルにあるとおり)にしておく。
2行目、3行目は spamass-milter のマニュアルに「指定しろ」と書いてある。 これは sendmail → spamass-milter へ情報を送信するときに、付加情報をつける指定だ。
あとは、sendmail.cf を作り直して、sendmail を再起動する。 /var/log/maillog を確認。何も問題なし。

じゃ、動作確認をしてみよう。 しばらく、これで動かして、誤判定がないようなら、spam を拒否するようにしよう。

まとめ

なんだかんだで、2ヶ月もかかったけど、 半分以上は sendmail.cf の設定で悩んでいた時間。 実質、ハマらなければ1週間もあればできるだろう。
SpamAssassin を導入した後は、 spam 拒否率 98% 以上、サイト単位では 99% 以上 を確保している。

最後に、これまでの作業の結果として各設定ファイルを示す。

sendmail.mc

divert(-1)
divert(0)
dnl #############
dnl ## Version ##
dnl #############
VERSIONID(`バージョン')
dnl #############
dnl ## OS Type ##
dnl #############
OSTYPE(bsd4.4)
dnl #######################
dnl ## Domain Macro Name ##
dnl #######################
DOMAIN(generic)
dnl ##############
dnl ## Features ##
dnl ##############
FEATURE(`access_db', `hash -o -T /etc/mail/access')dnl
FEATURE(blacklist_recipients)
FEATURE(local_lmtp)
FEATURE(mailertable, `hash -o /etc/mail/mailertable')
FEATURE(relay_based_on_MX)
FEATURE(virtusertable, `hash -o /etc/mail/virtusertable')
dnl -- Disable UUCP --
FEATURE(`nouucp', `reject')dnl
dnl -- for smtpfeed --
FEATURE(`nocanonify')dnl
dnl -- Greeting pause --
FEATURE(`greet_pause',`35000')dnl
dnl #######################
dnl ## Macro Definitions ##
dnl #######################
dnl -- Trusted users table --
define(`confCT_FILE', `-o /etc/mail/trusted-users')dnl
dnl -- Local hostnames(virtual domain) --
define(`confCW_FILE', `-o /etc/mail/local-host-names')dnl
dnl -- Alias filename ^--
define(`ALIAS_FILE', `/etc/mail/aliases')dnl
dnl -- Alias validation --
define(`confCHECK_ALIASES', `True')dnl
dnl -- SMTPF:smtpfeed --
define(`SMART_HOST', `smtpf:LMTP')dnl
dnl -- MIME header length --
define(`confMAX_MIME_HEADER_LENGTH', `256/128')
dnl -- Action at non recpient header --
define(`confNO_RCPT_ACTION', `add-to-undisclosed')
dnl -- Privacy flags --
define(`confPRIVACY_FLAGS', `authwarnings,noexpn,novrfy')
dnl -- Disable UUCP/BITNET --
undefine(`UUCP_RELAY')dnl
undefine(`BITNET_RELAY')dnl
dnl -- Cut off sendmail version --
define(`confSMTP_LOGIN_MSG', `$j $b')dnl
define(`confRECEIVED_HEADER', `$?sfrom $s $.$?_($?s$|from $.$_)
	$.$?{auth_type}(authenticated$?{auth_ssf} bits=${auth_ssf}$.)
	$.by $j $?r with $r$. id $i$?{tls_version}
	(version=${tls_version} cipher=${cipher} bits=${cipher_bits} verify=${verify})$.$?u
	for $u; $|;
	$.$b')dnl
dnl -- Refuse spam database --
FEATURE(dnsbl, `inputs.orbs.org')dnl
FEATURE(dnsbl, `outputs.orbs.org')dnl
FEATURE(dnsbl, `dul.maps.vix.com')dnl
FEATURE(dnsbl, `rbl.maps.vix.com')dnl
FEATURE(dnsbl, `relays.mail-abuse.org')dnl
FEATURE(dnsbl, `list.dsbl.org')dnl
FEATURE(dnsbl, `sbl-xbl.spamhaus.org')dnl
dnl -- SMTP-AUTH ---
define(`confAUTH_MECHANISMS', `DIGEST-MD5 CRAM-MD5 LOGIN')dnl
TRUST_AUTH_MECH(`DIGEST-MD5 CRAM-MD5 LOGIN')dnl
define(`confDEF_AUTH_INFO', `/etc/mail/auth-info')dnl
dnl -- SMTP over SSL ---
define(`confCACERT_PATH', `/etc/mail/certs')dnl
define(`confCACERT', `/etc/mail/certs/cacert.pem')dnl
define(`confSERVER_CERT', `/etc/mail/certs/smtls.crt')dnl
define(`confSERVER_KEY', `/etc/mail/certs/smtls.key')dnl
dnl -- Timeout --
define(`confTO_IDENT', `0s')dnl			# Unuse IDENT
define(`confTO_CONNECT', `2m')dnl		# Connect timeout(2 min)
define(`confTO_COMMAND', `5m')dnl		# SMTP command timeout(5 min)
define(`confTO_DATABLOCK',`5m')dnl		# Mail body sending timeout(5 min)
define(`confMIN_QUEUE_AGE', `15m')dnl		# Retry send message(15 min)
define(`confTO_QUEUEWARN', `5h')dnl		# Send retry warning message to sender(after 5h)
define(`confTO_QUEUERETURN', `4d')dnl		# Re-send message timeout(4 days)
define(`confTO_RESOLVER_RETRANS', `3s')dnl	# DNS resolve timeout(3 sec)
define(`confTO_RESOLVER_RETRY', `2')dnl		# DNS resolve retry(2)
dnl -- Resouce --
define(`confBAD_RCPT_THROTTLE', `3')dnl		# Bad recipt count(3)
define(`confCONNECTION_RATE_THROTTLE', `10')dnl	# Connection count within 1sec(10)
define(`confMAX_DAEMON_CHILDREN', `10')dnl	# Max processes(10)
define(`confDOUBLE_BOUNCE_ADDRESS', `')dnl	# Bounce mail error address(drop error mail)
dnl -- Other setting --
define(`confMAX_HOP', `25')dnl			# Max hops(25)
define(`confCHECKPOINT_INTERVAL', 10)dnl	# Distribute checkpoint count(10)
define(`confCOLON_OK_IN_ADDR', `False')dnl	# Reject address with COLON
define(`confOPERATORS', `.:@!^/[]+')dnl		# Disable Source routing by '%'
define(`confNO_RCPT_ACTION', `add-to-undisclosed')dnl	# Action at non recpient header
dnl -- Daemon ports --
FEATURE(`no_default_msa')dnl			# Cancel default setting for MSA port
DAEMON_OPTIONS(`Port=smtp, Name=MTA, M=A')dnl	# MTA port: SMTP(25) and NON authentication
DAEMON_OPTIONS(`Port=587, Name=MSA, M=Ea')dnl	# MSA port: Submission(587)
dnl ##################
dnl ## Mail filters ##
dnl ##################
dnl -- DKIM --
INPUT_MAIL_FILTER(`dkim-filter', `S=inet:8891@localhost,F=,T=R:4m')dnl
dnl -- SPF/Sender-ID --
INPUT_MAIL_FILTER(`sid-filter', `S=inet:8892@localhost,F=,T=R:4m')dnl
dnl -- DomainKey --
INPUT_MAIL_FILTER(`dk-filter', `S=inet:8893@localhost,F=,T=R:4m')dnl
dnl -- SpamAssassin --
INPUT_MAIL_FILTER(`spamassassin', `S=local:/var/run/spamass-filter.sock, F=T, T=C:15m;S:4m;R:4m;E:10m')dnl
define(`confMILTER_MACROS_CONNECT',`t, b, j, _, {daemon_name}, {if_name}, {if_addr}')dnl
define(`confMILTER_MACROS_HELO',`s, {tls_version}, {cipher}, {cipher_bits}, {cert_subject}, {cert_issuer}')dnl
dnl ###############################
dnl ## MAILER/Distribution Agent ##
dnl ###############################
dnl -- Local distribute --
MAILER(local)
dnl -- SMTP distribute --
MAILER(smtp)
dnl -- SMTPF:smtpfeed --
MAILER_DEFINITIONS
Msmtpf, P=/usr/libexec/smtpfeed, F=mDFMuXz, S=11/31, R=21, E=\n, L=990,
        T=DNS/RFC822/SMTP, A=smtpfeed -E
dnl ####################
dnl ## Local rulesets ##
dnl ####################
dnl -- Reject un-resolve IP address --
LOCAL_RULESETS
SLocal_check_relay
R$*	$: $&{client_resolve}
RTEMP	$#error $@ 4.7.1 $: "450 Access denied. Cannot resolve PTR record for " $&{client_addr}
RFAIL	$#error $@ 4.7.1 $: "450 Access denied. IP name lookup failed " $&{client_name}
dnl ########################
dnl ## END OF SENDMAIL.MC ##
dnl ########################
submit.mc
divert(-1)
divert(0)dnl
dnl ###########
dnl # Version #
dnl ###########
VERSIONID(`バージョン')
dnl #####################
dnl # Macro Ditinitions #
dnl #####################
define(`confCF_VERSION', `Submit')dnl
define(`__OSTYPE__',`')dnl dirty hack to keep proto.m4 from complaining
define(`_USE_DECNET_SYNTAX_', `1')dnl support DECnet
define(`confTIME_ZONE', `USE_TZ')dnl
dnl -- Run as smmsp --
define(`confRUN_AS_USER', `smmsp')dnl
dnl -- Trusted user --
define(`confTRUSTED_USER', `smmsp')dnl
dnl -- Cut off sendmail version --
define(`confRECEIVED_HEADER', `$?sfrom $s $.$?_($?s$|from $.$_)
	$.$?{auth_type}(authenticated$?{auth_ssf} bits=${auth_ssf}$.)
	$.by $j $?r with $r$. id $i$?{tls_version}
	(version=${tls_version} cipher=${cipher} bits=${cipher_bits} verify=${verify})$.$?u
	for $u; $|;
	$.$b')dnl
dnl ###############
dnl # MSP Support #
dnl ###############
FEATURE(`msp')dnl
dnl ######################
dnl ## END OF SUBMIT.MC ##
dnl ######################
/etc/mail/startdkim.sh
#!/bin/sh

SOCKETSPEC="inet:8891@localhost"
SELECTOR="alpha"
PRIVATEKEY="/etc/mail/dkim/alpha.private"
DOMAINS="marimo.org,cherokee.info,marimo.info"
PIDFILE="/var/run/dk-filter.pid"
ILIST="/etc/mail/dk/ilist"
HDRLIST="Return-Path,Received,Comments,Keywords,Bcc,Resent-Bcc"
# Action
BADSIGN="reject"
DNSERR="accept"
INTERR="accept"
NOSIGN="accept"

/usr/libexec/dkim-filter -l -D \
	-p $SOCKETSPEC \
	-P $PIDFILE \
	-C badsignature=$BADSIGN,dnserror=$DNSERR,internal=$INTERR,nosignature=$NOSIGN \
	-d $DOMAINS \
	-k $PRIVATEKEY \
	-s $SELECTOR \
	-i $ILIST \
        -o $HDRLIST
/etc/mail/startsid.sh
#!/bin/sh

SOCKETSPEC="inet:8892@localhost"
PIDFILE="/var/run/sid-filter.pid"
PEERLIST="/etc/mail/sid/peerlist"
# Action
# 0:accept all, 1:reject SID&&SPF failed
# 2:reject SID||SPF failed, 3:reject SID||SPF not passed
# 4:reject SID&&SPF not passed
REJECTMODE=0

/usr/libexec/sid-filter -l \
	-p $SOCKETSPEC \
	-a $PEERLIST \
	-P $PIDFILE \
	-r $REJECTMODE
/etc/mail/startdk.sh
#!/bin/sh

SOCKETSPEC="inet:8893@localhost"
SELECTOR="omega"
PRIVATEKEY="/etc/mail/dk/omega.private"
DOMAINS="marimo.org,sample.net"
PIDFILE="/var/run/dk-filter.pid"
ILIST="/etc/mail/dk/ilist"
HDRLIST="Return-Path,Received,Comments,Keywords,Bcc,Resent-Bcc"
# Action
BADSIGN="reject"
DNSERR="accept"
INTERR="accept"
NOSIGN="accept"
SIGNMISS="reject"

/usr/libexec/dk-filter -l -D \
	-p $SOCKETSPEC \
	-P $PIDFILE \
	-C badsignature=$BADSIGN,dnserror=$DNSERR,internal=$INTERR,nosignature=$NOSIGN,signaturemissing=$SIGNMISS \
	-d $DOMAINS \
	-s $PRIVATEKEY \
	-S $SELECTOR \
	-i $ILIST \
        -o $HDRLIST
/etc/mail/startspamd.sh
#!/bin/sh

PIDFILE=/var/run/spamd.pid
SER=spamd
GROUP=spamd
OPTIONS="--max-children=2 --max-conn-per-child=10"

/usr/local/bin/spamd -d -r $PIDFILE -u $USER -g $GROUP $OPTIONS -H
/etc/mail/startspamass.sh
#!/bin/sh

SOCKFILE=/var/run/spamass-filter.sock
PIDFILE=/var/run/spamass-filter.pid
OPTIONS="-m"
#OPTIONS="-m -r 15"
EXTRAFLAGS="-i 127.0.0.1,200.100.200.150/24"

/usr/libexec/spamass-filter -f -p $SOCKFILE -P $PIDFILE $OPTIONS $EXTRAFLAGS
/etc/rc の sendmail 起動前に挿入した部分
# Spamd
if [ -x /etc/mail/startspamd.sh ]; then
	echo -n ' spamd'
	/etc/mail/startspamd.sh
fi

# SpamAssassin Filter
if [ -x /etc/mail/startspamass.sh ]; then
	echo -n ' spamass-filter'
	/etc/mail/startspamass.sh
fi

# DKIM Filter
if [ -x /etc/mail/startdkim.sh ]; then
	echo -n ' dkim-filter'
	/etc/mail/startdkim.sh
fi

# SPF/Sender-ID Filter
if [ -x /etc/mail/startsid.sh ]; then
	echo -n ' sid-filter'
	/etc/mail/startsid.sh
fi

# DomainKey Filter
if [ -x /etc/mail/startdk.sh ]; then
	echo -n ' dk-filter'
	/etc/mail/startdk.sh
fi
/etc/mail/spamassassin/local.cf に追加した部分
# LOCAL RULESET
report_safe 0
use_bayes 1
bayes_auto_learn 1
bayes_path /var/spool/spamassassin/bayes
bayes_file_mode 0664
use_auto_whitelist 0
trusted_networks 200.100.200.150/24
bayes_ignore_header X-Spam-Flag
bayes_ignore_header X-Spam-Status

# Score cusmtamize
# -- Invalid Date(3.308)
score DATE_IN_FUTURE_12_24 1.0
# -- To: undisclosed-recipients(4.034)
score UNDISC_RECIPS 1.0
# -- Contains 'Dear Somebody'(-0.694)
score DEAR_SOMEBODY 1.5
# -- How dear can you be if you don't know my name?(-0.694)
score DEAR_FRIEND 1.5
# -- Subject looks like order info
score ORDER_STATUS 3.0
# -- "$$$" (0.182)
score CASHCASHCASH 0
# -- Subject has ?(0.7)
score SUBJ_HAS_Q_MARK 0.3
# -- Frame wanted to load outside URL(0.031)
score RELAYING_FRAME 3.0
# -- HTML-only mail, with no text version(1.665)
score CTYPE_JUST_HTML 3.0

# Whitelist
whitelist_from *@docomo.ne.jp
    :

これでおしまい!!

追記