aboutsummaryrefslogtreecommitdiffstats
path: root/Mailman/Handlers/Hold.py
diff options
context:
space:
mode:
author <>2003-01-02 05:25:50 +0000
committer <>2003-01-02 05:25:50 +0000
commitb132a73f15e432eaf43310fce9196ca0c0651465 (patch)
treec15f816ba7c4de99fef510e3bd75af0890d47441 /Mailman/Handlers/Hold.py
downloadmailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.gz
mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.xz
mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.zip
This commit was manufactured by cvs2svn to create branch
'Release_2_1-maint'.
Diffstat (limited to 'Mailman/Handlers/Hold.py')
-rw-r--r--Mailman/Handlers/Hold.py280
1 files changed, 280 insertions, 0 deletions
diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py
new file mode 100644
index 00000000..15223959
--- /dev/null
+++ b/Mailman/Handlers/Hold.py
@@ -0,0 +1,280 @@
+# Copyright (C) 1998,1999,2000,2001,2002 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""Determine whether this message should be held for approval.
+
+This modules tests only for hold situations, such as messages that are too
+large, messages that have potential administrivia, etc. Definitive approvals
+or denials are handled by a different module.
+
+If no determination can be made (i.e. none of the hold criteria matches), then
+we do nothing. If the message must be held for approval, then the hold
+database is updated and any administrator notification messages are sent.
+Finally an exception is raised to let the pipeline machinery know that further
+message handling should stop.
+
+"""
+
+import email
+from email.MIMEText import MIMEText
+from email.MIMEMessage import MIMEMessage
+import email.Utils
+from types import ClassType
+
+from Mailman import mm_cfg
+from Mailman import Utils
+from Mailman import Errors
+from Mailman import Message
+from Mailman import i18n
+from Mailman import Pending
+from Mailman.Logging.Syslog import syslog
+
+# First, play footsie with _ so that the following are marked as translated,
+# but aren't actually translated until we need the text later on.
+def _(s):
+ return s
+
+
+
+class ForbiddenPoster(Errors.HoldMessage):
+ reason = _('Sender is explicitly forbidden')
+ rejection = _('You are forbidden from posting messages to this list.')
+
+class ModeratedPost(Errors.HoldMessage):
+ reason = _('Post to moderated list')
+ rejection = _('Your message was deemed inappropriate by the moderator.')
+
+class NonMemberPost(Errors.HoldMessage):
+ reason = _('Post by non-member to a members-only list')
+ rejection = _('Non-members are not allowed to post messages to this list.')
+
+class NotExplicitlyAllowed(Errors.HoldMessage):
+ reason = _('Posting to a restricted list by sender requires approval')
+ rejection = _('This list is restricted; your message was not approved.')
+
+class TooManyRecipients(Errors.HoldMessage):
+ reason = _('Too many recipients to the message')
+ rejection = _('Please trim the recipient list; it is too long.')
+
+class ImplicitDestination(Errors.HoldMessage):
+ reason = _('Message has implicit destination')
+ rejection = _('''Blind carbon copies or other implicit destinations are
+not allowed. Try reposting your message by explicitly including the list
+address in the To: or Cc: fields.''')
+
+class Administrivia(Errors.HoldMessage):
+ reason = _('Message may contain administrivia')
+
+ def rejection_notice(self, mlist):
+ listurl = mlist.GetScriptURL('listinfo', absolute=1)
+ request = mlist.GetRequestEmail()
+ return _("""Please do *not* post administrative requests to the mailing
+list. If you wish to subscribe, visit %(listurl)s or send a message with the
+word `help' in it to the request address, %(request)s, for further
+instructions.""")
+
+class SuspiciousHeaders(Errors.HoldMessage):
+ reason = _('Message has a suspicious header')
+ rejection = _('Your message had a suspicious header.')
+
+class MessageTooBig(Errors.HoldMessage):
+ def __init__(self, msgsize, limit):
+ self.__msgsize = msgsize
+ self.__limit = limit
+
+ def reason_notice(self):
+ size = self.__msgsize
+ limit = self.__limit
+ return _('''Message body is too big: %(size)d bytes with a limit of
+%(limit)d KB''')
+
+ def rejection_notice(self, mlist):
+ kb = self.__limit
+ return _('''Your message was too big; please trim it to less than
+%(kb)d KB in size.''')
+
+class ModeratedNewsgroup(ModeratedPost):
+ reason = _('Posting to a moderated newsgroup')
+
+
+
+# And reset the translator
+_ = i18n._
+
+
+
+def ackp(msg):
+ ack = msg.get('x-ack', '').lower()
+ precedence = msg.get('precedence', '').lower()
+ if ack <> 'yes' and precedence in ('bulk', 'junk', 'list'):
+ return 0
+ return 1
+
+
+
+def process(mlist, msg, msgdata):
+ if msgdata.get('approved'):
+ return
+ # Get the sender of the message
+ listname = mlist.internal_name()
+ adminaddr = listname + '-admin'
+ sender = msg.get_sender()
+ # Special case an ugly sendmail feature: If there exists an alias of the
+ # form "owner-foo: bar" and sendmail receives mail for address "foo",
+ # sendmail will change the envelope sender of the message to "bar" before
+ # delivering. This feature does not appear to be configurable. *Boggle*.
+ if not sender or sender[:len(listname)+6] == adminaddr:
+ sender = msg.get_sender(use_envelope=0)
+ #
+ # Possible administrivia?
+ if mlist.administrivia and Utils.is_administrivia(msg):
+ hold_for_approval(mlist, msg, msgdata, Administrivia)
+ # no return
+ #
+ # Are there too many recipients to the message?
+ if mlist.max_num_recipients > 0:
+ # figure out how many recipients there are
+ recips = email.Utils.getaddresses(msg.get_all('to', []) +
+ msg.get_all('cc', []))
+ if len(recips) >= mlist.max_num_recipients:
+ hold_for_approval(mlist, msg, msgdata, TooManyRecipients)
+ # no return
+ #
+ # Implicit destination? Note that message originating from the Usenet
+ # side of the world should never be checked for implicit destination.
+ if mlist.require_explicit_destination and \
+ not mlist.HasExplicitDest(msg) and \
+ not msgdata.get('fromusenet'):
+ # then
+ hold_for_approval(mlist, msg, msgdata, ImplicitDestination)
+ # no return
+ #
+ # Suspicious headers?
+ if mlist.bounce_matching_headers:
+ triggered = mlist.hasMatchingHeader(msg)
+ if triggered:
+ # TBD: Darn - can't include the matching line for the admin
+ # message because the info would also go to the sender
+ hold_for_approval(mlist, msg, msgdata, SuspiciousHeaders)
+ # no return
+ #
+ # Is the message too big?
+ if mlist.max_message_size > 0:
+ bodylen = 0
+ for line in email.Iterators.body_line_iterator(msg):
+ bodylen += len(line)
+ if bodylen/1024.0 > mlist.max_message_size:
+ hold_for_approval(mlist, msg, msgdata,
+ MessageTooBig(bodylen, mlist.max_message_size))
+ # no return
+ #
+ # Are we gatewaying to a moderated newsgroup and is this list the
+ # moderator's address for the group?
+ if mlist.news_moderation == 2:
+ hold_for_approval(mlist, msg, msgdata, ModeratedNewsgroup)
+
+
+
+def hold_for_approval(mlist, msg, msgdata, exc):
+ # BAW: This should really be tied into the email confirmation system so
+ # that the message can be approved or denied via email as well as the
+ # web.
+ if type(exc) is ClassType:
+ # Go ahead and instantiate it now.
+ exc = exc()
+ listname = mlist.real_name
+ sender = msgdata.get('sender', msg.get_sender())
+ owneraddr = mlist.GetOwnerEmail()
+ adminaddr = mlist.GetBouncesEmail()
+ requestaddr = mlist.GetRequestEmail()
+ # We need to send both the reason and the rejection notice through the
+ # translator again, because of the games we play above
+ reason = Utils.wrap(exc.reason_notice())
+ msgdata['rejection_notice'] = Utils.wrap(exc.rejection_notice(mlist))
+ id = mlist.HoldMessage(msg, reason, msgdata)
+ # Now we need to craft and send a message to the list admin so they can
+ # deal with the held message.
+ d = {'listname' : listname,
+ 'hostname' : mlist.host_name,
+ 'reason' : _(reason),
+ 'sender' : sender,
+ 'subject' : msg.get('subject', _('(no subject)')),
+ 'admindb_url': mlist.GetScriptURL('admindb', absolute=1),
+ }
+ # We may want to send a notification to the original sender too
+ fromusenet = msgdata.get('fromusenet')
+ # Since we're sending two messages, which may potentially be in different
+ # languages (the user's preferred and the list's preferred for the admin),
+ # we need to play some i18n games here. Since the current language
+ # context ought to be set up for the user, let's craft his message first.
+ #
+ # This message should appear to come from <list>-admin so as to handle any
+ # bounce processing that might be needed.
+ cookie = Pending.new(Pending.HELD_MESSAGE, id)
+ if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \
+ mlist.autorespondToSender(sender):
+ # Get a confirmation cookie
+ d['confirmurl'] = '%s/%s' % (mlist.GetScriptURL('confirm', absolute=1),
+ cookie)
+ lang = msgdata.get('lang', mlist.getMemberLanguage(sender))
+ subject = _('Your message to %(listname)s awaits moderator approval')
+ text = Utils.maketext('postheld.txt', d, lang=lang, mlist=mlist)
+ nmsg = Message.UserNotification(sender, adminaddr, subject, text, lang)
+ nmsg.send(mlist)
+ # Now the message for the list owners. Be sure to include the list
+ # moderators in this message. This one should appear to come from
+ # <list>-owner since we really don't need to do bounce processing on it.
+ if mlist.admin_immed_notify:
+ # Now let's temporarily set the language context to that which the
+ # admin is expecting.
+ otranslation = i18n.get_translation()
+ i18n.set_language(mlist.preferred_language)
+ try:
+ lang = mlist.preferred_language
+ charset = Utils.GetCharSet(lang)
+ # We need to regenerate or re-translate a few values in d
+ usersubject = msg.get('subject', _('(no subject)'))
+ d['reason'] = _(reason)
+ d['subject'] = usersubject
+ # craft the admin notification message and deliver it
+ subject = _('%(listname)s post from %(sender)s requires approval')
+ nmsg = Message.UserNotification(owneraddr, owneraddr, subject,
+ lang=lang)
+ nmsg.set_type('multipart/mixed')
+ text = MIMEText(
+ Utils.maketext('postauth.txt', d, raw=1, mlist=mlist),
+ _charset=charset)
+ dmsg = MIMEText(Utils.wrap(_("""\
+If you reply to this message, keeping the Subject: header intact, Mailman will
+discard the held message. Do this if the message is spam. If you reply to
+this message and include an Approved: header with the list password in it, the
+message will be approved for posting to the list. The Approved: header can
+also appear in the first line of the body of the reply.""")),
+ _charset=Utils.GetCharSet(lang))
+ dmsg['Subject'] = 'confirm ' + cookie
+ dmsg['Sender'] = requestaddr
+ dmsg['From'] = requestaddr
+ nmsg.attach(text)
+ nmsg.attach(MIMEMessage(msg))
+ nmsg.attach(MIMEMessage(dmsg))
+ nmsg.send(mlist, **{'tomoderators': 1})
+ finally:
+ i18n.set_translation(otranslation)
+ # Log the held message
+ syslog('vette', '%s post from %s held: %s', listname, sender, reason)
+ # raise the specific MessageHeld exception to exit out of the message
+ # delivery pipeline
+ raise exc