diff options
| -rw-r--r-- | mailman/Defaults.py | 11 | ||||
| -rw-r--r-- | mailman/archiving/mailarchive.py | 7 | ||||
| -rw-r--r-- | mailman/archiving/mhonarc.py | 95 | ||||
| -rw-r--r-- | mailman/archiving/prototype.py | 2 | ||||
| -rw-r--r-- | mailman/bin/testall.py | 4 | ||||
| -rw-r--r-- | mailman/docs/archivers.txt | 40 | ||||
| -rw-r--r-- | mailman/loginit.py | 1 | ||||
| -rw-r--r-- | setup.py | 3 |
8 files changed, 157 insertions, 6 deletions
diff --git a/mailman/Defaults.py b/mailman/Defaults.py index e665c1870..6fa457425 100644 --- a/mailman/Defaults.py +++ b/mailman/Defaults.py @@ -225,6 +225,17 @@ MAIL_ARCHIVE_BASEURL = 'http://go.mail-archive.com/' # The posting address for the Mail-Archive.com service MAIL_ARCHIVE_RECIPIENT = 'archive@mail-archive.com' +# The command for archiving to a local MHonArc instance. +MHONARC_COMMAND = """\ +/usr/bin/mhonarc \ +-add \ +-dbfile $PRIVATE_ARCHIVE_FILE_DIR/${listname}.mbox/mhonarc.db \ +-outdir $VAR_DIR/mhonarc/${listname} \ +-stderr $LOG_DIR/mhonarc \ +-stdout $LOG_DIR/mhonarc \ +-spammode \ +-umask 022""" + # Are archives on or off by default? DEFAULT_ARCHIVE = On diff --git a/mailman/archiving/mailarchive.py b/mailman/archiving/mailarchive.py index 2a83b9a8d..fb78a2257 100644 --- a/mailman/archiving/mailarchive.py +++ b/mailman/archiving/mailarchive.py @@ -64,9 +64,10 @@ class MailArchive: # It is not the archiver's job to ensure the message has a Message-ID. assert message_id is not None, 'No Message-ID found' # The angle brackets are not part of the Message-ID. See RFC 2822. - start = (1 if message_id.startswith('<') else 0) - end = (-1 if message_id.endswith('>') else None) - message_id = message_id[start:end] + if message_id.startswith('<') and message_id.endswith('>'): + message_id = message_id[1:-1] + else: + message_id = message_id.strip() sha = hashlib.sha1(message_id) sha.update(str(mlist.posting_address)) message_id_hash = urlsafe_b64encode(sha.digest()) diff --git a/mailman/archiving/mhonarc.py b/mailman/archiving/mhonarc.py new file mode 100644 index 000000000..cc549dee8 --- /dev/null +++ b/mailman/archiving/mhonarc.py @@ -0,0 +1,95 @@ +# Copyright (C) 2008 by the Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""MHonArc archiver.""" + +__metaclass__ = type +__all__ = [ + 'MHonArc', + ] + + +import hashlib +import logging +import subprocess + +from base64 import b32encode +from string import Template +from urlparse import urljoin +from zope.interface import implements + +from mailman.configuration import config +from mailman.interfaces.archiver import IArchiver + + +log = logging.getLogger('mailman.archiver') + + + +class MHonArc: + """Local MHonArc archiver.""" + + implements(IArchiver) + + name = 'mhonarc' + is_enabled = False + + @staticmethod + def list_url(mlist): + """See `IArchiver`.""" + # XXX What about private MHonArc archives? + web_host = config.domains.get(mlist.host_name, mlist.host_name) + return Template(config.PUBLIC_ARCHIVE_URL).safe_substitute( + listname=mlist.fqdn_listname, + hostname=web_host, + fqdn_listname=mlist.fqdn_listname, + ) + + @staticmethod + def permalink(mlist, msg): + """See `IArchiver`.""" + # XXX What about private MHonArc archives? + message_id = msg.get('message-id') + # It is not the archiver's job to ensure the message has a Message-ID. + assert message_id is not None, 'No Message-ID found' + # The angle brackets are not part of the Message-ID. See RFC 2822. + if message_id.startswith('<') and message_id.endswith('>'): + message_id = message_id[1:-1] + else: + message_id = message_id.strip() + sha = hashlib.sha1(message_id) + message_id_hash = b32encode(sha.digest()) + del msg['x-message-id-hash'] + msg['X-Message-ID-Hash'] = message_id_hash + return urljoin(MHonArc.list_url(mlist), message_id_hash) + + @staticmethod + def archive_message(mlist, msg): + """See `IArchiver`.""" + substitutions = config.__dict__.copy() + substitutions['listname'] = mlist.fqdn_listname + command = Template(config.MHONARC_COMMAND).safe_substitute( + substitutions) + proc = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + shell=True) + stdout, stderr = proc.communicate(msg.as_string()) + if proc.returncode <> 0: + log.error('%s: mhonarc subprocess had non-zero exit code: %s' % + (msg['message-id'], proc.returncode)) + log.info(stdout) + log.error(stderr) diff --git a/mailman/archiving/prototype.py b/mailman/archiving/prototype.py index b2efbc5e0..deeaaa624 100644 --- a/mailman/archiving/prototype.py +++ b/mailman/archiving/prototype.py @@ -61,6 +61,8 @@ class Prototype: # The angle brackets are not part of the Message-ID. See RFC 2822. if message_id.startswith('<') and message_id.endswith('>'): message_id = message_id[1:-1] + else: + message_id = message_id.strip() digest = hashlib.sha1(message_id).digest() message_id_hash = b32encode(digest) del msg['x-message-id-hash'] diff --git a/mailman/bin/testall.py b/mailman/bin/testall.py index 880130172..ed90980ae 100644 --- a/mailman/bin/testall.py +++ b/mailman/bin/testall.py @@ -31,6 +31,7 @@ import tempfile import unittest import pkg_resources +from mailman import Defaults from mailman.configuration import config from mailman.i18n import _ from mailman.initialize import initialize_1, initialize_2 @@ -232,6 +233,9 @@ def main(): print >> fp, 'MAILMAN_GID =', group_id print >> fp, "LANGUAGES = 'en'" print >> fp, 'SMTPPORT =', SMTPServer.port + # A fake MHonArc command, for testing. + print >> fp, 'MHONARC_COMMAND = """/bin/echo', \ + Defaults.MHONARC_COMMAND, '"""' initialize_1(cfg_out, propagate_logs=parser.options.stderr) mailman_uid = pwd.getpwnam(config.MAILMAN_USER).pw_uid diff --git a/mailman/docs/archivers.txt b/mailman/docs/archivers.txt index 96cdb5c3d..c8ddb73e4 100644 --- a/mailman/docs/archivers.txt +++ b/mailman/docs/archivers.txt @@ -32,6 +32,9 @@ interoperate. mail-archive http://go.mail-archive.dev/test%40example.com http://go.mail-archive.dev/ZaXPPxRMM9_hFZL4vTRlQlBx8pc= + mhonarc + http://www.example.com/.../test@example.com + http://www.example.com/.../RSZCG7IGPHFIRW3EMTVMMDNJMNCVCOLE pipermail http://www.example.com/pipermail/test@example.com None @@ -134,14 +137,47 @@ Additionally, this archiver can handle malformed Message-IDs. >>> del msg['message-id'] >>> msg['Message-ID'] = '12345>' >>> archiver.permalink(mlist, msg) - 'http://go.mail-archive.dev/ZaXPPxRMM9_hFZL4vTRlQlBx8pc=' + 'http://go.mail-archive.dev/bXvG32YzcDEIVDaDLaUSVQekfo8=' >>> del msg['message-id'] >>> msg['Message-ID'] = '<12345' >>> archiver.permalink(mlist, msg) - 'http://go.mail-archive.dev/ZaXPPxRMM9_hFZL4vTRlQlBx8pc=' + 'http://go.mail-archive.dev/9rockPrT1Mm-jOsLWS6_hseR_OY=' >>> del msg['message-id'] >>> msg['Message-ID'] = '12345' >>> archiver.permalink(mlist, msg) 'http://go.mail-archive.dev/ZaXPPxRMM9_hFZL4vTRlQlBx8pc=' + + >>> del msg['message-id'] + >>> msg['Message-ID'] = ' 12345 ' + >>> archiver.permalink(mlist, msg) + 'http://go.mail-archive.dev/ZaXPPxRMM9_hFZL4vTRlQlBx8pc=' + + +MHonArc +------- + +The MHonArc archiver <http://www.mhonarc.org> is also available. + + >>> archiver = config.archivers['mhonarc'] + >>> archiver.name + 'mhonarc' + +Messages sent to a local MHonArc instance are added to its archive via a +subprocess call. + + >>> archiver.archive_message(mlist, msg) + >>> archive_log = open(os.path.join(config.LOG_DIR, 'archiver')) + >>> try: + ... contents = archive_log.read() + ... finally: + ... archive_log.close() + >>> print 'LOG:', contents + LOG: ... /usr/bin/mhonarc -add + -dbfile /.../private/test@example.com.mbox/mhonarc.db + -outdir /.../mhonarc/test@example.com + -stderr /.../logs/mhonarc + -stdout /.../logs/mhonarc + -spammode -umask 022 + ... diff --git a/mailman/loginit.py b/mailman/loginit.py index 844c2543e..68debff8d 100644 --- a/mailman/loginit.py +++ b/mailman/loginit.py @@ -35,6 +35,7 @@ FMT = '%(asctime)s (%(process)d) %(message)s' DATEFMT = '%b %d %H:%M:%S %Y' LOGGERS = ( + 'archiver', # All archiver output 'bounce', # All bounce processing logs go here 'config', # Configuration issues 'debug', # Only used for development @@ -91,9 +91,10 @@ Any other spelling is incorrect.""", 'console_scripts': list(scripts), # Entry point for plugging in different database backends. 'mailman.archiver' : [ + 'mail-archive = mailman.archiving.mailarchive:MailArchive', + 'mhonarc = mailman.archiving.mhonarc:MHonArc', 'pipermail = mailman.archiving.pipermail:Pipermail', 'prototype = mailman.archiving.prototype:Prototype', - 'mail-archive = mailman.archiving.mailarchive:MailArchive', ], 'mailman.scrubber' : 'stock = mailman.archiving.pipermail:Pipermail', 'mailman.commands' : list(commands), |
