From 98e5c178581900d582ad7457cc42cfdefd77635c Mon Sep 17 00:00:00 2001 From: Scott Kitterman Date: Mon, 19 Feb 2018 13:31:28 -0500 Subject: [PATCH] - 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 --- CHANGES | 4 ++++ TODO | 11 ++++++++++- dkimpy_milter/__init__.py | 12 +++++++----- dkimpy_milter/config.py | 31 +++++++++++++++++++++++++------ man/dkimpy-milter.8 | 19 +++++++++---------- man/dkimpy-milter.conf.5 | 4 ++-- 6 files changed, 57 insertions(+), 24 deletions(-) diff --git a/CHANGES b/CHANGES index 77d9132..edfb929 100644 --- a/CHANGES +++ b/CHANGES @@ -12,3 +12,7 @@ - Added systemd unit file and (untested) sysv init file - Added dkim-milter.8 (based on opendim.8) - 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 diff --git a/TODO b/TODO index 0c4b180..b6b9326 100644 --- a/TODO +++ b/TODO @@ -20,8 +20,10 @@ dkimpy-milter.service implemented verified sysv init implemented remove PidFile on stop implemented verified dkimpy-milter.8 provided needs work +Basic dataset (csl) implemented verified +Sign based on Domain implemented verified Canonicalization implemented verified -SyslogFacility implemented +SyslogFacility implemented verified 0.9.5 (Beta) AuthservID @@ -35,6 +37,13 @@ SyslogSuccess Convert dkim-milter-python config No additional features planned +Plannedataset type support: +file:// +refile: +db:/.db +csl: +mdb: + Considered for near-term feature release AlwaysAddARHeader diff --git a/dkimpy_milter/__init__.py b/dkimpy_milter/__init__.py index 3f67986..635809d 100644 --- a/dkimpy_milter/__init__.py +++ b/dkimpy_milter/__init__.py @@ -119,6 +119,7 @@ class dkimMilter(Milter.Base): self.has_dkim += 1 if lname == 'from': fname,self.author = parseaddr(val) + self.fdomain = self.author.split('@')[1] if milterconfig.get('Syslog'): syslog.syslog("{0}: {1}".format(name,val)) elif lname == 'authentication-results': @@ -154,11 +155,11 @@ class dkimMilter(Milter.Base): syslog.syslog('REMOVE: {0}'.format(val)) # Check or sign DKIM 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() self.sign_dkim(txt) 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() self.check_dkim(txt) else: @@ -174,6 +175,7 @@ class dkimMilter(Milter.Base): def sign_dkim(self,txt): canon = milterconfig.get('Canonicalization') + canonicalize = [] if len(canon.split('/')) == 2: canonicalize.append(canon.split('/')[0]) canonicalize.append(canon.split('/')[1]) @@ -183,13 +185,13 @@ class dkimMilter(Milter.Base): syslog.syslog('canonicalize: {0}'.format(canonicalize)) try: 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])) name,val = h.split(': ',1) self.addheader(name,val.strip().replace('\r\n','\n'),0) if privateEd25519: 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') name,val = h.split(': ',1) self.addheader(name,val.strip().replace('\r\n','\n'),0) @@ -259,7 +261,7 @@ def main(): configFile = sys.argv[1] milterconfig = config._processConfigFile(filename = configFile) 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) setExceptHook() pid = write_pid(milterconfig) diff --git a/dkimpy_milter/config.py b/dkimpy_milter/config.py index 9d370f7..fccca4d 100644 --- a/dkimpy_milter/config.py +++ b/dkimpy_milter/config.py @@ -84,6 +84,20 @@ def _find_boolean(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'^(.*)#.*$') def _readConfigFile(path, configData = None, configGlobal = {}): @@ -105,7 +119,7 @@ def _readConfigFile(path, configData = None, configGlobal = {}): 'Socket' : 'str', 'PidFile' : 'str', 'UserID' : 'str', - 'Domain' : 'str', + 'Domain' : 'dataset', 'KeyFile' : 'str', 'KeyFileEd25519' : 'str', 'Selector' : 'str', @@ -138,11 +152,14 @@ def _readConfigFile(path, configData = None, configGlobal = {}): if debugLevel >= 1: syslog.syslog('Configuration item "%s" not defined in file "%s"' % ( line, path )) - else: - syslog.syslog('ERROR parsing line "%s" from file "%s"' - % ( line, path )) - continue - name, value = data + if len(data) == 1: + name = data + value = '' + if len(data) == 2: + name, value = data + if len(data) >= 3: + name = data[0] + value = data[1:] # check validity of name conversion = nameConversion.get(name) @@ -158,6 +175,8 @@ def _readConfigFile(path, configData = None, configGlobal = {}): configData[name] = str(value) elif conversion == 'int': configData[name] = int(value) + elif conversion == 'dataset': + configData[name] = _dataset_to_list(value) else: syslog.syslog(str('name: ' + name + ' value: ' + value + ' conversion: ' + conversion)) configData[name] = conversion(value) diff --git a/man/dkimpy-milter.8 b/man/dkimpy-milter.8 index c771a17..39c9156 100644 --- a/man/dkimpy-milter.8 +++ b/man/dkimpy-milter.8 @@ -152,13 +152,12 @@ a milter-aware MTA. .SH DATA SETS 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, or to a file that contains them, or (if enabled at compile time) a -database containing the data. +values, or to a file that contains them, or a database containing the data. 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 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. 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 are ignored, and the hash ("#") character denotes the start of a comment. If a value contains multiple entries, the entries should be separated by -colons. +colons. [Not implemented yet] .TP .I b) 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 wildcard patterns, matching all text except that the asterisk ("*") character 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 .I c) 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. 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, -the entries should be separated by colons. +the entries should be separated by colons. [Not implemented yet] .TP .I h) 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 compiled in). +is compiled in). [Not implemented yet] .TP .I i) 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 .I j) 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 .I l) 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 .I m) In any other case, the string is presumed to be a comma-separated list. diff --git a/man/dkimpy-milter.conf.5 b/man/dkimpy-milter.conf.5 index d723bc0..2d14075 100644 --- a/man/dkimpy-milter.conf.5 +++ b/man/dkimpy-milter.conf.5 @@ -236,11 +236,11 @@ domains will be verified rather than being signed. This parameter is not required if a .I SigningTable 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 .I KeyTable -is defined. [NOT IMPLEMENTED] +is defined. [KeyTable NOT IMPLEMENTED] .TP .I InternalHosts (dataset)