SOURCES: python-elspy-cvs20050901.patch (NEW) - cvs snap
arekm
arekm at pld-linux.org
Thu Sep 1 17:56:21 CEST 2005
Author: arekm Date: Thu Sep 1 15:56:21 2005 GMT
Module: SOURCES Tag: HEAD
---- Log message:
- cvs snap
---- Files affected:
SOURCES:
python-elspy-cvs20050901.patch (NONE -> 1.1) (NEW)
---- Diffs:
================================================================
Index: SOURCES/python-elspy-cvs20050901.patch
diff -u /dev/null SOURCES/python-elspy-cvs20050901.patch:1.1
--- /dev/null Thu Sep 1 17:56:21 2005
+++ SOURCES/python-elspy-cvs20050901.patch Thu Sep 1 17:56:16 2005
@@ -0,0 +1,1431 @@
+diff -urN elspy-0.1.1.org/CHANGES.txt elspy-0.1.1/CHANGES.txt
+--- elspy-0.1.1.org/CHANGES.txt 2002-10-23 00:52:18.000000000 +0200
++++ elspy-0.1.1/CHANGES.txt 2003-04-09 00:54:20.000000000 +0200
+@@ -15,3 +15,11 @@
+ * Change to accept messages by default if there's any Python
+ problem, not just an import problem. Made this behaviour a bit more
+ customizable with some #define's at the top of elspy.c
++
++
++0.1.2? 0.2? (?? ??? 2002)
++-------------------------
++
++ * Added elspy.subject_charset module, for rejecting mail with a
++ subject encoded using one of various banned charsets (which are
++ configurable).
+diff -urN elspy-0.1.1.org/elspy.c elspy-0.1.1/elspy.c
+--- elspy-0.1.1.org/elspy.c 2002-10-23 00:43:58.000000000 +0200
++++ elspy-0.1.1/elspy.c 2003-08-24 19:08:32.000000000 +0200
+@@ -58,7 +58,21 @@
+ #define INTERNAL_ERROR_RETURN LOCAL_SCAN_ACCEPT
+ #define ERROR_RETURN LOCAL_SCAN_ACCEPT
+
+-static char * __revision__ = "$Id$";
++static char * __revision__ = "$Id$";
++
++
++#ifdef DLOPEN_LOCAL_SCAN
++/* Return the version of the local_scan ABI, if being compiled as a .so */
++int local_scan_version_major(void)
++{
++ return LOCAL_SCAN_ABI_VERSION_MAJOR;
++}
++
++int local_scan_version_minor(void)
++{
++ return LOCAL_SCAN_ABI_VERSION_MINOR;
++}
++#endif
+
+
+ /* ----------------------------------------------------------------------
+@@ -247,6 +261,7 @@
+ mod = Py_InitModule("_elspy", elspy_methods);
+
+ /* Add necessary constants to module's dictionary. */
++ /* XXX should use PyModule_AddIntConstant() here! */
+ mod_dict = PyModule_GetDict(mod);
+ PyDict_SetItemString(mod_dict, "LOCAL_SCAN_ACCEPT",
+ PyInt_FromLong((long) LOCAL_SCAN_ACCEPT));
+@@ -352,15 +367,12 @@
+ mod_dict = PyModule_GetDict(module);
+ func = PyDict_GetItemString(mod_dict, func_name);
+
+- if (func) {
+- if (! PyCallable_Check(func)) {
+- log_error("error: %s object in %s module not callable",
+- func_name, mod_name);
+- return NULL;
+- }
+- else
+- return func;
++ if (func != NULL && !PyCallable_Check(func)) {
++ log_error("error: %s object in %s module not callable",
++ func_name, mod_name);
++ return NULL;
+ }
++ return func;
+ }
+
+
+@@ -509,6 +521,48 @@
+ }
+
+
++#if DEBUG_LEVEL > 3
++void dump_sys_module ()
++{
++ PyObject * sys_mod;
++ PyObject * sys_executable;
++ PyObject * sys_version;
++ PyObject * sys_path;
++ PyObject * cur_dir;
++
++ sys_mod = PyImport_ImportModule("sys");
++ if (sys_mod == NULL)
++ return;
++ log_debug(3, "sys module: %s", PyString_AsString(PyObject_Repr(sys_mod)));
++
++ /* show sys.executable (just curious) */
++ sys_executable = PyObject_GetAttrString(sys_mod, "executable");
++ if (sys_executable != NULL)
++ log_debug(3, "sys.executable: >%s<", PyString_AsString(sys_executable));
++
++ /* show sys.version */
++ sys_version = PyObject_GetAttrString(sys_mod, "version");
++ if (sys_version != NULL)
++ log_debug(3, "sys.version: >%s<", PyString_AsString(sys_version));
++
++ /* and sys.path */
++ sys_path = PyObject_GetAttrString(sys_mod, "path");
++ if (sys_path != NULL) {
++ int n, i;
++ n = PySequence_Size(sys_path);
++ log_debug(3, "sys.path has %d elements", n);
++
++ for (i = 0; i < n; i++) {
++ cur_dir = PySequence_GetItem(sys_path, i);
++ log_debug(3, "sys.path[%d] = %s", i,
++ PyString_AsString(PyObject_Repr(cur_dir)));
++ }
++ }
++}
++#else
++#define dump_sys_module()
++#endif
++
+ static int py_initialized = 0;
+
+ int
+@@ -535,6 +589,7 @@
+ }
+
+ /* Import the elspy package. */
++ dump_sys_module();
+ log_debug(1, "attempting to import module 'elspy'");
+ elspy_mod = PyImport_ImportModule("elspy");
+ if (elspy_mod == NULL) {
+@@ -662,12 +717,3 @@
+ return result;
+
+ }
+-
+-#ifdef DLOPEN_LOCAL_SCAN
+-/* Return the verion of the local_scan ABI, if being compiled as a .so */
+-int
+-local_scan_version(void)
+-{
+- return LOCAL_SCAN_ABI_VERSION ;
+-}
+-#endif
+diff -urN elspy-0.1.1.org/lib/bogus_date.py elspy-0.1.1/lib/bogus_date.py
+--- elspy-0.1.1.org/lib/bogus_date.py 2002-10-22 00:20:14.000000000 +0200
++++ elspy-0.1.1/lib/bogus_date.py 2003-04-08 01:12:04.000000000 +0200
+@@ -2,7 +2,7 @@
+ #
+ # This code is based on a strict interpretation of RFC 2822 section 3.3;
+ # the idea is to reject *any* sort of bogosity in the "Date:" header:
+-#
++#
+ # * invalid syntax, eg. any of the following would be rejected:
+ # "Mon 1 Jul 2002 10:11:41 -0400" (missing comma)
+ # "Mon, 1 Jul 2002 10:11:41" (missing timezone)
+@@ -22,7 +22,7 @@
+ #
+ # * too far ahead of the current date; configurable by editing
+ # MESSAGE_TOO_NEW below
+-#
++#
+
+ # Implementation notes.
+ #
+@@ -45,7 +45,7 @@
+ #
+ # The elspy home page is at http://elspy.sourceforge.net/ .
+
+-__revision__ = "$Id$"
++__revision__ = "$Id$"
+
+ import re
+ from time import time, localtime, gmtime, mktime, asctime
+@@ -211,7 +211,7 @@
+ # remote timezone out of the "Date" header, it's easy to adjust
+ # this bogus time value to get the correct time that the "Date"
+ # header represents, as seconds since epoch UTC.
+- #
++ #
+ # Given that, it's a simple subtraction to compute the difference
+ # between the "Date" header and the current time, and reject the
+ # message if the "Date" header is wildly off. The definition of
+diff -urN elspy-0.1.1.org/lib/defanged_virus.py elspy-0.1.1/lib/defanged_virus.py
+--- elspy-0.1.1.org/lib/defanged_virus.py 1970-01-01 01:00:00.000000000 +0100
++++ elspy-0.1.1/lib/defanged_virus.py 2005-05-19 01:51:03.000000000 +0200
+@@ -0,0 +1,172 @@
++# elspy library -- detection of defanged viruses
++#
++# This code is intended to detect and reject defanged viruses,
++# as well as related junk like "caught a virus for you" or
++# "you sent us a virus" messages. Virus detection software that
++# sends *more* mail just perpetuates the problem; the authors
++# of such software are only one step up from virus authors and
++# will be up against the wall (right after spammers) when the
++# revolution comes.
++#
++# Naturally, this is a hodge-podge of heuristic hacks. I very
++# much doubt there's any nice way to do it.
++
++# Copyright (c) 2003 Gregory P. Ward. All rights reserved.
++# See README.txt for licensing information.
++#
++# The elspy home page is at http://elspy.sourceforge.net/ .
++
++__revision__ = "$Id$"
++
++
++import re
++from elspy.util import RejectVirusRelated, RejectVirus, parse_message
++
++
++INTERSCAN_SUBJECT = "InterScan NT Alert"
++INTERSCAN_BODY = "Receiver, InterScan has detected virus(es) in the e-mail attachment."
++INTERSCAN_FILENAME = "InterScan_SafeStamp.txt"
++
++# Rejection messages (consistency is good!)
++REJECT_DEFANGED = "defanged viruses not wanted here"
++REJECT_ALERT = "virus alerts not wanted here"
++REJECT_NOTIFICATION = "misguided virus notifications not wanted here"
++
++
++def local_scan (info, headers, fd, msg=None):
++ if msg is None:
++ msg = parse_message(headers, fd, onerror="raise")
++
++ html_body = plain_body = None
++ attach_names = []
++ if msg.is_multipart():
++ for part in msg.walk():
++ name = (part.get_param('name') or
++ part.get_param('filename',
++ header='content-disposition'))
++ if name:
++ attach_names.append(name)
++
++ part_type = part.get_type()
++ if html_body is None and part_type == "text/html":
++ html_body = part.get_payload().strip()
++ elif plain_body is None and part_type == "text/plain":
++ plain_body = part.get_payload().strip()
++
++ body = plain_body or html_body or ""
++ else:
++ plain_body = body = msg.get_payload().strip()
++
++ subject = msg.get('subject', '')
++
++ # Number One offender: InterScan NT Alert, which doubles your
++ # pleasure by first sending you a "Virus detected" notice,
++ # and *then* sending the defanged virus itself. Arrghh!
++ # First, reject the "virus detected" message based on the
++ # subject and first line of the body.
++ if (subject == INTERSCAN_SUBJECT and
++ plain_body and
++ plain_body.startswith(INTERSCAN_BODY)):
++ raise RejectVirusRelated(REJECT_ALERT)
++
++ # Now the second half: viruses defanged by InterScan.
++ if INTERSCAN_FILENAME in attach_names:
++ raise RejectVirusRelated(REJECT_DEFANGED)
++
++ # InterScan also has the wonderful feature of notifying
++ # user at example.com that he sent a virus when, of course, the virus
++ # just forged that sender address. Sigh.
++ if (subject.startswith("Failed to clean virus file") and
++ plain_body and
++ plain_body.startswith("The file you have sent was infected with a virus")):
++ raise RejectVirusRelated(REJECT_NOTIFICATION)
++
++ if (subject == "Virus Alert !" and
++ body.find("McAfee.com Has recieved an infected message from you") != -1):
++ raise RejectVirusRelated(REJECT_NOTIFICATION)
++
++ if (subject.startswith("Warning: A possible virus has been detected in one of your messages.") and
++ body.startswith("A virus or an infected file has been detected in a message:")):
++ raise RejectVirusRelated(REJECT_NOTIFICATION)
++
++ if (subject.startswith("MailMarshal has detected a Virus") and
++ body.startswith("MailMarshal (an automated content monitoring gateway) has stopped")):
++ raise RejectVirusRelated(REJECT_NOTIFICATION)
++
++ if (subject.startswith("Your mail server sent us a virus") and
++ body.find("Declude")):
++ raise RejectVirusRelated(REJECT_NOTIFICATION)
++
++ if (subject.startswith("RAV AntiVirus") and
++ re.search(r"This e-mail is generated by the \S+ mail server to warn you that the e-mail", body)):
++ raise RejectVirusRelated(REJECT_NOTIFICATION)
++
++ if (subject.startswith("Your mail server sent us a virus!") and
++ body.startswith("The anti-virus servers of IDnet detected")):
++ raise RejectVirusRelated(REJECT_NOTIFICATION)
++
++ if (subject == "Virus intercepted" and
++ body.startswith("A message you sent to")):
++ raise RejectVirusRelated(REJECT_NOTIFICATION)
++
++ # Oh great, they come in Italian too.
++ if (subject == "ATTENZIONE: Virus trovato" and
++ body.startswith("I software anti-virus presenti sul nostro server")):
++ raise RejectVirusRelated(REJECT_NOTIFICATION)
++
++ if subject.find("WARNING: YOU MAY HAVE A VIRUS") != -1:
++ raise RejectVirusRelated(REJECT_NOTIFICATION)
++
++ if subject.find("Inflex scan report") != -1:
++ raise RejectVirusRelated(REJECT_NOTIFICATION)
++
++ if subject.find("This is an alert from eSafe") != -1:
++ raise RejectVirusRelated(REJECT_NOTIFICATION)
++
++
++
++ # Something called MailScanner adds a header stating that it found a
++ # virus, and then just passes the whole thing on intact. Duh.
++ mailscanner = msg.get('x-mailscanner', '')
++ if (mailscanner.lower().find("found to be infected") != -1):
++ raise RejectVirusRelated("viruses not wanted here")
++
++ if (subject.startswith("Virus Detected by Network Associates") and
++ body.startswith("Network Associates WebShield") and
++ body.find("detected virus") != -1):
++ raise RejectVirusRelated(REJECT_DEFANGED)
++
++ if ("DELETED0.TXT" in attach_names or
++ "Norton AntiVirus Deleted1.txt" in attach_names):
++ raise RejectVirusRelated(REJECT_DEFANGED)
++
++ if msg['X-SpamHunter'] == "Found to be infected":
++ raise RejectVirusRelated("viruses not wanted here")
++
++ if body.strip().startswith("RAV AntiVirus has deleted this file"):
++ raise RejectVirusRelated(REJECT_DEFANGED)
++
++ if ("WARNING.TXT" in attach_names and
++ body.startswith("WARNING: This e-mail has been altered by MIMEDefang.")):
++ raise RejectVirusRelated(REJECT_DEFANGED)
++
++ if body.find("NOTICE: One or more files attached to this message "
++ "were found to contain a malicious virus") != -1:
++ raise RejectVirusRelated(REJECT_DEFANGED)
++
++ if body.find("VIRUS INFECTION ALERT") != -1:
++ raise RejectVirusRelated(REJECT_DEFANGED)
++
++ # Detect W32/Palyh and W32.Sobig.F at mm viruses. This isn't really a
++ # "defanged" virus (AFAIK), but it doesn't bear the usual signature,
++ # ie. a Windows executable attachment, and it's easiest to catch
++ # using the machinery in this module.
++ if (plain_body == "All information is in the attached file." or
++ plain_body == "Please see the attached file." or
++ plain_body == "See the attached file for details"):
++ payload = msg.get_payload()
++ # Make sure message has the same MIME structure as W32/Palyh
++ if (msg.get_type() == "multipart/mixed" and
++ len(payload) == 1 and
++ payload[0].get_type() == "text/plain"):
++ raise RejectVirus("W32/Palyh or W32.Sobig.F at mm virus detected")
+diff -urN elspy-0.1.1.org/lib/execontent_simple.py elspy-0.1.1/lib/execontent_simple.py
+--- elspy-0.1.1.org/lib/execontent_simple.py 2002-10-22 00:20:14.000000000 +0200
++++ elspy-0.1.1/lib/execontent_simple.py 2004-07-02 15:03:43.000000000 +0200
+@@ -30,10 +30,10 @@
+ #
+ # The elspy home page is at http://elspy.sourceforge.net/ .
+
+-__revision__ = "$Id$"
++__revision__ = "$Id$"
+
+ import os, re
+-from elspy import RejectMessage, get_fdpos, set_fdpos
++from elspy import RejectVirus, get_fdpos, set_fdpos
+
+
+ BAD_EXTENSIONS = ("vbs", "vbe", "wsf", "wsh", "js", "jse",
+@@ -72,26 +72,72 @@
+ flags|re.MULTILINE)
+
+
++def check_sober_h(info, headers, fd):
++ # Boy, these anti-immigration Germans are really starting to bug me...
++ banned_prefixes = ["Bankrott des Gesundheitswesens durch Auslaender!",
++ "Wer an ein Tabu ruehrt, muss und darf vernichtet werden",
++ "EU Beitritt der Tuerkei ?",
++ "Bin ich zu weltfremd? Ich glaube wohl kaum",
++ "Die Deform der sozialen Ordnung",
++ "Moschee-Bau in Deutschland",
++ "Augen auf! (So sieht es aus!)",
++ "Paradies Bundesrepublik - Rente fuer die Welt -",
++ "Libanesen in Berlin",
++ "Garather klagen ueber eskalierende Gewalt im Stadtteil!",
++ "Auslaender erschleichen sich zunehmend Sozialleistungen",
++ "Auslaenderkriminalitaet steigt weiter!",
++ "Das kann unmoeglich sein -Leserbrief-",
++ "Nein zum Zuwanderungsgesetz !",
++ "Skandalurteil in Darmstadt",
++ "Auf Kosten der deutschen Beitragszahler und Rentner!",
++ "Wir haben die Auslaender doch geholt?!",
++ "TUERKEN-TERROR AM HIMMELFAHRTSTAG",
++ "MULTI-KULTI-BANDE TYRANNISIERTE MITSCHUELER",
++ "ASYLANTEN BEGRABSCHTEN DEUTSCHES MAEDCHEN",
++ "Was Deutschland braucht, sind deutsche Kinder!",
++ "Diplomatische Zensur",
++ "EU gibt Erwerbslosen volle Freizuegigkeit",
++ "Richter unterstuetzt kriminelle Auslaenderin",
++ "Auslaenderanteile in Schweizer Gefaengnissen",
++ "Augen auf! (So sieht es aus!)",
++ "Neue Voelkerwanderung droht!",
++ "Mehr fuer Auslaender als fuer Deutsche tun!",
++ "Skandal in Berlin",
++ "Auslaendergewalt: Herr Rau, wo waren Sie?",
++ "Marokkanischer Wiederholungstaeter vergewaltigte 17-jaehriges Maedel",
++ "DEUTSCHES MAEDCHEN FAST VERGEWALTIGT",
++ "So sieht die Wahrheit aus!",
++ "Geschrieben von Margrit",
++ ]
++ subject = headers.get("Subject", "").strip()
++ for prefix in banned_prefixes:
++ if subject.startswith(prefix):
++ raise RejectVirus("looks like Sober.h virus")
++
++
+ def check_filename (match, what):
+ if match is None:
+ return
+ fn = match.group('fn1') or match.group('fn2')
+ ext = match.group('ext1') or match.group('ext2')
+ if ext.lower() in BAD_EXTENSIONS:
+- raise RejectMessage(
++ raise RejectVirus(
+ "message rejected -- looks like a virus\n"
+ "(name of %s (%r) indicates executable content)"
+ % (what, fn))
+
+ def local_scan (info, headers, fd):
+
++ # Easy check for Sober.h virus (German far-right propaganda virus)
++ check_sober_h(info, headers, fd)
++
+ # First check the message's "Content-type" header, to handle the
+ # case of a message whose entire body is a Windows executable.
+ ctype = headers.get("Content-type")
+ if ctype:
+ match = msgname_re.search(ctype)
+ check_filename(match, "message body")
+-
++
+ # Read in the first chunk (32k by default) of the message body, and
+ # scan it for evil attachments or uuencoded files.
+ fpos = get_fdpos(fd)
+@@ -130,5 +176,5 @@
+ assert fn == fn2[1:-1], "%r != %r" % (fn, fn2[1:-1])
+ assert ext == "scr" and ext.lower() in BAD_EXTENSIONS
+
+-
+-
++
++
+diff -urN elspy-0.1.1.org/lib/execontent_smart.py elspy-0.1.1/lib/execontent_smart.py
+--- elspy-0.1.1.org/lib/execontent_smart.py 1970-01-01 01:00:00.000000000 +0100
++++ elspy-0.1.1/lib/execontent_smart.py 2004-07-28 02:56:09.000000000 +0200
+@@ -0,0 +1,106 @@
++# elspy library -- smart detection of evil attachments
++#
++# This is based on Neil Schemenauer's qmail-queue wrapper to reject
++# messages with executable attachments:
++# http://arctrix.com/~nas/misc/qmail-filter-exe.py
++#
++# It parses the message's MIME structure, looks at the name of
++# each attachment, and rejects the whole message if there are
++# any attachments with suspicious-looking names.
++
++# Copyright (c) 2003 Gregory P. Ward. All rights reserved.
++# See README.txt for licensing information.
++#
++# The elspy home page is at http://elspy.sourceforge.net/ .
++
++__revision__ = "$Id$"
++
++import re
++from cStringIO import StringIO
++import struct
++from zipfile import ZipFile, BadZipfile
++from elspy.util import parse_message, RejectVirus
++from elspy.execontent_simple import BAD_EXTENSIONS
++
++# %s expands to eg. "vbs|vbe|wsf|..." (depending on BAD_EXTENSIONS).
++# Regex is compiled lazily so outside code can tweak BAD_EXTENSIONS.
++fname_pat = r'\.(%s)\b'
++fname_re = None
++
++def is_executable (data):
++ """
++ Return true if 'data' (which should be the initial contents of
++ a file) signifies a Windows executable.
++ """
++ if not data:
++ return 0
++ return len(data) > 24 and data[0:2] == "MZ" and data[24] == "@"
++
++def scan_zipfile (attach_name, attachment):
++ try:
++ file = StringIO(attachment.get_payload(decode=1))
++ zipfile = ZipFile(file)
++
++ # Iterate over all files in the ZIP file; if any of them look like a
++ # Windows executable (either by name or contents), reject the whole
++ # message.
++ names = zipfile.namelist()
++ for name in names:
++ # The name gives it away (too easy!)
++ if fname_re.search(name):
++ raise RejectVirus(
++ "message rejected -- looks like a virus\n"
++ "(attachment %r contains executable file %r)"
++ % (attach_name, name))
++
++ # OK, let's look at the content of the file. If it looks like
++ # a Windows executable, then reject it.
++ contents = zipfile.read(names[0])
++ if is_executable(contents):
++ raise RejectVirus(
++ "message rejected -- looks like a virus\n"
++ ("attachment %r contains a Windows executable file"
++ % attach_name))
++ except (BadZipfile, RuntimeError, struct.error), err:
++ raise RejectVirus("message rejected -- looks like a virus\n"
++ "(contains an invalid ZIP file attachment)")
++
++
++
++def local_scan (info, headers, fd, msg=None):
++ global fname_pat, fname_re
++ if msg is None:
++ msg = parse_message(headers, fd)
++
++ if fname_re is None:
++ fname_re = re.compile(fname_pat % '|'.join(BAD_EXTENSIONS),
++ re.IGNORECASE)
++
++ i = 0
++ for part in msg.walk():
++ # Only interested in the leaves of the tree
++ i += 1
++ if part.is_multipart():
++ continue
++
++ attachname = (part.get_param('name') or
++ part.get_param('filename',
++ header='content-disposition'))
++ if attachname and fname_re.search(attachname):
++ # Look paw -- I caught me a virus!
++ raise RejectVirus(
++ "message rejected -- looks like a virus\n"
++ "(attachment name %r indicates executable content)"
++ % attachname)
++
++ # Check for executables embedded in ZIP file.
++ elif attachname and attachname.lower().endswith(".zip"):
++ scan_zipfile(attachname, part)
++
++ elif is_executable(part.get_payload(decode=1)):
++ if attachname:
++ why = "(attachment %r is executable on Windows)" % attachname
++ else:
++ why = "(contains an attachment executable on Windows)"
++ raise RejectVirus("message rejected -- looks like a virus\n" +
++ why)
+diff -urN elspy-0.1.1.org/lib/__init__.py elspy-0.1.1/lib/__init__.py
+--- elspy-0.1.1.org/lib/__init__.py 2002-10-23 00:55:30.000000000 +0200
++++ elspy-0.1.1/lib/__init__.py 2003-04-09 00:57:57.000000000 +0200
+@@ -9,9 +9,9 @@
+ #
+ # The elspy home page is at http://elspy.sourceforge.net/ .
+
+-__revision__ = "$Id$"
++__revision__ = "$Id$"
+
+-__version__ = "0.1.1"
++__version__ = "0.2pre"
+
+ import sys
+ from traceback import format_exception
+@@ -80,7 +80,4 @@
+ # Other useful stuff lives in util.py -- import it from there
+ # so local_scan() authors don't have to know about
+ # the internal structure of this package.
+-from elspy.util import \
+- MessageDisposition, AcceptMessage, RejectMessage, TempRejectMessage, \
+- log_error, log_warning, get_fdpos, set_fdpos, \
+- HeaderList, EximInfo
++from elspy.util import *
+diff -urN elspy-0.1.1.org/lib/maildir.py elspy-0.1.1/lib/maildir.py
+--- elspy-0.1.1.org/lib/maildir.py 2002-10-22 00:20:14.000000000 +0200
++++ elspy-0.1.1/lib/maildir.py 2003-04-19 03:30:59.000000000 +0200
<<Diff was trimmed, longer than 597 lines>>
More information about the pld-cvs-commit
mailing list