Merge dkimpy-milter:dkg/python3 into dkimpy-milter:1_0

Proposed by dkg
Status: Merged
Merge reported by: Scott Kitterman
Merged at revision: ea09bab1a8fb9eeed3429e9a8411ee42f9c423f3
Proposed branch: dkimpy-milter:dkg/python3
Merge into: dkimpy-milter:1_0
Prerequisite: dkimpy-milter:dkg/test-suite
Diff against target: 327 lines (+55/-56)
6 files modified
dkimpy_milter/__init__.py (+31/-32)
dkimpy_milter/__main__.py (+1/-1)
dkimpy_milter/config.py (+10/-10)
dkimpy_milter/dnsplug.py (+8/-8)
setup.py (+4/-4)
tests/dkimpy-milter (+1/-1)
Reviewer Review Type Date Requested Status
Scott Kitterman Pending
Review via email: mp+363528@code.launchpad.net

Commit message

This brief series converts dkimpy-milter to python3. It depends on the test-suite branch to verify that the major functionality remains functional.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/dkimpy_milter/__init__.py b/dkimpy_milter/__init__.py
index 5345fc7..bcdf2a7 100644
--- a/dkimpy_milter/__init__.py
+++ b/dkimpy_milter/__init__.py
@@ -1,4 +1,4 @@
1#! /usr/bin/python21#! /usr/bin/python3
2# Original dkim-milter.py code:2# Original dkim-milter.py code:
3# Author: Stuart D. Gathman <stuart@bmsi.com>3# Author: Stuart D. Gathman <stuart@bmsi.com>
4# Copyright 2007 Business Management Systems, Inc.4# Copyright 2007 Business Management Systems, Inc.
@@ -28,8 +28,9 @@ import dkim
28import authres28import authres
29import os29import os
30import tempfile30import tempfile
31import StringIO31import io
32import re32import re
33import codecs
33from Milter.utils import parse_addr, parseaddr34from Milter.utils import parse_addr, parseaddr
34import dkimpy_milter.config as config35import dkimpy_milter.config as config
35from dkimpy_milter.util import drop_privileges36from dkimpy_milter.util import drop_privileges
@@ -110,7 +111,7 @@ class dkimMilter(Milter.Base):
110 def envfrom(self, f, *str):111 def envfrom(self, f, *str):
111 if milterconfig.get('Syslog') and milterconfig.get('debugLevel') >= 2:112 if milterconfig.get('Syslog') and milterconfig.get('debugLevel') >= 2:
112 syslog.syslog("mail from: {0} {1}".format(f, str))113 syslog.syslog("mail from: {0} {1}".format(f, str))
113 self.fp = StringIO.StringIO()114 self.fp = io.BytesIO()
114 self.mailfrom = f115 self.mailfrom = f
115 t = parse_addr(f)116 t = parse_addr(f)
116 if len(t) == 2:117 if len(t) == 2:
@@ -142,13 +143,13 @@ class dkimMilter(Milter.Base):
142 elif lname == 'authentication-results':143 elif lname == 'authentication-results':
143 self.arheaders.append(val)144 self.arheaders.append(val)
144 if self.fp:145 if self.fp:
145 self.fp.write("%s: %s\n" % (name, val))146 self.fp.write(b"%s: %s\n" % (codecs.encode(name, 'ascii'), codecs.encode(val, 'ascii')))
146 return Milter.CONTINUE147 return Milter.CONTINUE
147148
148 @Milter.noreply149 @Milter.noreply
149 def eoh(self):150 def eoh(self):
150 if self.fp:151 if self.fp:
151 self.fp.write("\n") # terminate headers152 self.fp.write(b"\n") # terminate headers
152 self.bodysize = 0153 self.bodysize = 0
153 return Milter.CONTINUE154 return Milter.CONTINUE
154155
@@ -195,20 +196,20 @@ class dkimMilter(Milter.Base):
195 h = authres.AuthenticationResultsHeader(authserv_id=196 h = authres.AuthenticationResultsHeader(authserv_id=
196 self.AuthservID,197 self.AuthservID,
197 results=self.arresults)198 results=self.arresults)
198 h = fold(str(h))199 h = fold(codecs.encode(str(h), 'ascii'))
199 if (milterconfig.get('Syslog') and200 if (milterconfig.get('Syslog') and
200 milterconfig.get('debugLevel') >= 2):201 milterconfig.get('debugLevel') >= 2):
201 syslog.syslog(str(h))202 syslog.syslog(codecs.decode(h, 'ascii'))
202 name, val = str(h).split(': ', 1)203 name, val = codecs.decode(h, 'ascii').split(': ', 1)
203 self.addheader(name, val, 0)204 self.addheader(name, val, 0)
204 return Milter.CONTINUE205 return Milter.CONTINUE
205206
206 def sign_dkim(self, txt):207 def sign_dkim(self, txt):
207 canon = milterconfig.get('Canonicalization')208 canon = codecs.encode(milterconfig.get('Canonicalization'), 'ascii')
208 canonicalize = []209 canonicalize = []
209 if len(canon.split('/')) == 2:210 if len(canon.split(b'/')) == 2:
210 canonicalize.append(canon.split('/')[0])211 canonicalize.append(canon.split(b'/')[0])
211 canonicalize.append(canon.split('/')[1])212 canonicalize.append(canon.split(b'/')[1])
212 else:213 else:
213 canonicalize.append(canon)214 canonicalize.append(canon)
214 canonicalize.append(canon)215 canonicalize.append(canon)
@@ -218,11 +219,12 @@ class dkimMilter(Milter.Base):
218 try:219 try:
219 if privateRSA:220 if privateRSA:
220 d = dkim.DKIM(txt)221 d = dkim.DKIM(txt)
221 h = d.sign(milterconfig.get('Selector'), self.fdomain,222 h = d.sign(codecs.encode(milterconfig.get('Selector'), 'ascii'), codecs.encode(self.fdomain, 'ascii'),
222 privateRSA, canonicalize=(canonicalize[0],223 codecs.encode(privateRSA, 'ascii'),
223 canonicalize[1]))224 canonicalize=(canonicalize[0],
224 name, val = h.split(': ', 1)225 canonicalize[1]))
225 self.addheader(name, val.strip().replace('\r\n', '\n'), 0)226 name, val = h.split(b': ', 1)
227 self.addheader(codecs.decode(name, 'ascii'), codecs.decode(val, 'ascii').strip().replace('\r\n', '\n'), 0)
226 if (milterconfig.get('Syslog') and228 if (milterconfig.get('Syslog') and
227 (milterconfig.get('SyslogSuccess')229 (milterconfig.get('SyslogSuccess')
228 or milterconfig.get('debugLevel') >= 1)):230 or milterconfig.get('debugLevel') >= 1)):
@@ -233,12 +235,12 @@ class dkimMilter(Milter.Base):
233 d.domain.lower()))235 d.domain.lower()))
234 if privateEd25519:236 if privateEd25519:
235 d = dkim.DKIM(txt)237 d = dkim.DKIM(txt)
236 h = d.sign(milterconfig.get('SelectorEd25519'), self.fdomain,238 h = d.sign(codecs.encode(milterconfig.get('SelectorEd25519'), 'ascii'), codecs.encode(self.fdomain, 'ascii'),
237 privateEd25519, canonicalize=(canonicalize[0],239 privateEd25519, canonicalize=(canonicalize[0],
238 canonicalize[1]),240 canonicalize[1]),
239 signature_algorithm='ed25519-sha256')241 signature_algorithm=b'ed25519-sha256')
240 name, val = h.split(': ', 1)242 name, val = h.split(b': ', 1)
241 self.addheader(name, val.strip().replace('\r\n', '\n'), 0)243 self.addheader(codecs.decode(name, 'ascii'), codecs.decode(val, 'ascii').strip().replace('\r\n', '\n'), 0)
242 if (milterconfig.get('Syslog') and244 if (milterconfig.get('Syslog') and
243 (milterconfig.get('SyslogSuccess')245 (milterconfig.get('SyslogSuccess')
244 or milterconfig.get('debugLevel') >= 1)):246 or milterconfig.get('debugLevel') >= 1)):
@@ -266,20 +268,17 @@ class dkimMilter(Milter.Base):
266 res = d.verify(idx=y, dnsfunc=lambda _x: dnsoverride)268 res = d.verify(idx=y, dnsfunc=lambda _x: dnsoverride)
267 else:269 else:
268 res = d.verify(idx=y)270 res = d.verify(idx=y)
271 algo = codecs.decode(d.signature_fields.get(b'a'), 'ascii')
269 if res:272 if res:
270 if d.signature_fields.get(b'a') == 'ed25519-sha256':273 if algo == 'ed25519-sha256':
271 self.dkim_comment = ('Good {0} signature'274 self.dkim_comment = ('Good {0} signature'
272 .format(d.signature_fields275 .format(algo))
273 .get(b'a')))
274 else:276 else:
275 self.dkim_comment = ('Good {0} bit {1} signature'277 self.dkim_comment = ('Good {0} bit {1} signature'
276 .format(d.keysize,278 .format(d.keysize, algo))
277 d.signature_fields
278 .get(b'a')))
279 else:279 else:
280 self.dkim_comment = ('Bad {0} bit {1} signature.'280 self.dkim_comment = ('Bad {0} bit {1} signature.'
281 .format(d.keysize,281 .format(d.keysize, algo))
282 d.signature_fields.get(b'a')))
283 except dkim.DKIMException as x:282 except dkim.DKIMException as x:
284 self.dkim_comment = str(x)283 self.dkim_comment = str(x)
285 if milterconfig.get('Syslog'):284 if milterconfig.get('Syslog'):
@@ -288,9 +287,9 @@ class dkimMilter(Milter.Base):
288 self.dkim_comment = str(x)287 self.dkim_comment = str(x)
289 if milterconfig.get('Syslog'):288 if milterconfig.get('Syslog'):
290 syslog.syslog("check_dkim: {0}".format(x))289 syslog.syslog("check_dkim: {0}".format(x))
291 self.header_i = d.signature_fields.get(b'i')290 self.header_i = codecs.decode(d.signature_fields.get(b'i'), 'ascii')
292 self.header_d = d.signature_fields.get(b'd')291 self.header_d = codecs.decode(d.signature_fields.get(b'd'), 'ascii')
293 self.header_a = d.signature_fields.get(b'a')292 self.header_a = codecs.decode(d.signature_fields.get(b'a'), 'ascii')
294 if res:293 if res:
295 if (milterconfig.get('Syslog') and294 if (milterconfig.get('Syslog') and
296 (milterconfig.get('SyslogSuccess') or295 (milterconfig.get('SyslogSuccess') or
diff --git a/dkimpy_milter/__main__.py b/dkimpy_milter/__main__.py
index 8c5cf9c..f95075f 100644
--- a/dkimpy_milter/__main__.py
+++ b/dkimpy_milter/__main__.py
@@ -1,4 +1,4 @@
1#!/usr/bin/python21#!/usr/bin/python3
22
3from dkimpy_milter import main3from dkimpy_milter import main
44
diff --git a/dkimpy_milter/config.py b/dkimpy_milter/config.py
index d562e97..bf6551a 100644
--- a/dkimpy_milter/config.py
+++ b/dkimpy_milter/config.py
@@ -31,13 +31,13 @@ import stat
31import dkim31import dkim
32import socket32import socket
33import ipaddress33import ipaddress
34from dnsplug import Session34from .dnsplug import Session
3535
36# default values36# default values
37defaultConfigData = {37defaultConfigData = {
38 'Syslog': 'yes',38 'Syslog': 'yes',
39 'SyslogFacility': 'mail',39 'SyslogFacility': 'mail',
40 'UMask': 007,40 'UMask': 0o07,
41 'Mode': 'sv',41 'Mode': 'sv',
42 'Socket': 'local:/var/run/dkimpy-milter/dkimpy-milter.sock',42 'Socket': 'local:/var/run/dkimpy-milter/dkimpy-milter.sock',
43 'PidFile': '/var/run/dkimpy-milter/dkimpy-milter.pid',43 'PidFile': '/var/run/dkimpy-milter/dkimpy-milter.pid',
@@ -85,14 +85,14 @@ class HostsDataset(object):
85 self.item = item[1:]85 self.item = item[1:]
86 self.negative = True86 self.negative = True
87 try:87 try:
88 self.item = ipaddress.ip_address(unicode(self.item, "utf-8"))88 self.item = ipaddress.ip_address(str(self.item, "utf-8"))
89 if isinstance(self.item, ipaddress.IPv4Address):89 if isinstance(self.item, ipaddress.IPv4Address):
90 self.isipv4 = True90 self.isipv4 = True
91 elif isinstance(self.item, ipaddress.IPv6Address):91 elif isinstance(self.item, ipaddress.IPv6Address):
92 self.isipv6 = True92 self.isipv6 = True
93 except ValueError as e:93 except ValueError as e:
94 try:94 try:
95 self.item = ipaddress.ip_network(unicode95 self.item = ipaddress.ip_network(str
96 (self.item, "utf-8"),96 (self.item, "utf-8"),
97 strict=False)97 strict=False)
98 if isinstance(self.item, ipaddress.IPv4Network):98 if isinstance(self.item, ipaddress.IPv4Network):
@@ -110,7 +110,7 @@ class HostsDataset(object):
110110
111 def match(self, connectip):111 def match(self, connectip):
112 '''Check if the connect IP is part of the dataset'''112 '''Check if the connect IP is part of the dataset'''
113 source = ipaddress.ip_address(unicode(connectip, "utf-8"))113 source = ipaddress.ip_address(str(connectip, "utf-8"))
114 for item in self.dataset:114 for item in self.dataset:
115 if item.isdomain or item.ishostname:115 if item.isdomain or item.ishostname:
116 result = self.matchname(source) # Match host/domains first116 result = self.matchname(source) # Match host/domains first
@@ -160,13 +160,13 @@ class HostsDataset(object):
160 if isinstance(source, ipaddress.IPv4Address):160 if isinstance(source, ipaddress.IPv4Address):
161 ips = s.dns(name, 'A')161 ips = s.dns(name, 'A')
162 for ip in ips:162 for ip in ips:
163 ip = ipaddress.IPv4Address(unicode(ip, 'UTF-8'))163 ip = ipaddress.IPv4Address(str(ip, 'UTF-8'))
164 if ip == source:164 if ip == source:
165 results.append(name)165 results.append(name)
166 if isinstance(source, ipaddress.IPv6Address):166 if isinstance(source, ipaddress.IPv6Address):
167 ips = s.dns(name, 'AAAA')167 ips = s.dns(name, 'AAAA')
168 for ip in ips:168 for ip in ips:
169 ip = ipaddress.IPv6Address(unicode(ip, 'UTF-8'))169 ip = ipaddress.IPv6Address(str(ip, 'UTF-8'))
170 if ip == source:170 if ip == source:
171 results.append(name)171 results.append(name)
172 return results172 return results
@@ -225,13 +225,13 @@ def _processConfigFile(filename=None, configdata=None, useSyslog=1,
225 '''Load the specified config file, exit and log errors if it fails,225 '''Load the specified config file, exit and log errors if it fails,
226 otherwise return a config dictionary.'''226 otherwise return a config dictionary.'''
227227
228 import config228 from . import config
229 if configdata is None:229 if configdata is None:
230 configdata = config.defaultConfigData230 configdata = config.defaultConfigData
231 if filename is not None:231 if filename is not None:
232 try:232 try:
233 _readConfigFile(filename, configdata)233 _readConfigFile(filename, configdata)
234 except Exception, e:234 except Exception as e:
235 raise235 raise
236 if useSyslog:236 if useSyslog:
237 syslog.syslog(e.args[0])237 syslog.syslog(e.args[0])
@@ -342,7 +342,7 @@ def _readConfigFile(path, configData=None, configGlobal={}):
342 # check to see if it's a file342 # check to see if it's a file
343 try:343 try:
344 mode = os.stat(path)[0]344 mode = os.stat(path)[0]
345 except OSError, e:345 except OSError as e:
346 syslog.syslog(syslog.LOG_ERR, 'ERROR stating "%s": %s'346 syslog.syslog(syslog.LOG_ERR, 'ERROR stating "%s": %s'
347 % (path, e.strerror))347 % (path, e.strerror))
348 return(configData)348 return(configData)
diff --git a/dkimpy_milter/dnsplug.py b/dkimpy_milter/dnsplug.py
index aa357ec..33067e9 100644
--- a/dkimpy_milter/dnsplug.py
+++ b/dkimpy_milter/dnsplug.py
@@ -84,7 +84,7 @@ class Session(object):
84 raise DNSError('Length of CNAME chain exceeds %d' % MAX_CNAME)84 raise DNSError('Length of CNAME chain exceeds %d' % MAX_CNAME)
85 cnames[name] = cname85 cnames[name] = cname
86 if cname in cnames:86 if cname in cnames:
87 raise DNSError, 'CNAME loop'87 raise DNSError('CNAME loop')
88 result = self.dns(cname, qtype, cnames=cnames)88 result = self.dns(cname, qtype, cnames=cnames)
89 return result89 return result
9090
@@ -103,16 +103,16 @@ def DNSLookup_pydns(name, qtype, tcpfallback=True, timeout=30):
103 #103 #
104 if resp.header['tc'] == True:104 if resp.header['tc'] == True:
105 if not tcpfallback:105 if not tcpfallback:
106 raise DNS.DNSError, 'DNS: Truncated UDP Reply, SPF records should fit in a UDP packet'106 raise DNS.DNSError('DNS: Truncated UDP Reply, SPF records should fit in a UDP packet')
107 try:107 try:
108 req = DNS.DnsRequest(name, qtype=qtype, protocol='tcp',108 req = DNS.DnsRequest(name, qtype=qtype, protocol='tcp',
109 timeout=timeout)109 timeout=timeout)
110 resp = req.req()110 resp = req.req()
111 except DNS.DNSError, x:111 except DNS.DNSError as x:
112 raise DNS.DNSError, 'TCP Fallback error: ' + str(x)112 raise DNS.DNSError('TCP Fallback error: ' + str(x))
113 return [((a['name'], a['typename']), a['data']) for a in resp.answers]113 return [((a['name'], a['typename']), a['data']) for a in resp.answers]
114 except IOError, x:114 except IOError as x:
115 raise DNS.DNSError, 'DNS: ' + str(x)115 raise DNS.DNSError('DNS: ' + str(x))
116116
117def DNSLookup_dnspython(name,qtype,tcpfallback=True,timeout=30):117def DNSLookup_dnspython(name,qtype,tcpfallback=True,timeout=30):
118 retVal = []118 retVal = []
@@ -164,5 +164,5 @@ if __name__ == '__main__':
164 import sys164 import sys
165 s = Session()165 s = Session()
166 for n,t in zip(*[iter(sys.argv[1:])]*2):166 for n,t in zip(*[iter(sys.argv[1:])]*2):
167 print n,t167 print(n,t)
168 print s.dns(n,t)168 print(s.dns(n,t))
diff --git a/setup.py b/setup.py
index 18c2ec9..c7990d7 100644
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,4 @@
1#! /usr/bin/python1#! /usr/bin/python3
2# dkimpy-milter: A DKIM signing/verification Milter application2# dkimpy-milter: A DKIM signing/verification Milter application
3# Author: Scott Kitterman <scott@kitterman.com>3# Author: Scott Kitterman <scott@kitterman.com>
4# Copyright 2018 Scott Kitterman4# Copyright 2018 Scott Kitterman
@@ -24,9 +24,9 @@ description = "Domain Keys Identified Mail (DKIM) signing/verifying milter for P
24kw = {} # Work-around for lack of 'or' requires in setuptools.24kw = {} # Work-around for lack of 'or' requires in setuptools.
25try:25try:
26 import dns26 import dns
27 kw['install_requires'] = ['dkimpy>=0.7', 'pymilter', 'authres>=1.1.0', 'PyNaCl', 'ipaddress', 'dnspython']27 kw['install_requires'] = ['dkimpy>=0.7', 'pymilter', 'authres>=1.1.0', 'PyNaCl', 'dnspython']
28except ImportError: # If PyDNS is not installed, prefer dnspython28except ImportError: # If PyDNS is not installed, prefer dnspython
29 kw['install_requires'] = ['dkimpy>=0.7', 'pymilter', 'authres>=1.1.0', 'PyNaCl', 'ipaddress', 'PyDNS']29 kw['install_requires'] = ['dkimpy>=0.7', 'pymilter', 'authres>=1.1.0', 'PyNaCl', 'PyDNS']
3030
31setup(31setup(
32 name='dkimpy-milter',32 name='dkimpy-milter',
@@ -43,7 +43,7 @@ setup(
43 'License :: OSI Approved :: GNU General Public License (GPL)',43 'License :: OSI Approved :: GNU General Public License (GPL)',
44 'Natural Language :: English',44 'Natural Language :: English',
45 'Operating System :: POSIX',45 'Operating System :: POSIX',
46 'Programming Language :: Python :: 2 :: Only',46 'Programming Language :: Python :: 3 :: Only',
47 'Topic :: Communications :: Email :: Mail Transport Agents',47 'Topic :: Communications :: Email :: Mail Transport Agents',
48 'Topic :: Communications :: Email :: Filters',48 'Topic :: Communications :: Email :: Filters',
49 'Topic :: Security',49 'Topic :: Security',
diff --git a/tests/dkimpy-milter b/tests/dkimpy-milter
index 39b64d5..9ee02e9 100755
--- a/tests/dkimpy-milter
+++ b/tests/dkimpy-milter
@@ -1,2 +1,2 @@
1#!/bin/sh1#!/bin/sh
2python2 -m dkimpy_milter "$@"2python3 -m dkimpy_milter "$@"

Subscribers

People subscribed via source and target branches

to all changes: