HOME 備忘録 書き込む

メールサーバー上でのメールの振り分け


Jul 21, 2006

1. はじめに

せっかく IMAP を使っているのでメールサーバー上でメールを振り分けたり、スパムメールを判定するようにしました。 こうすることによって、クライアントごとに設定する手間が省け、IMAP を使っているありがたみが増します。

サーバー上で、MTA が受け取ったメールをメール振り分けソフトに渡してメールを振り分けます。また、その際に スパムメールを判定することができます。サーバー上でスパムメールを学習させることによって学習する場所を1つにすることができ、 学習効率が向上します。

振り分けソフトには maildrop を、スパム判定には bogofilter を使います。 また、日本語のスパムメール判定を補助するために nkf, kakasi を使います。

念のため紫藤の環境を書いておきます。

ディストリビューション Debian/GNU Linux, sarge
MTA postfix
MDA courier-imap
保存形式 Maildir

参考にしたサイトは以下の通りです。

  1. Bogofilter Home Page
  2. maildrop 利用のメモというかtips
  3. http://www.ginganet.org/ginga/memo/200309bogofilter.txt

2. 必要なソフトにインストール

2.1. maildrop

振り分けソフトです。

紫藤は apt-get install しました。 また、ソースは http://www.courier-mta.org/maildrop/ にあります。 Courier をソースからインストールした場合はすでにインストールされています。

2.2. bogofilter

Bayesian filter を使用したスパムメール判定ソフトです。使用する前にスパムメールと正常メールを学習させる必要があります。 また、継続して学習させることによって賢くなっていきます。

ソースは http://bogofilter.sourceforge.net/ にあります。tar ball を展開して、
./configure → make → sudo make install
でインストールできます。

2.3. nkf

日本語文字コード変換ソフトです。文字コードを統一することで bogofilter の学習効率を上げます。

ソースは http://sourceforge.jp/projects/nkf/ からダウンロードできます。

展開 → ./configure → make → sudo make install
でインストールできます。

2.4. kakasi

日本語分かち書きソフトです。日本語を単語に分けてあげることで bogofilter が学習しやすくします。 ソースは http://kakasi.namazu.org/ にあります。これも、
展開 → ./configure → make → sudo make install
でインストールできます。

3. スパムの学習

これ以降は ~/Maildir が以下の構造になっているとして話を進めます。

ディレクトリ 説明
.Business 仕事関係の正常メール
.Private 私用の正常メール
.Others それ以外の正常メール
.Spam スパムメール
.Business0 スパムメールと誤判定された仕事用メール
.Private0 スパムメールと誤判定された私用メール
.Others0 スパムメールと誤判定されたその他のメール
.Spam0 正常メールと誤判定されたスパムメール

3.1. 初期学習

bogofilter を使うためにはまず、どれがスパムでどれが正常か教え込む必要があります。

初期状態では .Spam にスパムメール、.Business, .Private, および .Others に正常メールがあるとします。 次のコマンドで学習させることができます。

$ find ~/Maildir/.Spam/cur -name "*" -type f -exec nkf -Z -m -j {} \; | kakasi -w | bogofilter -s
$ find ~/Maildir/.Business/cur -name "*" -type f -exec nkf -Z -m -j {} \; | kakasi -w | bogofilter -n
$ find ~/Maildir/.Private/cur -name "*" -type f -exec nkf -Z -m -j {} \; | kakasi -w | bogofilter -n
$ find ~/Maildir/.Others/cur -name "*" -type f -exec nkf -Z -m -j {} \; | kakasi -w | bogofilter -n
  1. まず、find -name "*" -type f でメールが保存されているディレクトリの全てのファイルを取り出し、
  2. それを nkf で文字コードを iso-2022-jp に変換します (-j)。ついでに、-Z で一部を ASCII 文字に変換し -m で MINE の解読もします。 変換する文字コードはメーラーに合わせて -j (iso-2022-jp), -e (euc-jp) -s (shift_jis) から選択します。
  3. 次に kakasi -w で分かち書きをします。
  4. 最後に bogofilter で学習します -s でスパム、 -n で正常メールとして学習します。

3.2. 日々の学習

次のスクリプトを /etc/cron.daily に入れておくと毎日スパムと正常メールを学習します。 赤文字のところは各自の環境に合わせて設定してください。 dropmail の設定で、日本語の前処理が済んでいるので、bogofilter だけを呼び出します。

.Spam0 にあるメールをスパムとして学習し、 .Spam に移動します。 また、.Business0, .Private0, および .Others0 にあるメールを正常メールとして学習し、 それぞれ .Business, .Private, および .Other に移動します。

bogofiliter の -Ns オプションは正常メールであることを取り消してスパムとして登録すること、また、 -Sn オプションはスパムであることを取り消して正常メールとして登録することを意味します。

01:     #! /usr/bin/env python
02:     """
03:     learing spam and nonspam mails automatically using with cron
04:     """
05:     
06:     import os, os.path
07:     
08:     USER="YourUserName"
09:     MAILDIR=os.path.join("/home", USER, "Maildir")
10:     BOGOFILTER="/usr/local/bin/bogofilter -B "
11:     MV="/bin/mv "
12:     CHECKDIRS=[ 
13:       (".Spam0", ".Spam", "-Ns"), 
14:       (".Business0", ".Business", "-Sn"), 
15:       (".Private0", ".Private", "-Sn"), 
16:       (".Others0", ".Others", "-Sn")
17:     ]
18:     
19:     def su_c(usr, cmd):
20:         return "su - " + usr + " -c \"" + cmd + "\""
21:     
22:     def bogo(dir, flag):
23:         return BOGOFILTER + flag + " " + dir
24:     
25:     def mv_all(dir0, dir1):
26:         return MV + os.path.join(dir0, "*") + " " + dir1
27:     
28:     def reg_mail(dir0, dir1, flag):
29:         dir0 = os.path.join(MAILDIR, dir0, "cur")
30:         if os.listdir(dir0):
31:             dir1 = os.path.join(MAILDIR, dir1, "cur")
32:             os.system(su_c(USER, bogo(dir0, flag)))
33:             os.system(su_c(USER, mv_all(dir0, dir1)))
34:     
35:     if __name__=='__main__':
36:         for dir0, dir1, flag in CHECKDIRS:
37:             reg_mail(dir0, dir1, flag)

4. 設定

4.1. MTA の設定

Postfix の場合 ~/.forward を次のようにします。スパムフィルターを働かせるためには -d オプションを指定して 自分のユーザー名を続ける必要があります。
"|/usr/bin/maildrop -d YourUserName"

4.2. maildrop の設定

maildrop の設定は ~/.mailfilter で行います。~/.mailfilter のモードは 600 でないと正常に動作しません。 以下に例を示します。
01:     MAILDIR="/home/YourUserName/Maildir/"
02:     DEFAULT=$MAILDIR
03:     NKF="/usr/local/bin/nkf"
04:     NKF_OPTION="-Z -m -j"
05:     WAKATI="/usr/local/bin/kakasi -w"
06:     BOGOFILTER="/usr/local/bin/bogofilter"
07:     BOGOFILTER_OPTION="-u -e -p"
08:     BOGOJP="${NKF} ${NKF_OPTION} | ${WAKATI} | ${BOGOFILTER} ${BOGOFILTER_OPTION}"
09:     
10:     xfilter "${BOGOJP}"
11:     
12:     if (/^X-Bogosity: Spam, tests=bogofilter/)
13:     {
14:     to "${MAILDIR}.Spam/"
15:     }
16:     if (/^From:.*@mycompany\.co\.jp/:h)
17:     {
18:     to "${MAILDIR}.Business/"
19:     }
20:     
21:     if (/^From:.*myfriend@/:h)
22:     {
23:     to "${MAILDIR}.Private/"
24:     }
25:     
26:     if (/^From:.*sale/:h)
27:     {
28:     to "${MAILDIR}.Others/"
29:     }
if に続けて振り分けルールを正規表現で書きます。:h はヘッダーだけ調べるという意味です。 if(...) のあと改行して { を書かないと文法エラーになります。

bogofilter に '-u -e -p' オプションをつけるとスパムだと判定された場合
X-Bogosity: Spam, tests=bogofilter
という行が挿入されるので、それを振り分けルールに加えてスパムを振り分けます。 -u, -e, -p オプションはそれぞれ、判定後メールを登録する、エラーが起きなければ OS に 0 を返す、 メッセージヘッダの最後に X-Bogosity ... の行を追加する、という意味です。

振り分けルールに合致しないメールは DEFAULT に振り分けられます。

4.3. ~/.mailfilter 生成スクリプト

~/.mailfilter は頻繁に変更するのでいつも使っている PC 上で作成し、メールサーバにアップロードできるようにしておくと便利です。 そのための簡単なスクリプトを python で書いてみました。mailfilter.inp を読んで、.mailfilter を作り、それをアップロードします。
01:     #! /usr/bin/env python
02:     
03:     """
04:     changing .mailfilter from a cliant
05:     The script takes mailfilter.inp as a input file
06:     The format of the input file is:
07:     ---
08:     # input file for mailfilter.py to make a .mailfilter at your mail server
09:     # regexp(TAB)directory
10:     ^From:.*@mycompany\.co\.jp	.Business
11:     ^From:.*myfriend@	.Private
12:     ^From:.*sale	.Others
13:     ---
14:     """
15:     
16:     import os, os.path, sys, socket
17:     from ftplib import FTP
18:     from cStringIO import StringIO
19:     
20:     DIR="C:\\doc\\misc\\"
21:     INP=os.path.join(DIR, "mailfilter.inp")
22:     OUT=os.path.join(DIR, ".mailfilter")
23:     LOG=os.path.join(DIR, "mailfilter.log")
24:     SERVER="your.mail.server"
25:     USER="YourUserName"
26:     PASSWD="YourPassword"
27:     HEADER= \
28:     """MAILDIR=\"/home/takafumi/Maildir/\"
29:     DEFAULT=$MAILDIR
30:     NKF=\"/usr/local/bin/nkf\"
31:     NKF_OPTION=\"-Z -m -j\"
32:     WAKATI=\"/usr/local/bin/kakasi -w\"
33:     BOGOFILTER=\"/usr/local/bin/bogofilter\"
34:     BOGOFILTER_OPTION=\"-u -e -p\"
35:     BOGOJP=\"${NKF} ${NKF_OPTION} | ${WAKATI} | ${BOGOFILTER} ${BOGOFILTER_OPTION}\"
36:     
37:     xfilter \"${BOGOJP}\"
38:     
39:     if (/^X-Bogosity: Spam, tests=bogofilter/)
40:     {
41:     to \"${MAILDIR}.Junk/\"
42:     }
43:     
44:     """
45:     
46:     def make_rule(line):
47:         """making a rule for .mailfilter from a line of the input file"""
48:         regexp, dir = tuple(line.split('\t', 2))
49:         return "if (/" + regexp + "/:h)\n{\nto \"${MAILDIR}" + dir + "/\"\n}\n\n"
50:     
51:     def create_mailfilter(nfin, nfout):
52:         """creating a .mailfilter at client"""
53:         fin=file(nfin)
54:         fout=file(nfout, 'w')
55:         fout.write(HEADER)
56:         for line in fin:
57:             line = line.rstrip('\n')
58:             len(line)==0 or \
59:               line[0]=='#' or \
60:               fout.write(make_rule(line))
61:         fin.close()
62:         fout.close()
63:     
64:     def upload_file(server, user, passwd, fname, log_name):
65:         """FTP upload a file to a server"""
66:         try:
67:             ftp = FTP(server)
68:         except:
69:             write_log(log_name, "Cannot connect to " + server, False)
70:     
71:         try:
72:             ftp.login(user, passwd)
73:         except:
74:             ftp.close()
75:             write_log(log_name, "Cannot login: " + server, False)
76:     
77:         put(ftp, fname, log_name)
78:         ftp.quit()
79:         write_log(log_name, "Upload " + fname + " done.", True)
80:     
81:     def put(ftp, fname, log_name):
82:         """ put a file through ftp"""
83:         try:
84:             f = file(fname)
85:         except:
86:             ftp.close()
87:             write_log(log_name, "Cannot open local file: " + fname, False)
88:         try:
89:             ftp.storlines("STOR " + os.path.basename(fname), f)
90:         except:
91:             ftp.close()
92:             write_log(log_name, "Cannot Transfer local file: " + fname, False)
93:                 
94:         f.close()
95:             
96:     def write_log(logfile, str, success):
97:         """writing a log file"""
98:         log = file(logfile, 'w')
99:         log.write( ((not success) and  'Error: ' or '') + str + "\n")
100:         log.close()
101:         success or sys.exit(1)
102:     
103:     if __name__=='__main__':
104:         create_mailfilter(INP, OUT)
105:         upload_file(SERVER, USER, PASSWD, OUT, LOG)

5. 終わりに

これで IMAP が使いやすくなり、出先でノート PC を使って自宅サーバにアクセスするときスパムに悩まされなくてすむようになりました。

ちなみに、公衆無線 LAN は不用心なので、出先からアクセスするときは メールの送受信には SSL (imap-ssl, smtp-ssl) と SMTP-AUTH を使っています。

今はサーバー証明書も安いので個人レベルでも利用できるようになりました。