From 6577e40bfbbef51f07c529af3fa832edd295b3ac Mon Sep 17 00:00:00 2001 From: Stuart Gathman <stuart@gathman.org> Date: Wed, 25 Jul 2007 15:32:09 +0000 Subject: [PATCH] Build pymilter as separate package. --- milter-template.py | 155 +++++++++++++++++++++++++++++++++++++++++++++ milter.spec | 17 ++++- setup.py | 2 +- 3 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 milter-template.py diff --git a/milter-template.py b/milter-template.py new file mode 100644 index 0000000..510bf02 --- /dev/null +++ b/milter-template.py @@ -0,0 +1,155 @@ +## To roll your own milter, create a class that extends Milter. +# See the pymilter project at http://bmsi.com/python/milter.html +# based on Sendmail's milter API http://www.milter.org/milter_api/api.html +# This code is open-source on the same terms as Python. + +## Milter calls methods of your class at milter events. +## Return REJECT,TEMPFAIL,ACCEPT to short circuit processing for a message. +## You can also add/del recipients, replacebody, add/del headers, etc. + +import Milter +import StringIO +import time +import email +from socket import AF_INET, AF_INET6 + +def parse_addr(t): + """Split email into user,domain. + + >>> parse_addr('user@example.com') + ['user', 'example.com'] + >>> parse_addr('"user@example.com"') + ['user@example.com'] + >>> parse_addr('"user@bar"@example.com') + ['user@bar', 'example.com'] + >>> parse_addr('foo') + ['foo'] + """ + if t.startswith('<') and t.endswith('>'): t = t[1:-1] + if t.startswith('"'): + if t.endswith('"'): return [t[1:-1]] + pos = t.find('"@') + if pos > 0: return [t[1:pos],t[pos+2:]] + return t.split('@') + +class myMilter(Milter.Milter): + + def __init__(self): # A new instance with each new connection. + self.id = Milter.uniqueID() # Integer incremented with each call. + + # each connection runs in its own thread and has its own myMilter + # instance. Python code must be thread safe. This is trivial if only stuff + # in myMilter instances is referenced. + def connect(self, IPname, family, hostaddr): + # (self, 'ip068.subnet71.example.com', AF_INET, ('215.183.71.68', 4720) ) + # (self, 'ip6.mxout.example.com', AF_INET6, + # ('3ffe:80e8:d8::1', 4720, 1, 0) ) + self.IP = hostaddr[0] + self.port = hostaddr[1] + if family == AF_INET6: + self.flow = hostaddr[2] + self.scope = hostaddr[3] + else: + self.flow = None + self.scope = None + self.IPname = IPname # Name from a reverse IP lookup + self.H = None + self.fp = None + self.receiver = self.getsymval('j') + self.log("connect from %s at %s" % (IPname, hostaddr) ) + + return Milter.CONTINUE + + + ## def hello(self,hostname): + def hello(self, heloname): + # (self, 'mailout17.dallas.texas.example.com') + self.H = heloname + self.log("HELO %s" % heloname) + if heloname.find('.') < 0: # illegal helo name + # NOTE: example only - too many real braindead clients to reject on this + self.setreply('550','5.7.1','Sheesh people! Use a proper helo name!') + return Milter.REJECT + + return Milter.CONTINUE + + ## def envfrom(self,f,*str): + def envfrom(self, mailfrom, *str): + self.F = mailfrom + self.R = [] # list of recipients + self.fromparms = Milter.dictfromlist(str) # ESMTP parms + self.user = self.getsymval('{auth_authen}') # authenticated user + self.log("mail from:", mailfrom, *str) + self.fp = StringIO.StringIO() + self.canon_from = '@'.join(parse_addr(mailfrom)) + self.fp.write('From %s %s\n' % (self.canon_from,time.ctime())) + return Milter.CONTINUE + + + ## def envrcpt(self, to, *str): + def envrcpt(self, recipient, *str): + rcptinfo = to,Milter.dictfromlist(str) + self.R.append(rcptinfo) + + return Milter.CONTINUE + + + def header(self, name, hval): + self.fp.write("%s: %s\n" % (name,hval)) # add header to buffer + return Milter.CONTINUE + + + def eoh(self): + self.fp.write("\n") # terminate headers + return Milter.CONTINUE + + + def body(self, chunk): + self.fp.write(chunk) + return Milter.CONTINUE + + + def eom(self): + self.fp.seek(0) + msg = email.message_from_file(self.fp) + self.setreply('250','2.5.1','Grokked by pymilter') + # many milter functions can only be called from eom() + # example of adding a Bcc: + self.addrcpt('<%s>' % 'spy@example.com') + return Milter.ACCEPT + + + def close(self): + # always called, even when abort is called. Clean up + # any external resources here. + return Milter.CONTINUE + + def abort(self): + # client disconnected prematurely + return Milter.CONTINUE + + ## === Support Functions === + + def log(self,*msg): + print "%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S'),self.id), + # 2005Oct13 02:34:11 [1] msg1 msg2 msg3 ... + for i in msg: print i, + print + + +## === + +def main(): + # Register to have the Milter factory create instances of your class: + Milter.factory = myMilter + flags = Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS + flags += Milter.ADDRCPT + flags += Milter.DELRCPT + Milter.set_flags(flags) # tell Sendmail which features we use + print "%s milter startup" % time.strftime('%Y%b%d %H:%M:%S') + sys.stdout.flush() + Milter.runmilter("pythonfilter",socketname,timeout) + print "%s bms milter shutdown" % time.strftime('%Y%b%d %H:%M:%S') + +if __name__ == "__main__": + main() diff --git a/milter.spec b/milter.spec index ac12761..36e198e 100644 --- a/milter.spec +++ b/milter.spec @@ -1,5 +1,5 @@ -%define name milter -%define version 0.8.7 +%define name pymilter +%define version 0.8.8 %define release 1 # what version of RH are we building for? %define redhat7 0 @@ -37,7 +37,7 @@ Prefix: %{_prefix} Vendor: Stuart D. Gathman <stuart@bmsi.com> Packager: Stuart D. Gathman <stuart@bmsi.com> Url: http://www.bmsi.com/python/milter.html -Requires: %{python} >= 2.4, sendmail >= 8.13, pyspf >= 2.0.4 +Requires: %{python} >= 2.4, sendmail >= 8.13 %ifos Linux Requires: chkconfig %endif @@ -49,6 +49,14 @@ attach to sendmail's libmilter functionality. Additional python modules provide for navigating and modifying MIME parts, sending DSNs, and doing CBV. +%package -n milter +Group: Applications/System +Summary: BMS spam and reputation milter +Requires: pyspf >= 2.0.4 + +%description -n milter +An effective spam filtering and reputation tracking mail application. + %prep %setup #patch -p0 -b .bms @@ -158,6 +166,9 @@ rm -rf $RPM_BUILD_ROOT %files -f INSTALLED_FILES %defattr(-,root,root) %doc README HOWTO ChangeLog NEWS TODO CREDITS sample.py milter-template.py + +%files -n milter +%defattr(-,root,root) /etc/logrotate.d/milter /etc/cron.daily/milter %ifos aix4.1 diff --git a/setup.py b/setup.py index d3a13e7..abeb732 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ if sys.version < '2.2.3': DistributionMetadata.download_url = None # NOTE: importing Milter to obtain version fails when milter.so not built -setup(name = "milter", version = '0.8.8', +setup(name = "pymilter", version = '0.8.8', description="Python interface to sendmail milter API", long_description="""\ This is a python extension module to enable python scripts to -- GitLab