Move parse_header to Milter.utils.
Test case for delayed DSN parsing. Fix plock when source missing or cannot set owner/group.
This commit is contained in:
+1
-1
@@ -8,7 +8,7 @@ include ChangeLog
|
|||||||
include MANIFEST.in
|
include MANIFEST.in
|
||||||
include testsample.py
|
include testsample.py
|
||||||
include testmime.py
|
include testmime.py
|
||||||
include testcache.py
|
include testutils.py
|
||||||
include testbms.py
|
include testbms.py
|
||||||
include rejects.py
|
include rejects.py
|
||||||
include report.py
|
include report.py
|
||||||
|
|||||||
+8
-1
@@ -10,6 +10,9 @@
|
|||||||
# CBV results.
|
# CBV results.
|
||||||
#
|
#
|
||||||
# $Log$
|
# $Log$
|
||||||
|
# Revision 1.5 2007/01/11 19:59:40 customdesigned
|
||||||
|
# Purge old entries in auto_whitelist and send_dsn logs.
|
||||||
|
#
|
||||||
# Revision 1.4 2007/01/11 04:31:26 customdesigned
|
# Revision 1.4 2007/01/11 04:31:26 customdesigned
|
||||||
# Negative feedback for bad headers. Purge cache logs on startup.
|
# Negative feedback for bad headers. Purge cache logs on startup.
|
||||||
#
|
#
|
||||||
@@ -51,7 +54,11 @@ class AddrCache(object):
|
|||||||
changed = False
|
changed = False
|
||||||
try:
|
try:
|
||||||
too_old = now - age*24*60*60 # max age in days
|
too_old = now - age*24*60*60 # max age in days
|
||||||
for ln in open(self.fname):
|
try:
|
||||||
|
fp = open(self.fname)
|
||||||
|
except OSError:
|
||||||
|
fp = ()
|
||||||
|
for ln in fp:
|
||||||
try:
|
try:
|
||||||
rcpt,ts = ln.strip().split(None,1)
|
rcpt,ts = ln.strip().split(None,1)
|
||||||
l = time.strptime(ts,AddrCache.time_format)
|
l = time.strptime(ts,AddrCache.time_format)
|
||||||
|
|||||||
+6
-2
@@ -11,22 +11,26 @@ class PLock(object):
|
|||||||
self.basename = basename
|
self.basename = basename
|
||||||
self.fp = None
|
self.fp = None
|
||||||
|
|
||||||
def lock(self,lockname=None):
|
def lock(self,lockname=None,mode=0660,strict_perms=False):
|
||||||
"Start an update transaction. Return FILE to write new version."
|
"Start an update transaction. Return FILE to write new version."
|
||||||
self.unlock()
|
self.unlock()
|
||||||
if not lockname:
|
if not lockname:
|
||||||
lockname = self.basename + '.lock'
|
lockname = self.basename + '.lock'
|
||||||
self.lockname = lockname
|
self.lockname = lockname
|
||||||
|
try:
|
||||||
st = os.stat(self.basename)
|
st = os.stat(self.basename)
|
||||||
|
mode |= st.st_mode
|
||||||
|
except OSError: pass
|
||||||
u = os.umask(0002)
|
u = os.umask(0002)
|
||||||
try:
|
try:
|
||||||
fd = os.open(lockname,os.O_WRONLY+os.O_CREAT+os.O_EXCL,st.st_mode|0660)
|
fd = os.open(lockname,os.O_WRONLY+os.O_CREAT+os.O_EXCL,mode)
|
||||||
finally:
|
finally:
|
||||||
os.umask(u)
|
os.umask(u)
|
||||||
self.fp = os.fdopen(fd,'w')
|
self.fp = os.fdopen(fd,'w')
|
||||||
try:
|
try:
|
||||||
os.chown(self.lockname,-1,st.st_gid)
|
os.chown(self.lockname,-1,st.st_gid)
|
||||||
except:
|
except:
|
||||||
|
if strict_perms:
|
||||||
self.unlock()
|
self.unlock()
|
||||||
raise
|
raise
|
||||||
return self.fp
|
return self.fp
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import re
|
import re
|
||||||
import struct
|
import struct
|
||||||
import socket
|
import socket
|
||||||
|
import email.Errors
|
||||||
from fnmatch import fnmatchcase
|
from fnmatch import fnmatchcase
|
||||||
|
from email.Header import decode_header
|
||||||
|
|
||||||
ip4re = re.compile(r'^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*$')
|
ip4re = re.compile(r'^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*$')
|
||||||
|
|
||||||
@@ -56,3 +58,28 @@ def parse_addr(t):
|
|||||||
pos = t.find('"@')
|
pos = t.find('"@')
|
||||||
if pos > 0: return [t[1:pos],t[pos+2:]]
|
if pos > 0: return [t[1:pos],t[pos+2:]]
|
||||||
return t.split('@')
|
return t.split('@')
|
||||||
|
|
||||||
|
def parse_header(val):
|
||||||
|
"""Decode headers gratuitously encoded to hide the content.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
h = decode_header(val)
|
||||||
|
if not len(h) or (not h[0][1] and len(h) == 1): return val
|
||||||
|
u = []
|
||||||
|
for s,enc in h:
|
||||||
|
if enc:
|
||||||
|
try:
|
||||||
|
u.append(unicode(s,enc))
|
||||||
|
except LookupError:
|
||||||
|
u.append(unicode(s))
|
||||||
|
else:
|
||||||
|
u.append(unicode(s))
|
||||||
|
u = ''.join(u)
|
||||||
|
for enc in ('us-ascii','iso-8859-1','utf8'):
|
||||||
|
try:
|
||||||
|
return u.encode(enc)
|
||||||
|
except UnicodeError: continue
|
||||||
|
except UnicodeDecodeError: pass
|
||||||
|
except LookupError: pass
|
||||||
|
except email.Errors.HeaderParseError: pass
|
||||||
|
return val
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# A simple milter that has grown quite a bit.
|
# A simple milter that has grown quite a bit.
|
||||||
# $Log$
|
# $Log$
|
||||||
|
# Revision 1.87 2007/01/18 16:48:44 customdesigned
|
||||||
|
# Doc update.
|
||||||
|
# Parse From header for delayed failure detection.
|
||||||
|
# Don't check reputation of trusted host.
|
||||||
|
# Track IP reputation only when missing PTR.
|
||||||
|
#
|
||||||
# Revision 1.86 2007/01/16 05:17:29 customdesigned
|
# Revision 1.86 2007/01/16 05:17:29 customdesigned
|
||||||
# REJECT after data for blacklisted emails - so in case of mistakes, a
|
# REJECT after data for blacklisted emails - so in case of mistakes, a
|
||||||
# legitimate sender will know what happened.
|
# legitimate sender will know what happened.
|
||||||
@@ -76,11 +82,10 @@ import gc
|
|||||||
import anydbm
|
import anydbm
|
||||||
import Milter.dsn as dsn
|
import Milter.dsn as dsn
|
||||||
from Milter.dynip import is_dynip as dynip
|
from Milter.dynip import is_dynip as dynip
|
||||||
from Milter.utils import iniplist,parse_addr,ip4re
|
from Milter.utils import iniplist,parse_addr,parse_header,ip4re
|
||||||
from Milter.config import MilterConfigParser
|
from Milter.config import MilterConfigParser
|
||||||
|
|
||||||
from fnmatch import fnmatchcase
|
from fnmatch import fnmatchcase
|
||||||
from email.Header import decode_header
|
|
||||||
from email.Utils import getaddresses,parseaddr
|
from email.Utils import getaddresses,parseaddr
|
||||||
|
|
||||||
# Import gossip if available
|
# Import gossip if available
|
||||||
@@ -328,30 +333,26 @@ def read_config(list):
|
|||||||
srs_domain.add(cp.getdefault('srs','fwdomain'))
|
srs_domain.add(cp.getdefault('srs','fwdomain'))
|
||||||
banned_users = cp.getlist('srs','banned_users')
|
banned_users = cp.getlist('srs','banned_users')
|
||||||
|
|
||||||
def parse_header(val):
|
def findsrs(fp):
|
||||||
"""Decode headers gratuitously encoded to hide the content.
|
lastln = None
|
||||||
"""
|
for ln in fp:
|
||||||
|
if lastln:
|
||||||
|
if ln[0].isspace() and ln[0] != '\n':
|
||||||
|
lastln += ln
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
h = decode_header(val)
|
name,val = lastln.rstrip().split(None,1)
|
||||||
if not len(h) or (not h[0][1] and len(h) == 1): return val
|
pos = val.find('<SRS')
|
||||||
u = []
|
if pos >= 0:
|
||||||
for s,enc in h:
|
return srs.reverse(val[pos+1:-1])
|
||||||
if enc:
|
except: continue
|
||||||
try:
|
lnl = ln.lower()
|
||||||
u.append(unicode(s,enc))
|
if lnl.startswith('action:'):
|
||||||
except LookupError:
|
if lnl.split()[-1] != 'failed': break
|
||||||
u.append(unicode(s))
|
for k in ('message-id:','x-mailer:','sender:'):
|
||||||
else:
|
if lnl.startswith(k):
|
||||||
u.append(unicode(s))
|
lastln = ln
|
||||||
u = ''.join(u)
|
break
|
||||||
for enc in ('us-ascii','iso-8859-1','utf8'):
|
|
||||||
try:
|
|
||||||
return u.encode(enc)
|
|
||||||
except UnicodeError: continue
|
|
||||||
except UnicodeDecodeError: pass
|
|
||||||
except LookupError: pass
|
|
||||||
except email.Errors.HeaderParseError: pass
|
|
||||||
return val
|
|
||||||
|
|
||||||
class SPFPolicy(object):
|
class SPFPolicy(object):
|
||||||
"Get SPF policy by result from sendmail style access file."
|
"Get SPF policy by result from sendmail style access file."
|
||||||
@@ -1362,17 +1363,8 @@ class bmsMilter(Milter.Milter):
|
|||||||
# check for delayed bounce
|
# check for delayed bounce
|
||||||
if self.delayed_failure:
|
if self.delayed_failure:
|
||||||
self.fp.seek(0)
|
self.fp.seek(0)
|
||||||
lastln = None
|
sender = findsrs(self.fp)
|
||||||
for ln in self.fp:
|
if sender:
|
||||||
if lastln:
|
|
||||||
if ln[0].isspace() and ln[0] != '\n':
|
|
||||||
lastln += ln
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
name,val = lastln.rstrip().split(None,1)
|
|
||||||
pos = val.find('<SRS')
|
|
||||||
if pos >= 0:
|
|
||||||
sender = srs.reverse(val[pos+1:-1])
|
|
||||||
cbv_cache[sender] = 500,self.delayed_failure,time.time()
|
cbv_cache[sender] = 500,self.delayed_failure,time.time()
|
||||||
try:
|
try:
|
||||||
# save message for debugging
|
# save message for debugging
|
||||||
@@ -1383,14 +1375,7 @@ class bmsMilter(Milter.Milter):
|
|||||||
self.tempname = None
|
self.tempname = None
|
||||||
self.log('BLACKLIST:',sender,fname)
|
self.log('BLACKLIST:',sender,fname)
|
||||||
return Milter.DISCARD
|
return Milter.DISCARD
|
||||||
except: continue
|
|
||||||
lnl = ln.lower()
|
|
||||||
if lnl.startswith('action:'):
|
|
||||||
if lnl.split()[-1] != 'failed': break
|
|
||||||
for k in ('message-id:','x-mailer:','sender:'):
|
|
||||||
if lnl.startswith(k):
|
|
||||||
lastln = ln
|
|
||||||
break
|
|
||||||
|
|
||||||
# analyze external mail for spam
|
# analyze external mail for spam
|
||||||
spam_checked = self.check_spam() # tag or quarantine for spam
|
spam_checked = self.check_spam() # tag or quarantine for spam
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import unittest
|
|||||||
import testbms
|
import testbms
|
||||||
import testmime
|
import testmime
|
||||||
import testsample
|
import testsample
|
||||||
import testcache
|
import testutils
|
||||||
import os
|
import os
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
@@ -10,7 +10,7 @@ def suite():
|
|||||||
s.addTest(testbms.suite())
|
s.addTest(testbms.suite())
|
||||||
s.addTest(testmime.suite())
|
s.addTest(testmime.suite())
|
||||||
s.addTest(testsample.suite())
|
s.addTest(testsample.suite())
|
||||||
s.addTest(testcache.suite())
|
s.addTest(testutils.suite())
|
||||||
return s
|
return s
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
+19
@@ -277,6 +277,25 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
fp = milter._body
|
fp = milter._body
|
||||||
open("test/test1.tstout","w").write(fp.getvalue())
|
open("test/test1.tstout","w").write(fp.getvalue())
|
||||||
|
|
||||||
|
def testFindsrs(self):
|
||||||
|
if not bms.srs:
|
||||||
|
import SRS
|
||||||
|
bms.srs = SRS.new(secret='test')
|
||||||
|
sender = bms.srs.forward('foo@bar.com','mail.example.com')
|
||||||
|
sndr = bms.findsrs(StringIO.StringIO(
|
||||||
|
"""Received: from [1.16.33.86] (helo=mail.example.com)
|
||||||
|
by bastion4.mail.zen.co.uk with smtp (Exim 4.50) id 1H3IBC-00013b-O9
|
||||||
|
for foo@bar.com; Sat, 06 Jan 2007 20:30:17 +0000
|
||||||
|
X-Mailer: "PyMilter-0.8.5"
|
||||||
|
<%s> foo
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain
|
||||||
|
To: foo@bar.com
|
||||||
|
From: postmaster@mail.example.com
|
||||||
|
""" % sender
|
||||||
|
))
|
||||||
|
self.assertEqual(sndr,'foo@bar.com')
|
||||||
|
|
||||||
# def testReject(self):
|
# def testReject(self):
|
||||||
# "Test content based spam rejection."
|
# "Test content based spam rejection."
|
||||||
# milter = TestMilter()
|
# milter = TestMilter()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
import doctest
|
||||||
import os
|
import os
|
||||||
|
import Milter.utils
|
||||||
from Milter.cache import AddrCache
|
from Milter.cache import AddrCache
|
||||||
|
|
||||||
class AddrCacheTestCase(unittest.TestCase):
|
class AddrCacheTestCase(unittest.TestCase):
|
||||||
@@ -26,7 +27,10 @@ class AddrCacheTestCase(unittest.TestCase):
|
|||||||
self.failUnless(s[0].startswith('foo@bar.com '))
|
self.failUnless(s[0].startswith('foo@bar.com '))
|
||||||
self.assertEquals(s[1].strip(),'baz@bar.com')
|
self.assertEquals(s[1].strip(),'baz@bar.com')
|
||||||
|
|
||||||
def suite(): return unittest.makeSuite(AddrCacheTestCase,'test')
|
def suite():
|
||||||
|
s = unittest.makeSuite(AddrCacheTestCase,'test')
|
||||||
|
s.addTest(doctest.DocTestSuite(Milter.utils))
|
||||||
|
return s
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.TextTestRunner().run(suite())
|
||||||
Reference in New Issue
Block a user