diff --git a/Milter/cache.py b/Milter/cache.py index 82bf046..8d7a959 100644 --- a/Milter/cache.py +++ b/Milter/cache.py @@ -48,7 +48,7 @@ from __future__ import print_function import time -from plock import PLock +from Milter.plock import PLock class AddrCache(object): time_format = '%Y%b%d %H:%M:%S %Z' @@ -132,8 +132,8 @@ class AddrCache(object): if not ts or ts > too_old: return res del self.cache[lsender] - raise KeyError, sender - except KeyError,x: + raise KeyError(sender) + except KeyError as x: try: user,host = sender.split('@',1) return self.__getitem__(host) diff --git a/Milter/greysql.py b/Milter/greysql.py index b85a4d7..4c290b1 100644 --- a/Milter/greysql.py +++ b/Milter/greysql.py @@ -2,7 +2,10 @@ import time import logging import urllib import sqlite3 -import thread +try: + import thread +except: + import _thread as thread from datetime import datetime log = logging.getLogger('milter.greylist') diff --git a/Milter/plock.py b/Milter/plock.py index bcbe51a..f551df2 100644 --- a/Milter/plock.py +++ b/Milter/plock.py @@ -11,7 +11,7 @@ class PLock(object): self.basename = basename self.fp = None - def lock(self,lockname=None,mode=0660,strict_perms=False): + def lock(self,lockname=None,mode=0o660,strict_perms=False): "Start an update transaction. Return FILE to write new version." self.unlock() if not lockname: @@ -21,7 +21,7 @@ class PLock(object): st = os.stat(self.basename) mode |= st.st_mode except OSError: pass - u = os.umask(0002) + u = os.umask(0o2) try: fd = os.open(lockname,os.O_WRONLY+os.O_CREAT+os.O_EXCL,mode) finally: @@ -46,7 +46,7 @@ class PLock(object): def commit(self,backname=None): "Commit update transaction with optional backup file." if not self.fp: - raise IOError,"File not locked" + raise IOError("File not locked") self.fp.close() self.fp = None if backname: diff --git a/Milter/pyip6.py b/Milter/pyip6.py index ea62496..e28f7c9 100644 --- a/Milter/pyip6.py +++ b/Milter/pyip6.py @@ -85,7 +85,7 @@ def inet_pton(p): ::1.2.3.4.5 """ if p == '::': - return '\0'*16 + return b'\0'*16 s = p m = RE_IP4.search(s) try: diff --git a/Milter/test.py b/Milter/test.py index 095b76c..5077f4c 100644 --- a/Milter/test.py +++ b/Milter/test.py @@ -2,7 +2,7 @@ # A test framework for milters from __future__ import print_function -import rfc822 +import mime try: from StringIO import StringIO except: @@ -62,11 +62,11 @@ class TestBase(object): self._body.write(chunk) self._bodyreplaced = True else: - raise IOError,"replacebody not called from eom()" + raise IOError("replacebody not called from eom()") def chgfrom(self,sender,params=None): if not self._body: - raise IOError,"chgfrom not called from eom()" + raise IOError("chgfrom not called from eom()") self.log('chgfrom: sender=%s' % (sender)) self._envfromchanged = True self._sender = sender @@ -83,7 +83,7 @@ class TestBase(object): # work for a %milter def chgheader(self,field,idx,value): if not self._body: - raise IOError,"chgheader not called from eom()" + raise IOError("chgheader not called from eom()") self.log('chgheader: %s[%d]=%s' % (field,idx,value)) if value == '': del self._msg[field] @@ -93,19 +93,19 @@ class TestBase(object): def addheader(self,field,value,idx=-1): if not self._body: - raise IOError,"addheader not called from eom()" + raise IOError("addheader not called from eom()") self.log('addheader: %s=%s' % (field,value)) self._msg[field] = value self._headerschanged = True def delrcpt(self,rcpt): if not self._body: - raise IOError,"delrcpt not called from eom()" + raise IOError("delrcpt not called from eom()") self._delrcpt.append(rcpt) def addrcpt(self,rcpt): if not self._body: - raise IOError,"addrcpt not called from eom()" + raise IOError("addrcpt not called from eom()") self._addrcpt.append(rcpt) ## Save the reply codes and messages in self._reply. @@ -143,34 +143,21 @@ class TestBase(object): self._headerschanged = False self._reply = None self._sender = '<%s>'%sender - msg = rfc822.Message(fp) + msg = mime.message_from_file(fp) rc = self.envfrom(self._sender) if rc != Milter.CONTINUE: return rc for rcpt in (rcpt,) + rcpts: rc = self.envrcpt('<%s>'%rcpt) if rc != Milter.CONTINUE: return rc - line = None - for h in msg.headers: - if h[:1].isspace(): - line = line + h - continue - if not line: - line = h - continue - s = line.split(': ',1) - if len(s) > 1: val = s[1].strip() - else: val = '' - rc = self.header(s[0],val) - if rc != Milter.CONTINUE: return rc - line = h - if line: - s = line.split(': ',1) - rc = self.header(s[0],s[1]) + for h,val in msg.items(): + rc = self.header(h,val) if rc != Milter.CONTINUE: return rc rc = self.eoh() if rc != Milter.CONTINUE: return rc + header,body = msg.as_bytes().split(b'\n\n',1) + bfp = StringIO(body) while 1: - buf = fp.read(8192) + buf = bfp.read(8192) if len(buf) == 0: break rc = self.body(buf) if rc != Milter.CONTINUE: return rc @@ -179,12 +166,9 @@ class TestBase(object): rc = self.eom() if self._bodyreplaced: body = self._body.getvalue() - else: - msg.rewindbody() - body = msg.fp.read() self._body = StringIO() - self._body.writelines(msg.headers) - self._body.write('\n') + self._body.write(header) + self._body.write('\n\n') self._body.write(body) return rc diff --git a/Milter/utils.py b/Milter/utils.py index 563d17c..cd27b00 100644 --- a/Milter/utils.py +++ b/Milter/utils.py @@ -5,13 +5,11 @@ import re import struct import socket -import email.Errors +import email.errors +from email.header import decode_header import email.base64mime from fnmatch import fnmatchcase -from email.Header import decode_header from binascii import a2b_base64 -#import email.Utils -import rfc822 dnsre = re.compile(r'^[a-z][-a-z\d.]+$', re.IGNORECASE) PAT_IP4 = r'\.'.join([r'(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])']*4) @@ -56,8 +54,8 @@ if hasattr(socket,'has_ipv6') and socket.has_ipv6: else: from pyip6 import inet_ntop, inet_pton -MASK = 0xFFFFFFFFL -MASK6 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL +MASK = 0xFFFFFFFF +MASK6 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF def cidr(i,n,mask=MASK): return ~(mask >> n) & mask & i @@ -119,7 +117,7 @@ def iniplist(ipaddr,iplist): return False ## Split email into Fullname and address. -# This replaces email.Utils.parseaddr but fixes +# This replaces email.utils.parseaddr but fixes # some tricky test cases. # Additional tricky cases are still broken. Patches welcome. # @@ -139,8 +137,8 @@ def parseaddr(t): >>> parseaddr('a(WRONG)@b') ('WRONG', 'a@b') """ - #return email.Utils.parseaddr(t) - res = rfc822.parseaddr(t) + #return email.utils.parseaddr(t) + res = email.utils.parseaddr(t) # dirty fix for some broken cases if not res[0]: pos = t.find('<') @@ -149,7 +147,7 @@ def parseaddr(t): pos1 = addrspec.rfind(':') if pos1 > 0: addrspec = addrspec[pos1+1:] - return rfc822.parseaddr('"%s" <%s>' % (t[:pos].strip(),addrspec)) + return email.utils.parseaddr('"%s" <%s>' % (t[:pos].strip(),addrspec)) if not res[1]: pos = t.find('<') if pos > 0 and t[-1] == '>': @@ -157,7 +155,7 @@ def parseaddr(t): pos1 = addrspec.rfind(':') if pos1 > 0: addrspec = addrspec[pos1+1:] - return rfc822.parseaddr('%s<%s>' % (t[:pos].strip(),addrspec)) + return email.utils.parseaddr('%s<%s>' % (t[:pos].strip(),addrspec)) return res ## Fix email.base64mime.decode to add any missing padding @@ -227,5 +225,5 @@ def parse_header(val): except UnicodeDecodeError: pass except LookupError: pass except ValueError: pass - except email.Errors.HeaderParseError: pass + except email.errors.HeaderParseError: pass return val diff --git a/mime.py b/mime.py index c23da72..8106d2e 100644 --- a/mime.py +++ b/mime.py @@ -103,15 +103,12 @@ import Milter import zipfile import email -import email.Message -from email.Message import Message -from email.Generator import Generator -from email.Utils import quote -from email import Utils -from email.Parser import Parser -from email import Errors +from email.message import Message +from email.generator import Generator +from email.utils import quote -from types import ListType,StringType +if not getattr(Message,'as_bytes',None): + Message.as_bytes = Message.as_string ## Return a list of filenames in a zip file. # Embedded zip files are recursively expanded. @@ -154,19 +151,17 @@ def unquote(s): return s[1:-1] return s -from types import TupleType - def _unquotevalue(value): - if isinstance(value, TupleType): + if isinstance(value, tuple): return value[0], value[1], unquote(value[2]) else: return unquote(value) #email.Message._unquotevalue = _unquotevalue -from email.Message import _parseparam +from email.message import _parseparam -## Enhance email.Message +## Enhance email.message.Message # # Tracks modifications to headers of body or any part independently. @@ -207,7 +202,7 @@ class MimeMessage(Message): interpret as a name - and hence decide to execute this message.""" names = [] for attr,val in self._get_params_preserve([],'content-type'): - if isinstance(val, TupleType): + if isinstance(val, tuple): # It's an RFC 2231 encoded parameter newvalue = _unquotevalue(val) if val[0]: @@ -355,8 +350,6 @@ def check_name(msg,savname=None,ckname=check_ext,scan_zip=False): msg["Content-Type"] = "text/plain; name="+name return Milter.CONTINUE -import email.Iterators - def check_attachments(msg,check): """Scan attachments. msg MimeMessage @@ -402,18 +395,21 @@ class _defang: # emulate old defang function defang = _defang() -import sgmllib +try: + from html.parser import HTMLParser +except: + from sgmllib import SGMLParser as HTMLParser import re declname = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*') declstringlit = re.compile(r'(\'[^\']*\'|"[^"]*")\s*') -class SGMLFilter(sgmllib.SGMLParser): +class SGMLFilter(HTMLParser): """Parse HTML and pass through all constructs unchanged. It is intended for derived classes to implement exceptional processing for selected cases. """ def __init__(self,out): - sgmllib.SGMLParser.__init__(self) + HTMLParser.__init__(self) self.out = out def handle_comment(self,comment): @@ -444,7 +440,7 @@ class SGMLFilter(sgmllib.SGMLParser): self.out.write("" % data) def write(self,buf): - "Act like a writer. Why doesn't SGMLParser do this by default?" + "Act like a writer. Why doesn't HTMLParser do this by default?" self.feed(buf) # Python-2.1 sgmllib rejects illegal declarations. Since various Microsoft @@ -545,5 +541,5 @@ if __name__ == '__main__': for fname in sys.argv[1:]: fp = open(fname) msg = message_from_file(fp) - email.Iterators._structure(msg) + email.iterators._structure(msg) check_attachments(msg,_list_attach) diff --git a/sample.py b/sample.py index c4fc033..7f92994 100644 --- a/sample.py +++ b/sample.py @@ -11,7 +11,6 @@ try: from StringIO import StringIO except: from io import StringIO -import rfc822 import mime import Milter import tempfile @@ -141,19 +140,16 @@ class sampleMilter(Milter.Milter): self.log("Temp file:",self.tempname) self.tempname = None # prevent removal of original message copy # copy defanged message to a temp file - out = tempfile.TemporaryFile() - try: + with tempfile.TemporaryFile() as out: msg.dump(out) out.seek(0) - msg = rfc822.Message(out) - msg.rewindbody() + msg = mime.message_from_file(out) + fp = StringIO(msg.as_bytes().split(b'\n\n',1)[1]) while 1: - buf = out.read(8192) + buf = fp.read(8192) if len(buf) == 0: break self.replacebody(buf) # feed modified message to sendmail return Milter.ACCEPT # ACCEPT modified message - finally: - out.close() return Milter.TEMPFAIL def close(self): diff --git a/testmime.py b/testmime.py index 28c7c16..03189bf 100644 --- a/testmime.py +++ b/testmime.py @@ -37,7 +37,10 @@ except: import email import sys import Milter -from email import Errors +try: + from email import Errors as errors +except: + from email import errors samp1_txt1 = """Dear Agent 1 I hope you can read this. Whenever you write label it P.B.S kids. @@ -77,7 +80,7 @@ class MimeTestCase(unittest.TestCase): # if message is modified, output is readable by mail clients if sys.hexversion < 0x02040000: self.fail('should get boundary error parsing bad rfc822 attachment') - except Errors.BoundaryError: + except errors.BoundaryError: pass def testDefang(self,vname='virus1',part=1, diff --git a/testsample.py b/testsample.py index 285dee3..5bff76a 100644 --- a/testsample.py +++ b/testsample.py @@ -2,7 +2,6 @@ import unittest import Milter import sample import mime -import rfc822 from Milter.test import TestBase class TestMilter(TestBase,sample.sampleMilter):