Compare commits

...

9 Commits

Author SHA1 Message Date
Scott Kitterman 2528632ba6 Release 1.0.2 2019-10-07 00:14:20 -04:00
Scott Kitterman 3ea22f1529 Spelling fix 2019-10-07 00:14:03 -04:00
Scott Kitterman 097e053309 Update README now that sysv init is tested 2019-10-06 00:15:30 -04:00
Scott Kitterman 419d2b54ea Set version in setup.py to 1.0.2~rc1 2019-10-05 21:51:43 -04:00
Scott Kitterman 3ff685205c Fix sysv init so it works (LP: #1839487) 2019-10-05 21:50:47 -04:00
Scott Kitterman 7986de6629 - Catch more ascii encoding errors to improve resilience against bad data
(LP: #1844189)
2019-09-23 11:52:17 -04:00
Scott Kitterman 5322c81027 0Fix variable initialization so mailformed mails missing body From do not
cause a traceback (LP: #1844161)
2019-09-16 20:09:55 -04:00
Scott Kitterman 94538ffa6b Fix startup logging so it provides information at a useful time 2019-09-06 00:44:04 -04:00
Scott Kitterman 721da801fe - Fix message extraction so that signing in the same pass through the milter
as verifying works correctly
2019-09-05 23:57:36 -04:00
5 changed files with 59 additions and 25 deletions
+10
View File
@@ -1,3 +1,13 @@
1.0.2 2019-10-07
- Fix startup logging so it provides information at a useful time
- Fix message extraction so that signing in the same pass through the milter
as verifying works correctly
- Fix variable initialization so mailformed mails missing body From do not
cause a traceback (LP: #1844161)
- Catch more ascii encoding errors to improve resilience against bad data
(LP: #1844189)
- Fix sysv init so it works (LP: #1839487)
1.0.1 2019-02-11 1.0.1 2019-02-11
* Reorder milter start and dropping privileges so permissions on Unix socket * Reorder milter start and dropping privileges so permissions on Unix socket
are correct (LP: 1797720) are correct (LP: 1797720)
+3 -4
View File
@@ -37,7 +37,7 @@ an C compiler. Alternately, install these dependencies from dsitribution/OS
packages and then pip install dkimpy_milter. packages and then pip install dkimpy_milter.
The milter will work with either pydns (DNS) or dnspython (dns), preferring The milter will work with either pydns (DNS) or dnspython (dns), preferring
dnspython is both are available. The dkimpy DKIM module also works with dnspython if both are available. The dkimpy DKIM module also works with
either. either.
@@ -84,9 +84,8 @@ MTA INTEGRATION
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 uses start-stop-deamon
untested, since the developers are not using sysv init. Feedback/patches from Debian. It is not portable to systems without that available.
welcome.
The dkimpy-milter drops priviledges after setup to the user/group specified in The dkimpy-milter drops priviledges after setup to the user/group specified in
UserID. During initial setup, this system user needs to be manually created. UserID. During initial setup, this system user needs to be manually created.
+30 -7
View File
@@ -54,6 +54,7 @@ class dkimMilter(Milter.Base):
self.privatersa = privateRSA self.privatersa = privateRSA
self.privateed25519 = privateEd25519 self.privateed25519 = privateEd25519
self.fp = None self.fp = None
self.fdomain = ''
@Milter.noreply @Milter.noreply
def connect(self, hostname, unused, hostaddr): def connect(self, hostname, unused, hostaddr):
@@ -133,14 +134,18 @@ class dkimMilter(Milter.Base):
try: try:
self.fdomain = self.author.split('@')[1].lower() self.fdomain = self.author.split('@')[1].lower()
except IndexError as er: except IndexError as er:
self.fdomain = '' # self.author was not a proper email address pass # self.author was not a proper email address
if (milterconfig.get('Syslog') and if (milterconfig.get('Syslog') and
milterconfig.get('debugLevel') >= 1): milterconfig.get('debugLevel') >= 1):
syslog.syslog("{0}: {1}".format(name, val)) syslog.syslog("{0}: {1}".format(name, val))
elif lname == 'authentication-results': elif lname == 'authentication-results':
self.arheaders.append(val) self.arheaders.append(val)
if self.fp: if self.fp:
try:
self.fp.write("%s: %s\n" % (name, val)) self.fp.write("%s: %s\n" % (name, val))
except:
# Don't choke on header fields with garbage in them.
pass
return Milter.CONTINUE return Milter.CONTINUE
@Milter.noreply @Milter.noreply
@@ -174,20 +179,19 @@ class dkimMilter(Milter.Base):
except: except:
# Don't error out on unparseable AR header fiels # Don't error out on unparseable AR header fiels
pass pass
# Check or sign DKIM # Check and/or sign DKIM
self.fp.seek(0) self.fp.seek(0)
txt = self.fp.read()
if milterconfig.get('Domain'): if milterconfig.get('Domain'):
domain = milterconfig.get('Domain') domain = milterconfig.get('Domain')
else: else:
domain = '' domain = ''
if ((self.fdomain in domain) and not milterconfig.get('Mode') == 'v' if ((self.fdomain in domain) and not milterconfig.get('Mode') == 'v'
and not self.external_connection): and not self.external_connection):
txt = self.fp.read()
self.sign_dkim(txt) self.sign_dkim(txt)
if ((self.has_dkim) and (not self.internal_connection) and if ((self.has_dkim) and (not self.internal_connection) and
(milterconfig.get('Mode') == 'v' or (milterconfig.get('Mode') == 'v' or
milterconfig.get('Mode') == 'sv')): milterconfig.get('Mode') == 'sv')):
txt = self.fp.read()
self.check_dkim(txt) self.check_dkim(txt)
if self.arresults: if self.arresults:
h = authres.AuthenticationResultsHeader(authserv_id= h = authres.AuthenticationResultsHeader(authserv_id=
@@ -255,6 +259,7 @@ class dkimMilter(Milter.Base):
def check_dkim(self, txt): def check_dkim(self, txt):
res = False res = False
self.header_a = None
for y in range(self.has_dkim): # Verify _ALL_ the signatures for y in range(self.has_dkim): # Verify _ALL_ the signatures
d = dkim.DKIM(txt) d = dkim.DKIM(txt)
try: try:
@@ -281,9 +286,20 @@ class dkimMilter(Milter.Base):
self.dkim_comment = str(x) self.dkim_comment = str(x)
if milterconfig.get('Syslog'): if milterconfig.get('Syslog'):
syslog.syslog("check_dkim: {0}".format(x)) syslog.syslog("check_dkim: {0}".format(x))
try:
self.header_i = d.signature_fields.get(b'i') self.header_i = d.signature_fields.get(b'i')
except TypeError as x:
self.header_i = None
try:
self.header_d = d.signature_fields.get(b'd') self.header_d = d.signature_fields.get(b'd')
self.header_a = d.signature_fields.get(b'a') self.header_a = d.signature_fields.get(b'a')
except Exception as x:
self.dkim_comment = str(x)
if milterconfig.get('Syslog'):
syslog.syslog("check_dkim: {0}".format(x))
self.header_d = None
if not self.header_a:
self.header_a = 'rsa-sha256'
if res: if res:
if (milterconfig.get('Syslog') and if (milterconfig.get('Syslog') and
(milterconfig.get('SyslogSuccess') or (milterconfig.get('SyslogSuccess') or
@@ -303,12 +319,18 @@ class dkimMilter(Milter.Base):
syslog.syslog('DKIM: Fail (saved as {0})' syslog.syslog('DKIM: Fail (saved as {0})'
.format(fname)) .format(fname))
else: else:
syslog.syslog('DKIM: Fail ({0})'.format(d.domain.lower())) if milterconfig.get('Syslog'):
if d.domain:
syslog.syslog('DKIM: Fail ({0})'
.format(d.domain.lower()))
else:
syslog.syslog('DKIM: Fail, unextractable domain')
if res: if res:
result = 'pass' result = 'pass'
else: else:
result = 'fail' result = 'fail'
res = False res = False
if self.header_d:
self.arresults.append( self.arresults.append(
authres.DKIMAuthenticationResult(result=result, authres.DKIMAuthenticationResult(result=result,
header_i=self.header_i, header_i=self.header_i,
@@ -317,6 +339,7 @@ class dkimMilter(Milter.Base):
result_comment= result_comment=
self.dkim_comment) self.dkim_comment)
) )
self.header_a = None
return return
@@ -351,10 +374,10 @@ def main():
own_socketfile(milterconfig) own_socketfile(milterconfig)
drop_privileges(milterconfig) drop_privileges(milterconfig)
sys.stdout.flush() sys.stdout.flush()
Milter.runmilter(miltername, socketname, 240)
if milterconfig.get('Syslog'): if milterconfig.get('Syslog'):
syslog.syslog('dkimpy-milter started:{0} user:{1}' syslog.syslog('dkimpy-milter starting:{0} user:{1}'
.format(pid, milterconfig.get('UserID'))) .format(pid, milterconfig.get('UserID')))
Milter.runmilter(miltername, socketname, 240)
if __name__ == "__main__": if __name__ == "__main__":
main() main()
+1 -1
View File
@@ -30,7 +30,7 @@ except ImportError: # If PyDNS is not installed, prefer dnspython
setup( setup(
name='dkimpy-milter', name='dkimpy-milter',
version='1.0.1', version='1.0.2',
author='Scott Kitterman', author='Scott Kitterman',
author_email='scott@kitterman.com', author_email='scott@kitterman.com',
url='https://launchpad.net/dkimpy-milter', url='https://launchpad.net/dkimpy-milter',
+9 -7
View File
@@ -20,9 +20,9 @@
### END INIT INFO ### END INIT INFO
prefix="/usr/local" prefix="/usr/local"
exec_prefix=${prefix} exec_prefix=${prefix}
sysconfdir="/etc/dkimpy-milter" sysconfdir="/usr/local/etc"
bindir="${exec_prefix}/bin/" bindir="${exec_prefix}/bin/"
RUNDIR="/var/run/dkimpy-milter" RUNDIR="/run/dkimpy-milter"
DAEMON=${bindir}/dkimpy-milter DAEMON=${bindir}/dkimpy-milter
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin: PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:
NAME=dkimpy-milter NAME=dkimpy-milter
@@ -67,14 +67,14 @@ case "$1" in
fi fi
fi fi
fi fi
start-stop-daemon --start --background --quiet --pidfile \
start-stop-daemon --start --quiet --pidfile $RUNDIR/$NAME.pid --startas \ $RUNDIR/$NAME.pid --exec $DAEMON $sysconfdir/$NAME.conf
$DAEMON $sysconfdir/$NAME.conf --name $NAME --test > /dev/null \
echo "$NAME." echo "$NAME."
;; ;;
stop) stop)
echo -n "Stopping $DESC: " echo -n "Stopping $DESC: "
if [ -f $RUNDIR/$NAME.pid ]; then if [ -f $RUNDIR/$NAME.pid ]; then
chown root:root $RUNDIR/$NAME.pid
start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid
rm $RUNDIR/$NAME.pid rm $RUNDIR/$NAME.pid
#echo $SOCKET #echo $SOCKET
@@ -87,6 +87,7 @@ case "$1" in
force-reload) force-reload)
echo -n "Force reloading $DESC: " echo -n "Force reloading $DESC: "
if [ -f $RUNDIR/$NAME.pid ]; then if [ -f $RUNDIR/$NAME.pid ]; then
chown root:root $RUNDIR/$NAME.pid
start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid
rm $RUNDIR/$NAME.pid rm $RUNDIR/$NAME.pid
#echo $SOCKET #echo $SOCKET
@@ -95,7 +96,7 @@ case "$1" in
fi fi
fi fi
sleep 1 sleep 1
start-stop-daemon --start --chuid $USER --background --quiet --pidfile \ start-stop-daemon --start --background --quiet --pidfile \
$RUNDIR/$NAME.pid --exec $DAEMON $sysconfdir/$NAME.conf $RUNDIR/$NAME.pid --exec $DAEMON $sysconfdir/$NAME.conf
echo "$NAME." echo "$NAME."
;; ;;
@@ -103,6 +104,7 @@ case "$1" in
echo "Restarting $DESC: " echo "Restarting $DESC: "
echo -n "Stopping $DESC: " echo -n "Stopping $DESC: "
if [ -f $RUNDIR/$NAME.pid ]; then if [ -f $RUNDIR/$NAME.pid ]; then
chown root:root $RUNDIR/$NAME.pid
start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid start-stop-daemon --stop --pidfile $RUNDIR/$NAME.pid
rm $RUNDIR/$NAME.pid rm $RUNDIR/$NAME.pid
#echo $SOCKET #echo $SOCKET
@@ -113,7 +115,7 @@ case "$1" in
echo "$NAME." echo "$NAME."
sleep 1 sleep 1
echo -n "Starting $DESC: " echo -n "Starting $DESC: "
start-stop-daemon --start --chuid $USER --background --quiet --pidfile \ start-stop-daemon --start --background --quiet --pidfile \
$RUNDIR/$NAME.pid --exec $DAEMON $sysconfdir/$NAME.conf $RUNDIR/$NAME.pid --exec $DAEMON $sysconfdir/$NAME.conf
echo "$NAME." echo "$NAME."
;; ;;