- 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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user