diff -Nru pysrs-1.0.2/debian/changelog pysrs-1.0.3/debian/changelog --- pysrs-1.0.2/debian/changelog 2017-11-04 10:31:47.000000000 +0000 +++ pysrs-1.0.3/debian/changelog 2017-12-16 15:12:32.000000000 +0000 @@ -1,3 +1,10 @@ +pysrs (1.0.3-1) unstable; urgency=medium + + * new upstream release. + * Bump Standards-Version to 4.1.2 (No changes needed). + + -- Sandro Knauß Sat, 16 Dec 2017 16:12:32 +0100 + pysrs (1.0.2-1) unstable; urgency=medium [ Ondřej Nový ] diff -Nru pysrs-1.0.2/debian/control pysrs-1.0.3/debian/control --- pysrs-1.0.2/debian/control 2017-11-04 10:31:47.000000000 +0000 +++ pysrs-1.0.3/debian/control 2017-12-16 15:11:29.000000000 +0000 @@ -11,7 +11,7 @@ Homepage: http://bmsi.com/python/pysrs.html Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/pysrs.git Vcs-Browser: https://anonscm.debian.org/cgit/python-modules/packages/pysrs.git -Standards-Version: 4.1.1 +Standards-Version: 4.1.2 Package: python-srs Architecture: all diff -Nru pysrs-1.0.2/debian/.git-dpm pysrs-1.0.3/debian/.git-dpm --- pysrs-1.0.2/debian/.git-dpm 2017-11-04 10:31:18.000000000 +0000 +++ pysrs-1.0.3/debian/.git-dpm 2017-12-16 15:06:18.000000000 +0000 @@ -1,11 +1,11 @@ # see git-dpm(1) from git-dpm package -3a0cd6f37f878edc80fbd65c17d5139d5bcd913b -3a0cd6f37f878edc80fbd65c17d5139d5bcd913b -db8b8ecd11327b44c3f3bfc4386941cdea67bfb1 -db8b8ecd11327b44c3f3bfc4386941cdea67bfb1 -pysrs_1.0.2.orig.tar.gz -e67268b96723c1f3e40acb35a555560bbd98fb89 -28444 +ed9d9a4124d8610d72dcbb64c553bbc19b06b48b +ed9d9a4124d8610d72dcbb64c553bbc19b06b48b +8eaab3ebe3d5de3a90cf8183a3ed67df24cac843 +8eaab3ebe3d5de3a90cf8183a3ed67df24cac843 +pysrs_1.0.3.orig.tar.gz +b7c8c975e8210388036e6bc7ead34fa5211d256e +29371 debianTag="debian/%e%v" patchedTag="patched/%e%v" upstreamTag="upstream/%e%u" diff -Nru pysrs-1.0.2/debian/patches/0001-update-versionnumbers-inside-SES-and-SRS.patch pysrs-1.0.3/debian/patches/0001-update-versionnumbers-inside-SES-and-SRS.patch --- pysrs-1.0.2/debian/patches/0001-update-versionnumbers-inside-SES-and-SRS.patch 2017-11-04 10:31:18.000000000 +0000 +++ pysrs-1.0.3/debian/patches/0001-update-versionnumbers-inside-SES-and-SRS.patch 2017-12-16 15:06:18.000000000 +0000 @@ -1,4 +1,4 @@ -From 3a0cd6f37f878edc80fbd65c17d5139d5bcd913b Mon Sep 17 00:00:00 2001 +From ed9d9a4124d8610d72dcbb64c553bbc19b06b48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20Knau=C3=9F?= Date: Sat, 4 Nov 2017 10:28:23 +0100 Subject: update versionnumbers inside SES and SRS diff -Nru pysrs-1.0.2/debian/watch pysrs-1.0.3/debian/watch --- pysrs-1.0.2/debian/watch 2017-11-04 10:31:18.000000000 +0000 +++ pysrs-1.0.3/debian/watch 2017-12-16 15:08:10.000000000 +0000 @@ -1,4 +1,4 @@ version=4 -opts="filenamemangle=s%(?:.*?)?pysrs-(\d[\d.]*)\.tar\.gz%-$1.tar.gz%" \ +opts="filenamemangle=s%(?:.*?)?pysrs-(\d[\d.]*)\.tar\.gz%pysrs-$1.tar.gz%" \ https://github.com/sdgathman/pysrs/tags \ (?:.*?/)?pysrs-(\d[\d.]*)\.tar\.gz debian uupdate diff -Nru pysrs-1.0.2/envfrom2srs.py pysrs-1.0.3/envfrom2srs.py --- pysrs-1.0.2/envfrom2srs.py 2017-11-03 17:37:49.000000000 +0000 +++ pysrs-1.0.3/envfrom2srs.py 2017-11-14 03:14:17.000000000 +0000 @@ -11,7 +11,7 @@ import SRS import re -from configparser import ConfigParser, DuplicateSectionError +from ConfigParser import ConfigParser, DuplicateSectionError # get SRS parameters from milter configuration cp = ConfigParser({ diff -Nru pysrs-1.0.2/makefile pysrs-1.0.3/makefile --- pysrs-1.0.2/makefile 2017-11-03 17:37:49.000000000 +0000 +++ pysrs-1.0.3/makefile 2017-11-14 03:14:17.000000000 +0000 @@ -2,7 +2,7 @@ doxygen rsync -ravK doc/html/ spidey2.bmsi.com:/Public/pymilter -VERSION=1.0.2 +VERSION=1.0.3 PKG=pysrs-$(VERSION) SRCTAR=$(PKG).tar.gz diff -Nru pysrs-1.0.2/pysrs.cfg pysrs-1.0.3/pysrs.cfg --- pysrs-1.0.2/pysrs.cfg 2017-11-03 17:37:49.000000000 +0000 +++ pysrs-1.0.3/pysrs.cfg 2017-11-14 03:14:17.000000000 +0000 @@ -15,3 +15,14 @@ ;srs = otherdomain.com # do not rewrite mail to these domains ;nosrs = braindeadmail.com +# +[srsmilter] +;datadir=/var/lib/milter +socketname = /var/run/milter/srsmilter +miltername = pysrsfilter +# reject DSNs to unsigned recipients (bounce spam) +reject_spoofed = true +;trusted_relay = 1.2.3.4 +internal_connect = 192.168.*.*,127.0.0.1,::1 +# Enable outgoing SRS via CHGFROM (see code for limitations) +miltersrs = false diff -Nru pysrs-1.0.2/pysrs.spec pysrs-1.0.3/pysrs.spec --- pysrs-1.0.2/pysrs.spec 2017-11-03 17:37:49.000000000 +0000 +++ pysrs-1.0.3/pysrs.spec 2017-11-14 03:14:17.000000000 +0000 @@ -8,7 +8,7 @@ Summary: Python SRS (Sender Rewriting Scheme) library Name: %{pythonbase}-pysrs -Version: 1.0.2 +Version: 1.0.3 Release: 1%{?dist} Source0: pysrs-%{version}.tar.gz License: Python license @@ -73,9 +73,11 @@ mkdir -p $RPM_BUILD_ROOT/var/log/milter mkdir -p $RPM_BUILD_ROOT%{_libexecdir}/milter cp -p pysrs.py $RPM_BUILD_ROOT%{_libexecdir}/milter/pysrs +cp -p srsmilter.py $RPM_BUILD_ROOT%{_libexecdir}/milter/srsmilter %if %{use_systemd} mkdir -p $RPM_BUILD_ROOT%{_unitdir} cp -p pysrs.service $RPM_BUILD_ROOT%{_unitdir} +cp -p srsmilter.service $RPM_BUILD_ROOT%{_unitdir} %else mkdir -p $RPM_BUILD_ROOT/etc/rc.d/init.d cp %{sysvinit} $RPM_BUILD_ROOT/etc/rc.d/init.d/pysrs @@ -133,6 +135,7 @@ /etc/logrotate.d/pysrs /usr/share/sendmail-cf/hack/* %{_libexecdir}/milter/pysrs +%{_libexecdir}/milter/srsmilter %if %{use_systemd} %{_unitdir}/* %else @@ -140,7 +143,10 @@ %endif %changelog -* Tue Nov 3 2017 Stuart Gathman 1.0.2-1 +* Mon Nov 13 2017 Stuart Gathman 1.0.3-1 +- Include srsmilter + +* Fri Nov 3 2017 Stuart Gathman 1.0.2-1 - Fix daemon to run in python2 - Move daemons to /usr/libexec/milter so they get bin_t selinux label diff -Nru pysrs-1.0.2/setup.py pysrs-1.0.3/setup.py --- pysrs-1.0.2/setup.py 2017-11-03 17:37:49.000000000 +0000 +++ pysrs-1.0.3/setup.py 2017-11-14 03:14:17.000000000 +0000 @@ -19,7 +19,7 @@ #-- Package description name = 'pysrs', license = 'Python license', - version = '1.0.2', + version = '1.0.3', description = 'Python SRS (Sender Rewriting Scheme) library', long_description = """Python SRS (Sender Rewriting Scheme) library. As SPF is implemented, MTAs that check SPF must account for any forwarders. diff -Nru pysrs-1.0.2/SRS/DB.py pysrs-1.0.3/SRS/DB.py --- pysrs-1.0.2/SRS/DB.py 2017-11-03 17:37:49.000000000 +0000 +++ pysrs-1.0.3/SRS/DB.py 2017-11-14 03:14:17.000000000 +0000 @@ -21,7 +21,10 @@ # This program is free software; you can redistribute it and/or modify # it under the same terms as Python itself. -import bsddb3 +try: + import bsddb3 as bsddb +except: + import bsddb import time import SRS from .Base import Base @@ -53,7 +56,7 @@ def __init__(self,database='/var/run/srs.db',hashlength=24,*args,**kw): Base.__init__(self,hashlength=hashlength,*args,**kw) assert database, "No database specified for SRS.DB" - self.dbm = bsddb3.btopen(database,'c') + self.dbm = bsddb.btopen(database,'c') def compile(self,sendhost,senduser,srshost=None): ts = time.time() diff -Nru pysrs-1.0.2/srs2envtol.py pysrs-1.0.3/srs2envtol.py --- pysrs-1.0.2/srs2envtol.py 2017-11-03 17:37:49.000000000 +0000 +++ pysrs-1.0.3/srs2envtol.py 2017-11-14 03:14:17.000000000 +0000 @@ -11,7 +11,7 @@ import SRS import re -from configparser import ConfigParser, DuplicateSectionError +from ConfigParser import ConfigParser, DuplicateSectionError # get SRS parameters from milter configuration cp = ConfigParser({ diff -Nru pysrs-1.0.2/srsmilter.py pysrs-1.0.3/srsmilter.py --- pysrs-1.0.2/srsmilter.py 2017-11-03 17:37:49.000000000 +0000 +++ pysrs-1.0.3/srsmilter.py 2017-11-14 03:14:17.000000000 +0000 @@ -1,7 +1,9 @@ +#!/usr/bin/python2 +# # A simple SRS milter for Sendmail-8.14/Postfix-? - # -# INCOMPLETE!! +# NOTE: use with pysrs socketmap and sendmail-cf macro to handle +# multiple recipients. # # The logical problem is that a milter gets to change MFROM only once for # multiple recipients. When there is a conflict between recipients, we @@ -12,8 +14,9 @@ # http://www.sendmail.org/doc/sendmail-current/libmilter/docs/installation.html -# Author: Stuart D. Gathman +# Author: Stuart D. Gathman # Copyright 2007 Business Management Systems, Inc. +# Copyright 2017 Stuart D. Gathman # This program is free software; you can redistribute it and/or modify # it under the same terms as Python itself. @@ -21,8 +24,8 @@ import SES import sys import Milter -import spf import syslog +import re from Milter.config import MilterConfigParser from Milter.utils import iniplist,parse_addr @@ -33,27 +36,29 @@ def __init__(conf,cfglist): cp = MilterConfigParser() cp.read(cfglist) - if cp.has_option('milter','datadir'): - os.chdir(cp.get('milter','datadir')) # FIXME: side effect! - conf.socketname = cp.getdefault('milter','socketname', - '/var/run/milter/pysrs') - conf.miltername = cp.getdefault('milter','name','pysrsfilter') - conf.trusted_relay = cp.getlist('milter','trusted_relay') - conf.internal_connect = cp.getlist('milter','internal_connect') + if cp.has_option('srsmilter','datadir'): + os.chdir(cp.get('srsmilter','datadir')) # FIXME: side effect! + conf.socketname = cp.getdefault('srsmilter','socketname', + '/var/run/milter/srsmilter') + conf.miltername = cp.getdefault('srsmilter','name','pysrsfilter') + conf.trusted_relay = cp.getlist('srsmilter','trusted_relay') + conf.miltersrs = cp.getboolean('srsmilter','miltersrs') + conf.internal_connect = cp.getlist('srsmilter','internal_connect') + conf.srs_reject_spoofed = cp.getboolean('srsmilter','reject_spoofed') conf.trusted_forwarder = cp.getlist('srs','trusted_forwarder') conf.secret = cp.getdefault('srs','secret','shhhh!') conf.maxage = cp.getintdefault('srs','maxage',21) conf.hashlength = cp.getintdefault('srs','hashlength',5) conf.separator = cp.getdefault('srs','separator','=') conf.database = cp.getdefault('srs','database') - conf.srs_reject_spoofed = cp.getboolean('srs','reject_spoofed') conf.nosrsdomain = cp.getlist('srs','nosrs') # no SRS rcpt conf.banned_users = cp.getlist('srs','banned_users') conf.srs_domain = set(cp.getlist('srs','srs')) # check rcpt conf.sesdomain = set(cp.getlist('srs','ses')) # sign from with ses conf.signdomain = set(cp.getlist('srs','sign')) # sign from with srs conf.fwdomain = cp.getdefault('srs','fwdomain',None) # forwarding domain - if database: + if conf.database: + global SRS import SRS.DB conf.srs = SRS.DB.DB(database=conf.database,secret=conf.secret, maxage=conf.maxage,hashlength=conf.hashlength,separator=conf.separator) @@ -167,12 +172,12 @@ self.discard_list.append(rcpt) ## Accumulate added recipients to be applied in eom callback. - def add_recipient(self,rcpt): + def add_recipient(self,rcpt,params): rcpt = rcpt.lower() - if not rcpt in self.redirect_list: - self.redirect_list.append(rcpt) + if not rcpt in (r[0] for r in self.redirect_list): + self.redirect_list.append((rcpt,params)) - def envrcpt(self,to,*str): + def envrcpt(self,to,*params): conf = self.conf t = parse_addr(to) if len(t) == 2: @@ -193,10 +198,11 @@ newaddr = srs.reverse(oldaddr) self.log("srs rcpt:",newaddr) self.del_recipient(to) - self.add_recipient('<%s>',newaddr) + self.add_recipient('<%s>',newaddr,params) except: # no valid SRS signature if not (self.internal_connection or self.trusted_relay): + # reject specific recipients with bad sig if self.srsre.match(oldaddr): self.log("REJECT: srs spoofed:",oldaddr) self.setreply('550','5.7.1','Invalid SRS signature') @@ -205,10 +211,11 @@ self.log("REJECT: ses spoofed:",oldaddr) self.setreply('550','5.7.1','Invalid SES signature') return Milter.REJECT + # reject message for any missing sig self.data_allowed = not conf.srs_reject_spoofed else: # sign "outgoing" from - if domain in nosrsdomain: + if domain in self.conf.nosrsdomain: self.nosrsrcpt.append(to) else: self.srsrcpt.append(to) @@ -216,19 +223,33 @@ self.nosrsrcpt.append(to) return Milter.CONTINUE + def data(self): + if not self.data_allowed: + return Milter.REJECT + return Milter.CONTINUE + def eom(self): - for name,val,idx in self.new_headers: - try: - self.addheader(name,val,idx) - except: - self.addheader(name,val) # older sendmail can't insheader + # apply recipient changes + for to in self.discard_list: + self.delrcpt(to) + for to,p in self.redirect_list: + self.addrcpt(to,p) + # optionally, do outgoing SRS for all recipients + if self.conf.miltersrs and self.srsrcpt: + newaddr = self.make_srs(self.canon_from) + if newaddr != self.canon_from: + self.chgfrom(newaddr) return Milter.CONTINUE if __name__ == "__main__": + global config + config = Config(['pysrs.cfg','/etc/mail/pysrs.cfg']) Milter.factory = srsMilter + if config.miltersrs: + flags = Milter.CHGFROM + Milter.DELRCPT + else: + flags = Milter.DELRCPT Milter.set_flags(Milter.CHGFROM + Milter.DELRCPT) - global config - config = Config(['spfmilter.cfg','/etc/mail/spfmilter.cfg']) miltername = config.miltername socketname = config.socketname print("""To use this with sendmail, add the following to sendmail.cf: @@ -239,5 +260,5 @@ See the sendmail README for libmilter. sample srsmilter startup""" % (miltername,miltername,socketname)) sys.stdout.flush() - Milter.runmilter("pysrsfilter",socketname,240) - print("sample srsmilter shutdown") + Milter.runmilter(miltername,socketname,240) + print("srsmilter shutdown") diff -Nru pysrs-1.0.2/srsmilter.service pysrs-1.0.3/srsmilter.service --- pysrs-1.0.2/srsmilter.service 1970-01-01 00:00:00.000000000 +0000 +++ pysrs-1.0.3/srsmilter.service 2017-11-14 03:14:17.000000000 +0000 @@ -0,0 +1,15 @@ +[Unit] +Description=Python SRS milter +Wants=network.target +After=network-online.target sendmail.service + +[Service] +Type=simple +WorkingDirectory=/var/log/milter +User=mail +Group=mail +SyslogIdentifier=srsmilter +ExecStart=/usr/libexec/milter/srsmilter + +[Install] +WantedBy=multi-user.target diff -Nru pysrs-1.0.2/testSRS.py pysrs-1.0.3/testSRS.py --- pysrs-1.0.2/testSRS.py 2017-11-03 17:37:49.000000000 +0000 +++ pysrs-1.0.3/testSRS.py 2017-11-14 03:14:17.000000000 +0000 @@ -27,20 +27,80 @@ # Translated to Python by stuart@bmsi.com # http://bmsi.com/python/milter.html # +# Copyright (c) 2017 Stuart Gathman All rights reserved. # Portions Copyright (c) 2004 Shevek. All rights reserved. -# Portions Copyright (c) 2004 Business Management Systems. All rights reserved. +# Portions Copyright (c) 2004,2006 Business Management Systems. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the same terms as Python itself. import unittest +import Milter +from Milter.test import TestBase from SRS.Guarded import Guarded from SRS.DB import DB from SRS.Reversible import Reversible from SRS.Daemon import Daemon +import srsmilter import SRS import threading import socket +try: + from StringIO import StringIO +except: + from io import StringIO + +class TestMilter(TestBase,srsmilter.srsMilter): + def __init__(self): + TestBase.__init__(self) + srsmilter.config = srsmilter.Config(['pysrs.cfg']) + srsmilter.srsMilter.__init__(self) + self.setsymval('j','test.milter.org') + +class SRSMilterTestCase(unittest.TestCase): + + msg = '''From: good@example.com +Subject: test + +test +''' + + ## Test rejecting bounce spam + def testReject(self): + milter = TestMilter() + milter.conf.srs_domain = set(['example.com']) + milter.conf.srs_reject_spoofed = False + fp = StringIO(self.msg) + rc = milter.connect('testReject',ip='192.0.3.1') + self.assertEqual(rc,Milter.CONTINUE) + rc = milter.feedFile(fp,sender='',rcpt='good@example.org') + self.assertEqual(rc,Milter.CONTINUE) + milter.conf.srs_reject_spoofed = True + fp.seek(0) + rc = milter.feedFile(fp,sender='',rcpt='bad@example.com') + self.assertEqual(rc,Milter.REJECT) + milter.close() + + ## Test SRS coding of MAIL FROM + def testSign(self): + milter = TestMilter() + milter.conf.signdomain = set(['example.com']) + milter.conf.miltersrs = True + fp = StringIO(self.msg) + rc = milter.connect('testSign',ip='192.0.3.1') + self.assertEqual(rc,Milter.CONTINUE) + fp.seek(0) + rc = milter.feedFile(fp,sender='good@example.com',rcpt='good@example.org') + self.assertEqual(rc,Milter.CONTINUE) + s = milter.conf.srs.reverse(milter._sender[1:-1]) + self.assertEqual(s,'good@example.com') + # check that it doesn't happen when disabled + milter.conf.miltersrs = False + fp.seek(0) + rc = milter.feedFile(fp,sender='good@example.com',rcpt='good@example.org') + self.assertEqual(rc,Milter.CONTINUE) + self.assertEqual(milter._sender,'') + milter.close() class SRSTestCase(unittest.TestCase): @@ -191,7 +251,11 @@ self.assertEqual(addr2,orig) self.assertTrue(self.case_smashed) -def suite(): return unittest.makeSuite(SRSTestCase,'test') +def suite(): + s = unittest.makeSuite(SRSTestCase,'test') + s.addTest(makeSuite(SRSMilterTestCase,'test')) + #s.addTest(doctest.DocTestSuite(bms)) + return s if __name__ == '__main__': unittest.main()