- Implemented support for Canonicalization option

- Implemented support for SyslogFacility option
 - Initial dataset support: csl
 - Only sign if mail from from a domain in Domain and only if Mode is not
   verfication only
 - Fixed Canonicalize option
This commit is contained in:
Scott Kitterman
2018-02-19 13:31:28 -05:00
parent a71d3b5d99
commit 98e5c17858
6 changed files with 57 additions and 24 deletions
+4
View File
@@ -12,3 +12,7 @@
- Added systemd unit file and (untested) sysv init file - Added systemd unit file and (untested) sysv init file
- Added dkim-milter.8 (based on opendim.8) - Added dkim-milter.8 (based on opendim.8)
- Implemented support for Canonicalization option - Implemented support for Canonicalization option
- Implemented support for SyslogFacility option
- Initial dataset support: csl
- Only sign if mail from from a domain in Domain and only if Mode is not
verfication only
+10 -1
View File
@@ -20,8 +20,10 @@ dkimpy-milter.service implemented verified
sysv init implemented sysv init implemented
remove PidFile on stop implemented verified remove PidFile on stop implemented verified
dkimpy-milter.8 provided needs work dkimpy-milter.8 provided needs work
Basic dataset (csl) implemented verified
Sign based on Domain implemented verified
Canonicalization implemented verified Canonicalization implemented verified
SyslogFacility implemented SyslogFacility implemented verified
0.9.5 (Beta) 0.9.5 (Beta)
AuthservID AuthservID
@@ -35,6 +37,13 @@ SyslogSuccess
Convert dkim-milter-python config Convert dkim-milter-python config
No additional features planned No additional features planned
Plannedataset type support:
file://
refile:
db:/.db
csl:
mdb:
Considered for near-term feature release Considered for near-term feature release
AlwaysAddARHeader AlwaysAddARHeader
+7 -5
View File
@@ -119,6 +119,7 @@ class dkimMilter(Milter.Base):
self.has_dkim += 1 self.has_dkim += 1
if lname == 'from': if lname == 'from':
fname,self.author = parseaddr(val) fname,self.author = parseaddr(val)
self.fdomain = self.author.split('@')[1]
if milterconfig.get('Syslog'): if milterconfig.get('Syslog'):
syslog.syslog("{0}: {1}".format(name,val)) syslog.syslog("{0}: {1}".format(name,val))
elif lname == 'authentication-results': elif lname == 'authentication-results':
@@ -154,11 +155,11 @@ class dkimMilter(Milter.Base):
syslog.syslog('REMOVE: {0}'.format(val)) syslog.syslog('REMOVE: {0}'.format(val))
# Check or sign DKIM # Check or sign DKIM
self.fp.seek(0) self.fp.seek(0)
if self.internal_connection or milterconfig.get('Mode') == 's' or milterconfig.get('Mode') == 'sv': if (self.fdomain in milterconfig.get('Domain')) and (not milterconfig.get('Mode') == 'v'):
txt = self.fp.read() txt = self.fp.read()
self.sign_dkim(txt) self.sign_dkim(txt)
result = None result = None
if self.has_dkim and (milterconfig.get('Mode') == 'v' or milterconfig.get('Mode') == 'sv'): if (self.has_dkim) and (not self.internal_connection) and (milterconfig.get('Mode') == 'v' or milterconfig.get('Mode') == 'sv'):
txt = self.fp.read() txt = self.fp.read()
self.check_dkim(txt) self.check_dkim(txt)
else: else:
@@ -174,6 +175,7 @@ class dkimMilter(Milter.Base):
def sign_dkim(self,txt): def sign_dkim(self,txt):
canon = milterconfig.get('Canonicalization') canon = milterconfig.get('Canonicalization')
canonicalize = []
if len(canon.split('/')) == 2: if len(canon.split('/')) == 2:
canonicalize.append(canon.split('/')[0]) canonicalize.append(canon.split('/')[0])
canonicalize.append(canon.split('/')[1]) canonicalize.append(canon.split('/')[1])
@@ -183,13 +185,13 @@ class dkimMilter(Milter.Base):
syslog.syslog('canonicalize: {0}'.format(canonicalize)) syslog.syslog('canonicalize: {0}'.format(canonicalize))
try: try:
d = dkim.DKIM(txt) d = dkim.DKIM(txt)
h = d.sign(milterconfig.get('Selector'),milterconfig.get('Domain'), privateRSA, h = d.sign(milterconfig.get('Selector'), self.fdomain, privateRSA,
canonicalize=(canonicalize[0], canonicalize[1])) canonicalize=(canonicalize[0], canonicalize[1]))
name,val = h.split(': ',1) name,val = h.split(': ',1)
self.addheader(name,val.strip().replace('\r\n','\n'),0) self.addheader(name,val.strip().replace('\r\n','\n'),0)
if privateEd25519: if privateEd25519:
d = dkim.DKIM(txt) d = dkim.DKIM(txt)
h = d.sign(milterconfig.get('SelectorEd25519'),milterconfig.get('Domain'), privateEd25519, h = d.sign(milterconfig.get('SelectorEd25519'), self.fdomain, privateEd25519,
canonicalize=(canonicalize[0], canonicalize[1]), signature_algorithm='ed25519-sha256') canonicalize=(canonicalize[0], canonicalize[1]), signature_algorithm='ed25519-sha256')
name,val = h.split(': ',1) name,val = h.split(': ',1)
self.addheader(name,val.strip().replace('\r\n','\n'),0) self.addheader(name,val.strip().replace('\r\n','\n'),0)
@@ -259,7 +261,7 @@ def main():
configFile = sys.argv[1] configFile = sys.argv[1]
milterconfig = config._processConfigFile(filename = configFile) milterconfig = config._processConfigFile(filename = configFile)
if milterconfig.get('Syslog'): if milterconfig.get('Syslog'):
facility = "syslog.LOG_{0}".format(milterconfig.get('SyslogFacility').upper()) facility = eval("syslog.LOG_{0}".format(milterconfig.get('SyslogFacility').upper()))
syslog.openlog(os.path.basename(sys.argv[0]), syslog.LOG_PID, facility) syslog.openlog(os.path.basename(sys.argv[0]), syslog.LOG_PID, facility)
setExceptHook() setExceptHook()
pid = write_pid(milterconfig) pid = write_pid(milterconfig)
+24 -5
View File
@@ -84,6 +84,20 @@ def _find_boolean(item):
return item return item
def _dataset_to_list(dataset):
"""Convert a dataset (as defined in dkimpymilter.8) and return a python
list of values."""
if not isinstance(dataset, basestring):
# If it was a csl, it's already a list, we only need to remove the name
# from the first value
if dataset[0][:4] == 'csl:':
dataset[0] = dataset[0][4:]
for item in dataset:
dataset[dataset.index(item)] = item.strip().strip(',')
return dataset
else:
raise dkim.ParameterError('Unimplmented dataset type')
############################################################### ###############################################################
commentRx = re.compile(r'^(.*)#.*$') commentRx = re.compile(r'^(.*)#.*$')
def _readConfigFile(path, configData = None, configGlobal = {}): def _readConfigFile(path, configData = None, configGlobal = {}):
@@ -105,7 +119,7 @@ def _readConfigFile(path, configData = None, configGlobal = {}):
'Socket' : 'str', 'Socket' : 'str',
'PidFile' : 'str', 'PidFile' : 'str',
'UserID' : 'str', 'UserID' : 'str',
'Domain' : 'str', 'Domain' : 'dataset',
'KeyFile' : 'str', 'KeyFile' : 'str',
'KeyFileEd25519' : 'str', 'KeyFileEd25519' : 'str',
'Selector' : 'str', 'Selector' : 'str',
@@ -138,11 +152,14 @@ def _readConfigFile(path, configData = None, configGlobal = {}):
if debugLevel >= 1: if debugLevel >= 1:
syslog.syslog('Configuration item "%s" not defined in file "%s"' syslog.syslog('Configuration item "%s" not defined in file "%s"'
% ( line, path )) % ( line, path ))
else: if len(data) == 1:
syslog.syslog('ERROR parsing line "%s" from file "%s"' name = data
% ( line, path )) value = ''
continue if len(data) == 2:
name, value = data name, value = data
if len(data) >= 3:
name = data[0]
value = data[1:]
# check validity of name # check validity of name
conversion = nameConversion.get(name) conversion = nameConversion.get(name)
@@ -158,6 +175,8 @@ def _readConfigFile(path, configData = None, configGlobal = {}):
configData[name] = str(value) configData[name] = str(value)
elif conversion == 'int': elif conversion == 'int':
configData[name] = int(value) configData[name] = int(value)
elif conversion == 'dataset':
configData[name] = _dataset_to_list(value)
else: else:
syslog.syslog(str('name: ' + name + ' value: ' + value + ' conversion: ' + conversion)) syslog.syslog(str('name: ' + name + ' value: ' + value + ' conversion: ' + conversion))
configData[name] = conversion(value) configData[name] = conversion(value)
+9 -10
View File
@@ -152,13 +152,12 @@ a milter-aware MTA.
.SH DATA SETS .SH DATA SETS
Many of the configuration file parameters will refer to a "dataset" as their Many of the configuration file parameters will refer to a "dataset" as their
values. This refers to a string that either contains the list of desirable values. This refers to a string that either contains the list of desirable
values, or to a file that contains them, or (if enabled at compile time) a values, or to a file that contains them, or a database containing the data.
database containing the data.
Some data sets require that the value contain more than one entry. How this Some data sets require that the value contain more than one entry. How this
is done depends on which data set type is used. Not all these datasets are is done depends on which data set type is used. Not all these datasets are
currently used by dkimp-milter. See currently used by dkimp-milter. See
.B opendkim-milter.conf(5) .B dkimpy-milter.conf(5)
for details about specific options and which dataset types they use. for details about specific options and which dataset types they use.
In particular: In particular:
@@ -170,7 +169,7 @@ one per line. If a line contains whitespace-separated values, then the
line is presumed to define a key and its corresponding value. Blank lines line is presumed to define a key and its corresponding value. Blank lines
are ignored, and the hash ("#") character denotes the start of a comment. are ignored, and the hash ("#") character denotes the start of a comment.
If a value contains multiple entries, the entries should be separated by If a value contains multiple entries, the entries should be separated by
colons. colons. [Not implemented yet]
.TP .TP
.I b) .I b)
If the string begins with "refile:", then the remainder of the string is If the string begins with "refile:", then the remainder of the string is
@@ -180,7 +179,7 @@ to the first whitespace, and the portion after that whitespace is taken as
the value to be used when that pattern is matched. Patterns are simple the value to be used when that pattern is matched. Patterns are simple
wildcard patterns, matching all text except that the asterisk ("*") character wildcard patterns, matching all text except that the asterisk ("*") character
is considered a wildcard. If a value contains multiple entries, the entries is considered a wildcard. If a value contains multiple entries, the entries
should be separated by colons. should be separated by colons. [Not implemented yet]
.TP .TP
.I c) .I c)
If the string begins with "db:" and the program was compiled with If the string begins with "db:" and the program was compiled with
@@ -188,24 +187,24 @@ Sleepycat DB support, then the remainder of the string is presumed to
identify a Sleepycat database containing keys and corresponding values. identify a Sleepycat database containing keys and corresponding values.
These may be used only to test for membership in the data set, or for These may be used only to test for membership in the data set, or for
storing keys and corresponding values. If a value contains multiple entries, storing keys and corresponding values. If a value contains multiple entries,
the entries should be separated by colons. the entries should be separated by colons. [Not implemented yet]
.TP .TP
.I h) .I h)
If the string contains none of these prefixes but ends with ".db", it If the string contains none of these prefixes but ends with ".db", it
is presumed to be a Sleepycat DB as described above (if support for same is presumed to be a Sleepycat DB as described above (if support for same
is compiled in). is compiled in). [Not implemented yet]
.TP .TP
.I i) .I i)
If the string contains none of these prefixes but starts with a slash ("/") If the string contains none of these prefixes but starts with a slash ("/")
character, it is presumed to be a flat file as described above. character, it is presumed to be a flat file as described above. [Not implemented yet]
.TP .TP
.I j) .I j)
If the string begins with "csl:", the string is treated as a comma-separated If the string begins with "csl:", the string is treated as a comma-separated
list as described in m) below. list as described in m) below. [Not implemented yet]
.TP .TP
.I l) .I l)
If the string begins with "mdb:", it refers to a directory that contains If the string begins with "mdb:", it refers to a directory that contains
a memory database, as provided by libmdb from OpenLDAP. a memory database, as provided by libmdb from OpenLDAP. [Not implemented yet]
.TP .TP
.I m) .I m)
In any other case, the string is presumed to be a comma-separated list. In any other case, the string is presumed to be a comma-separated list.
+2 -2
View File
@@ -236,11 +236,11 @@ domains will be verified rather than being signed.
This parameter is not required if a This parameter is not required if a
.I SigningTable .I SigningTable
is in use; in that case, the list of signed domains is implied by the is in use; in that case, the list of signed domains is implied by the
lines in that file. [NOT IMPLEMENTED] lines in that file. [SigningTable NOT IMPLEMENTED]
This parameter is ignored if a This parameter is ignored if a
.I KeyTable .I KeyTable
is defined. [NOT IMPLEMENTED] is defined. [KeyTable NOT IMPLEMENTED]
.TP .TP
.I InternalHosts (dataset) .I InternalHosts (dataset)