Select Git revision
-
Stuart Gathman authoredStuart Gathman authored
cache.py 3.95 KiB
# 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$
# Revision 1.6 2007/01/19 23:31:38 customdesigned
# Move parse_header to Milter.utils.
# Test case for delayed DSN parsing.
# Fix plock when source missing or cannot set owner/group.
#
# Revision 1.5 2007/01/11 19:59:40 customdesigned
# Purge old entries in auto_whitelist and send_dsn logs.
#
# Revision 1.4 2007/01/11 04:31:26 customdesigned
# Negative feedback for bad headers. Purge cache logs on startup.
#
# Revision 1.3 2007/01/08 23:20:54 customdesigned
# Get user feedback.
#
# Revision 1.2 2007/01/05 23:33:55 customdesigned
# Make blacklist an AddrCache
#
# Revision 1.1 2007/01/05 21:25:40 customdesigned
# Move AddrCache to Milter package.
#
# 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
from plock import PLock
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()
lock = PLock(self.fname)
wfp = lock.lock()
changed = False
try:
too_old = now - age*24*60*60 # max age in days
try:
fp = open(self.fname)
except OSError:
fp = ()
for ln in fp:
try:
rcpt,ts = ln.strip().split(None,1)
l = time.strptime(ts,AddrCache.time_format)
t = time.mktime(l)
if t < too_old:
changed = True
continue
cache[rcpt.lower()] = (t,None)
except:
cache[ln.strip().lower()] = (now,None)
wfp.write(ln)
if changed:
lock.commit(self.fname+'.old')
else:
lock.unlock()
except IOError:
lock.unlock()
def has_key(self,sender):
"True if sender is cached and has not expired."
try:
lsender = sender and 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: pass
return False
__contains__ = has_key
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()
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)