Compare commits

..

8 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
7 changed files with 108 additions and 42 deletions
+17 -8
View File
@@ -1,11 +1,11 @@
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
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.2 2018-02-19
- Improved package requirements definition
@@ -16,3 +16,12 @@
- 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
+13
View File
@@ -13,6 +13,19 @@ 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
+11 -4
View File
@@ -39,8 +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.2"
__version__ = "0.9.3"
FWS = re.compile(r'\r?\n[ \t]+')
class dkimMilter(Milter.Base):
@@ -148,11 +149,15 @@ 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.fdomain in milterconfig.get('Domain')) and (not milterconfig.get('Mode') == 'v'):
@@ -167,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)
@@ -269,15 +275,16 @@ def main():
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:{0} user:{1}'.format(pid,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()
+27 -4
View File
@@ -87,16 +87,39 @@ def _find_boolean(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 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:
raise dkim.ParameterError('Unimplmented dataset type')
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'^(.*)#.*$')
+30 -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,12 +91,23 @@ 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):
"""Read private key from file."""
+3 -3
View File
@@ -11,9 +11,9 @@ 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 relaxed/simple
+1 -1
View File
@@ -55,6 +55,6 @@ setup(
(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.0.2', 'PyNaCl'],
install_requires = ['dkimpy>=0.7', 'pymilter', 'authres>=1.1.0', 'PyNaCl'],
zip_safe = False,
)