Index: ACKNOWLEDGMENTS
===================================================================
RCS file: /cvsroot/mailman/mailman/ACKNOWLEDGMENTS,v
retrieving revision 1.35
retrieving revision 1.35.2.1
diff -u -r1.35 -r1.35.2.1
--- ACKNOWLEDGMENTS	16 Nov 2000 21:57:06 -0000	1.35
+++ ACKNOWLEDGMENTS	3 Apr 2002 05:07:52 -0000	1.35.2.1
@@ -39,6 +39,7 @@
     Stan Bubrouski
     Ted Cabeen
     David Champion
+    Rob Ellis
     Fil
     Erik Forsberg
     Darrell Fuhriman
@@ -50,6 +51,7 @@
     Andrew Kuchling
     Ricardo Kustner
     J C Lawrence
+    Greg Lindahl
     Christopher P. Lindsey
     Tanner Lovelace
     Gergely Madarasz
Index: NEWS
===================================================================
RCS file: /cvsroot/mailman/mailman/NEWS,v
retrieving revision 1.25.2.11
retrieving revision 1.25.2.13
diff -u -r1.25.2.11 -r1.25.2.13
--- NEWS	27 Nov 2001 22:52:39 -0000	1.25.2.11
+++ NEWS	3 Apr 2002 22:50:10 -0000	1.25.2.13
@@ -4,6 +4,26 @@
 
 Here is a history of user visible changes to Mailman.
 
+2.0.9 (02-Apr-2002)
+
+    - Closed a race condition which could, under rare circumstances,
+      cause the occasional message to get lost.
+
+    - HTML escape message excerpts and headers on the admindb page so
+      JavaScript and other evil tags can't mess up the display.
+
+    - Some additional Python 2.2 compatibility fixes.
+
+    - Unlink the footer logos so as not to bug the python.org and
+      gnu.org maintainers as much. :(
+
+    - Fix a crash in the DSN bounce detection module, which could
+      cause some bounce messages to remain in the queue.
+
+    - Add the RFC-2822 mandated Date: header on internally generated
+      outgoing messages.  Not all MTAs add this field if missing
+      (read: Qmail).
+
 2.0.8 (27-Nov-2001)
 
     Security fix release to prevent cross-site scripting exploits.
Index: Mailman/Message.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/Message.py,v
retrieving revision 1.40
retrieving revision 1.40.2.2
diff -u -r1.40 -r1.40.2.2
--- Mailman/Message.py	2 Aug 2000 02:00:13 -0000	1.40
+++ Mailman/Message.py	3 Apr 2002 22:40:41 -0000	1.40.2.2
@@ -177,11 +177,14 @@
         marshal.dump(msgdata, dbfp)
         dbfp.close()
         # If it doesn't already exist, or if the text of the message has
-        # changed, write the message file to disk.
+        # changed, write the message file to disk, but do it atomically so as
+        # not to hit a race condition with the qrunner.
         if not existsp or dirty:
-            msgfp = Utils.open_ex(msgfile, 'w')
+            tmpfile = msgfile + '.tmp'
+            msgfp = Utils.open_ex(tmpfile, 'w')
             msgfp.write(text)
             msgfp.close()
+            os.rename(tmpfile, msgfile)
 
 
 
@@ -192,6 +195,10 @@
         # also begin with the body of the message but in that case you better
         # make sure that the first line does NOT contain a colon!
         Message.__init__(self, StringIO(text))
+        # RFC 2822 requires a Date: header, and while most MTAs add one if
+        # it's missing, Qmail does not.
+        if not self.get('date'):
+            self['Date'] = Utils.formatdate(localtime=1)
 
 
 
Index: Mailman/Utils.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/Utils.py,v
retrieving revision 1.104.2.4
retrieving revision 1.104.2.5
diff -u -r1.104.2.4 -r1.104.2.5
--- Mailman/Utils.py	25 Jul 2001 18:06:46 -0000	1.104.2.4
+++ Mailman/Utils.py	3 Apr 2002 22:47:12 -0000	1.104.2.5
@@ -27,6 +27,7 @@
 import os
 import string
 import re
+import time
 from UserDict import UserDict
 from types import StringType
 import random
@@ -690,3 +691,49 @@
     if kws:
         raise TypeError('unexpected keywords')
     file.write(string.join(map(str, args), sep) + end)
+
+
+
+def formatdate(timeval=None, localtime=0):
+    """Returns a date string as specified by RFC 2822, e.g.:
+
+    Fri, 09 Nov 2001 01:08:47 -0000
+
+    Optional timeval if given is a floating point time value as accepted by
+    gmtime() and localtime(), otherwise the current time is used.
+
+    Optional localtime is a flag that when true, interprets timeval, and
+    returns a date relative to the local timezone instead of UTC, properly
+    taking daylight savings time into account.
+    """
+    # Note: we cannot use strftime() because that honors the locale and RFC
+    # 2822 requires that day and month names be the English abbreviations.
+    if timeval is None:
+        timeval = time.time()
+    if localtime:
+        now = time.localtime(timeval)
+        # Calculate timezone offset, based on whether the local zone has
+        # daylight savings time, and whether DST is in effect.
+        if time.daylight and now[-1]:
+            offset = time.altzone
+        else:
+            offset = time.timezone
+        hours, minutes = divmod(abs(offset), 3600)
+        # Remember offset is in seconds west of UTC, but the timezone is in
+        # minutes east of UTC, so the signs differ.
+        if offset > 0:
+            sign = '-'
+        else:
+            sign = '+'
+        zone = '%s%02d%02d' % (sign, hours, minutes / 60)
+    else:
+        now = time.gmtime(timeval)
+        # Timezone offset is always -0000
+        zone = '-0000'
+    return '%s, %02d %s %04d %02d:%02d:%02d %s' % (
+        ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][now[6]],
+        now[2],
+        ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+         'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now[1] - 1],
+        now[0], now[3], now[4], now[5],
+        zone)
Index: Mailman/Version.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/Version.py,v
retrieving revision 1.20.2.8
retrieving revision 1.20.2.9
diff -u -r1.20.2.8 -r1.20.2.9
--- Mailman/Version.py	27 Nov 2001 22:47:00 -0000	1.20.2.8
+++ Mailman/Version.py	2 Apr 2002 23:36:35 -0000	1.20.2.9
@@ -15,7 +15,7 @@
 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
 # Mailman version
-VERSION = "2.0.8"
+VERSION = "2.0.9"
 
 # And as a hex number in the manner of PY_VERSION_HEX
 ALPHA = 0xa
@@ -27,7 +27,7 @@
 
 MAJOR_REV = 2
 MINOR_REV = 0
-MICRO_REV = 8
+MICRO_REV = 9
 REL_LEVEL = FINAL
 # at most 15 beta releases!
 REL_SERIAL = 0
Index: Mailman/htmlformat.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/htmlformat.py,v
retrieving revision 1.31
retrieving revision 1.31.2.1
diff -u -r1.31 -r1.31.2.1
--- Mailman/htmlformat.py	12 Sep 2000 04:02:36 -0000	1.31
+++ Mailman/htmlformat.py	2 Apr 2002 22:28:59 -0000	1.31.2.1
@@ -519,23 +519,13 @@
     if mm_cfg.IMAGE_LOGOS:
         def logo(file):
             return mm_cfg.IMAGE_LOGOS + file
-        mmlink = Link(MAILMAN_URL,
-                      '<img src="%s" alt="Delivered by Mailman" border=0>'
-                      '<br>version %s'
-                      % (logo(DELIVERED_BY), mm_cfg.VERSION))
-        pylink = Link(PYTHON_URL,
-                      '<img src="%s" alt="Python Powered" border=0>' %
-                      logo(PYTHON_POWERED))
-        gnulink = Link(GNU_URL,
-                       '<img src="%s" alt="GNU\'s Not Unix" border=0>' %
-                       logo(GNU_HEAD))
-        text = Container(Link(MAILMAN_URL, 'Mailman home page'),
-                         '<br>',
-                         Link(PYTHON_URL, 'Python home page'),
-                         '<br>',
-                         Link(GNU_URL, 'GNU home page'),
-                         )
-        t.AddRow([mmlink, pylink, gnulink, text])
+        mmlink = '<img src="%s" alt="Delivered by Mailman" border=0>' \
+                 '<br>version %s' % (logo(DELIVERED_BY), mm_cfg.VERSION)
+        pylink = '<img src="%s" alt="Python Powered" border=0>' % \
+                 logo(PYTHON_POWERED)
+        gnulink = '<img src="%s" alt="GNU\'s Not Unix" border=0>' % \
+                  logo(GNU_HEAD)
+        t.AddRow([mmlink, pylink, gnulink])
     else:
         # use only textual links
         mmlink = Link(MAILMAN_URL,
Index: Mailman/Archiver/HyperArch.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/Archiver/HyperArch.py,v
retrieving revision 1.46
retrieving revision 1.46.2.1
diff -u -r1.46 -r1.46.2.1
--- Mailman/Archiver/HyperArch.py	13 Nov 2000 21:50:05 -0000	1.46
+++ Mailman/Archiver/HyperArch.py	2 Apr 2002 23:39:35 -0000	1.46.2.1
@@ -39,11 +39,12 @@
 import time
 import pickle
 import os
-import posixfile
 import HyperDatabase
 import pipermail
 
-from Mailman import mm_cfg, EncWord
+from Mailman import mm_cfg
+from Mailman import LockFile
+from Mailman import EncWord
 from Mailman.Logging.Syslog import syslog
 
 from Mailman.Utils import mkdir, open_ex
@@ -699,27 +700,18 @@
     def GetArchLock(self):
         if self._lock_file:
             return 1
-        # TBD: This needs to be rewritten to use the generalized locking
-        # mechanism (when that exists).  -baw
-        ou = os.umask(0)
+        self._lock_file = LockFile.LockFile(
+            os.path.join(mm_cfg.LOCK_DIR,
+                         self.maillist.internal_name() + '-arch.lock'))
         try:
-            self._lock_file = posixfile.open(
-                os.path.join(mm_cfg.LOCK_DIR, '%s@arch.lock'
-                             % self.maillist.internal_name()), 'a+')
-        finally:
-            os.umask(ou)
-        # minor race condition here, there is no way to atomicly 
-        # check & get a lock. That shouldn't matter here tho' -ddm
-        if not self._lock_file.lock('w?', 1):
-            self._lock_file.lock('w|', 1)
-        else:
+            self._lock_file.lock(timeout=0.5)
+        except LockFile.TimeOutError:
             return 0
         return 1
 
     def DropArchLock(self):
         if self._lock_file:
-            self._lock_file.lock('u')
-            self._lock_file.close()
+            self._lock_file.unlock(unconditionally=1)
             self._lock_file = None
 
     def processListArch(self):
Index: Mailman/Bouncers/DSN.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/Bouncers/DSN.py,v
retrieving revision 1.7.2.2
retrieving revision 1.7.2.4
diff -u -r1.7.2.2 -r1.7.2.4
--- Mailman/Bouncers/DSN.py	6 Nov 2001 04:27:30 -0000	1.7.2.2
+++ Mailman/Bouncers/DSN.py	29 Mar 2002 18:44:05 -0000	1.7.2.4
@@ -43,7 +43,7 @@
 
 
 def process(msg):
-    ctype = msg.gettype()
+    ctype = msg.gettype() or ''
     param = msg.getparam('report-type') or ''
     if string.lower(ctype) <> 'multipart/report' or \
        string.lower(param) <> 'delivery-status':
@@ -59,6 +59,11 @@
             more = mfile.next()
         except multifile.Error:
             # the message *looked* like a DSN, but it really wasn't :(
+            return None
+        except TypeError:
+            # This message has serious problems, pass it on.  Seen in the
+            # wild: it is a MIME document with no boundary parameter in its
+            # Content-Type: header.
             return None
         if not more:
             # we didn't find it
Index: Mailman/Cgi/admindb.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/Cgi/admindb.py,v
retrieving revision 1.36.2.5
retrieving revision 1.36.2.7
diff -u -r1.36.2.5 -r1.36.2.7
--- Mailman/Cgi/admindb.py	27 Nov 2001 20:23:53 -0000	1.36.2.5
+++ Mailman/Cgi/admindb.py	1 Apr 2002 18:47:46 -0000	1.36.2.7
@@ -207,7 +207,7 @@
             return
         raise
     t = Table(cellspacing=0, cellpadding=0, width='100%')
-    t.AddRow([Bold('From:'), sender])
+    t.AddRow([Bold('From:'), cgi.escape(sender)])
     row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex()
     t.AddCellInfo(row, col-1, align='right')
     t.AddRow([Bold('Subject:'), cgi.escape(subject)])
@@ -247,12 +247,14 @@
     row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex()
     t.AddCellInfo(row, col-1, align='right')
     t.AddRow([Bold('Message Headers:'),
-              TextArea('headers-%d' % id, string.join(msg.headers, ''),
+              TextArea('headers-%d' % id,
+                       cgi.escape(string.join(msg.headers, '')),
                        rows=10, cols=80)])
     row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex()
     t.AddCellInfo(row, col-1, align='right')
     t.AddRow([Bold('Message Excerpt:'),
-              TextArea('fulltext-%d' % id, text, rows=10, cols=80)])
+              TextArea('fulltext-%d' % id, cgi.escape(text),
+                       rows=10, cols=80)])
     t.AddCellInfo(row+1, col-1, align='right')
     form.AddItem(t)
     form.AddItem('<p>')
Index: admin/www/download.ht
===================================================================
RCS file: /cvsroot/mailman/mailman/admin/www/download.ht,v
retrieving revision 1.5.2.9
retrieving revision 1.5.2.10
diff -u -r1.5.2.9 -r1.5.2.10
--- admin/www/download.ht	27 Nov 2001 22:27:42 -0000	1.5.2.9
+++ admin/www/download.ht	3 Apr 2002 05:11:22 -0000	1.5.2.10
@@ -65,9 +65,9 @@
 <h3>Downloading</h3>
 
 <p>Version
-(<!-VERSION--->2.0.8<!-VERSION--->,
+(<!-VERSION--->2.0.9<!-VERSION--->,
 released on
-<!-DATE--->Nov 27 2001<!-DATE--->)
+<!-DATE--->Apr  3 2002<!-DATE--->)
 is the current GNU release.  It is available from the following mirror sites:
 
 <ul>
Index: admin/www/download.html
===================================================================
RCS file: /cvsroot/mailman/mailman/admin/www/download.html,v
retrieving revision 1.6.2.11
retrieving revision 1.6.2.12
diff -u -r1.6.2.11 -r1.6.2.12
--- admin/www/download.html	27 Nov 2001 22:27:42 -0000	1.6.2.11
+++ admin/www/download.html	3 Apr 2002 05:11:22 -0000	1.6.2.12
@@ -1,8 +1,9 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 <HTML>
 <!-- THIS PAGE IS AUTOMATICALLY GENERATED.  DO NOT EDIT. -->
-<!-- Tue Nov 27 17:26:44 2001 -->
-<!-- USING HT2HTML 1.2 -->
-<!-- SEE http://barry.wooz.org/software/ht2html -->
+<!-- Wed Apr  3 00:10:44 2002 -->
+<!-- USING HT2HTML 2.0 -->
+<!-- SEE http://ht2html.sf.net -->
 <!-- User-specified headers:
 Title: Downloading Mailman
 
@@ -10,30 +11,36 @@
 
 <HEAD>
 <TITLE>Downloading Mailman</TITLE>
+<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=us-ascii">
 
+<STYLE TYPE="text/css">
+<!--
+body { margin: 0px; }
+-->
+</STYLE>
 </HEAD>
 <BODY BGCOLOR="#ffffff" TEXT="#000000"
-      TOPMARGIN="0" LEFTMARGIN="0" MARGINWIDTH="0" MARGINHEIGHT="0"
+      MARGINWIDTH="0" MARGINHEIGHT="0"
       LINK="#0000bb"  VLINK="#551a8b"
       ALINK="#ff0000">
 <!-- start of page table -->
-<TABLE WIDTH="100%" BORDER=0 CELLSPACING=0 CELLPADDING=0>
+<TABLE WIDTH="100%" BORDER="0" CELLSPACING="0" CELLPADDING="0">
 <!-- start of banner row -->
 <TR>
 <!-- start of corner cells -->
-<TD WIDTH=150 VALIGN=CENTER BGCOLOR="black">
+<TD WIDTH="150" VALIGN="MIDDLE" BGCOLOR="black">
 
 <center>
     <a href="./index.html">
     <img border=0 src="./images/logo-70.jpg"></a></center> </TD>
-<TD WIDTH=15 BGCOLOR="#99997c">&nbsp;&nbsp;</TD><!--spacer-->
+<TD WIDTH="15" BGCOLOR="#99997c">&nbsp;&nbsp;</TD><!--spacer-->
 <!-- end of corner cells -->
 <!-- start of banner -->
 <TD WIDTH="90%" BGCOLOR="#99997c">
 <!-- start of site links table -->
-<TABLE WIDTH="100%" BORDER=0
+<TABLE WIDTH="100%" BORDER="0"
 CELLSPACING=0 CELLPADDING=0
-       COLS=3 ROWS=4 BGCOLOR="#ffffff">
+       BGCOLOR="#ffffff">
 <TR>
     <TD BGCOLOR="#99997c">
 <a href="./index.html">Home</a>
@@ -81,9 +88,9 @@
 </TR><!-- end of banner row -->
 <TR><!-- start of sidebar/body row -->
 <!-- start of sidebar cells -->
-<TD WIDTH=150 VALIGN=TOP BGCOLOR="#99997c">
+<TD WIDTH="150" VALIGN="TOP" BGCOLOR="#99997c">
 <!-- start of sidebar table -->
-<TABLE WIDTH="100%" BORDER=0 CELLSPACING=0 CELLPADDING=3
+<TABLE WIDTH="100%" BORDER="0" CELLSPACING="0" CELLPADDING="3"
        BGCOLOR="#ffffff">
 <TR><TD BGCOLOR="#663300"><B><FONT COLOR="#ffffff">
 Overview
@@ -184,10 +191,10 @@
 </TABLE><!-- end of sidebar table -->
 
 </TD>
-<TD WIDTH=15>&nbsp;&nbsp;</TD><!--spacer-->
+<TD WIDTH="15">&nbsp;&nbsp;</TD><!--spacer-->
 <!-- end of sidebar cell -->
 <!-- start of body cell -->
-<TD VALIGN=TOP WIDTH="90%"><BR>
+<TD VALIGN="TOP" WIDTH="90%"><BR>
 <h3>Requirements</h3>
 
 <p>Mailman currently runs only on Unix-y systems, such as Linux,
@@ -240,9 +247,9 @@
 <h3>Downloading</h3>
 
 <p>Version
-(<!-VERSION--->2.0.8<!-VERSION--->,
+(<!-VERSION--->2.0.9<!-VERSION--->,
 released on
-<!-DATE--->Nov 27 2001<!-DATE--->)
+<!-DATE--->Apr  3 2002<!-DATE--->)
 is the current GNU release.  It is available from the following mirror sites:
 
 <ul>
Index: cron/qrunner
===================================================================
RCS file: /cvsroot/mailman/mailman/cron/Attic/qrunner,v
retrieving revision 1.18.2.3
retrieving revision 1.18.2.4
diff -u -r1.18.2.3 -r1.18.2.4
--- cron/qrunner	18 Apr 2001 03:58:35 -0000	1.18.2.3
+++ cron/qrunner	2 Apr 2002 22:38:50 -0000	1.18.2.4
@@ -195,13 +195,13 @@
         lock.refresh()
         root, ext = os.path.splitext(os.path.join(mm_cfg.QUEUE_DIR, file))
         if ext == '.db':
-            # If the .db file has no corresponding .msg file, we might as well
-            # remove the .db file, since there's little we can do about it.
-            if not os.path.exists(root+'.msg'):
-                syslog('qrunner', 'Unlinking orphaned .db file: %s.db' % root)
-                os.unlink(root+'.db')
-            # just trigger off the .msg files
+            # Just trigger off the .msg files.  This may leave stale .db files
+            # in qfiles, but these can't be cleaned up without storing a
+            # timestamp and watching out for race conditions.
             continue
+        if not os.path.exists(root+'.db'):
+            syslog('qrunner', 'Unlinking orphaned .msg file: %s.msg' % root)
+            os.unlink(root+'.msg')
         # Have we exceeded either resource limit?
         if mm_cfg.QRUNNER_PROCESS_LIFETIME is not None and \
                (time.time() - t0) > mm_cfg.QRUNNER_PROCESS_LIFETIME: