From c0aa632e166f1637177238b26dda3ef46dbc2c93 Mon Sep 17 00:00:00 2001
From: Stuart Gathman <stuart@gathman.org>
Date: Thu, 11 Jan 2007 04:31:26 +0000
Subject: [PATCH] Negative feedback for bad headers.  Purge cache logs on
 startup.

---
 Milter/cache.py | 15 +++++++++---
 Milter/plock.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++
 bms.py          |  8 ++++++-
 3 files changed, 81 insertions(+), 4 deletions(-)
 create mode 100644 Milter/plock.py

diff --git a/Milter/cache.py b/Milter/cache.py
index 8f9c824..17b2765 100644
--- a/Milter/cache.py
+++ b/Milter/cache.py
@@ -10,6 +10,9 @@
 # CBV results.
 #
 # $Log$
+# 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
 #
@@ -22,6 +25,7 @@
 # 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'
@@ -39,6 +43,8 @@ class AddrCache(object):
     cache = {}
     self.cache = cache
     now = time.time()
+    lock = PLock(self.fname)
+    wfp = lock.lock()
     try:
       too_old = now - age*24*60*60	# max age in days
       for ln in open(self.fname):
@@ -46,11 +52,14 @@ class AddrCache(object):
 	  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)
+	  if t < too_old: continue
+	  cache[rcpt.lower()] = (t,None)
 	except:
 	  cache[ln.strip().lower()] = (now,None)
-    except IOError: pass
+	wfp.write(ln)
+      lock.commit(self.fname+'.old')
+    except IOError:
+      lock.unlock()
 
   def has_key(self,sender):
     "True if sender is cached and has not expired."
diff --git a/Milter/plock.py b/Milter/plock.py
new file mode 100644
index 0000000..d4df9b7
--- /dev/null
+++ b/Milter/plock.py
@@ -0,0 +1,62 @@
+# Author: Stuart D. Gathman <stuart@bmsi.com>
+# Copyright 2001 Business Management Systems, Inc.
+# This code is under the GNU General Public License.  See COPYING for details.
+
+import os
+from time import sleep
+
+class PLock(object):
+  "A simple /etc/passwd style lock,update,rename protocol for updating files."
+  def __init__(self,basename):
+    self.basename = basename
+    self.fp = None
+
+  def lock(self,lockname=None):
+    "Start an update transaction.  Return FILE to write new version."
+    self.unlock()
+    if not lockname:
+      lockname = self.basename + '.lock'
+    self.lockname = lockname
+    st = os.stat(self.basename)
+    u = os.umask(0002)
+    try:
+      fd = os.open(lockname,os.O_WRONLY+os.O_CREAT+os.O_EXCL,st.st_mode|0660)
+    finally:
+      os.umask(u)
+    self.fp = os.fdopen(fd,'w')
+    try:
+      os.chown(self.lockname,-1,st.st_gid)
+    except:
+      self.unlock()
+      raise
+    return self.fp
+
+  def wlock(self,lockname=None):
+    "Wait until lock is free, then start an update transaction."
+    while True:
+      try:
+        return self.lock(lockname)
+      except OSError:
+        sleep(2)
+
+  def commit(self,backname=None):
+    "Commit update transaction with optional backup file."
+    if not self.fp:
+      raise IOError,"File not locked"
+    self.fp.close()
+    self.fp = None
+    if backname:
+      try:
+	os.remove(backname)
+      except OSError: pass
+      os.link(self.basename,backname)
+    os.rename(self.lockname,self.basename)
+
+  def unlock(self):
+    "Cancel update transaction."
+    if self.fp:
+      try:
+        self.fp.close()
+      except: pass
+      self.fp = None
+      os.remove(self.lockname)
diff --git a/bms.py b/bms.py
index 4032c5d..5b63069 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.84  2007/01/10 04:44:25  customdesigned
+# Documentation updates.
+#
 # Revision 1.83  2007/01/08 23:20:54  customdesigned
 # Get user feedback.
 #
@@ -1017,7 +1020,10 @@ class bmsMilter(Milter.Milter):
     val = parse_header(hval)
     if not self.internal_connection and not (self.blacklist or self.whitelist):
       rc = self.check_header(name,val)
-      if rc != Milter.CONTINUE: return rc
+      if rc != Milter.CONTINUE:
+        if gossip and self.umis:
+	  gossip_node.feedback(self.umis,1)
+        return rc
     elif self.whitelist_sender and lname == 'subject':
 	# check for AutoReplys
 	vl = val.lower()
-- 
GitLab