From ea09bab1a8fb9eeed3429e9a8411ee42f9c423f3 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 20 Feb 2019 19:01:30 -0500 Subject: [PATCH] Convert __init__.py to python3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The main work here is about bytes vs. strings. This work was confusing for several reasons: * pymilter thinks that headers are all strings, but body is bytes * dkimpy wants to deal with bytes objects generally (though it accepts a string object as an ed25519 secret key for some reason, despite requiring bytes as an RSA secret key) * authres.AuthenticationResultsHeader object converts easily to a string, but has no direct bytes conversion. meanwhile, it wants its arguments as strings, but will accept them if they are bytes and convert them with something like str(), which leaves weird cruft like "header.a=b'ed25519-sha256'" * dkimpy_milter/utils.py contains fold() which expects bytes * self.fp needs to accumulate the on-the-wire version of the message as a whole (so it needs to be bytes). That means converting the headers. Header names and values are US-ASCII, per ยง2.2 of RFC 5322, so they should be convertible cleanly, but we still have to convert them explicitly so that python knows the right thing to do. At any rate, tests/runtests all passes with these changes, and the output for both Authentication-Results: and DKIM-Signature headers looks the same. --- dkimpy_milter/__init__.py | 61 +++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/dkimpy_milter/__init__.py b/dkimpy_milter/__init__.py index c4c407b..bcdf2a7 100644 --- a/dkimpy_milter/__init__.py +++ b/dkimpy_milter/__init__.py @@ -28,8 +28,9 @@ import dkim import authres import os import tempfile -import StringIO +import io import re +import codecs from Milter.utils import parse_addr, parseaddr import dkimpy_milter.config as config from dkimpy_milter.util import drop_privileges @@ -110,7 +111,7 @@ class dkimMilter(Milter.Base): def envfrom(self, f, *str): if milterconfig.get('Syslog') and milterconfig.get('debugLevel') >= 2: syslog.syslog("mail from: {0} {1}".format(f, str)) - self.fp = StringIO.StringIO() + self.fp = io.BytesIO() self.mailfrom = f t = parse_addr(f) if len(t) == 2: @@ -142,13 +143,13 @@ class dkimMilter(Milter.Base): elif lname == 'authentication-results': self.arheaders.append(val) if self.fp: - self.fp.write("%s: %s\n" % (name, val)) + self.fp.write(b"%s: %s\n" % (codecs.encode(name, 'ascii'), codecs.encode(val, 'ascii'))) return Milter.CONTINUE @Milter.noreply def eoh(self): if self.fp: - self.fp.write("\n") # terminate headers + self.fp.write(b"\n") # terminate headers self.bodysize = 0 return Milter.CONTINUE @@ -195,20 +196,20 @@ class dkimMilter(Milter.Base): h = authres.AuthenticationResultsHeader(authserv_id= self.AuthservID, results=self.arresults) - h = fold(str(h)) + h = fold(codecs.encode(str(h), 'ascii')) if (milterconfig.get('Syslog') and milterconfig.get('debugLevel') >= 2): - syslog.syslog(str(h)) - name, val = str(h).split(': ', 1) + syslog.syslog(codecs.decode(h, 'ascii')) + name, val = codecs.decode(h, 'ascii').split(': ', 1) self.addheader(name, val, 0) return Milter.CONTINUE def sign_dkim(self, txt): - canon = milterconfig.get('Canonicalization') + canon = codecs.encode(milterconfig.get('Canonicalization'), 'ascii') canonicalize = [] - if len(canon.split('/')) == 2: - canonicalize.append(canon.split('/')[0]) - canonicalize.append(canon.split('/')[1]) + if len(canon.split(b'/')) == 2: + canonicalize.append(canon.split(b'/')[0]) + canonicalize.append(canon.split(b'/')[1]) else: canonicalize.append(canon) canonicalize.append(canon) @@ -218,11 +219,12 @@ class dkimMilter(Milter.Base): try: if privateRSA: d = dkim.DKIM(txt) - h = d.sign(milterconfig.get('Selector'), self.fdomain, - privateRSA, canonicalize=(canonicalize[0], - canonicalize[1])) - name, val = h.split(': ', 1) - self.addheader(name, val.strip().replace('\r\n', '\n'), 0) + h = d.sign(codecs.encode(milterconfig.get('Selector'), 'ascii'), codecs.encode(self.fdomain, 'ascii'), + codecs.encode(privateRSA, 'ascii'), + canonicalize=(canonicalize[0], + canonicalize[1])) + name, val = h.split(b': ', 1) + self.addheader(codecs.decode(name, 'ascii'), codecs.decode(val, 'ascii').strip().replace('\r\n', '\n'), 0) if (milterconfig.get('Syslog') and (milterconfig.get('SyslogSuccess') or milterconfig.get('debugLevel') >= 1)): @@ -233,12 +235,12 @@ class dkimMilter(Milter.Base): d.domain.lower())) if privateEd25519: d = dkim.DKIM(txt) - h = d.sign(milterconfig.get('SelectorEd25519'), self.fdomain, + h = d.sign(codecs.encode(milterconfig.get('SelectorEd25519'), 'ascii'), codecs.encode(self.fdomain, 'ascii'), privateEd25519, canonicalize=(canonicalize[0], canonicalize[1]), - signature_algorithm='ed25519-sha256') - name, val = h.split(': ', 1) - self.addheader(name, val.strip().replace('\r\n', '\n'), 0) + signature_algorithm=b'ed25519-sha256') + name, val = h.split(b': ', 1) + self.addheader(codecs.decode(name, 'ascii'), codecs.decode(val, 'ascii').strip().replace('\r\n', '\n'), 0) if (milterconfig.get('Syslog') and (milterconfig.get('SyslogSuccess') or milterconfig.get('debugLevel') >= 1)): @@ -266,20 +268,17 @@ class dkimMilter(Milter.Base): res = d.verify(idx=y, dnsfunc=lambda _x: dnsoverride) else: res = d.verify(idx=y) + algo = codecs.decode(d.signature_fields.get(b'a'), 'ascii') if res: - if d.signature_fields.get(b'a') == 'ed25519-sha256': + if algo == 'ed25519-sha256': self.dkim_comment = ('Good {0} signature' - .format(d.signature_fields - .get(b'a'))) + .format(algo)) else: self.dkim_comment = ('Good {0} bit {1} signature' - .format(d.keysize, - d.signature_fields - .get(b'a'))) + .format(d.keysize, algo)) else: self.dkim_comment = ('Bad {0} bit {1} signature.' - .format(d.keysize, - d.signature_fields.get(b'a'))) + .format(d.keysize, algo)) except dkim.DKIMException as x: self.dkim_comment = str(x) if milterconfig.get('Syslog'): @@ -288,9 +287,9 @@ class dkimMilter(Milter.Base): self.dkim_comment = str(x) if milterconfig.get('Syslog'): syslog.syslog("check_dkim: {0}".format(x)) - self.header_i = d.signature_fields.get(b'i') - self.header_d = d.signature_fields.get(b'd') - self.header_a = d.signature_fields.get(b'a') + self.header_i = codecs.decode(d.signature_fields.get(b'i'), 'ascii') + self.header_d = codecs.decode(d.signature_fields.get(b'd'), 'ascii') + self.header_a = codecs.decode(d.signature_fields.get(b'a'), 'ascii') if res: if (milterconfig.get('Syslog') and (milterconfig.get('SyslogSuccess') or