diff --git a/Milter/cache.py b/Milter/cache.py new file mode 100644 index 0000000000000000000000000000000000000000..94df3b1ec7b7e6d9da51eb99b0de17abe43f0a59 --- /dev/null +++ b/Milter/cache.py @@ -0,0 +1,108 @@ +# Email address list with expiration +# +# This class acts like a map. Entries with a value of None are persistent, +# but disappear after a time limit. This is useful for automatic whitelists +# and blacklists with expiration. The persistent store is a simple ascii +# file with sender and timestamp on each line. Entries can be appended +# to the store, and will be picked up the next time it is loaded. +# +# Entries with other values are not persistent. This is used to hold failed +# CBV results. +# +# $Log$ + +# Author: Stuart D. Gathman <stuart@bmsi.com> +# Copyright 2001,2002,2003,2004,2005 Business Management Systems, Inc. +# This code is under the GNU General Public License. See COPYING for details. + +import time + +class AddrCache(object): + time_format = '%Y%b%d %H:%M:%S %Z' + + def __init__(self,renew=7,fname=None): + self.age = renew + self.cache = {} + self.fname = fname + + def load(self,fname,age=0): + "Load address cache from persistent store." + if not age: + age = self.age + self.fname = fname + cache = {} + self.cache = cache + now = time.time() + try: + too_old = now - age*24*60*60 # max age in days + for ln in open(self.fname): + try: + rcpt,ts = ln.strip().split(None,1) + l = time.strptime(ts,AddrCache.time_format) + t = time.mktime(l) + if t > too_old: + cache[rcpt.lower()] = (t,None) + except: + cache[ln.strip().lower()] = (now,None) + except IOError: pass + + def has_key(self,sender): + "True if sender is cached and has not expired." + try: + lsender = sender.lower() + ts,res = self.cache[lsender] + too_old = time.time() - self.age*24*60*60 # max age in days + if not ts or ts > too_old: + return True + del self.cache[lsender] + try: + user,host = sender.split('@',1) + return self.has_key(host) + except ValueError: + pass + except KeyError: + try: + user,host = sender.split('@',1) + return self.has_key(host) + except ValueError: + pass + return False + + def __getitem__(self,sender): + try: + lsender = sender.lower() + ts,res = self.cache[lsender] + too_old = time.time() - self.age*24*60*60 # max age in days + if not ts or ts > too_old: + return res + del self.cache[lsender] + raise KeyError, sender + except KeyError,x: + try: + user,host = sender.split('@',1) + return self.__getitem__(host) + except ValueError: + raise x + + def addperm(self,sender,res=None): + "Add a permanent sender." + lsender = sender.lower() + if self.has_key(lsender): + ts,res = self.cache[lsender] + if not ts: return # already permanent + self.cache[lsender] = (None,res) + if not res: + print >>open(self.fname,'a'),sender + + def __setitem__(self,sender,res): + lsender = sender.lower() + now = time.time() + cached = self.has_key(sender) + if not cached: + self.cache[lsender] = (now,res) + if not res and self.fname: + s = time.strftime(AddrCache.time_format,time.localtime(now)) + print >>open(self.fname,'a'),sender,s # log refreshed senders + + def __len__(self): + return len(self.cache) diff --git a/TODO b/TODO index 5b1db9b172f92e55a59b4c53a47bc833c34b1054..e3e282197ea6d30053495c0e4b7da6bce1fb94e0 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,6 @@ -When bms.py can't find templates, it passes None to dsn.create_msg(), -which uses local variable as backup, which no longer exist. - -Purge old GOSSiP records nightly. +DONE When bms.py can't find templates, it passes None to dsn.create_msg(), +which uses local variable as backup, which no longer exist. Do plain +CBV in that case instead. Find and use X-GOSSiP: header for SPAM: and FP: submissions. Would need to keep tags longer. diff --git a/bms.py b/bms.py index 248f7aac6e7e42deb6fa909af2f2ba0257b0fa4c..164f17370f51eaafca8dba60f46e34cc623c47c7 100644 --- a/bms.py +++ b/bms.py @@ -1,6 +1,9 @@ #!/usr/bin/env python # A simple milter that has grown quite a bit. # $Log$ +# Revision 1.78 2007/01/04 18:01:10 customdesigned +# Do plain CBV when template missing. +# # Revision 1.77 2006/12/31 03:07:20 customdesigned # Use HELO identity if good when MAILFROM is bad. # @@ -510,79 +513,7 @@ def iniplist(ipaddr,iplist): return True return False -class AddrCache(object): - time_format = '%Y%b%d %H:%M:%S %Z' - - def __init__(self,renew=7): - self.age = renew - - def load(self,fname,age=0): - if not age: - age = self.age - self.fname = fname - cache = {} - self.cache = cache - now = time.time() - try: - too_old = now - age*24*60*60 # max age in days - for ln in open(self.fname): - try: - rcpt,ts = ln.strip().split(None,1) - l = time.strptime(ts,AddrCache.time_format) - t = time.mktime(l) - if t > too_old: - cache[rcpt.lower()] = (t,None) - except: - cache[ln.strip().lower()] = (now,None) - except IOError: pass - - def has_key(self,sender): - try: - ts,res = self.cache[sender.lower()] - too_old = time.time() - self.age*24*60*60 # max age in days - if ts > too_old: - return True - del self.cache[sender.lower()] - try: - user,host = sender.split('@',1) - return self.has_key(host) - except ValueError: - pass - except KeyError: - try: - user,host = sender.split('@',1) - return self.has_key(host) - except ValueError: - pass - return False - - def __getitem__(self,sender): - try: - ts,res = self.cache[sender.lower()] - too_old = time.time() - self.age*24*60*60 # max age in days - if ts > too_old: - return res - del self.cache[sender.lower()] - raise KeyError, sender - except KeyError,x: - try: - user,host = sender.split('@',1) - return self.__getitem__(host) - except ValueError: - raise x - - def __setitem__(self,sender,res): - lsender = sender.lower() - now = time.time() - cached = self.has_key(sender) - if not cached: - self.cache[lsender] = (now,res) - if not res: - s = time.strftime(AddrCache.time_format,time.localtime(now)) - print >>open(self.fname,'a'),sender,s # log refreshed senders - - def __len__(self): - return len(self.cache) +from Milter.cache import AddrCache cbv_cache = AddrCache(renew=7) cbv_cache.load('send_dsn.log',age=7) diff --git a/milter.spec b/milter.spec index c674a29cc3e6c134cef74395e9fecccb85b50f12..4da9f8bed71fe670e31078d3a24eb0252b658df5 100644 --- a/milter.spec +++ b/milter.spec @@ -11,7 +11,8 @@ # some systems dont have initrddir defined %{?_initrddir:%define _initrddir /etc/rc.d/init.d} -%if %{redhat7} # Redhat 7.x and earlier (multiple ps lines per thread) +%if %{redhat7} +# Redhat 7.x and earlier (multiple ps lines per thread) %define sysvinit milter.rc7 %else %define sysvinit milter.rc @@ -167,6 +168,7 @@ rm -rf $RPM_BUILD_ROOT %config /var/log/milter/bms.py %config(noreplace) /var/log/milter/strike3.txt %config(noreplace) /var/log/milter/softfail.txt +%config(noreplace) /var/log/milter/fail.txt %config(noreplace) /var/log/milter/neutral.txt %config(noreplace) /var/log/milter/quarantine.txt %config(noreplace) /var/log/milter/permerror.txt diff --git a/test.py b/test.py index 0e12658b3f4bd559a60ef3a76608a11890862cdf..90c3d427d17e0a4cdd0b57a7a063847f94fdc6e8 100644 --- a/test.py +++ b/test.py @@ -2,6 +2,7 @@ import unittest import testbms import testmime import testsample +import testcache import os def suite(): @@ -9,6 +10,7 @@ def suite(): s.addTest(testbms.suite()) s.addTest(testmime.suite()) s.addTest(testsample.suite()) + s.addTest(testcache.suite()) return s if __name__ == '__main__':