Compare commits

...

19 Commits

Author SHA1 Message Date
Scott Kitterman 5e2cff5e5d - Fold added authres header fields
- Fix pidfile permissions
 - Fix socket setup sequence so Unix sockets work
2018-03-02 16:14:46 -05:00
Scott Kitterman 5886edda42 Fixup file dataset support 2018-03-02 15:04:19 -05:00
Scott Kitterman e4a17d7be6 Ignore errors parsing broken authres header fields 2018-03-02 15:02:31 -05:00
Scott Kitterman 96978c2747 Bump minimum authres version to 1.1.0 due to known issues with 1.0.2 2018-03-02 06:52:21 -05:00
Scott Kitterman 77722a0ffd Domain/KeyFile/Selector commented out by default - there is no useful default and not needed for verifying only milter 2018-02-26 11:55:00 -05:00
Scott Kitterman ced16fda72 Fixup csl dataset and initial (untested) file dataset 2018-02-25 15:57:41 -05:00
Scott Kitterman f381986f7a Add more about isntallation options/instructions to README 2018-02-21 07:38:20 -05:00
Scott Kitterman 02ad614657 Bump version for next release 2018-02-19 17:32:55 -05:00
Scott Kitterman 6f6cd77587 Updates to mark 0.9.2 Alpha 2 release 2018-02-19 17:15:44 -05:00
Scott Kitterman d8e7e7830e Rename sysv init file 2018-02-19 17:14:42 -05:00
Scott Kitterman f9c8039582 More init file cleanup 2018-02-19 17:13:27 -05:00
Scott Kitterman f8011611a2 Sysv init fixes 2018-02-19 16:17:40 -05:00
Scott Kitterman 98e5c17858 - 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
2018-02-19 13:31:28 -05:00
Scott Kitterman a71d3b5d99 Impelment SyslogFacility, enhance README. 2018-02-19 02:11:13 -05:00
Scott Kitterman 51464bd7f8 Add support for Canonicalization option 2018-02-18 00:56:02 -05:00
Scott Kitterman 76f2a34fe4 Add initial draft of dkimpy-milter.8 based on opendkim.8 2018-02-18 00:54:39 -05:00
Scott Kitterman 162c115fa5 Added untested sysv init file and added warnings to README 2018-02-17 18:42:19 -05:00
Scott Kitterman 57a626d157 Added systemd unit file 2018-02-17 17:20:50 -05:00
Scott Kitterman 716752e2fb Move version definition to dkim_milter/__init__.py and improve setup.py requirements 2018-02-17 15:08:25 -05:00
13 changed files with 644 additions and 58 deletions
+20 -1
View File
@@ -1,8 +1,27 @@
0.9.3 2018-03-02
- Fixup csl dataset processing for single item lists
- file: dataset support
- Bump minimum authres version to 1.1.0 due to known issues with 1.0.2
- Ignore errors parsing broken authres header fields
- Fold added authres header fields
- Fix pidfile permissions
- Fix socket setup sequence so Unix sockets work
0.9.1 UNRELEASED
0.9.2 2018-02-19
- Improved package requirements definition
- 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
0.9.1 2018-02-17
- DKIM signing and verification using both RSA and Ed25519
- The following configuration options are supported (same definition as
OpenDKIM): Domain, KeyFile, KeyFileEd25519, Mode, PidFile, Selector,
Socket, Syslog, UMask, and UserID (see dkimpy-milter.conf.5)
- This is an Alpha grade release and while the implemented features work, it
is nowhere near being a complete package
+1
View File
@@ -1,5 +1,6 @@
include etc/*
include man/*
include system/*
include Authors.conf
include TODO
include README
+23
View File
@@ -13,6 +13,29 @@ default is a feature:
python setup.py install --single-version-externally-managed --record=/dev/null
For users of Debian Stable (Debian 9, Codename Squueze), all dependencies are
available in either the main or backports repositories:
[sudo] apt install python-milter python-nacl
[sudo] apt install -t squeeze-backports python-authres python-dkim
The preferred method of installation is from PyPi using pip:
[sudo] pip install dkimpy_milter
Using pip will cause required packages to be installed via easy_install if they
have not been previously installed.
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
used, they will need to be updated. The sysv init file is Debian specific and
untested, since the developers are not using sysv init. Feedback/patches
welcome.
The python DKIM library, dkimpy, requires the entire message being signed or
verified to be in memory, so dkimpy-milter does not write messages out to a temp
file. This may impact performance on low-memory systems.
WARNING: This is an alpha grade release to support interoperability testing with
Ed25519 signatures and basic functionality. It is known to be incomplete and
not suitable for general use.
+19 -5
View File
@@ -15,21 +15,35 @@ UMask implemented
UserID implemented verified
DKIM 'a' in AR implemented verified
0.9.2 (Alpha)
dkimpy-milter.service implemented verified
sysv init implemented lightly tested
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 verified
0.9.5 (Beta)
dkimpy-milter.8
dkimpy-milter.service
remove PidFile on stop
AuthservID
Canonicalization
Diagnostics
DiagnosticDirectory
InternalHosts
SyslogFacility
SyslogSuccess
1.0.0
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
+30 -12
View File
@@ -39,7 +39,9 @@ from dkimpy_milter.util import drop_privileges
from dkimpy_milter.util import setExceptHook
from dkimpy_milter.util import write_pid
from dkimpy_milter.util import read_keyfile
from dkimpy_milter.util import own_socketfile
__version__ = "0.9.3"
FWS = re.compile(r'\r?\n[ \t]+')
class dkimMilter(Milter.Base):
@@ -118,6 +120,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':
@@ -146,18 +149,22 @@ class dkimMilter(Milter.Base):
# Remove existing Authentication-Results headers for our authserv_id
for i,val in enumerate(self.arheaders,1):
# FIXME: don't delete A-R headers from trusted MTAs
try:
ar = authres.AuthenticationResultsHeader.parse_value(FWS.sub('',val))
if ar.authserv_id == self.receiver:
self.chgheader('authentication-results',i,'')
if milterconfig.get('Syslog'):
syslog.syslog('REMOVE: {0}'.format(val))
except:
# Don't error out on unparseable AR header fiels
pass
# 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:
@@ -165,6 +172,7 @@ class dkimMilter(Milter.Base):
if self.arresults:
h = authres.AuthenticationResultsHeader(authserv_id = self.receiver,
results=self.arresults)
h = dkim.fold(str(h))
if milterconfig.get('Syslog'):
syslog.syslog(str(h))
name,val = str(h).split(': ',1)
@@ -172,17 +180,25 @@ class dkimMilter(Milter.Base):
return Milter.CONTINUE
def sign_dkim(self,txt):
conf = self.conf
canon = milterconfig.get('Canonicalization')
canonicalize = []
if len(canon.split('/')) == 2:
canonicalize.append(canon.split('/')[0])
canonicalize.append(canon.split('/')[1])
else:
canonicalize.append(canon)
canonicalize.append(canon)
syslog.syslog('canonicalize: {0}'.format(canonicalize))
try:
d = dkim.DKIM(txt)
h = d.sign(milterconfig.get('Selector'),milterconfig.get('Domain'), privateRSA,
canonicalize=('relaxed','simple'))
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,
canonicalize=('relaxed','simple'), signature_algorithm='ed25519-sha256')
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)
except dkim.DKIMException as x:
@@ -251,22 +267,24 @@ def main():
configFile = sys.argv[1]
milterconfig = config._processConfigFile(filename = configFile)
if milterconfig.get('Syslog'):
syslog.openlog(os.path.basename(sys.argv[0]), syslog.LOG_PID, syslog.LOG_MAIL)
facility = eval("syslog.LOG_{0}".format(milterconfig.get('SyslogFacility').upper()))
syslog.openlog(os.path.basename(sys.argv[0]), syslog.LOG_PID, facility)
setExceptHook()
write_pid(milterconfig)
pid = write_pid(milterconfig)
if milterconfig.get('KeyFile'):
privateRSA = read_keyfile(milterconfig, 'RSA')
if milterconfig.get('KeyFileEd25519'):
privateEd25519 = read_keyfile(milterconfig, 'Ed25519')
drop_privileges(milterconfig)
if milterconfig.get('Syslog'):
syslog.syslog('dkimpy-milter started. user: {0}'.format(milterconfig.get('UserID')))
Milter.factory = dkimMilter
Milter.set_flags(Milter.CHGHDRS + Milter.ADDHDRS)
miltername = 'dkimpy-filter'
socketname = milterconfig.get('Socket')
if milterconfig.get('Syslog'):
syslog.syslog('dkimpy-milter started:{0} user:{1}'.format(pid,milterconfig.get('UserID')))
sys.stdout.flush()
Milter.runmilter(miltername,socketname,240)
own_socketfile(milterconfig)
drop_privileges(milterconfig)
if __name__ == "__main__":
main()
+48 -7
View File
@@ -42,7 +42,7 @@ defaultConfigData = {
'Socket' : 'local:/var/run/dkimpy-milter/dkimpy-milter.sock',
'PidFile' : '/var/run/dkimpy-milter/dkimpy-milter.pid',
'UserID' : 'dkimpy-milter',
'Canonicalization' : 'simple'
'Canonicalization' : 'relaxed/simple'
}
@@ -84,6 +84,43 @@ 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, str):
# If it was a csl with more than one value, 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
elif isinstance(dataset, str):
if dataset[0] == '/' or dataset[:5] == 'file:':
# This is a flat file dataset
ds = []
if dataset[0] == '/':
dsname = dataset
if dataset[:5] == 'file:':
dsname = dataset[5:]
dsf = open(dsname, 'r')
for line in dsf.readlines():
if line[0] != '#':
if len(line.split(':')) == 1:
ds.append(line.strip())
else:
for element in line.split(':'):
ds.append(element.strip().strip(':'))
dsf.close()
return ds
# If it's a str and csl, it has one value and we return a list
if dataset[:4] == 'csl:':
return [dataset[4:].strip().strip(',')]
else:
return [dataset.strip().strip(',')]
raise dkim.ParameterError('Unimplmented dataset type: {0}'.format(type(dataset)))
###############################################################
commentRx = re.compile(r'^(.*)#.*$')
def _readConfigFile(path, configData = None, configGlobal = {}):
@@ -105,13 +142,12 @@ def _readConfigFile(path, configData = None, configGlobal = {}):
'Socket' : 'str',
'PidFile' : 'str',
'UserID' : 'str',
'Domain' : 'str',
'Domain' : 'dataset',
'KeyFile' : 'str',
'KeyFileEd25519' : 'str',
'Selector' : 'str',
'SelectorEd25519': 'str',
'Canonicalization' : 'str',
'CanonicalizationEd25519' : 'str'
}
# check to see if it's a file
@@ -139,11 +175,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
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)
@@ -159,6 +198,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)
+31 -16
View File
@@ -16,10 +16,23 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
def drop_privileges(milterconfig):
import os
def user_group(userid):
"""Return user and group from UserID"""
import grp
import pwd
userlist = userid.split(':')
if len(userlist) == 1:
gidname = userlist[0]
else:
gidname = userlist[1]
# Get the uid/gid from the name
running_uid = pwd.getpwnam(userlist[0]).pw_uid
running_gid = grp.getgrnam(gidname).gr_gid
return running_uid, running_gid
def drop_privileges(milterconfig):
import os
import syslog
if os.getuid() != 0:
@@ -27,25 +40,15 @@ def drop_privileges(milterconfig):
syslog.syslog('drop_privileges: Not running as root. Cannot drop permissions.')
return
# Figure out if user and group are specified
userstr = milterconfig.get('UserID')
userlist = userstr.split(':')
if len(userlist) == 1:
gidname = userlist[0]
else:
gidname = userlist[1]
uidname = userlist[0]
# Get the uid/gid from the name
running_uid = pwd.getpwnam(uidname).pw_uid
running_gid = grp.getgrnam(gidname).gr_gid
# Get user and group
uid, gid = user_group(milterconfig.get('UserID'))
# Remove group privileges
os.setgroups([])
# Try setting the new uid/gid
os.setgid(running_gid)
os.setuid(running_uid)
os.setgid(gid)
os.setuid(uid)
# Set umask
old_umask = os.umask(milterconfig.get('UMask'))
@@ -88,10 +91,22 @@ def write_pid(milterconfig):
raise
f.write(pid)
f.close()
user, group = user_group(milterconfig.get('UserID'))
os.chown(milterconfig.get('PidFile'), user, group)
else:
if milterconfig.get('Syslog'):
syslog.syslog('Unable to write pidfle {0}. File exists.'.format(milterconfig.get('PidFile')))
raise RuntimeError('Unable to write pidfle {0}. File exists.'.format(milterconfig.get('PidFile')))
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'))
if milterconfig.get('Socket')[:1] == '/':
os.chown(milterconfig.get('Socket')[1:], user, group)
if milterconfig.get('Socket')[:6] == "local:":
os.chown(milterconfig.get('Socket')[6:], user, group)
####################
def read_keyfile(milterconfig, keytype):
+4 -4
View File
@@ -11,12 +11,12 @@ UMask 007
# Sign for example.com with key in /etc/dkimkeys/dkim.key using
# selector '2007' (e.g. 2007._domainkey.example.com)
Domain example.com
KeyFile /etc/mail/dkim.key
Selector default
#Domain example.com
#KeyFile /etc/mail/dkim.key
#Selector default
# Commonly-used options; the commented-out versions show the defaults.
#Canonicalization simple
#Canonicalization relaxed/simple
#Mode sv
# Socket local:/var/run/dkimpy-milter/dkimpy-milter.sock
+305
View File
@@ -0,0 +1,305 @@
\"
.\" Standard preamble:
.\" ========================================================================
.de Sh \" Subsection heading
.br
.if t .Sp
.ne 5
.PP
\fB\\$1\fR
.PP
..
.de Sp \" Vertical space (when we can't use .PP)
.if t .sp .5v
.if n .sp
..
.de Vb \" Begin verbatim text
.ft CW
.nf
.ne \\$1
..
.de Ve \" End verbatim text
.ft R
.fi
..
.\" Set up some character translations and predefined strings. \*(-- will
.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
.\" double quote, and \*(R" will give a right double quote. \*(C+ will
.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
.\" nothing in troff, for use with C<>.
.tr \(*W-
.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
.ie n \{\
. ds -- \(*W-
. ds PI pi
. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
. ds L" ""
. ds R" ""
. ds C` ""
. ds C' ""
'br\}
.el\{\
. ds -- \|\(em\|
. ds PI \(*p
. ds L" ``
. ds R" ''
'br\}
.\"
.\" If the F register is turned on, we'll generate index entries on stderr for
.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
.\" entries marked with X<> in POD. Of course, you'll have to process the
.\" output yourself in some meaningful fashion.
.if \nF \{\
. de IX
. tm Index:\\$1\t\\n%\t"\\$2"
..
. nr % 0
. rr F
.\}
.\"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.hy 0
.if n .na
.\"
.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
.\" Fear. Run. Save yourself. No user-serviceable parts.
. \" fudge factors for nroff and troff
.if n \{\
. ds #H 0
. ds #V .8m
. ds #F .3m
. ds #[ \f1
. ds #] \fP
.\}
.if t \{\
. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
. ds #V .6m
. ds #F 0
. ds #[ \&
. ds #] \&
.\}
. \" simple accents for nroff and troff
.if n \{\
. ds ' \&
. ds ` \&
. ds ^ \&
. ds , \&
. ds ~ ~
. ds /
.\}
.if t \{\
. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
.\}
. \" troff and (daisy-wheel) nroff accents
.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
.ds ae a\h'-(\w'a'u*4/10)'e
.ds Ae A\h'-(\w'A'u*4/10)'E
. \" corrections for vroff
.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
. \" for low resolution devices (crt and lpr)
.if \n(.H>23 .if \n(.V>19 \
\{\
. ds : e
. ds 8 ss
. ds o a
. ds d- d\h'-1'\(ga
. ds D- D\h'-1'\(hy
. ds th \o'bp'
. ds Th \o'LP'
. ds ae ae
. ds Ae AE
.\}
.rm #[ #] #H #V #F C
.\" ========================================================================
.\"
.IX Title "dkimpy-milter 8"
.TH dkimpyy-milter 8
.SH NAME
.B dkimpy
\- DKIM signing and verifying filter for MTAs
.SH SYNOPSIS
.B dkimpy-milter [configfile]
.SH DESCRIPTION
.B dkimpy-milter
implements the
.B DKIM
standard for signing and verifying e-mail messages on a per-domain basis.
.B dkimpy-milter
uses the
.I milter
interface, originally distributed as part of version 8.11 of
.B sendmail(8),
to provide DKIM signing and/or verifying service for mail transiting
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 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 dkimpy-milter.conf(5)
for details about specific options and which dataset types they use.
In particular:
.TP
.I a)
If the string begins with "file:", then the remainder of the string is
presumed to refer to a flat file that contains elements of the data set,
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. [Not implemented yet]
.TP
.I b)
If the string begins with "refile:", then the remainder of the string is
presumed to specify a file that contains a set of patterns, one per line,
and their associated values. The pattern is taken as the start of the line
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. [Not implemented yet]
.TP
.I c)
If the string begins with "db:" and the program was compiled with
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. [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). [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. [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. [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. [Not implemented yet]
.TP
.I m)
In any other case, the string is presumed to be a comma-separated list.
Elements in the list are either simple data elements that are part of the
set or, in the case of an entry of the form "x=y", are stored as key-value
pairs as described above.
.SH OPTIONS
.TP
See
.I dkimpy-milter.conf(5)
information about available options. Unlike OpenDKIM, dkimpy-milter does not
support command line option switches.
When signing a message, a
.I DKIM-Signature:
header will be prepended to the message. The signature is computed using
the private key provided. You must be running a version of
.I sendmail(8)
recent enough to be able to do header prepend operations (8.13.0 or later).
When verifying a message, an
.I Authentication-Results:
header will be prepended to indicate the presence of a signature and whether
or not it could be validated against the body of the message using the
public key advertised by the sender's nameserver. The value of this header
can be used by mail user agents to sort or discard messages that were not
signed or could not be verified.
.SH FILE PERMISSIONS
When the filter is started as the superuser and the UserID setting is
used, the filter gives up its root privileges by changing to the specified
user after the following steps are taken: (1) the configuration file (if any)
is loaded; (2) if the KeyFile or KeyFileEd25519 settings are used, the keys are
loaded into memory; (3) all data sets in the configuration file are opened, and
those that are based on flat files are also read into memory; and (4) if
ChangeRootDirectory is set, the process root is changed to that directory.
This means on configuration reload, the filter will not be accessing these
files or the configuration file as the superuser (and possibly from a
different root), and any key files referenced by the KeyTable will also be
accessed by the new user.
Thus, keys referenced by the KeyTable must always be accessible for read by
the unprivileged user. Also, run-time reloads are not possible if any of the
other files will not be readable by the unprivileged user.
.SH ENVIRONMENT
The following environment variable(s) can be used to adjust the behaviour
of this filter:
.TP
.I DKIM_TMPDIR
The directory to use when creating temporary files. The default is
.I /tmp.
.SH NOTES
When using DNS timeouts be sure not to use a timeout that is larger than the
timeout being used for interaction between
.I sendmail
and the filter. Otherwise, the MTA could abort a message while waiting for
a reply from the filter, which in turn is still waiting for a DNS reply.
Features that involve specification of IPv4 addresses or CIDR blocks
will use the
.I inet_addr(3)
function to parse that information. Users should be familiar with the
way that function handles the non-trivial cases (for example, "192.0.2/24"
and "192.0.2.0/24" are not the same thing).
.SH EXIT STATUS
Filter exit status codes are selected according to
.I sysexits(3).
.SH HISTORY
DKIM is an amalgam of Yahoo!'s
.B DomainKeys
proposal, and Cisco's
.B Internet Identified Mail
(IIM) proposal.
.SH VERSION
This man page covers version 0.9.2 of
.I dkimpy-milter.
.SH COPYRIGHT
Copyright (c) 2005-2008, Sendmail, Inc. and its suppliers. All rights
reserved.
Copyright (c) 2009-2013, 2015, The Trusted Domain Project.
All rights reserved.
Copyright (c) 2018 Scott Kitterman <scott@kitterman.com>
.SH SEE ALSO
.I dkimpy-milter.conf(5), sendmail(8)
.P
Sendmail Operations Guide
.P
RFC5321 - Simple Mail Transfer Protocol
.P
RFC5322 - Internet Messages
.P
RFC6376 - DomainKeys Identified Mail
.P
RFC7601 - Message Header Field for Indicating Message Authentication Status
.P
draft-ietf-dcrup-dkim-crypto - A new cryptographic signature method for DKIM
+5 -3
View File
@@ -133,7 +133,7 @@
dkimpy-milter \- Python milter for DKIM signing and validation
.SH "VERSION"
.IX Header "VERSION"
0\.9\.1
0\.9\.2
.SH "DESCRIPTION"
.IX Header "DESCRIPTION"
@@ -208,6 +208,8 @@ the canonicalization method. The recognized values are
and
.I simple
as defined by the DKIM specification. The default is
.I relaxed
/
.I simple.
The value may include two different canonicalizations separated by a
slash ("/") character, in which case the first will be applied to the
@@ -234,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)
+8 -4
View File
@@ -18,12 +18,13 @@
from setuptools import setup
import os
import dkimpy_milter
description = "Domain Keys Identified Mail (DKIM) signing/verifying milter for Postfix/Sendmail."
setup(
name='dkimpy-milter',
version='0.9.1',
version=dkimpy_milter.__version__,
author='Scott Kitterman',
author_email='scott@kitterman.com',
url='https://launchpad.net/dkimpy-milter',
@@ -49,8 +50,11 @@ setup(
},
include_package_data=True,
data_files=[(os.path.join('share', 'man', 'man5'),
['man/dkimpy-milter.conf.5']), ('etc', ['etc/dkimpy-milter.conf'])],
install_requires = ['dkimpy', 'pymilter', 'authres>=1.0.2'],
['man/dkimpy-milter.conf.5']), (os.path.join('share', 'man', 'man8'),
['man/dkimpy-milter.8']), ('etc', ['etc/dkimpy-milter.conf']),
(os.path.join('/lib', 'systemd', 'system'),
['system/dkimpy-milter.service']),(os.path.join('/etc', 'init.d'),
['system/dkimpy-milter'])],
install_requires = ['dkimpy>=0.7', 'pymilter', 'authres>=1.1.0', 'PyNaCl'],
zip_safe = False,
)
+133
View File
@@ -0,0 +1,133 @@
#! /bin/sh
#
# skeleton example file to build /etc/init.d/ scripts.
# This file should be used to construct scripts for /etc/init.d.
#
# Written by Miquel van Smoorenburg <miquels@cistron.nl>.
# Modified for Debian
# by Ian Murdock <imurdock@gnu.ai.mit.edu>.
#
# Version: @(#)skeleton 1.9 26-Feb-2001 miquels@cistron.nl
#
### BEGIN INIT INFO
# Provides: dkim-milter dkim-milter-python dkimpy-milter
# Required-Start: $remote_fs $syslog $network $time
# Required-Stop: $remote_fs $syslog $network
# Should-Start:
# Should-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: dkimpy-milter
# Description: Python DKIM Milter for Sendmail and Postfix
### END INIT INFO
prefix="/usr/local"
exec_prefix=${prefix}
sysconfdir="/etc/dkimpy-milter"
bindir="${exec_prefix}/bin/"
RUNDIR="/var/run/dkimpy-milter"
DAEMON=${bindir}/dkimpy-milter
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:
NAME=dkimpy-milter
DESC="Python DKIM Milter"
USER=dkimpy-milter
GROUP=dkimpy-milter
SOCKET=$RUNDIR/dkimpy-milter.pid
test -x $DAEMON || exit 0
# Include dkimpy-python defaults if available
# Typically not used
if [ -f /etc/default/dkimpy-milter ] ; then
. /etc/default/dkimpy-milter
fi
set -e
. /lib/lsb/init-functions
case "$1" in
start)
echo -n "Starting $DESC: "
# Create the run directory if it doesn't exist
if [ ! -d $RUNDIR ]; then
install -o $USER -g $GROUP -m 755 -d $RUNDIR || return 2
fi
# Clean up stale sockets
if [ -f $RUNDIR/$NAME.pid ]; then
pid=`cat $RUNDIR/$NAME.pid`
if ! ps -C $DAEMON -s $pid >/dev/null; then
rm $RUNDIR/$NAME.pid
# UNIX sockets may be specified with or without the
# local: prefix; handle both
t=`echo $SOCKET | cut -d: -f1`
s=`echo $SOCKET | cut -d: -f2`
if [ -e $s -a -S $s ]; then
if [ "$t" = "$s" -o "$t" = "local" ]; then
rm $s
fi
fi
fi
fi
start-stop-daemon --start --chuid $USER --background --quiet --pidfile \
$RUNDIR/$NAME.pid --exec $DAEMON $sysconfdir/$NAME.conf
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
if [ -f $RUNDIR/$NAME.pid ]; then
start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid
rm $RUNDIR/$NAME.pid
#echo $SOCKET
if [ -e $SOCKET ]; then
rm $SOCKET
fi
fi
echo "$NAME."
;;
force-reload)
echo -n "Force reloading $DESC: "
if [ -f $RUNDIR/$NAME.pid ]; then
start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid
rm $RUNDIR/$NAME.pid
#echo $SOCKET
if [ -e $SOCKET ]; then
rm $SOCKET
fi
fi
sleep 1
start-stop-daemon --start --chuid $USER --background --quiet --pidfile \
$RUNDIR/$NAME.pid --exec $DAEMON $sysconfdir/$NAME.conf
echo "$NAME."
;;
restart)
echo "Restarting $DESC: "
echo -n "Stopping $DESC: "
if [ -f $RUNDIR/$NAME.pid ]; then
start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid
rm $RUNDIR/$NAME.pid
#echo $SOCKET
if [ -e $SOCKET ]; then
rm $SOCKET
fi
fi
echo "$NAME."
sleep 1
echo -n "Starting $DESC: "
start-stop-daemon --start --chuid $USER --background --quiet --pidfile \
$RUNDIR/$NAME.pid --exec $DAEMON $sysconfdir/$NAME.conf
echo "$NAME."
;;
status)
status_of_proc -p /var/run/dkimpy-milter/dkimpy-milter.pid /usr/local/bin/dkimpy-milter dkimpy-milter
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|force-reload|restart|}" >&2
exit 1
;;
esac
exit 0
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=DKIMpy Milter
After=syslog.target network.target
[Service]
Type=simple
PIDFile=/var/run/dkimpy-milter/dkimpy-milter.pid
ExecStart=/usr/local/bin/dkimpy-milter
[Install]
WantedBy=multi-user.target