Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e2cff5e5d | |||
| 5886edda42 | |||
| e4a17d7be6 | |||
| 96978c2747 | |||
| 77722a0ffd | |||
| ced16fda72 | |||
| f381986f7a | |||
| 02ad614657 |
@@ -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,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
|
||||||
|
|||||||
@@ -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
|
||||||
|
try:
|
||||||
ar = authres.AuthenticationResultsHeader.parse_value(FWS.sub('',val))
|
ar = authres.AuthenticationResultsHeader.parse_value(FWS.sub('',val))
|
||||||
if ar.authserv_id == self.receiver:
|
if ar.authserv_id == self.receiver:
|
||||||
self.chgheader('authentication-results',i,'')
|
self.chgheader('authentication-results',i,'')
|
||||||
if milterconfig.get('Syslog'):
|
if milterconfig.get('Syslog'):
|
||||||
syslog.syslog('REMOVE: {0}'.format(val))
|
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()
|
||||||
|
|||||||
+27
-4
@@ -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
|
||||||
|
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:
|
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'^(.*)#.*$')
|
commentRx = re.compile(r'^(.*)#.*$')
|
||||||
|
|||||||
+30
-16
@@ -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."""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user