Configure banned extensions. Scan zipfile option with test case.
This commit is contained in:
@@ -4,6 +4,8 @@ Here is a history of user visible changes to Python milter.
|
||||
DSN support for Three strikes rule and SPF SOFTFAIL
|
||||
Move /*mime*/ and dynip to Milter subpackage
|
||||
Fix SPF unknown mechanism list not cleared
|
||||
Make banned extensions configurable.
|
||||
Option to scan zipfiles for bad extensions.
|
||||
0.7.3 Experimental release with python2.4 support
|
||||
0.7.2 Return unknown for invalid ip address in mechanism
|
||||
Recognize dynamic PTR names, and don't count them as authentication.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
# A simple milter that has grown quite a bit.
|
||||
# $Log$
|
||||
# Revision 1.4 2005/06/02 04:18:55 customdesigned
|
||||
# Update copyright notices after reading article on /.
|
||||
#
|
||||
# Revision 1.3 2005/06/02 02:09:00 customdesigned
|
||||
# Record timestamp in send_dsn.log
|
||||
#
|
||||
@@ -236,6 +239,8 @@ log_headers = False
|
||||
block_chinese = False
|
||||
spam_words = ()
|
||||
porn_words = ()
|
||||
banned_exts = mime.extlist.split(',')
|
||||
scan_zip = False
|
||||
scan_html = True
|
||||
scan_rfc822 = True
|
||||
internal_connect = ()
|
||||
@@ -340,10 +345,11 @@ def read_config(list):
|
||||
})
|
||||
cp.read(list)
|
||||
tempfile.tempdir = cp.get('milter','tempdir')
|
||||
global socketname, scan_rfc822, scan_html, block_chinese, timeout
|
||||
global socketname, scan_rfc822, scan_html, block_chinese, timeout, scan_zip
|
||||
socketname = cp.get('milter','socket')
|
||||
timeout = cp.getint('milter','timeout')
|
||||
scan_rfc822 = cp.getboolean('milter','scan_rfc822')
|
||||
scan_zip = cp.getboolean('milter','scan_zip')
|
||||
scan_html = cp.getboolean('milter','scan_html')
|
||||
block_chinese = cp.getboolean('milter','block_chinese')
|
||||
|
||||
@@ -366,9 +372,11 @@ def read_config(list):
|
||||
internal_domains = cp.getlist('milter','internal_domains')
|
||||
|
||||
global porn_words, spam_words, smart_alias, trusted_relay, hello_blacklist
|
||||
global banned_exts
|
||||
trusted_relay = cp.getlist('milter','trusted_relay')
|
||||
porn_words = cp.getlist('milter','porn_words')
|
||||
spam_words = cp.getlist('milter','spam_words')
|
||||
banned_exts = cp.getlist('milter','banned_exts')
|
||||
hello_blacklist = cp.getlist('milter','hello_blacklist')
|
||||
for sa in cp.getlist('wiretap','smart_alias'):
|
||||
sm = cp.getlist('wiretap',sa)
|
||||
@@ -897,11 +905,22 @@ class bmsMilter(Milter.Milter):
|
||||
for i in range(len(h),0,-1):
|
||||
self.chgheader(name,i-1,'')
|
||||
|
||||
def _chk_ext(self,name):
|
||||
"Check a name for dangerous Winblows extensions."
|
||||
if not name: return name
|
||||
lname = name.lower()
|
||||
for ext in self.bad_extensions:
|
||||
if lname.endswith(ext): return name
|
||||
return None
|
||||
|
||||
|
||||
def _chk_attach(self,msg):
|
||||
"Filter attachments by content."
|
||||
mime.check_name(msg,self.tempname) # check for bad extensions
|
||||
# check for bad extensions
|
||||
mime.check_name(msg,self.tempname,ckname=self._chk_ext,scan_zip=scan_zip)
|
||||
# remove scripts from HTML
|
||||
if scan_html:
|
||||
mime.check_html(msg,self.tempname) # remove scripts from HTML
|
||||
mime.check_html(msg,self.tempname)
|
||||
# don't let a tricky virus slip one past us
|
||||
if scan_rfc822:
|
||||
msg = msg.get_submsg()
|
||||
@@ -1014,6 +1033,7 @@ class bmsMilter(Milter.Milter):
|
||||
|
||||
# filter leaf attachments through _chk_attach
|
||||
assert not msg.ismodified()
|
||||
self.bad_extensions = ['.' + x for x in banned_exts]
|
||||
rc = mime.check_attachments(msg,self._chk_attach)
|
||||
except: # milter crashed trying to analyze mail
|
||||
exc_type,exc_value = sys.exc_info()[0:2]
|
||||
|
||||
@@ -28,6 +28,8 @@ log_headers = 0
|
||||
;[defang]
|
||||
# do virus scanning on attached messages also
|
||||
scan_rfc822 = 1
|
||||
# do virus scanning on attached zipfiles also
|
||||
scan_zip = 0
|
||||
# Comment out scripts in HTML attachments. Can be CPU intensive.
|
||||
scan_html = 0
|
||||
# reject messages with asian fonts because we can't read them
|
||||
@@ -45,6 +47,11 @@ porn_words = penis, breast, pussy, horse cock, porn, xenical, diet pill, d1ck,
|
||||
valium, rolex, sexual
|
||||
# reject mail with these case sensitive strings in the subject
|
||||
spam_words = $$$, !!!, XXX, FREE, HGH
|
||||
# attachments with these extensions will be replaced with a warning
|
||||
# message. A copy of the original will be saved.
|
||||
banned_exts = ade,adp,asd,asx,asp,bas,bat,chm,cmd,com,cpl,crt,dll,exe,hlp,hta,
|
||||
inf,ins,isp,js,jse,lnk,mdb,mde,msc,msi,msp,mst,ocx,pcd,pif,reg,scr,sct,
|
||||
shs,url,vb,vbe,vbs,wsc,wsf,wsh
|
||||
|
||||
# See http://bmsi.com/python/pysrs.html for details
|
||||
[srs]
|
||||
|
||||
@@ -169,6 +169,8 @@ rm -rf $RPM_BUILD_ROOT
|
||||
- DSN support for Three strikes rule and SPF SOFTFAIL
|
||||
- Move /*mime*/ and dynip to Milter subpackage
|
||||
- Fix SPF unknown mechanism list not cleared
|
||||
- Make banned extensions configurable.
|
||||
- Option to scan zipfiles for bad extensions.
|
||||
* Tue Feb 08 2005 Stuart Gathman <stuart@bmsi.com> 0.7.3-1.EL3
|
||||
- Support EL3 and Python2.4 (some scanning/defang support broken)
|
||||
* Mon Aug 30 2004 Stuart Gathman <stuart@bmsi.com> 0.7.2-1
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# $Log$
|
||||
# Revision 1.2 2005/06/02 04:18:55 customdesigned
|
||||
# Update copyright notices after reading article on /.
|
||||
#
|
||||
# Revision 1.1.1.4 2005/05/31 18:23:49 customdesigned
|
||||
# Development changes since 0.7.2
|
||||
#
|
||||
@@ -71,6 +74,7 @@
|
||||
import StringIO
|
||||
import socket
|
||||
import Milter
|
||||
import zipfile
|
||||
|
||||
import email
|
||||
import email.Message
|
||||
@@ -156,7 +160,7 @@ class MimeMessage(Message):
|
||||
def getname(self):
|
||||
return self.get_param('name')
|
||||
|
||||
def getnames(self):
|
||||
def getnames(self,scan_zip=False):
|
||||
"""Return a list of (attr,name) pairs of attributes that IE might
|
||||
interpret as a name - and hence decide to execute this message."""
|
||||
names = []
|
||||
@@ -171,7 +175,16 @@ class MimeMessage(Message):
|
||||
else:
|
||||
val = _unquotevalue(val.strip())
|
||||
names.append((attr,val))
|
||||
return names + [("filename",self.get_filename())]
|
||||
names += [("filename",self.get_filename())]
|
||||
if scan_zip:
|
||||
for key,name in names:
|
||||
if name and name.lower().endswith('.zip'):
|
||||
txt = self.get_payload(decode=True)
|
||||
fp = StringIO.StringIO(txt)
|
||||
zipf = zipfile.ZipFile(fp,'r')
|
||||
for nm in zipf.namelist():
|
||||
names.append(('zipname',nm))
|
||||
return names
|
||||
|
||||
def ismodified(self):
|
||||
"True if this message or a subpart has been modified."
|
||||
@@ -279,12 +292,14 @@ A copy of your original message was saved as '%s:%s'.
|
||||
See your administrator.
|
||||
"""
|
||||
|
||||
def check_name(msg,savname=None,ckname=check_ext):
|
||||
def check_name(msg,savname=None,ckname=check_ext,scan_zip=False):
|
||||
"Replace attachment with a warning if its name is suspicious."
|
||||
for key,name in msg.getnames():
|
||||
for key,name in msg.getnames(scan_zip):
|
||||
badname = ckname(name)
|
||||
if badname:
|
||||
hostname = socket.gethostname()
|
||||
if key == 'zipname':
|
||||
badname = msg.get_filename()
|
||||
msg.set_payload(virus_msg % (badname,hostname,savname))
|
||||
del msg["content-type"]
|
||||
del msg["content-disposition"]
|
||||
@@ -312,11 +327,11 @@ check function(MimeMessage): int
|
||||
# save call context for Python without nested_scopes
|
||||
class _defang:
|
||||
|
||||
def __init__(self):
|
||||
self.scan_html = True
|
||||
def __init__(self,scan_html=True):
|
||||
self.scan_html = scan_html
|
||||
|
||||
def _chk_name(self,msg):
|
||||
rc = check_name(msg,self._savname,self._check)
|
||||
rc = check_name(msg,self._savname,self._check,self.scan_zip)
|
||||
if self.scan_html:
|
||||
check_html(msg,self._savname) # remove scripts from HTML
|
||||
if self.scan_rfc822:
|
||||
@@ -325,12 +340,14 @@ class _defang:
|
||||
return check_attachments(msg,self._chk_name)
|
||||
return rc
|
||||
|
||||
def __call__(self,msg,savname=None,check=check_ext,scan_rfc822=True):
|
||||
def __call__(self,msg,savname=None,check=check_ext,scan_rfc822=True,
|
||||
scan_zip=False):
|
||||
"""Compatible entry point.
|
||||
Replace all attachments with dangerous names."""
|
||||
self._savname = savname
|
||||
self._check = check
|
||||
self.scan_rfc822 = scan_rfc822
|
||||
self.scan_zip = scan_zip
|
||||
check_attachments(msg,self._chk_name)
|
||||
if msg.ismodified():
|
||||
return True
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
From paulp@go2net.com Wed Jun 1 22:35:12 2005
|
||||
Return-Path: <paulp@go2net.com>
|
||||
Received: from mail.bmsi.com (spidey.bmsi.com [192.168.9.81])
|
||||
by bmsred.bmsi.com (8.13.1/8.12.10) with ESMTP id j522ZCQg014058
|
||||
for <stuart@bmsred.bmsi.com>; Wed, 1 Jun 2005 22:35:12 -0400
|
||||
Received: from 127.0.0.1 ([220.117.92.241])
|
||||
by mail.bmsi.com (8.13.1/8.13.1) with ESMTP id j522Ynjm028604
|
||||
for stuart@bmsi.com; Wed, 1 Jun 2005 22:34:51 -0400
|
||||
Message-Id: <200506020234.j522Ynjm028604@mail.bmsi.com>
|
||||
SUBJECT: urgent
|
||||
FROM: paulp@go2net.com
|
||||
TO: stuart@bmsi.com
|
||||
DATE: [[ ¸ñ, 02 6 2005 ¿ÀÀü 11:34:47 ]]
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/mixed; boundary="--------bound--"
|
||||
X-DSpam-Score: 0.081200
|
||||
Received-SPF: neutral (mail.bmsi.com: guessing: 220.117.92.241 is neither permitted nor denied by domain of go2net.com)
|
||||
Status: RO
|
||||
X-Status:
|
||||
X-Keywords: NonJunk
|
||||
|
||||
----------bound--
|
||||
Content-Type: text/plain; charset=us-ascii
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
Hi
|
||||
|
||||
Sorry, I forgot to send an important
|
||||
document to you in that last email. I had an important phone call.
|
||||
Please checkout attached doc file when you have a moment.
|
||||
|
||||
Best Regards
|
||||
|
||||
<!DSPAM:1043AE6B6492860536935410>
|
||||
|
||||
|
||||
----------bound--
|
||||
Content-Type: application/x-msdownload; name="zip.zip"
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename="zip.zip"
|
||||
|
||||
UEsDBAoAAAAAADVVwjLaV2nEGgAAABoAAAAzABUAemlwLmRvYyAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAuZXhlVVQJAAOmGp9CphqfQlV4BACGA2UAVGhpcyBw
|
||||
cm9ncmFtIHdhcyBhIHZpcnVzLgpQSwECFwMKAAAAAAA1VcIy2ldpxBoAAAAaAAAAMwANAAAA
|
||||
AAABAAAAtIEAAAAAemlwLmRvYyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAuZXhlVVQFAAOmGp9CVXgAAFBLBQYAAAAAAQABAG4AAACAAAAAAAA=
|
||||
----------bound--
|
||||
|
||||
|
||||
----------bound----
|
||||
|
||||
+12
-2
@@ -1,4 +1,7 @@
|
||||
# $Log$
|
||||
# Revision 1.1.1.2 2005/05/31 18:23:49 customdesigned
|
||||
# Development changes since 0.7.2
|
||||
#
|
||||
# Revision 1.23 2005/02/11 18:34:14 stuart
|
||||
# Handle garbage after quote in boundary.
|
||||
#
|
||||
@@ -63,7 +66,7 @@ class MimeTestCase(unittest.TestCase):
|
||||
def testDefang(self,vname='virus1',part=1,
|
||||
fname='LOVE-LETTER-FOR-YOU.TXT.vbs'):
|
||||
msg = mime.message_from_file(open('test/'+vname,"r"))
|
||||
mime.defang(msg)
|
||||
mime.defang(msg,scan_zip=True)
|
||||
self.failUnless(msg.ismodified(),"virus not removed")
|
||||
oname = vname + '.out'
|
||||
msg.dump(open('test/'+oname,"w"))
|
||||
@@ -71,7 +74,8 @@ class MimeTestCase(unittest.TestCase):
|
||||
txt2 = msg.get_payload()
|
||||
if type(txt2) == list:
|
||||
txt2 = txt2[part].get_payload()
|
||||
self.failUnless(txt2.rstrip()+'\n' == mime.virus_msg % (fname,hostname,None),txt2)
|
||||
self.failUnless(
|
||||
txt2.rstrip()+'\n' == mime.virus_msg % (fname,hostname,None),txt2)
|
||||
|
||||
def testDefang3(self):
|
||||
self.testDefang('virus3',0,'READER_DIGEST_LETTER.TXT.pif')
|
||||
@@ -121,6 +125,12 @@ class MimeTestCase(unittest.TestCase):
|
||||
name = parts[1].getname()
|
||||
self.failUnless(name == "Jim&amp;Girlz.jpg","name=%s"%name)
|
||||
|
||||
def testZip(self,vname="zip1",fname='zip.zip'):
|
||||
self.testDefang('zip1',1,'zip.zip')
|
||||
msg = mime.message_from_file(open('test/'+vname,"r"))
|
||||
mime.defang(msg,scan_zip=False)
|
||||
self.failIf(msg.ismodified())
|
||||
|
||||
def testHTML(self,fname=""):
|
||||
result = StringIO.StringIO()
|
||||
filter = mime.HTMLScriptFilter(result)
|
||||
|
||||
Reference in New Issue
Block a user