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.3 2018-03-02
0.9.1 2018-02-17 - Fixup csl dataset processing for single item lists
- DKIM signing and verification using both RSA and Ed25519 - file: dataset support
- The following configuration options are supported (same definition as - Bump minimum authres version to 1.1.0 due to known issues with 1.0.2
OpenDKIM): Domain, KeyFile, KeyFileEd25519, Mode, PidFile, Selector, - Ignore errors parsing broken authres header fields
Socket, Syslog, UMask, and UserID (see dkimpy-milter.conf.5) - Fold added authres header fields
- This is an Alpha grade release and while the implemented features work, it - Fix pidfile permissions
is nowhere near being a complete package - Fix socket setup sequence so Unix sockets work
0.9.2 2018-02-19 0.9.2 2018-02-19
- Improved package requirements definition - Improved package requirements definition
@@ -16,3 +16,12 @@
- Initial dataset support: csl - Initial dataset support: csl
- Only sign if mail from from a domain in Domain and only if Mode is not - Only sign if mail from from a domain in Domain and only if Mode is not
verfication only 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 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 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 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 used, they will need to be updated. The sysv init file is Debian specific and
+16 -9
View File
@@ -39,8 +39,9 @@ from dkimpy_milter.util import drop_privileges
from dkimpy_milter.util import setExceptHook from dkimpy_milter.util import setExceptHook
from dkimpy_milter.util import write_pid from dkimpy_milter.util import write_pid
from dkimpy_milter.util import read_keyfile 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]+') FWS = re.compile(r'\r?\n[ \t]+')
class dkimMilter(Milter.Base): class dkimMilter(Milter.Base):
@@ -148,11 +149,15 @@ class dkimMilter(Milter.Base):
# Remove existing Authentication-Results headers for our authserv_id # Remove existing Authentication-Results headers for our authserv_id
for i,val in enumerate(self.arheaders,1): for i,val in enumerate(self.arheaders,1):
# FIXME: don't delete A-R headers from trusted MTAs # FIXME: don't delete A-R headers from trusted MTAs
ar = authres.AuthenticationResultsHeader.parse_value(FWS.sub('',val)) try:
if ar.authserv_id == self.receiver: ar = authres.AuthenticationResultsHeader.parse_value(FWS.sub('',val))
self.chgheader('authentication-results',i,'') if ar.authserv_id == self.receiver:
if milterconfig.get('Syslog'): self.chgheader('authentication-results',i,'')
syslog.syslog('REMOVE: {0}'.format(val)) 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 # Check or sign DKIM
self.fp.seek(0) self.fp.seek(0)
if (self.fdomain in milterconfig.get('Domain')) and (not milterconfig.get('Mode') == 'v'): if (self.fdomain in milterconfig.get('Domain')) and (not milterconfig.get('Mode') == 'v'):
@@ -167,6 +172,7 @@ class dkimMilter(Milter.Base):
if self.arresults: if self.arresults:
h = authres.AuthenticationResultsHeader(authserv_id = self.receiver, h = authres.AuthenticationResultsHeader(authserv_id = self.receiver,
results=self.arresults) results=self.arresults)
h = dkim.fold(str(h))
if milterconfig.get('Syslog'): if milterconfig.get('Syslog'):
syslog.syslog(str(h)) syslog.syslog(str(h))
name,val = str(h).split(': ',1) name,val = str(h).split(': ',1)
@@ -269,15 +275,16 @@ def main():
privateRSA = read_keyfile(milterconfig, 'RSA') privateRSA = read_keyfile(milterconfig, 'RSA')
if milterconfig.get('KeyFileEd25519'): if milterconfig.get('KeyFileEd25519'):
privateEd25519 = read_keyfile(milterconfig, 'Ed25519') 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.factory = dkimMilter
Milter.set_flags(Milter.CHGHDRS + Milter.ADDHDRS) Milter.set_flags(Milter.CHGHDRS + Milter.ADDHDRS)
miltername = 'dkimpy-filter' miltername = 'dkimpy-filter'
socketname = milterconfig.get('Socket') socketname = milterconfig.get('Socket')
if milterconfig.get('Syslog'):
syslog.syslog('dkimpy-milter started:{0} user:{1}'.format(pid,milterconfig.get('UserID')))
sys.stdout.flush() sys.stdout.flush()
Milter.runmilter(miltername,socketname,240) Milter.runmilter(miltername,socketname,240)
own_socketfile(milterconfig)
drop_privileges(milterconfig)
if __name__ == "__main__": if __name__ == "__main__":
main() main()
+28 -5
View File
@@ -87,16 +87,39 @@ def _find_boolean(item):
def _dataset_to_list(dataset): def _dataset_to_list(dataset):
"""Convert a dataset (as defined in dkimpymilter.8) and return a python """Convert a dataset (as defined in dkimpymilter.8) and return a python
list of values.""" list of values."""
if not isinstance(dataset, basestring): if not isinstance(dataset, str):
# If it was a csl, it's already a list, we only need to remove the name # If it was a csl with more than one value, it's already a list, we
# from the first value # only need to remove the name from the first value.
if dataset[0][:4] == 'csl:': if dataset[0][:4] == 'csl:':
dataset[0] = dataset[0][4:] dataset[0] = dataset[0][4:]
for item in dataset: for item in dataset:
dataset[dataset.index(item)] = item.strip().strip(',') dataset[dataset.index(item)] = item.strip().strip(',')
return dataset return dataset
else: elif isinstance(dataset, str):
raise dkim.ParameterError('Unimplmented dataset type') 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'^(.*)#.*$') commentRx = re.compile(r'^(.*)#.*$')
+30 -16
View File
@@ -16,10 +16,23 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
def drop_privileges(milterconfig): def user_group(userid):
import os """Return user and group from UserID"""
import grp import grp
import pwd 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 import syslog
if os.getuid() != 0: if os.getuid() != 0:
@@ -27,25 +40,15 @@ def drop_privileges(milterconfig):
syslog.syslog('drop_privileges: Not running as root. Cannot drop permissions.') syslog.syslog('drop_privileges: Not running as root. Cannot drop permissions.')
return return
# Figure out if user and group are specified # Get user and group
userstr = milterconfig.get('UserID') uid, gid = user_group(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
# Remove group privileges # Remove group privileges
os.setgroups([]) os.setgroups([])
# Try setting the new uid/gid # Try setting the new uid/gid
os.setgid(running_gid) os.setgid(gid)
os.setuid(running_uid) os.setuid(uid)
# Set umask # Set umask
old_umask = os.umask(milterconfig.get('UMask')) old_umask = os.umask(milterconfig.get('UMask'))
@@ -88,12 +91,23 @@ def write_pid(milterconfig):
raise raise
f.write(pid) f.write(pid)
f.close() f.close()
user, group = user_group(milterconfig.get('UserID'))
os.chown(milterconfig.get('PidFile'), user, group)
else: else:
if milterconfig.get('Syslog'): if milterconfig.get('Syslog'):
syslog.syslog('Unable to write pidfle {0}. File exists.'.format(milterconfig.get('PidFile'))) 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'))) raise RuntimeError('Unable to write pidfle {0}. File exists.'.format(milterconfig.get('PidFile')))
return pid 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): def read_keyfile(milterconfig, keytype):
"""Read private key from file.""" """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 # Sign for example.com with key in /etc/dkimkeys/dkim.key using
# selector '2007' (e.g. 2007._domainkey.example.com) # selector '2007' (e.g. 2007._domainkey.example.com)
Domain example.com #Domain example.com
KeyFile /etc/mail/dkim.key #KeyFile /etc/mail/dkim.key
Selector default #Selector default
# Commonly-used options; the commented-out versions show the defaults. # Commonly-used options; the commented-out versions show the defaults.
#Canonicalization relaxed/simple #Canonicalization relaxed/simple
+1 -1
View File
@@ -55,6 +55,6 @@ setup(
(os.path.join('/lib', 'systemd', 'system'), (os.path.join('/lib', 'systemd', 'system'),
['system/dkimpy-milter.service']),(os.path.join('/etc', 'init.d'), ['system/dkimpy-milter.service']),(os.path.join('/etc', 'init.d'),
['system/dkimpy-milter'])], ['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, zip_safe = False,
) )