Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 539d50325b | |||
| 04dd916ab2 | |||
| 34b2edbb50 | |||
| d117330113 | |||
| 2528632ba6 | |||
| 3ea22f1529 | |||
| 097e053309 | |||
| 419d2b54ea | |||
| 3ff685205c | |||
| 7986de6629 | |||
| 5322c81027 | |||
| 94538ffa6b | |||
| 721da801fe |
@@ -1,4 +1,3 @@
|
|||||||
dist
|
dist
|
||||||
dkimpy_milter.egg-info
|
dkimpy_milter.egg-info
|
||||||
*.pyc
|
*.pyc
|
||||||
*~
|
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
1.0.3 2019-11-22
|
||||||
|
- Make error logging more explicit to aid debugging
|
||||||
|
- Delete own_socketfile to resolve race condition where the permissions
|
||||||
|
change fails on a Unix socket because it hasn't been created yet (libmilter
|
||||||
|
will do this correctly on its own based on umask, the milter doesn't need
|
||||||
|
to do it) (LP: #1849712)
|
||||||
|
|
||||||
|
1.0.2 2019-10-07
|
||||||
|
- Fix startup logging so it provides information at a useful time
|
||||||
|
- Fix message extraction so that signing in the same pass through the milter
|
||||||
|
as verifying works correctly
|
||||||
|
- Fix variable initialization so mailformed mails missing body From do not
|
||||||
|
cause a traceback (LP: #1844161)
|
||||||
|
- Catch more ascii encoding errors to improve resilience against bad data
|
||||||
|
(LP: #1844189)
|
||||||
|
- Fix sysv init so it works (LP: #1839487)
|
||||||
|
|
||||||
1.0.1 2019-02-11
|
1.0.1 2019-02-11
|
||||||
* Reorder milter start and dropping privileges so permissions on Unix socket
|
* Reorder milter start and dropping privileges so permissions on Unix socket
|
||||||
are correct (LP: 1797720)
|
are correct (LP: 1797720)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ an C compiler. Alternately, install these dependencies from dsitribution/OS
|
|||||||
packages and then pip install dkimpy_milter.
|
packages and then pip install dkimpy_milter.
|
||||||
|
|
||||||
The milter will work with either pydns (DNS) or dnspython (dns), preferring
|
The milter will work with either pydns (DNS) or dnspython (dns), preferring
|
||||||
dnspython is both are available. The dkimpy DKIM module also works with
|
dnspython if both are available. The dkimpy DKIM module also works with
|
||||||
either.
|
either.
|
||||||
|
|
||||||
|
|
||||||
@@ -84,9 +84,8 @@ MTA INTEGRATION
|
|||||||
|
|
||||||
Both a systemd unit file and a sysv init file are provided. Both make
|
Both a systemd unit file and a sysv init file are provided. Both make
|
||||||
assumptions about defaults being used, e.g. if a non-standard pidfile name is
|
assumptions about defaults being used, e.g. if a non-standard pidfile name is
|
||||||
used, they will need to be updated. The sysv init file is Debian specific and
|
used, they will need to be updated. The sysv init file uses start-stop-deamon
|
||||||
untested, since the developers are not using sysv init. Feedback/patches
|
from Debian. It is not portable to systems without that available.
|
||||||
welcome.
|
|
||||||
|
|
||||||
The dkimpy-milter drops priviledges after setup to the user/group specified in
|
The dkimpy-milter drops priviledges after setup to the user/group specified in
|
||||||
UserID. During initial setup, this system user needs to be manually created.
|
UserID. During initial setup, this system user needs to be manually created.
|
||||||
|
|||||||
+63
-48
@@ -1,4 +1,4 @@
|
|||||||
#! /usr/bin/python3
|
#! /usr/bin/python2
|
||||||
# Original dkim-milter.py code:
|
# Original dkim-milter.py code:
|
||||||
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
||||||
# Copyright 2007 Business Management Systems, Inc.
|
# Copyright 2007 Business Management Systems, Inc.
|
||||||
@@ -28,16 +28,14 @@ import dkim
|
|||||||
import authres
|
import authres
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import io
|
import StringIO
|
||||||
import re
|
import re
|
||||||
import codecs
|
|
||||||
from Milter.utils import parse_addr, parseaddr
|
from Milter.utils import parse_addr, parseaddr
|
||||||
import dkimpy_milter.config as config
|
import dkimpy_milter.config as config
|
||||||
from dkimpy_milter.util import drop_privileges
|
from dkimpy_milter.util import drop_privileges
|
||||||
from dkimpy_milter.util import setExceptHook
|
from dkimpy_milter.util import setExceptHook
|
||||||
from dkimpy_milter.util import write_pid
|
from dkimpy_milter.util import write_pid
|
||||||
from dkimpy_milter.util import read_keyfile
|
from dkimpy_milter.util import read_keyfile
|
||||||
from dkimpy_milter.util import own_socketfile
|
|
||||||
from dkimpy_milter.util import fold
|
from dkimpy_milter.util import fold
|
||||||
|
|
||||||
__version__ = "1.0.1"
|
__version__ = "1.0.1"
|
||||||
@@ -55,6 +53,7 @@ class dkimMilter(Milter.Base):
|
|||||||
self.privatersa = privateRSA
|
self.privatersa = privateRSA
|
||||||
self.privateed25519 = privateEd25519
|
self.privateed25519 = privateEd25519
|
||||||
self.fp = None
|
self.fp = None
|
||||||
|
self.fdomain = ''
|
||||||
|
|
||||||
@Milter.noreply
|
@Milter.noreply
|
||||||
def connect(self, hostname, unused, hostaddr):
|
def connect(self, hostname, unused, hostaddr):
|
||||||
@@ -62,9 +61,7 @@ class dkimMilter(Milter.Base):
|
|||||||
self.external_connection = False
|
self.external_connection = False
|
||||||
self.hello_name = None
|
self.hello_name = None
|
||||||
# sometimes people put extra space in sendmail config, so we strip
|
# sometimes people put extra space in sendmail config, so we strip
|
||||||
self.receiver = self.getsymval('j')
|
self.receiver = self.getsymval('j').strip()
|
||||||
if self.receiver is not None:
|
|
||||||
self.receiver = self.receiver.strip()
|
|
||||||
try:
|
try:
|
||||||
self.AuthservID = milterconfig['AuthservID']
|
self.AuthservID = milterconfig['AuthservID']
|
||||||
except:
|
except:
|
||||||
@@ -111,7 +108,7 @@ class dkimMilter(Milter.Base):
|
|||||||
def envfrom(self, f, *str):
|
def envfrom(self, f, *str):
|
||||||
if milterconfig.get('Syslog') and milterconfig.get('debugLevel') >= 2:
|
if milterconfig.get('Syslog') and milterconfig.get('debugLevel') >= 2:
|
||||||
syslog.syslog("mail from: {0} {1}".format(f, str))
|
syslog.syslog("mail from: {0} {1}".format(f, str))
|
||||||
self.fp = io.BytesIO()
|
self.fp = StringIO.StringIO()
|
||||||
self.mailfrom = f
|
self.mailfrom = f
|
||||||
t = parse_addr(f)
|
t = parse_addr(f)
|
||||||
if len(t) == 2:
|
if len(t) == 2:
|
||||||
@@ -136,20 +133,24 @@ class dkimMilter(Milter.Base):
|
|||||||
try:
|
try:
|
||||||
self.fdomain = self.author.split('@')[1].lower()
|
self.fdomain = self.author.split('@')[1].lower()
|
||||||
except IndexError as er:
|
except IndexError as er:
|
||||||
self.fdomain = '' # self.author was not a proper email address
|
pass # self.author was not a proper email address
|
||||||
if (milterconfig.get('Syslog') and
|
if (milterconfig.get('Syslog') and
|
||||||
milterconfig.get('debugLevel') >= 1):
|
milterconfig.get('debugLevel') >= 1):
|
||||||
syslog.syslog("{0}: {1}".format(name, val))
|
syslog.syslog("{0}: {1}".format(name, val))
|
||||||
elif lname == 'authentication-results':
|
elif lname == 'authentication-results':
|
||||||
self.arheaders.append(val)
|
self.arheaders.append(val)
|
||||||
if self.fp:
|
if self.fp:
|
||||||
self.fp.write(b"%s: %s\n" % (codecs.encode(name, 'ascii'), codecs.encode(val, 'ascii')))
|
try:
|
||||||
|
self.fp.write("%s: %s\n" % (name, val))
|
||||||
|
except:
|
||||||
|
# Don't choke on header fields with garbage in them.
|
||||||
|
pass
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
@Milter.noreply
|
@Milter.noreply
|
||||||
def eoh(self):
|
def eoh(self):
|
||||||
if self.fp:
|
if self.fp:
|
||||||
self.fp.write(b"\n") # terminate headers
|
self.fp.write("\n") # terminate headers
|
||||||
self.bodysize = 0
|
self.bodysize = 0
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
@@ -177,39 +178,38 @@ class dkimMilter(Milter.Base):
|
|||||||
except:
|
except:
|
||||||
# Don't error out on unparseable AR header fiels
|
# Don't error out on unparseable AR header fiels
|
||||||
pass
|
pass
|
||||||
# Check or sign DKIM
|
# Check and/or sign DKIM
|
||||||
self.fp.seek(0)
|
self.fp.seek(0)
|
||||||
|
txt = self.fp.read()
|
||||||
if milterconfig.get('Domain'):
|
if milterconfig.get('Domain'):
|
||||||
domain = milterconfig.get('Domain')
|
domain = milterconfig.get('Domain')
|
||||||
else:
|
else:
|
||||||
domain = ''
|
domain = ''
|
||||||
if ((self.fdomain in domain) and not milterconfig.get('Mode') == 'v'
|
if ((self.fdomain in domain) and not milterconfig.get('Mode') == 'v'
|
||||||
and not self.external_connection):
|
and not self.external_connection):
|
||||||
txt = self.fp.read()
|
|
||||||
self.sign_dkim(txt)
|
self.sign_dkim(txt)
|
||||||
if ((self.has_dkim) and (not self.internal_connection) and
|
if ((self.has_dkim) and (not self.internal_connection) and
|
||||||
(milterconfig.get('Mode') == 'v' or
|
(milterconfig.get('Mode') == 'v' or
|
||||||
milterconfig.get('Mode') == 'sv')):
|
milterconfig.get('Mode') == 'sv')):
|
||||||
txt = self.fp.read()
|
|
||||||
self.check_dkim(txt)
|
self.check_dkim(txt)
|
||||||
if self.arresults:
|
if self.arresults:
|
||||||
h = authres.AuthenticationResultsHeader(authserv_id=
|
h = authres.AuthenticationResultsHeader(authserv_id=
|
||||||
self.AuthservID,
|
self.AuthservID,
|
||||||
results=self.arresults)
|
results=self.arresults)
|
||||||
h = fold(codecs.encode(str(h), 'ascii'))
|
h = fold(str(h))
|
||||||
if (milterconfig.get('Syslog') and
|
if (milterconfig.get('Syslog') and
|
||||||
milterconfig.get('debugLevel') >= 2):
|
milterconfig.get('debugLevel') >= 2):
|
||||||
syslog.syslog(codecs.decode(h, 'ascii'))
|
syslog.syslog(str(h))
|
||||||
name, val = codecs.decode(h, 'ascii').split(': ', 1)
|
name, val = str(h).split(': ', 1)
|
||||||
self.addheader(name, val, 0)
|
self.addheader(name, val, 0)
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
def sign_dkim(self, txt):
|
def sign_dkim(self, txt):
|
||||||
canon = codecs.encode(milterconfig.get('Canonicalization'), 'ascii')
|
canon = milterconfig.get('Canonicalization')
|
||||||
canonicalize = []
|
canonicalize = []
|
||||||
if len(canon.split(b'/')) == 2:
|
if len(canon.split('/')) == 2:
|
||||||
canonicalize.append(canon.split(b'/')[0])
|
canonicalize.append(canon.split('/')[0])
|
||||||
canonicalize.append(canon.split(b'/')[1])
|
canonicalize.append(canon.split('/')[1])
|
||||||
else:
|
else:
|
||||||
canonicalize.append(canon)
|
canonicalize.append(canon)
|
||||||
canonicalize.append(canon)
|
canonicalize.append(canon)
|
||||||
@@ -219,12 +219,11 @@ class dkimMilter(Milter.Base):
|
|||||||
try:
|
try:
|
||||||
if privateRSA:
|
if privateRSA:
|
||||||
d = dkim.DKIM(txt)
|
d = dkim.DKIM(txt)
|
||||||
h = d.sign(codecs.encode(milterconfig.get('Selector'), 'ascii'), codecs.encode(self.fdomain, 'ascii'),
|
h = d.sign(milterconfig.get('Selector'), self.fdomain,
|
||||||
codecs.encode(privateRSA, 'ascii'),
|
privateRSA, canonicalize=(canonicalize[0],
|
||||||
canonicalize=(canonicalize[0],
|
|
||||||
canonicalize[1]))
|
canonicalize[1]))
|
||||||
name, val = h.split(b': ', 1)
|
name, val = h.split(': ', 1)
|
||||||
self.addheader(codecs.decode(name, 'ascii'), codecs.decode(val, 'ascii').strip().replace('\r\n', '\n'), 0)
|
self.addheader(name, val.strip().replace('\r\n', '\n'), 0)
|
||||||
if (milterconfig.get('Syslog') and
|
if (milterconfig.get('Syslog') and
|
||||||
(milterconfig.get('SyslogSuccess')
|
(milterconfig.get('SyslogSuccess')
|
||||||
or milterconfig.get('debugLevel') >= 1)):
|
or milterconfig.get('debugLevel') >= 1)):
|
||||||
@@ -235,12 +234,12 @@ class dkimMilter(Milter.Base):
|
|||||||
d.domain.lower()))
|
d.domain.lower()))
|
||||||
if privateEd25519:
|
if privateEd25519:
|
||||||
d = dkim.DKIM(txt)
|
d = dkim.DKIM(txt)
|
||||||
h = d.sign(codecs.encode(milterconfig.get('SelectorEd25519'), 'ascii'), codecs.encode(self.fdomain, 'ascii'),
|
h = d.sign(milterconfig.get('SelectorEd25519'), self.fdomain,
|
||||||
privateEd25519, canonicalize=(canonicalize[0],
|
privateEd25519, canonicalize=(canonicalize[0],
|
||||||
canonicalize[1]),
|
canonicalize[1]),
|
||||||
signature_algorithm=b'ed25519-sha256')
|
signature_algorithm='ed25519-sha256')
|
||||||
name, val = h.split(b': ', 1)
|
name, val = h.split(': ', 1)
|
||||||
self.addheader(codecs.decode(name, 'ascii'), codecs.decode(val, 'ascii').strip().replace('\r\n', '\n'), 0)
|
self.addheader(name, val.strip().replace('\r\n', '\n'), 0)
|
||||||
if (milterconfig.get('Syslog') and
|
if (milterconfig.get('Syslog') and
|
||||||
(milterconfig.get('SyslogSuccess')
|
(milterconfig.get('SyslogSuccess')
|
||||||
or milterconfig.get('debugLevel') >= 1)):
|
or milterconfig.get('debugLevel') >= 1)):
|
||||||
@@ -259,26 +258,25 @@ class dkimMilter(Milter.Base):
|
|||||||
|
|
||||||
def check_dkim(self, txt):
|
def check_dkim(self, txt):
|
||||||
res = False
|
res = False
|
||||||
|
self.header_a = None
|
||||||
for y in range(self.has_dkim): # Verify _ALL_ the signatures
|
for y in range(self.has_dkim): # Verify _ALL_ the signatures
|
||||||
d = dkim.DKIM(txt)
|
d = dkim.DKIM(txt)
|
||||||
try:
|
try:
|
||||||
dnsoverride = milterconfig.get('DNSOverride')
|
|
||||||
if isinstance(dnsoverride, str):
|
|
||||||
syslog.syslog("DNSOverride: {0}".format(dnsoverride))
|
|
||||||
res = d.verify(idx=y, dnsfunc=lambda _x: dnsoverride)
|
|
||||||
else:
|
|
||||||
res = d.verify(idx=y)
|
res = d.verify(idx=y)
|
||||||
algo = codecs.decode(d.signature_fields.get(b'a'), 'ascii')
|
|
||||||
if res:
|
if res:
|
||||||
if algo == 'ed25519-sha256':
|
if d.signature_fields.get(b'a') == 'ed25519-sha256':
|
||||||
self.dkim_comment = ('Good {0} signature'
|
self.dkim_comment = ('Good {0} signature'
|
||||||
.format(algo))
|
.format(d.signature_fields
|
||||||
|
.get(b'a')))
|
||||||
else:
|
else:
|
||||||
self.dkim_comment = ('Good {0} bit {1} signature'
|
self.dkim_comment = ('Good {0} bit {1} signature'
|
||||||
.format(d.keysize, algo))
|
.format(d.keysize,
|
||||||
|
d.signature_fields
|
||||||
|
.get(b'a')))
|
||||||
else:
|
else:
|
||||||
self.dkim_comment = ('Bad {0} bit {1} signature.'
|
self.dkim_comment = ('Bad {0} bit {1} signature.'
|
||||||
.format(d.keysize, algo))
|
.format(d.keysize,
|
||||||
|
d.signature_fields.get(b'a')))
|
||||||
except dkim.DKIMException as x:
|
except dkim.DKIMException as x:
|
||||||
self.dkim_comment = str(x)
|
self.dkim_comment = str(x)
|
||||||
if milterconfig.get('Syslog'):
|
if milterconfig.get('Syslog'):
|
||||||
@@ -286,10 +284,21 @@ class dkimMilter(Milter.Base):
|
|||||||
except Exception as x:
|
except Exception as x:
|
||||||
self.dkim_comment = str(x)
|
self.dkim_comment = str(x)
|
||||||
if milterconfig.get('Syslog'):
|
if milterconfig.get('Syslog'):
|
||||||
syslog.syslog("check_dkim: {0}".format(x))
|
syslog.syslog("check_dkim: Internal program fault while verifying: {0}".format(x))
|
||||||
self.header_i = codecs.decode(d.signature_fields.get(b'i'), 'ascii')
|
try:
|
||||||
self.header_d = codecs.decode(d.signature_fields.get(b'd'), 'ascii')
|
self.header_i = d.signature_fields.get(b'i')
|
||||||
self.header_a = codecs.decode(d.signature_fields.get(b'a'), 'ascii')
|
except TypeError as x:
|
||||||
|
self.header_i = None
|
||||||
|
try:
|
||||||
|
self.header_d = d.signature_fields.get(b'd')
|
||||||
|
self.header_a = d.signature_fields.get(b'a')
|
||||||
|
except Exception as x:
|
||||||
|
self.dkim_comment = str(x)
|
||||||
|
if milterconfig.get('Syslog'):
|
||||||
|
syslog.syslog("check_dkim: Internal proram fault extracting header a or d: {0}".format(x))
|
||||||
|
self.header_d = None
|
||||||
|
if not self.header_a:
|
||||||
|
self.header_a = 'rsa-sha256'
|
||||||
if res:
|
if res:
|
||||||
if (milterconfig.get('Syslog') and
|
if (milterconfig.get('Syslog') and
|
||||||
(milterconfig.get('SyslogSuccess') or
|
(milterconfig.get('SyslogSuccess') or
|
||||||
@@ -309,12 +318,18 @@ class dkimMilter(Milter.Base):
|
|||||||
syslog.syslog('DKIM: Fail (saved as {0})'
|
syslog.syslog('DKIM: Fail (saved as {0})'
|
||||||
.format(fname))
|
.format(fname))
|
||||||
else:
|
else:
|
||||||
syslog.syslog('DKIM: Fail ({0})'.format(d.domain.lower()))
|
if milterconfig.get('Syslog'):
|
||||||
|
if d.domain:
|
||||||
|
syslog.syslog('DKIM: Fail ({0})'
|
||||||
|
.format(d.domain.lower()))
|
||||||
|
else:
|
||||||
|
syslog.syslog('DKIM: Fail, unextractable domain')
|
||||||
if res:
|
if res:
|
||||||
result = 'pass'
|
result = 'pass'
|
||||||
else:
|
else:
|
||||||
result = 'fail'
|
result = 'fail'
|
||||||
res = False
|
res = False
|
||||||
|
if self.header_d:
|
||||||
self.arresults.append(
|
self.arresults.append(
|
||||||
authres.DKIMAuthenticationResult(result=result,
|
authres.DKIMAuthenticationResult(result=result,
|
||||||
header_i=self.header_i,
|
header_i=self.header_i,
|
||||||
@@ -323,6 +338,7 @@ class dkimMilter(Milter.Base):
|
|||||||
result_comment=
|
result_comment=
|
||||||
self.dkim_comment)
|
self.dkim_comment)
|
||||||
)
|
)
|
||||||
|
self.header_a = None
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -354,13 +370,12 @@ def main():
|
|||||||
Milter.set_flags(Milter.CHGHDRS + Milter.ADDHDRS)
|
Milter.set_flags(Milter.CHGHDRS + Milter.ADDHDRS)
|
||||||
miltername = 'dkimpy-filter'
|
miltername = 'dkimpy-filter'
|
||||||
socketname = milterconfig.get('Socket')
|
socketname = milterconfig.get('Socket')
|
||||||
own_socketfile(milterconfig)
|
|
||||||
drop_privileges(milterconfig)
|
drop_privileges(milterconfig)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
Milter.runmilter(miltername, socketname, 240)
|
|
||||||
if milterconfig.get('Syslog'):
|
if milterconfig.get('Syslog'):
|
||||||
syslog.syslog('dkimpy-milter started:{0} user:{1}'
|
syslog.syslog('dkimpy-milter starting:{0} user:{1}'
|
||||||
.format(pid, milterconfig.get('UserID')))
|
.format(pid, milterconfig.get('UserID')))
|
||||||
|
Milter.runmilter(miltername, socketname, 240)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
from dkimpy_milter import main
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
+11
-16
@@ -31,13 +31,13 @@ import stat
|
|||||||
import dkim
|
import dkim
|
||||||
import socket
|
import socket
|
||||||
import ipaddress
|
import ipaddress
|
||||||
from .dnsplug import Session
|
from dnsplug import Session
|
||||||
|
|
||||||
# default values
|
# default values
|
||||||
defaultConfigData = {
|
defaultConfigData = {
|
||||||
'Syslog': 'yes',
|
'Syslog': 'yes',
|
||||||
'SyslogFacility': 'mail',
|
'SyslogFacility': 'mail',
|
||||||
'UMask': 0o07,
|
'UMask': 007,
|
||||||
'Mode': 'sv',
|
'Mode': 'sv',
|
||||||
'Socket': 'local:/var/run/dkimpy-milter/dkimpy-milter.sock',
|
'Socket': 'local:/var/run/dkimpy-milter/dkimpy-milter.sock',
|
||||||
'PidFile': '/var/run/dkimpy-milter/dkimpy-milter.pid',
|
'PidFile': '/var/run/dkimpy-milter/dkimpy-milter.pid',
|
||||||
@@ -48,7 +48,6 @@ defaultConfigData = {
|
|||||||
'DiagnosticDirectory': '',
|
'DiagnosticDirectory': '',
|
||||||
'MacroList': '',
|
'MacroList': '',
|
||||||
'MacroListVerify': '',
|
'MacroListVerify': '',
|
||||||
'DNSOverride': None,
|
|
||||||
'debugLevel': 0 # Undocumented config item for developer use
|
'debugLevel': 0 # Undocumented config item for developer use
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,14 +84,14 @@ class HostsDataset(object):
|
|||||||
self.item = item[1:]
|
self.item = item[1:]
|
||||||
self.negative = True
|
self.negative = True
|
||||||
try:
|
try:
|
||||||
self.item = ipaddress.ip_address(str(self.item, "utf-8"))
|
self.item = ipaddress.ip_address(unicode(self.item, "utf-8"))
|
||||||
if isinstance(self.item, ipaddress.IPv4Address):
|
if isinstance(self.item, ipaddress.IPv4Address):
|
||||||
self.isipv4 = True
|
self.isipv4 = True
|
||||||
elif isinstance(self.item, ipaddress.IPv6Address):
|
elif isinstance(self.item, ipaddress.IPv6Address):
|
||||||
self.isipv6 = True
|
self.isipv6 = True
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
try:
|
try:
|
||||||
self.item = ipaddress.ip_network(str
|
self.item = ipaddress.ip_network(unicode
|
||||||
(self.item, "utf-8"),
|
(self.item, "utf-8"),
|
||||||
strict=False)
|
strict=False)
|
||||||
if isinstance(self.item, ipaddress.IPv4Network):
|
if isinstance(self.item, ipaddress.IPv4Network):
|
||||||
@@ -110,7 +109,7 @@ class HostsDataset(object):
|
|||||||
|
|
||||||
def match(self, connectip):
|
def match(self, connectip):
|
||||||
'''Check if the connect IP is part of the dataset'''
|
'''Check if the connect IP is part of the dataset'''
|
||||||
source = ipaddress.ip_address(str(connectip, "utf-8"))
|
source = ipaddress.ip_address(unicode(connectip, "utf-8"))
|
||||||
for item in self.dataset:
|
for item in self.dataset:
|
||||||
if item.isdomain or item.ishostname:
|
if item.isdomain or item.ishostname:
|
||||||
result = self.matchname(source) # Match host/domains first
|
result = self.matchname(source) # Match host/domains first
|
||||||
@@ -160,13 +159,13 @@ class HostsDataset(object):
|
|||||||
if isinstance(source, ipaddress.IPv4Address):
|
if isinstance(source, ipaddress.IPv4Address):
|
||||||
ips = s.dns(name, 'A')
|
ips = s.dns(name, 'A')
|
||||||
for ip in ips:
|
for ip in ips:
|
||||||
ip = ipaddress.IPv4Address(str(ip, 'UTF-8'))
|
ip = ipaddress.IPv4Address(unicode(ip, 'UTF-8'))
|
||||||
if ip == source:
|
if ip == source:
|
||||||
results.append(name)
|
results.append(name)
|
||||||
if isinstance(source, ipaddress.IPv6Address):
|
if isinstance(source, ipaddress.IPv6Address):
|
||||||
ips = s.dns(name, 'AAAA')
|
ips = s.dns(name, 'AAAA')
|
||||||
for ip in ips:
|
for ip in ips:
|
||||||
ip = ipaddress.IPv6Address(str(ip, 'UTF-8'))
|
ip = ipaddress.IPv6Address(unicode(ip, 'UTF-8'))
|
||||||
if ip == source:
|
if ip == source:
|
||||||
results.append(name)
|
results.append(name)
|
||||||
return results
|
return results
|
||||||
@@ -225,13 +224,13 @@ def _processConfigFile(filename=None, configdata=None, useSyslog=1,
|
|||||||
'''Load the specified config file, exit and log errors if it fails,
|
'''Load the specified config file, exit and log errors if it fails,
|
||||||
otherwise return a config dictionary.'''
|
otherwise return a config dictionary.'''
|
||||||
|
|
||||||
from . import config
|
import config
|
||||||
if configdata is None:
|
if configdata is None:
|
||||||
configdata = config.defaultConfigData
|
configdata = config.defaultConfigData
|
||||||
if filename is not None:
|
if filename is not None:
|
||||||
try:
|
try:
|
||||||
_readConfigFile(filename, configdata)
|
_readConfigFile(filename, configdata)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
raise
|
raise
|
||||||
if useSyslog:
|
if useSyslog:
|
||||||
syslog.syslog(e.args[0])
|
syslog.syslog(e.args[0])
|
||||||
@@ -335,14 +334,13 @@ def _readConfigFile(path, configData=None, configGlobal={}):
|
|||||||
'DiagnosticDirectory': 'str',
|
'DiagnosticDirectory': 'str',
|
||||||
'MacroList': 'dataset',
|
'MacroList': 'dataset',
|
||||||
'MacroListVerify': 'dataset',
|
'MacroListVerify': 'dataset',
|
||||||
'DNSOverride': 'str',
|
|
||||||
'debugLevel': 'int'
|
'debugLevel': 'int'
|
||||||
}
|
}
|
||||||
|
|
||||||
# check to see if it's a file
|
# check to see if it's a file
|
||||||
try:
|
try:
|
||||||
mode = os.stat(path)[0]
|
mode = os.stat(path)[0]
|
||||||
except OSError as e:
|
except OSError, e:
|
||||||
syslog.syslog(syslog.LOG_ERR, 'ERROR stating "%s": %s'
|
syslog.syslog(syslog.LOG_ERR, 'ERROR stating "%s": %s'
|
||||||
% (path, e.strerror))
|
% (path, e.strerror))
|
||||||
return(configData)
|
return(configData)
|
||||||
@@ -390,9 +388,6 @@ def _readConfigFile(path, configData=None, configGlobal={}):
|
|||||||
if conversion == 'bool':
|
if conversion == 'bool':
|
||||||
configData[name] = _find_boolean(value)
|
configData[name] = _find_boolean(value)
|
||||||
elif conversion == 'str':
|
elif conversion == 'str':
|
||||||
if isinstance(value, list):
|
|
||||||
configData[name] = line.split(None, 1)[1]
|
|
||||||
else:
|
|
||||||
configData[name] = str(value)
|
configData[name] = str(value)
|
||||||
elif conversion == 'int':
|
elif conversion == 'int':
|
||||||
configData[name] = int(value)
|
configData[name] = int(value)
|
||||||
@@ -404,7 +399,7 @@ def _readConfigFile(path, configData=None, configGlobal={}):
|
|||||||
configData[name] = conversion(value)
|
configData[name] = conversion(value)
|
||||||
fp.close()
|
fp.close()
|
||||||
try:
|
try:
|
||||||
configData['AuthservID'] = _make_authserv_id(configData.get('AuthservID', 'HOSTNAME'))
|
configData['AuthservID'] = _make_authserv_id(configData['AuthservID'])
|
||||||
configData['IntHosts'] = HostsDataset(configData['InternalHosts'])
|
configData['IntHosts'] = HostsDataset(configData['InternalHosts'])
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class Session(object):
|
|||||||
raise DNSError('Length of CNAME chain exceeds %d' % MAX_CNAME)
|
raise DNSError('Length of CNAME chain exceeds %d' % MAX_CNAME)
|
||||||
cnames[name] = cname
|
cnames[name] = cname
|
||||||
if cname in cnames:
|
if cname in cnames:
|
||||||
raise DNSError('CNAME loop')
|
raise DNSError, 'CNAME loop'
|
||||||
result = self.dns(cname, qtype, cnames=cnames)
|
result = self.dns(cname, qtype, cnames=cnames)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -103,16 +103,16 @@ def DNSLookup_pydns(name, qtype, tcpfallback=True, timeout=30):
|
|||||||
#
|
#
|
||||||
if resp.header['tc'] == True:
|
if resp.header['tc'] == True:
|
||||||
if not tcpfallback:
|
if not tcpfallback:
|
||||||
raise DNS.DNSError('DNS: Truncated UDP Reply, SPF records should fit in a UDP packet')
|
raise DNS.DNSError, 'DNS: Truncated UDP Reply, SPF records should fit in a UDP packet'
|
||||||
try:
|
try:
|
||||||
req = DNS.DnsRequest(name, qtype=qtype, protocol='tcp',
|
req = DNS.DnsRequest(name, qtype=qtype, protocol='tcp',
|
||||||
timeout=timeout)
|
timeout=timeout)
|
||||||
resp = req.req()
|
resp = req.req()
|
||||||
except DNS.DNSError as x:
|
except DNS.DNSError, x:
|
||||||
raise DNS.DNSError('TCP Fallback error: ' + str(x))
|
raise DNS.DNSError, 'TCP Fallback error: ' + str(x)
|
||||||
return [((a['name'], a['typename']), a['data']) for a in resp.answers]
|
return [((a['name'], a['typename']), a['data']) for a in resp.answers]
|
||||||
except IOError as x:
|
except IOError, x:
|
||||||
raise DNS.DNSError('DNS: ' + str(x))
|
raise DNS.DNSError, 'DNS: ' + str(x)
|
||||||
|
|
||||||
def DNSLookup_dnspython(name,qtype,tcpfallback=True,timeout=30):
|
def DNSLookup_dnspython(name,qtype,tcpfallback=True,timeout=30):
|
||||||
retVal = []
|
retVal = []
|
||||||
@@ -164,5 +164,5 @@ if __name__ == '__main__':
|
|||||||
import sys
|
import sys
|
||||||
s = Session()
|
s = Session()
|
||||||
for n,t in zip(*[iter(sys.argv[1:])]*2):
|
for n,t in zip(*[iter(sys.argv[1:])]*2):
|
||||||
print(n,t)
|
print n,t
|
||||||
print(s.dns(n,t))
|
print s.dns(n,t)
|
||||||
|
|||||||
@@ -146,24 +146,6 @@ def write_pid(milterconfig):
|
|||||||
return pid
|
return pid
|
||||||
|
|
||||||
|
|
||||||
def own_socketfile(milterconfig):
|
|
||||||
"""If socket is Unix socket, chown to UserID before dropping privileges"""
|
|
||||||
import os
|
|
||||||
user, group = user_group(milterconfig.get('UserID'))
|
|
||||||
offset = None
|
|
||||||
sockname = milterconfig.get('Socket')
|
|
||||||
if sockname[:1] == '/':
|
|
||||||
offset = 0
|
|
||||||
elif sockname[:6] == "local:":
|
|
||||||
offset = 6
|
|
||||||
elif sockname[:5] == "unix:":
|
|
||||||
offset = 5
|
|
||||||
|
|
||||||
if offset is not None:
|
|
||||||
if os.path.exists(sockname[offset:]):
|
|
||||||
os.chown(sockname[offset:], user, group)
|
|
||||||
|
|
||||||
|
|
||||||
def read_keyfile(milterconfig, keytype):
|
def read_keyfile(milterconfig, keytype):
|
||||||
"""Read private key from file."""
|
"""Read private key from file."""
|
||||||
import syslog
|
import syslog
|
||||||
|
|||||||
@@ -311,13 +311,6 @@ be set:
|
|||||||
(b) KeyTable, SigningTable, no Domain, no KeyFile, no Selector;
|
(b) KeyTable, SigningTable, no Domain, no KeyFile, no Selector;
|
||||||
[fooTable options NOT IMPLEMENTED]
|
[fooTable options NOT IMPLEMENTED]
|
||||||
|
|
||||||
.TP
|
|
||||||
.I DNSOverride (string)
|
|
||||||
Provide a text string that a verifying milter should use instead of
|
|
||||||
consulting the DNS on each message. This is useful primarily for
|
|
||||||
testing purposes in environments where it is awkward to modify the
|
|
||||||
system DNS resolution. It should not be used in production.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.I PeerList (dataset)
|
.I PeerList (dataset)
|
||||||
Identifies a set of "peers" that identifies clients whose connections
|
Identifies a set of "peers" that identifies clients whose connections
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#! /usr/bin/python3
|
#! /usr/bin/python
|
||||||
# dkimpy-milter: A DKIM signing/verification Milter application
|
# dkimpy-milter: A DKIM signing/verification Milter application
|
||||||
# Author: Scott Kitterman <scott@kitterman.com>
|
# Author: Scott Kitterman <scott@kitterman.com>
|
||||||
# Copyright 2018 Scott Kitterman
|
# Copyright 2018 Scott Kitterman
|
||||||
@@ -23,14 +23,14 @@ description = "Domain Keys Identified Mail (DKIM) signing/verifying milter for P
|
|||||||
|
|
||||||
kw = {} # Work-around for lack of 'or' requires in setuptools.
|
kw = {} # Work-around for lack of 'or' requires in setuptools.
|
||||||
try:
|
try:
|
||||||
import dns
|
import DNS
|
||||||
kw['install_requires'] = ['dkimpy>=0.7', 'pymilter', 'authres>=1.1.0', 'PyNaCl', 'dnspython']
|
kw['install_requires'] = ['dkimpy>=0.7', 'pymilter', 'authres>=1.1.0', 'PyNaCl', 'ipaddress', 'PyDNS']
|
||||||
except ImportError: # If PyDNS is not installed, prefer dnspython
|
except ImportError: # If PyDNS is not installed, prefer dnspython
|
||||||
kw['install_requires'] = ['dkimpy>=0.7', 'pymilter', 'authres>=1.1.0', 'PyNaCl', 'PyDNS']
|
kw['install_requires'] = ['dkimpy>=0.7', 'pymilter', 'authres>=1.1.0', 'PyNaCl', 'ipaddress', 'dnspython']
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='dkimpy-milter',
|
name='dkimpy-milter',
|
||||||
version='1.0.1',
|
version='1.0.3',
|
||||||
author='Scott Kitterman',
|
author='Scott Kitterman',
|
||||||
author_email='scott@kitterman.com',
|
author_email='scott@kitterman.com',
|
||||||
url='https://launchpad.net/dkimpy-milter',
|
url='https://launchpad.net/dkimpy-milter',
|
||||||
@@ -43,7 +43,7 @@ setup(
|
|||||||
'License :: OSI Approved :: GNU General Public License (GPL)',
|
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||||
'Natural Language :: English',
|
'Natural Language :: English',
|
||||||
'Operating System :: POSIX',
|
'Operating System :: POSIX',
|
||||||
'Programming Language :: Python :: 3 :: Only',
|
'Programming Language :: Python :: 2 :: Only',
|
||||||
'Topic :: Communications :: Email :: Mail Transport Agents',
|
'Topic :: Communications :: Email :: Mail Transport Agents',
|
||||||
'Topic :: Communications :: Email :: Filters',
|
'Topic :: Communications :: Email :: Filters',
|
||||||
'Topic :: Security',
|
'Topic :: Security',
|
||||||
|
|||||||
@@ -20,9 +20,9 @@
|
|||||||
### END INIT INFO
|
### END INIT INFO
|
||||||
prefix="/usr/local"
|
prefix="/usr/local"
|
||||||
exec_prefix=${prefix}
|
exec_prefix=${prefix}
|
||||||
sysconfdir="/etc/dkimpy-milter"
|
sysconfdir="/usr/local/etc"
|
||||||
bindir="${exec_prefix}/bin/"
|
bindir="${exec_prefix}/bin/"
|
||||||
RUNDIR="/var/run/dkimpy-milter"
|
RUNDIR="/run/dkimpy-milter"
|
||||||
DAEMON=${bindir}/dkimpy-milter
|
DAEMON=${bindir}/dkimpy-milter
|
||||||
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:
|
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:
|
||||||
NAME=dkimpy-milter
|
NAME=dkimpy-milter
|
||||||
@@ -67,14 +67,14 @@ case "$1" in
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
start-stop-daemon --start --background --quiet --pidfile \
|
||||||
start-stop-daemon --start --quiet --pidfile $RUNDIR/$NAME.pid --startas \
|
$RUNDIR/$NAME.pid --exec $DAEMON $sysconfdir/$NAME.conf
|
||||||
$DAEMON $sysconfdir/$NAME.conf --name $NAME --test > /dev/null \
|
|
||||||
echo "$NAME."
|
echo "$NAME."
|
||||||
;;
|
;;
|
||||||
stop)
|
stop)
|
||||||
echo -n "Stopping $DESC: "
|
echo -n "Stopping $DESC: "
|
||||||
if [ -f $RUNDIR/$NAME.pid ]; then
|
if [ -f $RUNDIR/$NAME.pid ]; then
|
||||||
|
chown root:root $RUNDIR/$NAME.pid
|
||||||
start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid
|
start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid
|
||||||
rm $RUNDIR/$NAME.pid
|
rm $RUNDIR/$NAME.pid
|
||||||
#echo $SOCKET
|
#echo $SOCKET
|
||||||
@@ -87,6 +87,7 @@ case "$1" in
|
|||||||
force-reload)
|
force-reload)
|
||||||
echo -n "Force reloading $DESC: "
|
echo -n "Force reloading $DESC: "
|
||||||
if [ -f $RUNDIR/$NAME.pid ]; then
|
if [ -f $RUNDIR/$NAME.pid ]; then
|
||||||
|
chown root:root $RUNDIR/$NAME.pid
|
||||||
start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid
|
start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid
|
||||||
rm $RUNDIR/$NAME.pid
|
rm $RUNDIR/$NAME.pid
|
||||||
#echo $SOCKET
|
#echo $SOCKET
|
||||||
@@ -95,7 +96,7 @@ case "$1" in
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
sleep 1
|
sleep 1
|
||||||
start-stop-daemon --start --chuid $USER --background --quiet --pidfile \
|
start-stop-daemon --start --background --quiet --pidfile \
|
||||||
$RUNDIR/$NAME.pid --exec $DAEMON $sysconfdir/$NAME.conf
|
$RUNDIR/$NAME.pid --exec $DAEMON $sysconfdir/$NAME.conf
|
||||||
echo "$NAME."
|
echo "$NAME."
|
||||||
;;
|
;;
|
||||||
@@ -103,6 +104,7 @@ case "$1" in
|
|||||||
echo "Restarting $DESC: "
|
echo "Restarting $DESC: "
|
||||||
echo -n "Stopping $DESC: "
|
echo -n "Stopping $DESC: "
|
||||||
if [ -f $RUNDIR/$NAME.pid ]; then
|
if [ -f $RUNDIR/$NAME.pid ]; then
|
||||||
|
chown root:root $RUNDIR/$NAME.pid
|
||||||
start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid
|
start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid
|
||||||
rm $RUNDIR/$NAME.pid
|
rm $RUNDIR/$NAME.pid
|
||||||
#echo $SOCKET
|
#echo $SOCKET
|
||||||
@@ -113,7 +115,7 @@ case "$1" in
|
|||||||
echo "$NAME."
|
echo "$NAME."
|
||||||
sleep 1
|
sleep 1
|
||||||
echo -n "Starting $DESC: "
|
echo -n "Starting $DESC: "
|
||||||
start-stop-daemon --start --chuid $USER --background --quiet --pidfile \
|
start-stop-daemon --start --background --quiet --pidfile \
|
||||||
$RUNDIR/$NAME.pid --exec $DAEMON $sysconfdir/$NAME.conf
|
$RUNDIR/$NAME.pid --exec $DAEMON $sysconfdir/$NAME.conf
|
||||||
echo "$NAME."
|
echo "$NAME."
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
-- -*- lua -*-
|
|
||||||
for _, keytype in ipairs({"ed25519", "rsa"}) do
|
|
||||||
for _, func in ipairs({"signing", "verify"}) do
|
|
||||||
mt.echo("testing "..keytype.." "..func)
|
|
||||||
conn = mt.connect("unix:"..keytype.."."..func..".sock")
|
|
||||||
if conn == nil then
|
|
||||||
error("mt.connect() failed "..keytype.." "..func)
|
|
||||||
end
|
|
||||||
mt.disconnect(conn)
|
|
||||||
mt.echo(keytype.." "..func.." complete")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
-- -*- lua -*-
|
|
||||||
for _, keytype in ipairs({"ed25519", "rsa"}) do
|
|
||||||
for _, func in ipairs({"signing", "verify"}) do
|
|
||||||
mt.echo("testing "..keytype.." "..func)
|
|
||||||
conn = mt.connect("unix:"..keytype.."."..func..".sock")
|
|
||||||
if conn == nil then
|
|
||||||
error("mt.connect() failed "..keytype.." "..func)
|
|
||||||
end
|
|
||||||
if mt.conninfo(conn, "localhost", "127.0.0.1") ~= nil then
|
|
||||||
error("mt.conninfo() failed "..keytype.." "..func)
|
|
||||||
end
|
|
||||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
|
||||||
error("mt.conninfo() unexpected reply "..keytype.." "..func)
|
|
||||||
end
|
|
||||||
|
|
||||||
if mt.test_action(conn, SMFIF_ADDHDRS) then
|
|
||||||
print("could add headers "..keytype.." "..func)
|
|
||||||
else
|
|
||||||
error("mt.test_action() says could not add headers "..keytype.." "..func)
|
|
||||||
end
|
|
||||||
|
|
||||||
if mt.test_action(conn, SMFIF_CHGHDRS) then
|
|
||||||
print("could change headers "..keytype.." "..func)
|
|
||||||
else
|
|
||||||
error("mt.test_action() says could not change headers "..keytype.." "..func)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- -- FIXME: this part of the test fails, as apparently the
|
|
||||||
-- -- dkimpy-milter claims the right to change the body of a message,
|
|
||||||
-- -- even though it shouldn't. How can we fix the negotiation?
|
|
||||||
-- if mt.test_action(conn, SMFIF_CHGBODY) then
|
|
||||||
-- error("mt.test_action() says could change body "..keytype.." "..func)
|
|
||||||
-- else
|
|
||||||
-- print("could not change body "..keytype.." "..func)
|
|
||||||
-- end
|
|
||||||
|
|
||||||
mt.disconnect(conn)
|
|
||||||
mt.echo(keytype.." "..func.." test complete")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
-- -*- lua -*-
|
|
||||||
|
|
||||||
msg = {
|
|
||||||
['headers'] = {
|
|
||||||
['From'] = 'Alice <alice@example.net>',
|
|
||||||
['Message-Id'] = '<dkimpy-milter-test-02@example.net>',
|
|
||||||
['To'] = 'Bob <bob@example.biz>',
|
|
||||||
['Date'] = 'Mon, 18 Feb 2019 08:32:50 -0500',
|
|
||||||
['Subject'] = 'Signing test',
|
|
||||||
['Content-Type'] = 'text/plain',
|
|
||||||
},
|
|
||||||
['body'] = "This is a test!\r\n",
|
|
||||||
}
|
|
||||||
|
|
||||||
-- returns miltertest connection object
|
|
||||||
function connect_and_send (sockname, headers, body)
|
|
||||||
conn = mt.connect(sockname)
|
|
||||||
if conn == nil then
|
|
||||||
error "mt.connect() failed"
|
|
||||||
end
|
|
||||||
if mt.conninfo(conn, "localhost", "127.0.0.1") ~= nil then
|
|
||||||
error "mt.conninfo() failed"
|
|
||||||
end
|
|
||||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
|
||||||
error "mt.conninfo() unexpected reply"
|
|
||||||
end
|
|
||||||
|
|
||||||
-- mt.macro(conn, SMFIC_MAIL, "i", "simple-message")
|
|
||||||
if mt.mailfrom(conn, "<alice@example.net>") ~= nil then
|
|
||||||
error "mt.mailfrom() failed"
|
|
||||||
end
|
|
||||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
|
||||||
error "mt.mailfrom() unexpected reply"
|
|
||||||
end
|
|
||||||
-- mt.rcptto() is called implicitly
|
|
||||||
|
|
||||||
-- send headers
|
|
||||||
for key,value in pairs(headers) do
|
|
||||||
if mt.header(conn, key, value) ~= nil then
|
|
||||||
error("mt.header(" .. key .. ") failed")
|
|
||||||
end
|
|
||||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
|
||||||
error("mt.header(" .. key .. ") unexpected reply")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- send EOH
|
|
||||||
if mt.eoh(conn) ~= nil then
|
|
||||||
error "mt.eoh() failed"
|
|
||||||
end
|
|
||||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
|
||||||
error "mt.eoh() unexpected reply"
|
|
||||||
end
|
|
||||||
|
|
||||||
-- send body
|
|
||||||
if mt.bodystring(conn, body) ~= nil then
|
|
||||||
error "mt.bodystring() failed"
|
|
||||||
end
|
|
||||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
|
||||||
error "mt.bodystring() unexpected reply"
|
|
||||||
end
|
|
||||||
-- end of message; let the filter react
|
|
||||||
if mt.eom(conn) ~= nil then
|
|
||||||
error "mt.eom() failed"
|
|
||||||
end
|
|
||||||
reply = mt.getreply(conn)
|
|
||||||
if reply ~= SMFIR_CONTINUE then
|
|
||||||
error ("mt.eom() unexpected reply: " .. reply)
|
|
||||||
end
|
|
||||||
return conn
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, keytype in ipairs({"ed25519", "rsa"}) do
|
|
||||||
mt.echo("testing "..keytype)
|
|
||||||
signing = connect_and_send("unix:"..keytype..".signing.sock", msg.headers, msg.body)
|
|
||||||
-- verify that a test header field got added
|
|
||||||
if not mt.eom_check(signing, MT_HDRINSERT) then
|
|
||||||
error "no header added by signer"
|
|
||||||
end
|
|
||||||
|
|
||||||
signature = mt.getheader(signing, "DKIM-Signature", 0)
|
|
||||||
|
|
||||||
mt.disconnect(signing)
|
|
||||||
|
|
||||||
mt.echo("DKIM-Signature: " .. signature)
|
|
||||||
|
|
||||||
msg.headers['DKIM-Signature'] = signature
|
|
||||||
|
|
||||||
verify = connect_and_send("unix:"..keytype..".verify.sock", msg.headers, msg.body)
|
|
||||||
|
|
||||||
if not mt.eom_check(verify, MT_HDRINSERT) then
|
|
||||||
error "no header added in verify"
|
|
||||||
end
|
|
||||||
|
|
||||||
authres = mt.getheader(verify, "Authentication-Results", 0)
|
|
||||||
mt.echo("Authentication-Results: "..authres)
|
|
||||||
|
|
||||||
mt.disconnect(verify)
|
|
||||||
|
|
||||||
mt.echo(keytype.." complete")
|
|
||||||
end
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
python3 -m dkimpy_milter "$@"
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
WORKDIR=$(mktemp -d)
|
|
||||||
TESTDIR=$(realpath "$(dirname "$0")")
|
|
||||||
DKIMPY_MILTER=${DKIMPY_MILTER:-"$TESTDIR/dkimpy-milter"}
|
|
||||||
KEY_TYPES=(ed25519 rsa)
|
|
||||||
|
|
||||||
cd "$WORKDIR"
|
|
||||||
|
|
||||||
printf "Testing %s from directory %s\n" "$DKIMPY_MILTER" "$WORKDIR"
|
|
||||||
|
|
||||||
for keytype in "${KEY_TYPES[@]}"; do
|
|
||||||
dknewkey --ktype "$keytype" "testkey.$keytype"
|
|
||||||
if [ "$keytype" = ed25519 ]; then
|
|
||||||
keyfile=KeyFileEd25519
|
|
||||||
selector=SelectorEd25519
|
|
||||||
else
|
|
||||||
keyfile=KeyFile
|
|
||||||
selector=Selector
|
|
||||||
fi
|
|
||||||
cat > "$keytype.signing.conf" <<EOF
|
|
||||||
Domain example.net
|
|
||||||
$keyfile testkey.$keytype.key
|
|
||||||
$selector testkey
|
|
||||||
Socket unix:$keytype.signing.sock
|
|
||||||
PidFile $keytype.signing.pid
|
|
||||||
Mode s
|
|
||||||
UserID $(id --name --user):$(id --name --group)
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat > "$keytype.verify.conf" <<EOF
|
|
||||||
Socket unix:$keytype.verify.sock
|
|
||||||
PidFile $keytype.verify.pid
|
|
||||||
Mode v
|
|
||||||
DNSOverride $(cat testkey.$keytype.dns)
|
|
||||||
UserID $(id --name --user):$(id --name --group)
|
|
||||||
EOF
|
|
||||||
done
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
echo cleaning up jobs:
|
|
||||||
jobs
|
|
||||||
for keytype in "${KEY_TYPES[@]}"; do
|
|
||||||
for func in signing verify; do
|
|
||||||
if [ -s "$keytype.$func.pid" ] && kill -0 "$(cat "$keytype.$func.pid")"; then
|
|
||||||
kill "$(cat $keytype.$func.pid)"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
done
|
|
||||||
wait
|
|
||||||
for keytype in "${KEY_TYPES[@]}"; do
|
|
||||||
for func in signing verify; do
|
|
||||||
errdata="$keytype.$func.stderr"
|
|
||||||
if [ -s "$errdata" ]; then
|
|
||||||
printf -- "-> %s:\n" "$errdata"
|
|
||||||
cat "$errdata"
|
|
||||||
printf -- "-> end %s\n" "$errdata"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
done
|
|
||||||
rm -rf "$WORKDIR"
|
|
||||||
}
|
|
||||||
|
|
||||||
for keytype in "${KEY_TYPES[@]}"; do
|
|
||||||
for func in signing verify; do
|
|
||||||
PYTHONPATH="$(dirname "$TESTDIR")" "$DKIMPY_MILTER" "$keytype.$func.conf" 2>"$keytype.$func.stderr" &
|
|
||||||
done
|
|
||||||
done
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
# ugly ugly (how are we supposed to know that the milters are all ready?):
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
# uses miltertest from opendkim:
|
|
||||||
for x in ${TESTS:-"$TESTDIR"/*.miltertest}; do
|
|
||||||
if ! [ -e "$x" ]; then
|
|
||||||
if [ -e "$TESTDIR/$x" ]; then
|
|
||||||
x="$TESTDIR/$x"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
printf -- "-> running %s...\n" "$x"
|
|
||||||
miltertest -s "$x"
|
|
||||||
done
|
|
||||||
Reference in New Issue
Block a user