tabnanny, restore missing test email

This commit is contained in:
Stuart Gathman
2013-03-12 01:46:08 +00:00
parent a180b212c6
commit 84eeecf9a6
12 changed files with 18732 additions and 133 deletions
+12 -9
View File
@@ -10,6 +10,9 @@
# CBV results.
#
# $Log$
# Revision 1.9 2008/05/08 21:35:57 customdesigned
# Allow explicitly whitelisted email from banned_users.
#
# Revision 1.8 2007/09/03 16:18:45 customdesigned
# Delete unparseable timestamps when loading address cache. These have
# arisen because of failure to parse MAIL FROM properly. Will have to
@@ -72,8 +75,8 @@ class AddrCache(object):
except OSError:
fp = ()
for ln in fp:
try:
rcpt,ts = ln.strip().split(None,1)
try:
rcpt,ts = ln.strip().split(None,1)
try:
l = time.strptime(ts,AddrCache.time_format)
t = time.mktime(l)
@@ -84,11 +87,11 @@ class AddrCache(object):
except: # unparsable timestamp - likely garbage
changed = True
continue
except: # manual entry (no timestamp)
cache[ln.strip().lower()] = (now,None)
wfp.write(ln)
except: # manual entry (no timestamp)
cache[ln.strip().lower()] = (now,None)
wfp.write(ln)
if changed:
lock.commit(self.fname+'.old')
lock.commit(self.fname+'.old')
else:
lock.unlock()
except IOError:
@@ -126,13 +129,13 @@ class AddrCache(object):
ts,res = self.cache[lsender]
too_old = time.time() - self.age*24*60*60 # max age in days
if not ts or ts > too_old:
return res
return res
del self.cache[lsender]
raise KeyError, sender
except KeyError,x:
try:
user,host = sender.split('@',1)
return self.__getitem__(host)
user,host = sender.split('@',1)
return self.__getitem__(host)
except ValueError:
raise x
+11 -11
View File
@@ -29,10 +29,10 @@ class MilterConfigParser(ConfigParser):
q = q.strip()
if q.startswith('file:'):
domain = q[5:].lower()
d[domain] = d.setdefault(domain,[]) + open(domain,'r').read().split()
d[domain] = d.setdefault(domain,[]) + open(domain,'r').read().split()
else:
user,domain = q.split('@')
d.setdefault(domain.lower(),[]).append(user)
user,domain = q.split('@')
d.setdefault(domain.lower(),[]).append(user)
return d
def getaddrdict(self,sect,opt):
@@ -43,14 +43,14 @@ class MilterConfigParser(ConfigParser):
q = q.strip()
if self.has_option(sect,q):
l = self.get(sect,q)
for addr in l.split(','):
addr = addr.strip()
if addr.startswith('file:'):
fname = addr[5:]
for a in open(fname,'r').read().split():
d[a] = q
else:
d[addr] = q
for addr in l.split(','):
addr = addr.strip()
if addr.startswith('file:'):
fname = addr[5:]
for a in open(fname,'r').read().split():
d[a] = q
else:
d[addr] = q
return d
def getdefault(self,sect,opt,default=None):
+11 -8
View File
@@ -5,6 +5,9 @@
# Send DSNs, do call back verification,
# and generate DSN messages from a template
# $Log$
# Revision 1.22 2011/03/18 20:41:31 customdesigned
# Python2.6 SMTP.close() fails when instance never connected.
#
# Revision 1.21 2011/03/03 05:11:58 customdesigned
# Release 0.9.4
#
@@ -114,17 +117,17 @@ def send_dsn(mailfrom,receiver,msg=None,timeout=600,session=None,ourfrom=''):
if a[0] == receiver:
return (553,'Fraudulent MX for %s: %s' % (domain,host))
if not (200 <= code <= 299):
raise smtplib.SMTPHeloError(code, resp)
raise smtplib.SMTPHeloError(code, resp)
if msg:
try:
smtp.sendmail('<%s>'%ourfrom,mailfrom,msg)
except smtplib.SMTPSenderRefused:
smtp.sendmail('<%s>'%ourfrom,mailfrom,msg)
except smtplib.SMTPSenderRefused:
# does not accept DSN, try postmaster (at the risk of mail loops)
smtp.sendmail('<postmaster@%s>'%receiver,mailfrom,msg)
smtp.sendmail('<postmaster@%s>'%receiver,mailfrom,msg)
else: # CBV
code,resp = smtp.docmd('MAIL FROM: <%s>'%ourfrom)
if code != 250:
raise smtplib.SMTPSenderRefused(code, resp, '<%s>'%ourfrom)
code,resp = smtp.docmd('MAIL FROM: <%s>'%ourfrom)
if code != 250:
raise smtplib.SMTPSenderRefused(code, resp, '<%s>'%ourfrom)
if isinstance(mailfrom,basestring):
mailfrom = [mailfrom]
badrcpts = {}
@@ -132,7 +135,7 @@ def send_dsn(mailfrom,receiver,msg=None,timeout=600,session=None,ourfrom=''):
code,resp = smtp.rcpt(rcpt)
if code not in (250,251):
badrcpts[rcpt] = (code,resp)# permanent error
smtp.quit()
smtp.quit()
if len(badrcpts) == 1:
return badrcpts.values()[0] # permanent error
if badrcpts:
+3 -3
View File
@@ -68,8 +68,8 @@ def is_dynip(host,addr):
if ia[2:] in (g[:2],g[-2:]): return True
for m in ip3.finditer(host):
if int(m.group()) == ia[3]:
h = host[:m.start()] + '<3>' + host[m.end():]
break
h = host[:m.start()] + '<3>' + host[m.end():]
break
if rehmac.search(h): return True
if host.find(''.join(a[:3])) >= 0: return True
if host.find(''.join(a[1:])) >= 0: return True
@@ -86,7 +86,7 @@ if __name__ == '__main__':
if a[3:5] == ['connect','from']:
host = a[5]
if host.startswith('[') and host.endswith(']'):
continue # no PTR
continue # no PTR
ip = a[7][2:-2]
if ip in seen: continue
seen.add(ip)
+3 -3
View File
@@ -31,8 +31,8 @@ class PLock(object):
os.chown(self.lockname,-1,st.st_gid)
except:
if strict_perms:
self.unlock()
raise
self.unlock()
raise
return self.fp
def wlock(self,lockname=None):
@@ -51,7 +51,7 @@ class PLock(object):
self.fp = None
if backname:
try:
os.remove(backname)
os.remove(backname)
except OSError: pass
os.link(self.basename,backname)
os.rename(self.lockname,self.basename)
+21 -21
View File
@@ -48,11 +48,11 @@ def inet_ntop(s):
e = n[:l]
for i in range(9-l):
if a[i:i+l] == e:
if i == 0:
return ':'+':%x'*(8-l) % a[l:]
if i == 8 - l:
return '%x:'*(8-l) % a[:-l] + ':'
return '%x:'*i % a[:i] + ':%x'*(8-l-i) % a[i+l:]
if i == 0:
return ':'+':%x'*(8-l) % a[l:]
if i == 8 - l:
return '%x:'*(8-l) % a[:-l] + ':'
return '%x:'*i % a[:i] + ':%x'*(8-l-i) % a[i+l:]
return "%x:%x:%x:%x:%x:%x:%x:%x" % a
def inet_pton(p):
@@ -89,29 +89,29 @@ def inet_pton(p):
m = RE_IP4.search(s)
try:
if m:
pos = m.start()
ip4 = [int(i) for i in s[pos:].split('.')]
if not pos:
return struct.pack('!QLBBBB',0,65535,*ip4)
s = s[:pos]+'%x%02x:%x%02x'%tuple(ip4)
pos = m.start()
ip4 = [int(i) for i in s[pos:].split('.')]
if not pos:
return struct.pack('!QLBBBB',0,65535,*ip4)
s = s[:pos]+'%x%02x:%x%02x'%tuple(ip4)
a = s.split('::')
if len(a) == 2:
l,r = a
if not l:
r = r.split(':')
return struct.pack('!HHHHHHHH',
l,r = a
if not l:
r = r.split(':')
return struct.pack('!HHHHHHHH',
*[0]*(8-len(r)) + [int(s,16) for s in r])
if not r:
l = l.split(':')
return struct.pack('!HHHHHHHH',
if not r:
l = l.split(':')
return struct.pack('!HHHHHHHH',
*[int(s,16) for s in l] + [0]*(8-len(l)))
l = l.split(':')
r = r.split(':')
return struct.pack('!HHHHHHHH',
l = l.split(':')
r = r.split(':')
return struct.pack('!HHHHHHHH',
*[int(s,16) for s in l] + [0]*(8-len(l)-len(r))
+ [int(s,16) for s in r])
if len(a) == 1:
return struct.pack('!HHHHHHHH',
return struct.pack('!HHHHHHHH',
*[int(s,16) for s in a[0].split(':')])
except ValueError: pass
raise ValueError,p
+4 -4
View File
@@ -128,11 +128,11 @@ class TestBase(object):
line = None
for h in msg.headers:
if h[:1].isspace():
line = line + h
continue
line = line + h
continue
if not line:
line = h
continue
line = h
continue
s = line.split(': ',1)
if len(s) > 1: val = s[1].strip()
else: val = ''
+7 -7
View File
@@ -84,14 +84,14 @@ def iniplist(ipaddr,iplist):
p = pat.split('/',1)
if ip4re.match(p[0]):
if len(p) > 1:
n = int(p[1])
n = int(p[1])
else:
n = 32
if cidr(addr2bin(p[0]),n) == cidr(ipnum,n):
return True
elif ip6re.match(p[0]):
if len(p) > 1:
n = int(p[1])
n = int(p[1])
else:
n = 128
if cidr(bin2long6(inet_pton(p[0])),n,MASK6) == cidr(ipnum,n,MASK6):
@@ -185,15 +185,15 @@ def parse_header(val):
for s,enc in h:
if enc:
try:
u.append(unicode(s,enc,'replace'))
except LookupError:
u.append(unicode(s))
u.append(unicode(s,enc,'replace'))
except LookupError:
u.append(unicode(s))
else:
u.append(unicode(s))
u.append(unicode(s))
u = ''.join(u)
for enc in ('us-ascii','iso-8859-1','utf8'):
try:
return u.encode(enc)
return u.encode(enc)
except UnicodeError: continue
except UnicodeDecodeError: pass
except LookupError: pass
+51 -48
View File
@@ -1,4 +1,7 @@
# $Log$
# Revision 1.8 2011/11/05 15:51:03 customdesigned
# New example
#
# Revision 1.7 2009/06/13 21:15:12 customdesigned
# Doxygen updates.
#
@@ -127,24 +130,24 @@ class MimeGenerator(Generator):
# full MIME type, then dispatch to self._handle_<maintype>(). If
# that's missing too, then dispatch to self._writeBody().
main = msg.get_content_maintype()
if msg.is_multipart() and main.lower() != 'multipart':
self._handle_multipart(msg)
else:
Generator._dispatch(self,msg)
if msg.is_multipart() and main.lower() != 'multipart':
self._handle_multipart(msg)
else:
Generator._dispatch(self,msg)
def unquote(s):
"""Remove quotes from a string."""
if len(s) > 1:
if s.startswith('"'):
if s.endswith('"'):
if s.endswith('"'):
s = s[1:-1]
else: # remove garbage after trailing quote
try: s = s[1:s[1:].index('"')+1]
except:
return s
return s.replace('\\\\', '\\').replace('\\"', '"')
else: # remove garbage after trailing quote
try: s = s[1:s[1:].index('"')+1]
except:
return s
return s.replace('\\\\', '\\').replace('\\"', '"')
if s.startswith('<') and s.endswith('>'):
return s[1:-1]
return s[1:-1]
return s
from types import TupleType
@@ -202,21 +205,21 @@ class MimeMessage(Message):
for attr,val in self._get_params_preserve([],'content-type'):
if isinstance(val, TupleType):
# It's an RFC 2231 encoded parameter
newvalue = _unquotevalue(val)
if val[0]:
val = unicode(newvalue[2], newvalue[0])
else:
val = unicode(newvalue[2])
newvalue = _unquotevalue(val)
if val[0]:
val = unicode(newvalue[2], newvalue[0])
else:
val = unicode(newvalue[2])
else:
val = _unquotevalue(val.strip())
val = _unquotevalue(val.strip())
names.append((attr,val))
names += [("filename",self.get_filename())]
if scan_zip:
for key,name in tuple(names): # copy by converting to tuple
if name and name.lower().endswith('.zip'):
txt = self.get_payload(decode=True)
if txt.strip():
names += zipnames(txt)
if name and name.lower().endswith('.zip'):
txt = self.get_payload(decode=True)
if txt.strip():
names += zipnames(txt)
return names
def ismodified(self):
@@ -287,13 +290,13 @@ class MimeMessage(Message):
if t == 'message/rfc822' or t.startswith('multipart/'):
if not self.submsg:
txt = self.get_payload()
if type(txt) == str:
txt = self.get_payload(decode=True)
self.submsg = email.message_from_string(txt,MimeMessage)
for part in self.submsg.walk():
part.modified = False
else:
self.submsg = txt[0]
if type(txt) == str:
txt = self.get_payload(decode=True)
self.submsg = email.message_from_string(txt,MimeMessage)
for part in self.submsg.walk():
part.modified = False
else:
self.submsg = txt[0]
return self.submsg
return None
@@ -333,7 +336,7 @@ def check_name(msg,savname=None,ckname=check_ext,scan_zip=False):
if badname:
if key == 'zipname':
badname = msg.get_filename()
break
break
else:
return Milter.CONTINUE
except zipfile.BadZipfile:
@@ -380,7 +383,7 @@ class _defang:
return rc
def __call__(self,msg,savname=None,check=check_ext,scan_rfc822=True,
scan_zip=False):
scan_zip=False):
"""Compatible entry point.
Replace all attachments with dangerous names."""
self._savname = savname
@@ -450,25 +453,25 @@ class SGMLFilter(sgmllib.SGMLParser):
n = len(rawdata)
j = i + 2
while j < n:
c = rawdata[j]
if c == ">":
# end of declaration syntax
self.handle_special(rawdata[i+2:j])
return j + 1
if c in "\"'":
m = declstringlit.match(rawdata, j)
if not m:
c = rawdata[j]
if c == ">":
# end of declaration syntax
self.handle_special(rawdata[i+2:j])
return j + 1
if c in "\"'":
m = declstringlit.match(rawdata, j)
if not m:
# incomplete or an error?
return -1
j = m.end()
elif c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
m = declname.match(rawdata, j)
if not m:
return -1
j = m.end()
elif c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
m = declname.match(rawdata, j)
if not m:
# incomplete or an error?
return -1
j = m.end()
else:
j += 1
return -1
j = m.end()
else:
j += 1
# end of buffer between tokens
return -1
@@ -497,7 +500,7 @@ def check_html(msg,savname=None):
if msgtype == 'application/octet-stream':
for (attr,name) in msg.getnames():
if name and name.lower().endswith(".htm"):
msgtype = 'text/html'
msgtype = 'text/html'
if msgtype == 'text/html':
out = StringIO.StringIO()
htmlfilter = HTMLScriptFilter(out)
+13 -13
View File
@@ -60,23 +60,23 @@ class sampleMilter(Milter.Milter):
# even if we wanted the Taiwanese spam, we can't read Chinese
# (delete if you read chinese mail)
if val.startswith('=?big5') or val.startswith('=?ISO-2022-JP'):
self.log('REJECT: %s: %s' % (name,val))
self.log('REJECT: %s: %s' % (name,val))
#self.setreply('550','','Go away spammer')
return Milter.REJECT
return Milter.REJECT
# check for common spam keywords
if val.find("$$$") >= 0 or val.find("XXX") >= 0 \
or val.find("!!!") >= 0 or val.find("FREE") >= 0:
self.log('REJECT: %s: %s' % (name,val))
or val.find("!!!") >= 0 or val.find("FREE") >= 0:
self.log('REJECT: %s: %s' % (name,val))
#self.setreply('550','','Go away spammer')
return Milter.REJECT
return Milter.REJECT
# check for spam that pretends to be legal
lval = val.lower()
if lval.startswith("adv:") or lval.startswith("adv.") \
or lval.find('viagra') >= 0:
self.log('REJECT: %s: %s' % (name,val))
return Milter.REJECT
or lval.find('viagra') >= 0:
self.log('REJECT: %s: %s' % (name,val))
return Milter.REJECT
# check for invalid message id
if lname == 'message-id' and len(val) < 4:
@@ -86,7 +86,7 @@ class sampleMilter(Milter.Milter):
# check for common bulk mailers
if lname == 'x-mailer' and \
val.lower() in ('direct email','calypso','mail bomber'):
val.lower() in ('direct email','calypso','mail bomber'):
self.log('REJECT: %s: %s' % (name,val))
#self.setreply('550','','Go away spammer')
return Milter.REJECT
@@ -123,7 +123,7 @@ class sampleMilter(Milter.Milter):
h = msg.getheaders(name)
cnt = len(h)
for i in range(cnt,0,-1):
self.chgheader(name,i-1,'')
self.chgheader(name,i-1,'')
def eom(self):
if not self.fp: return Milter.ACCEPT
@@ -145,9 +145,9 @@ class sampleMilter(Milter.Milter):
msg = rfc822.Message(out)
msg.rewindbody()
while 1:
buf = out.read(8192)
if len(buf) == 0: break
self.replacebody(buf) # feed modified message to sendmail
buf = out.read(8192)
if len(buf) == 0: break
self.replacebody(buf) # feed modified message to sendmail
return Milter.ACCEPT # ACCEPT modified message
finally:
out.close()
+18587
View File
File diff suppressed because it is too large Load Diff
+8 -5
View File
@@ -1,4 +1,7 @@
# $Log$
# Revision 1.5 2011/06/09 17:27:42 customdesigned
# Documentation updates.
#
# Revision 1.4 2005/07/20 14:49:44 customdesigned
# Handle corrupt and empty ZIP files.
#
@@ -69,12 +72,12 @@ class MimeTestCase(unittest.TestCase):
# python 2.4 doesn't get exceptions on missing boundaries, and
# if message is modified, output is readable by mail clients
if sys.hexversion < 0x02040000:
self.fail('should get boundary error parsing bad rfc822 attachment')
self.fail('should get boundary error parsing bad rfc822 attachment')
except Errors.BoundaryError:
pass
def testDefang(self,vname='virus1',part=1,
fname='LOVE-LETTER-FOR-YOU.TXT.vbs'):
fname='LOVE-LETTER-FOR-YOU.TXT.vbs'):
msg = mime.message_from_file(open('test/'+vname,"r"))
mime.defang(msg,scan_zip=True)
self.failUnless(msg.ismodified(),"virus not removed")
@@ -108,7 +111,7 @@ class MimeTestCase(unittest.TestCase):
self.failIf(msg.ismultipart())
txt2 = msg.get_payload()
self.failUnless(txt2 == mime.virus_msg % \
(fname,hostname,None),txt2)
(fname,hostname,None),txt2)
# honey virus has a sneaky ASP payload which is parsed correctly
# by email package in python-2.2.2, but not by mime.MimeMessage or 2.2.1
@@ -122,7 +125,7 @@ class MimeTestCase(unittest.TestCase):
txt2 = parts[1].get_payload()
txt3 = parts[2].get_payload()
self.failUnless(txt2.rstrip()+'\n' == mime.virus_msg % \
(fname,hostname,None),txt2)
(fname,hostname,None),txt2)
if txt3 != '':
self.failUnless(txt3.rstrip()+'\n' == mime.virus_msg % \
('story[1].asp',hostname,None),txt3)
@@ -170,7 +173,7 @@ class MimeTestCase(unittest.TestCase):
msg = mime.message_from_file(open('test/'+fname,'r'))
mime.defang(msg,scan_zip=True)
self.failIf(msg.ismodified())
msg = mime.message_from_file(open('test/tmpytgcE5.fail','r'))
msg = mime.message_from_file(open('test/test2','r'))
rc = mime.check_attachments(msg,self._chk_attach)
self.assertEquals(self.filename,"7501'S FOR TWO GOLDEN SOURCES SHIPMENTS FOR TAX & DUTY PURPOSES ONLY.PDF")
self.assertEquals(rc,Milter.CONTINUE)