summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2008-07-09 22:35:44 -0400
committerBarry Warsaw2008-07-09 22:35:44 -0400
commit82de03b4699cb9e841f4196ff7c92e043056d3e3 (patch)
tree06ffed62dc4ffb55ef1036f02268dee5a0aa6847
parent3929c688da2b275a1bb965152cca8a7352557ffc (diff)
downloadmailman-82de03b4699cb9e841f4196ff7c92e043056d3e3.tar.gz
mailman-82de03b4699cb9e841f4196ff7c92e043056d3e3.tar.zst
mailman-82de03b4699cb9e841f4196ff7c92e043056d3e3.zip
-rw-r--r--mailman/Defaults.py11
-rw-r--r--mailman/archiving/mailarchive.py7
-rw-r--r--mailman/archiving/mhonarc.py95
-rw-r--r--mailman/archiving/prototype.py2
-rw-r--r--mailman/bin/testall.py4
-rw-r--r--mailman/docs/archivers.txt40
-rw-r--r--mailman/loginit.py1
-rw-r--r--setup.py3
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
diff --git a/setup.py b/setup.py
index b3523e2c7..a1cf52064 100644
--- a/setup.py
+++ b/setup.py
@@ -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),