分享
 
 
 

实现qmail下邮件过滤和邮件到达短信通知

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

qmail下使用python编写脚本来过滤邮件和发送短信

作者:梅劲松

时间:2004年9月10日

感谢:HD、刘鑫

短信这么火热,如果自己的邮件服务器也能和短信结合起来,那多么好啊?在qmail下,我用qmail_queue来解决邮件过滤和短信到达通知。

本邮件系统在freebsd 4.10上实施成功。

1、对于邮件系统的安装,没有太多说的。建议大家看HD的http://bsd.huangdong.com/server/mail/qmail.html来安装。如果已经安装了有qmail+vpopmail+qmail_queue补丁的,请直接跳到4继续安装。在新装邮件服务器时有几点需要注意的是,安装qmail的时候应该将步骤改为:

cd /usr/ports/mail/qmail

make WITH_QMAILQUEUE_PATCH=yes WITH_BIG_TODO_PATCH=yes WITH_OUTGOINGIP_PATCH=yes WITH_PRESERVE_CONFIG_FILES=yes extract

cd /usr/ports/mail/qmail/work/qmail-1.03

fetch http://www.nimh.org/dl/qmail-smtpd.c

cd /usr/ports/mail/qmail

make WITH_QMAILQUEUE_PATCH=yes WITH_BIG_TODO_PATCH=yes WITH_OUTGOINGIP_PATCH=yes WITH_PRESERVE_CONFIG_FILES=yes install

make disable-sendmail

make enable-qmail

make clean

在安装vpopmail的时候可能因为port更新的原因让使用vpopmail的时候出现奇怪问题,最好是安装webmin将建立的vpopmail数据库删除后,重新建立,并指定数据库的用户权限。由于webmin的安装不是本文重点,这里将忽略。

2、关于文章里面的杀毒部分因为系统效率问题,我没有安装。各位请自行决定是否安装。

3、反垃圾邮件当然需要,我用的是http://anti-spam.org.cn的cbl+服务。使用的时候只需要将smtpd的脚本改为:

#!/bin/sh

QMAILDUID=`/usr/bin/id -u qmaild`

NOFILESGID=`/usr/bin/id -g qmaild`

exec /usr/local/bin/tcpserver -H -R -l 0 -p -x \

/usr/local/vpopmail/etc/tcp.smtp.cdb -u"$QMAILDUID" \

-g"$NOFILESGID" -v -c100 0 smtp rblsmtpd \

-r cblplus.anti-spam.org.cn \

-r relays.ordb.org \

/var/qmail/bin/qmail-smtpd /usr/local/vpopmail/bin/vchkpw-smtpd /usr/bin/true 2>&1

http://anti-spam.org.cn/cgi-bin/rblclient/(你的邮件服务器的dns服务器IP地址)看到你的邮件服务器的流量和使用cbl+服务的情况。

4、这里是重点了。过滤和短信到达是需要qmail_queue来完成的,一定要打这个包。

使用python来实现这个功能,当然需要安装python啦。

cd /usr/ports/lang/python

make;make install;make clean

一般来讲这个安装是非常顺利的。

安装结束后。

cd /var/qmail/bin

编辑qmfilt.py,内容如下:

#!/usr/local/bin/python --

import os, sys, string, time, traceback, re, socket

Version = '1.1'

PyVersion = '1.0'

Logging = 1

Debug = 0

QmailD = 82#这里需要和你/etc/password里面qmaild用户的一样

LogFile = None

TestMode = None

Filters = []

MailEnvelope = ''

MailData = ''

ValidActions = { 'trap': '', 'drop' : '', 'block' :'' }

#这里是你通过ucp协议将消息发送到哪个服务器的哪个端口

def mail_sms(msg):

host = "xxx.xxx.114.2"

port = 9999

buf = 500

addr = (host,port)

# Create socket

UDPSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

def_msg = msg;

UDPSock.sendto(def_msg,addr)

# Close socket

UDPSock.close()

def openlog():

global LogFile

if not Logging:

return

try:

LogFile = os.open('/var/log/qmfilt', os.O_APPEND|os.O_WRONLY|os.O_CREAT, 0744)

except:

if TestMode:

print "Can't create /var/log/qmfilt"

else:

# Indicate a write error

sys.exit(53)

def log(message):

message = time.asctime(time.localtime(time.time())) + " - " + message + "\n"

if Logging:

os.write(LogFile, message)

# Indicate a write error

#sys.exit(53)

if TestMode:

print message

def showFilters():

if len(Filters) == 0:

print "No filters"

for f in Filters:

print "Filter - %s - Action: %s Regex: %s" % (f[0], f[1], f[2])

def readFilters():

global Filters

try:

file = open('/var/qmail/control/qmfilt', 'r')

configLines = file.readlines()

file.close()

except:

log("Can't read /var/qmail/control/qmfilt")

if not TestMode:

# Indicate a 'cannot read config file error'

sys.exit(53)

reg = re.compile('(.*)::(.+)::(.+)::')

for line in configLines:

if line[0] == '#':

continue

m = reg.match(line)

if m != None:

action = string.lower(m.group(2))

if not ValidActions.has_key(action):

log("Invalid action in config file [%s] - SKIPPED" %(action))

continue

Filters.append( [m.group(1), string.lower(m.group(2)), m.group(3)] )

def readDescriptor(desc):

result = ''

while 1:

data = os.read(desc, 16384)

if data == '':

break

result = result + data

return result

def writeDescriptor(desc, data):

while data:

num = os.write(desc, data)

data = data[num:]

os.close(desc)

def filterHits():

for regEx in Filters:

reg = re.compile(regEx[2], re.M|re.I)

match = reg.search(MailData)

if match:

log("Matched [%s]" % (regEx[0]))

return regEx[1], regEx[0]

return None,None

def storeInTrap(hit):

try:

pid = os.getpid()

mailFile = os.open('/var/qmail/qmfilt/qmfilt.%s' %(pid), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744)

except:

log("Can't create /var/qmail/qmfilt/qmfilt.%s" %(pid))

# Indicate a write error

sys.exit(53)

try:

#如果不屏蔽会出现很多问题,至于为什么我还没弄明白,如果你找到问题所在,请告诉我。

"""(None, inode, None, None, None, None, size, None, None, None) = os.fstat(mailFile)"""

os.rename('/var/qmail/qmfilt/qmfilt.%s' %(pid), '/var/qmail/qmfilt/%s.mail' %(inode))

except:

log("Can't rename /var/qmail/qmfilt/qmfilt.%s -> /var/qmail/qmfilt/%s.mail" %(pid, inode))

# Indicate a write error

sys.exit(53)

try:

envFile = os.open('/var/qmail/qmfilt/%s.envelope' %(inode), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744)

mesgFile = os.open('/var/qmail/qmfilt/%s.qmfilt' %(inode), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744)

writeDescriptor(mailFile, MailData)

writeDescriptor(envFile, MailEnvelope)

writeDescriptor(mesgFile, "Matched filter [ %s] to file %s" %(hit, inode))

log("Matched filter [ %s] to file %s" %(hit, inode))

except:

log("Can't create/write files into trap")

# Indicate a write error

sys.exit(53)

return 0

def sendToQmailQueue():

# Open a pipe to qmail queue

fd0 = os.pipe()

fd1 = os.pipe()

pid = os.fork()

if pid < 0:

log("Error couldn't fork")

sys.exit(81)

if pid == 0:

# This is the child

os.dup2(fd0[0], 0)

os.dup2(fd1[0], 1)

i = 2

while (i < 64):

try:

os.close(i)

except:

pass

i = i + 1

os.chdir('/var/qmail')

#time.sleep(10)

os.execv("bin/qmail-queue", ('bin/qmail-queue',))

log("Something went wrong")

sys.exit(127) # This is only reached on error

else:

# This is the parent

# close the readable descriptors

os.close(fd0[0])

os.close(fd1[0])

# Send the data

recvHdr = "Received: (QMFILT: %s); " %(Version)

recvHdr = recvHdr + time.strftime("%d %b %Y %H:%M:%S", time.gmtime(time.time()))

recvHdr = recvHdr + " -0000\n"

os.write(fd0[1], recvHdr)

writeDescriptor(fd0[1], MailData)

writeDescriptor(fd1[1], MailEnvelope)

# Catch the exit code to return

result = os.waitpid(pid, 0)[1]

if PyVersion > '1.5.1':

if os.WIFEXITED(result):

return os.WEXITSTATUS(result)

else:

log("Didn't exit normally")

sys.exit(81)

else:

return result

def conver(msg):

msg = msg.replace(chr(00),' ')

msg = msg.replace('F','From:',1)

msg = msg.replace(' T',' To:')

return msg

def main(argv, stdout, environ):

global TestMode

global MailData

global MailEnvelope

global PyVersion

PyVersion = string.split(sys.version)[0]

if len(argv) > 1 and argv[1] == '--test':

TestMode = 1

os.setuid(QmailD)

# Insure Environment is OK

# Try to open log

openlog()

if not os.path.exists('/var/qmail/qmfilt'):

# Indicate a problem with the queue directory

log("Directory /var/qmail/qmfilt doesn't exist")

if not TestMode:

sys.exit(61)

if os.path.exists('/var/qmail/control/qmfilt'):

readFilters()

else:

if TestMode:

print "No filter file /var/qmail/control/qmfilt - no filters applied"

if TestMode:

showFilters()

# Go no further if in test mode

if TestMode:

sys.exit(0)

# Get the data

# Read the data

MailData = readDescriptor(0)

# Read the envelope

MailEnvelope = readDescriptor(1)

if Debug:

log(MailData)

log(MailEnvelope)

action,hit = filterHits()

if action == 'trap':

storeInTrap(hit)

if action == 'block':

log("Matched filter [ %s] and email was BLOCKED/Refused delivery" %(hit))

sys.exit(31)

if action == 'drop':

log("Matched filter [ %s] and email was DROPPED" %(hit))

if action == None:

sendToQmailQueue()

#Log

log(conver(MailEnvelope))

#send sms

mail_sms(conver(MailEnvelope))

if Debug:

log("qmailqueue returned [%d]" %(result))

sys.exit(0)

if __name__ == "__main__":

try:

main(sys.argv, sys.stdout, os.environ)

# Catch the sys.exit() errors

except SystemExit, val:

sys.exit(val)

except:

# return a fatal error for the unknown error

if TestMode:

traceback.print_exc()

sys.exit(81)

然后在cd /var/qmail/control

编辑qmfilt内容如下:

#

# This is the qmfilt control file

# If any email comes in that matches this

# filter, the mail will be redirected

# to the filter directory

#

# A filter regular expression must be on a single

# line and in between a pair of '::'

#

# Valid actions:

# trap - store in the trap directory

# block - tell the smtp sender that we won't take the mail

# drop - accept the mail, but don't queue it

#

# This will match any executable Visual Basic Scripts

VisualBasic::block::^Content-Type: application/octet-stream;\s+name="(.*\.vbs)"::

SHS::block::^Content-Type: application/octet-stream;\s+name="(.*\.shs)"::

SHB::block::^Content-Type: application/octet-stream;\s+name="(.*\.shb)"::

COM::block::^Content-Type: application/octet-stream;\s+name="(.*\.com)"::

EXE::block::^Content-Type: application/octet-stream;\s+name="(.*\.exe)"::

SCR::block::^Content-Type: application/octet-stream;\s+name="(.*\.scr)"::

PIF::block::^Content-Type: application/octet-stream;\s+name="(.*\.pif)"::

BAT::block::^Content-Type: application/octet-stream;\s+name="(.*\.bat)"::

# This is the Outlook date overflow bug

DATE::block::^Date:.{160,}::

在/var/log下建立qmfilt文件,内容为空

chown qmaild qmfilt

在/var/qmail下建立qmfilt目录。

chown -R qmaild qmfilt

好了,来让我们的程序启用吧

先测试一把

/var/qmail/bin/qmfilt.py --test

如果返回了先定义的qmfilt里面的内容,恭喜成功了一半了。内容应该和下面的相似:

Filter - VisualBasic - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.vbs)"

Filter - SHS - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.shs)"

Filter - SHB - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.shb)"

Filter - COM - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.com)"

Filter - EXE - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.exe)"

Filter - SCR - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.scr)"

Filter - PIF - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.pif)"

Filter - BAT - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.bat)"

Filter - DATE - Action: block Regex: ^Date:.{160,}

好了,让我们的过滤器用起来吧

cd /usr/local/vpopmail/etc

编译tcp.smtp内容如下

127.:allow,RELAYCLIENT="",QMAILQUEUE="bin/qmfilt.py"

:allow,QMAILQUEUE="bin/qmfilt.py"

然后生成tcp.smtp.cdb文件

tcprules tcp.smtp.cdb tcp.smtp.tmp < tcp.smtp

,发个带exe为附件的邮件看看。如果成功过滤,应该在/var/log/qmfilt里面看到如下信息:

Fri Sep 10 14:37:01 2004 - Matched filter [ EXE] and email was BLOCKED/Refused delivery

Fri Sep 10 14:38:09 2004 - Matched [SCR]

如果你是用foxmail等客户端发送带exe为结尾的邮件的话,服务器会提示你拒绝接收的。

到这里,你的邮件过滤已经成功了。

你可以在/var/qmail/control的qmfilt文件里面定义你需要过滤的指定代码,然后测试规则是否生效了。

关于短信到达通知,因为安全和商业上的问题。不能将服务器端的代码贴出来。我将机制给大家讲一下。

qmfilt.py这个程序里面mail_sms用udp将类似From:info@xxx.com.cn To:weilx@xxx.com发送到指定的服务器上。服务器接收到这个数据包后,将这个数据包的内容用短信发送出去。

当然可以将邮件主题等信息一起发送到手机上,大家根据自己的情况更改吧。

这个程序是根据http://sourceforge.net/projects/qmfilt/这个项目更改的,但是直接用他的代码会造成不能收信,大家可以在这个项目的cvs关注他的代码修改情况。

结束,谢谢!

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有