Qmail with MobilPhone notes 使用 Qmail 与手机短信!
-- Zoom.Quiet [2004-09-11 19:18:27]
Qmail in FreeBSD
python来过滤邮件和发送短信
实现qmail下邮件过滤和邮件到达短信通知实录
- 作者:梅劲松
- 时间:2004年9月10日
- 感谢:HD、刘鑫
- 短信这么火热,如果自己的邮件服务器也能和短信结合起来,那多么好啊?在qmail下,我用qmail_queue来解决邮件过滤和短信到达通知。
本邮件系统在freebsd 4.10上实施成功。
step1 Qmail install
对于邮件系统的安装,没有太多说的
建议大家看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的安装不是本文重点,这里将忽略。
关于文章里面的杀毒部分因为系统效率问题,我没有安装。各位请自行决定是否安装。
step2 SPAM
反垃圾邮件当然需要
我用的是http://anti-spam.org.cn的cbl+服务。使用的时候只需要将smtpd的脚本改为:
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+服务的情况。
step3 qmail_queue
这里是重点了。过滤和短信到达是需要qmail_queue来完成的,一定要打这个包。
- 使用python来实现这个功能,当然需要安装python啦。
cd /usr/ports/lang/python make;make install;make clean
- 一般来讲这个安装是非常顺利的。
- 安装结束后。
cd /var/qmail/bin
- 编辑qmfilt.py,内容如下:
1 #!/usr/local/bin/python --
2
3 import os, sys, string, time, traceback, re, socket
4
5 Version = '1.1'
6 PyVersion = '1.0'
7 Logging = 1
8 Debug = 0
9 QmailD = 82#这里需要和你/etc/password里面qmaild用户的一样
10 LogFile = None
11 TestMode = None
12 Filters = []
13 MailEnvelope = ''
14 MailData = ''
15 ValidActions = { 'trap': '', 'drop' : '', 'block' :'' }
16
17 #这里是你通过ucp协议将消息发送到哪个服务器的哪个端口
18 def mail_sms(msg):
19 host = "xxx.xxx.114.2"
20 port = 9999
21 buf = 500
22 addr = (host,port)
23
24 # Create socket
25 UDPSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
26
27 def_msg = msg;
28 UDPSock.sendto(def_msg,addr)
29
30 # Close socket
31 UDPSock.close()
32
33 def openlog():
34 global LogFile
35
36 if not Logging:
37 return
38 try:
39 LogFile = os.open('/var/log/qmfilt', os.O_APPEND|os.O_WRONLY|os.O_CREAT, 0744)
40 except:
41 if TestMode:
42 print "Can't create /var/log/qmfilt"
43 else:
44 # Indicate a write error
45 sys.exit(53)
46
47 def log(message):
48 message = time.asctime(time.localtime(time.time())) + " - " + message + "\n"
49 if Logging:
50 os.write(LogFile, message)
51 # Indicate a write error
52 #sys.exit(53)
53 if TestMode:
54 print message
55
56
57 def showFilters():
58
59 if len(Filters) == 0:
60 print "No filters"
61 for f in Filters:
62 print "Filter - %s - Action: %s Regex: %s" % (f[0], f[1], f[2])
63
64 def readFilters():
65 global Filters
66
67 try:
68 file = open('/var/qmail/control/qmfilt', 'r')
69 configLines = file.readlines()
70 file.close()
71 except:
72 log("Can't read /var/qmail/control/qmfilt")
73 if not TestMode:
74 # Indicate a 'cannot read config file error'
75 sys.exit(53)
76
77 reg = re.compile('(.*)::(.+)::(.+)::')
78 for line in configLines:
79 if line[0] == '#':
80 continue
81 m = reg.match(line)
82 if m != None:
83 action = string.lower(m.group(2))
84 if not ValidActions.has_key(action):
85 log("Invalid action in config file [%s] - SKIPPED" %(action))
86 continue
87 Filters.append( [m.group(1), string.lower(m.group(2)), m.group(3)] )
88
89 def readDescriptor(desc):
90 result = ''
91 while 1:
92 data = os.read(desc, 16384)
93 if data == '':
94 break
95 result = result + data
96
97 return result
98
99 def writeDescriptor(desc, data):
100 while data:
101 num = os.write(desc, data)
102 data = data[num:]
103
104 os.close(desc)
105
106
107 def filterHits():
108
109 for regEx in Filters:
110 reg = re.compile(regEx[2], re.M|re.I)
111 match = reg.search(MailData)
112 if match:
113 log("Matched [%s]" % (regEx[0]))
114 return regEx[1], regEx[0]
115
116 return None,None
117
118
119 def storeInTrap(hit):
120 try:
121 pid = os.getpid()
122 mailFile = os.open('/var/qmail/qmfilt/qmfilt.%s' %(pid), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744)
123 except:
124 log("Can't create /var/qmail/qmfilt/qmfilt.%s" %(pid))
125 # Indicate a write error
126 sys.exit(53)
127
128 try:
129 #如果不屏蔽会出现很多问题,至于为什么我还没弄明白,如果你找到问题所在,请告诉我。
130 """(None, inode, None, None, None, None, size, None, None, None) = os.fstat(mailFile)"""
131 os.rename('/var/qmail/qmfilt/qmfilt.%s' %(pid), '/var/qmail/qmfilt/%s.mail' %(inode))
132 except:
133 log("Can't rename /var/qmail/qmfilt/qmfilt.%s -> /var/qmail/qmfilt/%s.mail" %(pid, inode))
134 # Indicate a write error
135 sys.exit(53)
136
137 try:
138 envFile = os.open('/var/qmail/qmfilt/%s.envelope' %(inode), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744)
139 mesgFile = os.open('/var/qmail/qmfilt/%s.qmfilt' %(inode), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744)
140 writeDescriptor(mailFile, MailData)
141 writeDescriptor(envFile, MailEnvelope)
142 writeDescriptor(mesgFile, "Matched filter [ %s] to file %s" %(hit, inode))
143 log("Matched filter [ %s] to file %s" %(hit, inode))
144 except:
145 log("Can't create/write files into trap")
146 # Indicate a write error
147 sys.exit(53)
148
149 return 0
150
151
152 def sendToQmailQueue():
153
154 # Open a pipe to qmail queue
155 fd0 = os.pipe()
156 fd1 = os.pipe()
157 pid = os.fork()
158 if pid < 0:
159 log("Error couldn't fork")
160 sys.exit(81)
161 if pid == 0:
162 # This is the child
163 os.dup2(fd0[0], 0)
164 os.dup2(fd1[0], 1)
165 i = 2
166 while (i < 64):
167 try:
168 os.close(i)
169 except:
170 pass
171 i = i + 1
172 os.chdir('/var/qmail')
173 #time.sleep(10)
174 os.execv("bin/qmail-queue", ('bin/qmail-queue',))
175 log("Something went wrong")
176 sys.exit(127) # This is only reached on error
177 else:
178 # This is the parent
179 # close the readable descriptors
180 os.close(fd0[0])
181 os.close(fd1[0])
182
183
184 # Send the data
185 recvHdr = "Received: (QMFILT: %s); " %(Version)
186 recvHdr = recvHdr + time.strftime("%d %b %Y %H:%M:%S", time.gmtime(time.time()))
187 recvHdr = recvHdr + " -0000\n"
188 os.write(fd0[1], recvHdr)
189 writeDescriptor(fd0[1], MailData)
190 writeDescriptor(fd1[1], MailEnvelope)
191
192 # Catch the exit code to return
193 result = os.waitpid(pid, 0)[1]
194 if PyVersion > '1.5.1':
195 if os.WIFEXITED(result):
196 return os.WEXITSTATUS(result)
197 else:
198 log("Didn't exit normally")
199 sys.exit(81)
200 else:
201 return result
202
203 def conver(msg):
204 msg = msg.replace(chr(00),' ')
205 msg = msg.replace('F','From:',1)
206 msg = msg.replace(' T',' To:')
207 return msg
208
209
210 def main(argv, stdout, environ):
211
212 global TestMode
213 global MailData
214 global MailEnvelope
215 global PyVersion
216
217 PyVersion = string.split(sys.version)[0]
218
219 if len(argv) > 1 and argv[1] == '--test':
220 TestMode = 1
221
222 os.setuid(QmailD)
223
224 # Insure Environment is OK
225
226 # Try to open log
227 openlog()
228
229 if not os.path.exists('/var/qmail/qmfilt'):
230 # Indicate a problem with the queue directory
231 log("Directory /var/qmail/qmfilt doesn't exist")
232 if not TestMode:
233 sys.exit(61)
234
235 if os.path.exists('/var/qmail/control/qmfilt'):
236 readFilters()
237 else:
238 if TestMode:
239 print "No filter file /var/qmail/control/qmfilt - no filters applied"
240
241 if TestMode:
242 showFilters()
243
244 # Go no further if in test mode
245 if TestMode:
246 sys.exit(0)
247
248
249
250 # Get the data
251
252 # Read the data
253 MailData = readDescriptor(0)
254
255 # Read the envelope
256 MailEnvelope = readDescriptor(1)
257 if Debug:
258 log(MailData)
259 log(MailEnvelope)
260
261 action,hit = filterHits()
262
263 if action == 'trap':
264 storeInTrap(hit)
265 if action == 'block':
266 log("Matched filter [ %s] and email was BLOCKED/Refused delivery" %(hit))
267 sys.exit(31)
268 if action == 'drop':
269 log("Matched filter [ %s] and email was DROPPED" %(hit))
270 if action == None:
271 sendToQmailQueue()
272 #Log
273 log(conver(MailEnvelope))
274 #send sms
275 mail_sms(conver(MailEnvelope))
276
277 if Debug:
278 log("qmailqueue returned [%d]" %(result))
279 sys.exit(0)
280
281 if __name__ == "__main__":
282 try:
283 main(sys.argv, sys.stdout, os.environ)
284
285 # Catch the sys.exit() errors
286 except SystemExit, val:
287 sys.exit(val)
288 except:
289 # return a fatal error for the unknown error
290 if TestMode:
291 traceback.print_exc()
292 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:[email protected] To:[email protected] 发送到指定的服务器上。服务器接收到这个数据包后,将这个数据包的内容用短信发送出去。
- 当然可以将邮件主题等信息一起发送到手机上,大家根据自己的情况更改吧。
这个程序是根据http://sourceforge.net/projects/qmfilt/这个项目更改的,但是直接用他的代码会造成不能收信,大家可以在这个项目的cvs关注他的代码修改情况。
- 结束,谢谢!
讨论
- stephen 发表,Zoom.Quiet 整理格式