読者です 読者をやめる 読者になる 読者になる

postfixでのメール受信時ウィルスチェック(ClamAntiVirus)でメール削除、受信者への通知を行えるようにする

メール

目的
postfixでメールを受信したときにClamAVのウィルスチェックを行い、チェックに引っかかったものはメール削除、および受信者に対してメールの送信があったことを通知できるようにする。
procmailからpythonを呼び出したのだが、procmail周りの情報が少なくてこずったのでメモを残しておきます。

postfix設定
postfixがメール受信時にprocmailを実行するようにmaibox_commandを設定しておく
・/etc/postfix/main.cf

mailbox_command = /usr/bin/procmail

procmail設定
受信者全員に対して処理を行うので/etc/procmailrcに設定を記述する。
メール受信時にclamAVの常駐プロセス(clamd)でスキャンし、スキャン結果を新規でメールヘッダ(x-virus)に追加する。
それからprocmailのレシピでメールヘッダ(x-virus)の値を見て、ウィルス判定されていたらメール削除、メール送信があったことを受信者へ通知できるようにする。

procmailからpythonスクリプトを呼び出す場合は、以下のようにprocmailのシェル呼び出しのアクションを実行する

|/usr/bin/python /var/procmail/sh/mail.python

python側では標準入力からメールヘッダ、本文の内容が入力されるので、これを使って処理する。
標準入力からの受け取り方を知らなかったのでここら辺でもちょっとてこずった、サンプルをあげると以下のようになる。

import sys
if __name__ == "__main__":
    param = sys.argv
    print param
    f=open('/home/user/mail.txt', 'w')
    lines = sys.stdin.readlines()
    for line in lines:
        f.write(line.encode('utf-8'))
    f.close()

procmail側の処理をまとめると以下のようになる。

 パスを設定
PATH=/bin:/usr/bin:/usr/local/bin
# メールボックスの設定
MAILDIR=$HOME/Maildir
DEFAULT=$MAILDIR/
# Procmailのログファイル出力先を指定
LOGFILE=$MAILDIR/procmaillog
# ロックファイルのパスを指定
LOCKFILE=$HOME/.lockmail

# Clam AntiVirusによるウィルスチェック
AV_REPORT=`/usr/bin/clamdscan  --no-summary - 2>&1| awk -F\n -v ORS=' ' '{print}'|awk '{print $NF}'`
VIRUS=`if [ "$AV_REPORT" != "OK" ]; then echo Yes; else echo No;fi`

:0fw
| formail -i "X-Virus: $VIRUS"
# Clam AntiVirusによるウィルスチェック

# Clam AntiVirusがウィルス判定したメール通知後削除
:0
* ^X-Virus: Yes
|/usr/bin/python /var/procmail/sh/mail.python `echo "$DEFAULT"`

:0
* ^X-Virus: Yes
/dev/null

pythonに対して環境変数DEFAULTを渡しているのがポイントで、これを通知メールの送付先の情報として使っています。
もともとはメールヘッダのTOを見て通知しようとしていたのですが、よくよく考えると複数人に対してメールの送信
があると1人ずつprocmail実行されるので問題だと思ってやめました。
python側は以下のようになるかと思います。
タイトルとか本文は日本語のデコードが必要になります。

# coding: UTF-8
import sys
import codecs
import smtplib
import re
from email.MIMEText import MIMEText
from email.Header import Header
from email.Utils import formatdate
from datetime import datetime

def mail_send( from_address, to_address, mail_text, charset):
    msg = MIMEText(mail_text.encode(charset),"plain",charset)
    msg["Subject"] = Header(u"ウィルスチェックによるメール削除のご連絡",charset)
    msg["From"]    = from_address
    msg["To"]      = to_address
    msg["Date"]    = formatdate(localtime=True)
    smtp = smtplib.SMTP("localhost", 25)
    smtp.sendmail(from_address,to_address,msg.as_string())
    smtp.close()

def array2text(args, mail_info):
    msg = u"ウィルスチェックにより以下メールを削除しました。\n"
    msg += u"------------------------------------\n"
    #msg += u"送信者:" + args[1].encode('utf-8') + u"\n"
    msg += u"送信者:" + ",".join(mail_info[0]).encode('utf-8') + u"\n"
    msg += u"受信者:"
    for to_address in mail_info[1]:
        msg += to_address.encode('utf-8')
    msg += u"件名:" + mail_info[2].encode('utf-8') + "\n"
    msg += u"時間:" + datetime.now().strftime("%Y/%m/%d %H:%M:%S") + "\n"
    msg += u"------------------------------------\n"
    #msg += u"本文:\n"
    #for arg in args:
    #    msg += arg.encode('utf-8') + "\n"
    #msg += u"------------------------------------\n"
    return msg

def get_mail_info(lines):
    from_address = []
    to_address = []
    Info=[from_address, to_address, ""]
    find_to = False
    for line in lines:
        if find_to is True:
            if line.find(":") > 0:
                find_to = False
            else:
                to_address.append(line)
        if re.match("^To:", line) is not None:
            to_address.append(line[3:].strip())
            find_to = True
        if re.match("^From:", line) is not None:
            from_address.append(line[5:].strip())
        if re.match("^Subject:", line) is not None:
            Info[2] = line.encode("utf8")
    return Info


if __name__ == "__main__":
    param = sys.argv
    print param
    lines = sys.stdin.readlines()
    mail_info = get_mail_info(lines)
    mail_text = array2text(param, mail_info)
    mail_receiver = param[1].split("/")[2] + "@○○○"
    mail_send( '○○○@○○○', mail_receiver, mail_text, "UTF8")