diff options
| author | Barry Warsaw | 2009-01-25 13:01:41 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2009-01-25 13:01:41 -0500 |
| commit | eefd06f1b88b8ecbb23a9013cd223b72ca85c20d (patch) | |
| tree | 72c947fe16fce0e07e996ee74020b26585d7e846 /mailman/app | |
| parent | 07871212f74498abd56bef3919bf3e029eb8b930 (diff) | |
| download | mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.gz mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.zst mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.zip | |
Diffstat (limited to 'mailman/app')
| -rw-r--r-- | mailman/app/__init__.py | 0 | ||||
| -rw-r--r-- | mailman/app/bounces.py | 63 | ||||
| -rw-r--r-- | mailman/app/commands.py | 44 | ||||
| -rw-r--r-- | mailman/app/lifecycle.py | 114 | ||||
| -rw-r--r-- | mailman/app/membership.py | 137 | ||||
| -rw-r--r-- | mailman/app/moderator.py | 351 | ||||
| -rw-r--r-- | mailman/app/notifications.py | 136 | ||||
| -rw-r--r-- | mailman/app/registrar.py | 163 | ||||
| -rw-r--r-- | mailman/app/replybot.py | 125 |
9 files changed, 0 insertions, 1133 deletions
diff --git a/mailman/app/__init__.py b/mailman/app/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/mailman/app/__init__.py +++ /dev/null diff --git a/mailman/app/bounces.py b/mailman/app/bounces.py deleted file mode 100644 index 875f615a5..000000000 --- a/mailman/app/bounces.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright (C) 2007-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman 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 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman 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 -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Application level bounce handling.""" - -from __future__ import unicode_literals - -__metaclass__ = type -__all__ = [ - 'bounce_message', - ] - -import logging - -from email.mime.message import MIMEMessage -from email.mime.text import MIMEText - -from mailman import Message -from mailman import Utils -from mailman.i18n import _ - -log = logging.getLogger('mailman.config') - - - -def bounce_message(mlist, msg, e=None): - # Bounce a message back to the sender, with an error message if provided - # in the exception argument. - sender = msg.get_sender() - subject = msg.get('subject', _('(no subject)')) - subject = Utils.oneline(subject, - Utils.GetCharSet(mlist.preferred_language)) - if e is None: - notice = _('[No bounce details are available]') - else: - notice = _(e.notice) - # Currently we always craft bounces as MIME messages. - bmsg = Message.UserNotification(msg.get_sender(), - mlist.owner_address, - subject, - lang=mlist.preferred_language) - # BAW: Be sure you set the type before trying to attach, or you'll get - # a MultipartConversionError. - bmsg.set_type('multipart/mixed') - txt = MIMEText(notice, - _charset=Utils.GetCharSet(mlist.preferred_language)) - bmsg.attach(txt) - bmsg.attach(MIMEMessage(msg)) - bmsg.send(mlist) diff --git a/mailman/app/commands.py b/mailman/app/commands.py deleted file mode 100644 index d7676af9c..000000000 --- a/mailman/app/commands.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (C) 2008-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman 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 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman 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 -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Initialize the email commands.""" - -from __future__ import unicode_literals - -__metaclass__ = type -__all__ = [ - 'initialize', - ] - - -from mailman.config import config -from mailman.core.plugins import get_plugins -from mailman.interfaces.command import IEmailCommand - - - -def initialize(): - """Initialize the email commands.""" - for module in get_plugins('mailman.commands'): - for name in module.__all__: - command_class = getattr(module, name) - if not IEmailCommand.implementedBy(command_class): - continue - assert command_class.name not in config.commands, ( - 'Duplicate email command "{0}" found in {1}'.format( - command_class.name, module)) - config.commands[command_class.name] = command_class() diff --git a/mailman/app/lifecycle.py b/mailman/app/lifecycle.py deleted file mode 100644 index eec00dc86..000000000 --- a/mailman/app/lifecycle.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright (C) 2007-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman 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 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman 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 -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Application level list creation.""" - -from __future__ import unicode_literals - -__metaclass__ = type -__all__ = [ - 'create_list', - 'remove_list', - ] - - -import os -import sys -import shutil -import logging - -from mailman import Utils -from mailman.Utils import ValidateEmail -from mailman.config import config -from mailman.core import errors -from mailman.interfaces.member import MemberRole - - -log = logging.getLogger('mailman.error') - - - -def create_list(fqdn_listname, owners=None): - """Create the named list and apply styles.""" - if owners is None: - owners = [] - ValidateEmail(fqdn_listname) - listname, domain = fqdn_listname.split('@', 1) - if domain not in config.domains: - raise errors.BadDomainSpecificationError(domain) - mlist = config.db.list_manager.create(fqdn_listname) - for style in config.style_manager.lookup(mlist): - style.apply(mlist) - # Coordinate with the MTA, as defined in the configuration file. - module_name, class_name = config.mta.incoming.rsplit('.', 1) - __import__(module_name) - getattr(sys.modules[module_name], class_name)().create(mlist) - # Create any owners that don't yet exist, and subscribe all addresses as - # owners of the mailing list. - usermgr = config.db.user_manager - for owner_address in owners: - addr = usermgr.get_address(owner_address) - if addr is None: - # XXX Make this use an IRegistrar instead, but that requires - # sussing out the IDomain stuff. For now, fake it. - user = usermgr.create_user(owner_address) - addr = list(user.addresses)[0] - addr.subscribe(mlist, MemberRole.owner) - return mlist - - - -def remove_list(fqdn_listname, mailing_list=None, archives=True): - """Remove the list and all associated artifacts and subscriptions.""" - removeables = [] - # mailing_list will be None when only residual archives are being removed. - if mailing_list: - # Remove all subscriptions, regardless of role. - for member in mailing_list.subscribers.members: - member.unsubscribe() - # Delete the mailing list from the database. - config.db.list_manager.delete(mailing_list) - # Do the MTA-specific list deletion tasks - module_name, class_name = config.mta.incoming.rsplit('.', 1) - __import__(module_name) - getattr(sys.modules[module_name], class_name)().create(mailing_list) - # Remove the list directory. - removeables.append(os.path.join(config.LIST_DATA_DIR, fqdn_listname)) - # Remove any stale locks associated with the list. - for filename in os.listdir(config.LOCK_DIR): - fn_listname = filename.split('.')[0] - if fn_listname == fqdn_listname: - removeables.append(os.path.join(config.LOCK_DIR, filename)) - if archives: - private_dir = config.PRIVATE_ARCHIVE_FILE_DIR - public_dir = config.PUBLIC_ARCHIVE_FILE_DIR - removeables.extend([ - os.path.join(private_dir, fqdn_listname), - os.path.join(private_dir, fqdn_listname + '.mbox'), - os.path.join(public_dir, fqdn_listname), - os.path.join(public_dir, fqdn_listname + '.mbox'), - ]) - # Now that we know what files and directories to delete, delete them. - for target in removeables: - if os.path.islink(target): - os.unlink(target) - elif os.path.isdir(target): - shutil.rmtree(target) - elif os.path.isfile(target): - os.unlink(target) - else: - log.error('Could not delete list artifact: %s', target) diff --git a/mailman/app/membership.py b/mailman/app/membership.py deleted file mode 100644 index 4b9609469..000000000 --- a/mailman/app/membership.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright (C) 2007-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman 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 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman 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 -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Application support for membership management.""" - -from __future__ import unicode_literals - -__metaclass__ = type -__all__ = [ - 'add_member', - 'delete_member', - ] - - -from email.utils import formataddr - -from mailman import Message -from mailman import Utils -from mailman import i18n -from mailman.app.notifications import send_goodbye_message -from mailman.config import config -from mailman.core import errors -from mailman.interfaces.member import AlreadySubscribedError, MemberRole - -_ = i18n._ - - - -def add_member(mlist, address, realname, password, delivery_mode, language): - """Add a member right now. - - The member's subscription must be approved by whatever policy the list - enforces. - - :param mlist: the mailing list to add the member to - :type mlist: IMailingList - :param address: the address to subscribe - :type address: string - :param realname: the subscriber's full name - :type realname: string - :param password: the subscriber's password - :type password: string - :param delivery_mode: the delivery mode the subscriber has chosen - :type delivery_mode: DeliveryMode - :param language: the language that the subscriber is going to use - :type language: string - """ - # Let's be extra cautious. - Utils.ValidateEmail(address) - if mlist.members.get_member(address) is not None: - raise AlreadySubscribedError( - mlist.fqdn_listname, address, MemberRole.member) - # Check for banned address here too for admin mass subscribes and - # confirmations. - pattern = Utils.get_pattern(address, mlist.ban_list) - if pattern: - raise errors.MembershipIsBanned(pattern) - # Do the actual addition. First, see if there's already a user linked - # with the given address. - user = config.db.user_manager.get_user(address) - if user is None: - # A user linked to this address does not yet exist. Is the address - # itself known but just not linked to a user? - address_obj = config.db.user_manager.get_address(address) - if address_obj is None: - # Nope, we don't even know about this address, so create both the - # user and address now. - user = config.db.user_manager.create_user(address, realname) - # Do it this way so we don't have to flush the previous change. - address_obj = list(user.addresses)[0] - else: - # The address object exists, but it's not linked to a user. - # Create the user and link it now. - user = config.db.user_manager.create_user() - user.real_name = (realname if realname else address_obj.real_name) - user.link(address_obj) - # Since created the user, then the member, and set preferences on the - # appropriate object. - user.password = password - user.preferences.preferred_language = language - member = address_obj.subscribe(mlist, MemberRole.member) - member.preferences.delivery_mode = delivery_mode - else: - # The user exists and is linked to the address. - for address_obj in user.addresses: - if address_obj.address == address: - break - else: - raise AssertionError( - 'User should have had linked address: {0}'.format(address)) - # Create the member and set the appropriate preferences. - member = address_obj.subscribe(mlist, MemberRole.member) - member.preferences.preferred_language = language - member.preferences.delivery_mode = delivery_mode -## mlist.setMemberOption(email, config.Moderate, -## mlist.default_member_moderation) - - - -def delete_member(mlist, address, admin_notif=None, userack=None): - if userack is None: - userack = mlist.send_goodbye_msg - if admin_notif is None: - admin_notif = mlist.admin_notify_mchanges - # Delete a member, for which we know the approval has been made - member = mlist.members.get_member(address) - language = member.preferred_language - member.unsubscribe() - # And send an acknowledgement to the user... - if userack: - send_goodbye_message(mlist, address, language) - # ...and to the administrator. - if admin_notif: - user = config.db.user_manager.get_user(address) - realname = user.real_name - subject = _('$mlist.real_name unsubscription notification') - text = Utils.maketext( - 'adminunsubscribeack.txt', - {'listname': mlist.real_name, - 'member' : formataddr((realname, address)), - }, mlist=mlist) - msg = Message.OwnerNotification(mlist, subject, text) - msg.send(mlist) diff --git a/mailman/app/moderator.py b/mailman/app/moderator.py deleted file mode 100644 index b40a34344..000000000 --- a/mailman/app/moderator.py +++ /dev/null @@ -1,351 +0,0 @@ -# Copyright (C) 2007-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman 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 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman 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 -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Application support for moderators.""" - -from __future__ import unicode_literals - -__metaclass__ = type -__all__ = [ - 'handle_message', - 'handle_subscription', - 'handle_unsubscription', - 'hold_message', - 'hold_subscription', - 'hold_unsubscription', - ] - -import logging - -from datetime import datetime -from email.utils import formataddr, formatdate, getaddresses, make_msgid - -from mailman import Message -from mailman import Utils -from mailman import i18n -from mailman.app.membership import add_member, delete_member -from mailman.app.notifications import ( - send_admin_subscription_notice, send_welcome_message) -from mailman.config import config -from mailman.core import errors -from mailman.interfaces import Action -from mailman.interfaces.member import AlreadySubscribedError, DeliveryMode -from mailman.interfaces.requests import RequestType - -_ = i18n._ - -vlog = logging.getLogger('mailman.vette') -slog = logging.getLogger('mailman.subscribe') - - - -def hold_message(mlist, msg, msgdata=None, reason=None): - """Hold a message for moderator approval. - - The message is added to the mailing list's request database. - - :param mlist: The mailing list to hold the message on. - :param msg: The message to hold. - :param msgdata: Optional message metadata to hold. If not given, a new - metadata dictionary is created and held with the message. - :param reason: Optional string reason why the message is being held. If - not given, the empty string is used. - :return: An id used to handle the held message later. - """ - if msgdata is None: - msgdata = {} - else: - # Make a copy of msgdata so that subsequent changes won't corrupt the - # request database. TBD: remove the `filebase' key since this will - # not be relevant when the message is resurrected. - msgdata = msgdata.copy() - if reason is None: - reason = '' - # Add the message to the message store. It is required to have a - # Message-ID header. - message_id = msg.get('message-id') - if message_id is None: - msg['Message-ID'] = message_id = unicode(make_msgid()) - assert isinstance(message_id, unicode), ( - 'Message-ID is not a unicode: %s' % message_id) - config.db.message_store.add(msg) - # Prepare the message metadata with some extra information needed only by - # the moderation interface. - msgdata['_mod_message_id'] = message_id - msgdata['_mod_fqdn_listname'] = mlist.fqdn_listname - msgdata['_mod_sender'] = msg.get_sender() - msgdata['_mod_subject'] = msg.get('subject', _('(no subject)')) - msgdata['_mod_reason'] = reason - msgdata['_mod_hold_date'] = datetime.now().isoformat() - # Now hold this request. We'll use the message_id as the key. - requestsdb = config.db.requests.get_list_requests(mlist) - request_id = requestsdb.hold_request( - RequestType.held_message, message_id, msgdata) - return request_id - - - -def handle_message(mlist, id, action, - comment=None, preserve=False, forward=None): - requestdb = config.db.requests.get_list_requests(mlist) - key, msgdata = requestdb.get_request(id) - # Handle the action. - rejection = None - message_id = msgdata['_mod_message_id'] - sender = msgdata['_mod_sender'] - subject = msgdata['_mod_subject'] - if action is Action.defer: - # Nothing to do, but preserve the message for later. - preserve = True - elif action is Action.discard: - rejection = 'Discarded' - elif action is Action.reject: - rejection = 'Refused' - member = mlist.members.get_member(sender) - if member: - language = member.preferred_language - else: - language = None - _refuse(mlist, _('Posting of your message titled "$subject"'), - sender, comment or _('[No reason given]'), language) - elif action is Action.accept: - # Start by getting the message from the message store. - msg = config.db.message_store.get_message_by_id(message_id) - # Delete moderation-specific entries from the message metadata. - for key in msgdata.keys(): - if key.startswith('_mod_'): - del msgdata[key] - # Add some metadata to indicate this message has now been approved. - msgdata['approved'] = True - msgdata['moderator_approved'] = True - # Calculate a new filebase for the approved message, otherwise - # delivery errors will cause duplicates. - if 'filebase' in msgdata: - del msgdata['filebase'] - # Queue the file for delivery by qrunner. Trying to deliver the - # message directly here can lead to a huge delay in web turnaround. - # Log the moderation and add a header. - msg['X-Mailman-Approved-At'] = formatdate(localtime=True) - vlog.info('held message approved, message-id: %s', - msg.get('message-id', 'n/a')) - # Stick the message back in the incoming queue for further - # processing. - config.switchboards['in'].enqueue(msg, _metadata=msgdata) - else: - raise AssertionError('Unexpected action: {0}'.format(action)) - # Forward the message. - if forward: - # Get a copy of the original message from the message store. - msg = config.db.message_store.get_message_by_id(message_id) - # It's possible the forwarding address list is a comma separated list - # of realname/address pairs. - addresses = [addr[1] for addr in getaddresses(forward)] - language = mlist.preferred_language - if len(addresses) == 1: - # If the address getting the forwarded message is a member of - # the list, we want the headers of the outer message to be - # encoded in their language. Otherwise it'll be the preferred - # language of the mailing list. This is better than sending a - # separate message per recipient. - member = mlist.members.get_member(addresses[0]) - if member: - language = member.preferred_language - with i18n.using_language(language): - fmsg = Message.UserNotification( - addresses, mlist.bounces_address, - _('Forward of moderated message'), - lang=language) - fmsg.set_type('message/rfc822') - fmsg.attach(msg) - fmsg.send(mlist) - # Delete the message from the message store if it is not being preserved. - if not preserve: - config.db.message_store.delete_message(message_id) - requestdb.delete_request(id) - # Log the rejection - if rejection: - note = """%s: %s posting: -\tFrom: %s -\tSubject: %s""" - if comment: - note += '\n\tReason: ' + comment - vlog.info(note, mlist.fqdn_listname, rejection, sender, subject) - - - -def hold_subscription(mlist, address, realname, password, mode, language): - data = dict(when=datetime.now().isoformat(), - address=address, - realname=realname, - password=password, - delivery_mode=str(mode), - language=language) - # Now hold this request. We'll use the address as the key. - requestsdb = config.db.requests.get_list_requests(mlist) - request_id = requestsdb.hold_request( - RequestType.subscription, address, data) - vlog.info('%s: held subscription request from %s', - mlist.fqdn_listname, address) - # Possibly notify the administrator in default list language - if mlist.admin_immed_notify: - subject = _( - 'New subscription request to list $mlist.real_name from $address') - text = Utils.maketext( - 'subauth.txt', - {'username' : address, - 'listname' : mlist.fqdn_listname, - 'admindb_url': mlist.script_url('admindb'), - }, mlist=mlist) - # This message should appear to come from the <list>-owner so as - # to avoid any useless bounce processing. - msg = Message.UserNotification( - mlist.owner_address, mlist.owner_address, - subject, text, mlist.preferred_language) - msg.send(mlist, tomoderators=True) - return request_id - - - -def handle_subscription(mlist, id, action, comment=None): - requestdb = config.db.requests.get_list_requests(mlist) - if action is Action.defer: - # Nothing to do. - return - elif action is Action.discard: - # Nothing to do except delete the request from the database. - pass - elif action is Action.reject: - key, data = requestdb.get_request(id) - _refuse(mlist, _('Subscription request'), - data['address'], - comment or _('[No reason given]'), - lang=data['language']) - elif action is Action.accept: - key, data = requestdb.get_request(id) - enum_value = data['delivery_mode'].split('.')[-1] - delivery_mode = DeliveryMode(enum_value) - address = data['address'] - realname = data['realname'] - language = data['language'] - password = data['password'] - try: - add_member(mlist, address, realname, password, - delivery_mode, language) - except AlreadySubscribedError: - # The address got subscribed in some other way after the original - # request was made and accepted. - pass - else: - if mlist.send_welcome_msg: - send_welcome_message(mlist, address, language, delivery_mode) - if mlist.admin_notify_mchanges: - send_admin_subscription_notice( - mlist, address, realname, language) - slog.info('%s: new %s, %s %s', mlist.fqdn_listname, - delivery_mode, formataddr((realname, address)), - 'via admin approval') - else: - raise AssertionError('Unexpected action: {0}'.format(action)) - # Delete the request from the database. - requestdb.delete_request(id) - - - -def hold_unsubscription(mlist, address): - data = dict(address=address) - requestsdb = config.db.requests.get_list_requests(mlist) - request_id = requestsdb.hold_request( - RequestType.unsubscription, address, data) - vlog.info('%s: held unsubscription request from %s', - mlist.fqdn_listname, address) - # Possibly notify the administrator of the hold - if mlist.admin_immed_notify: - subject = _( - 'New unsubscription request from $mlist.real_name by $address') - text = Utils.maketext( - 'unsubauth.txt', - {'address' : address, - 'listname' : mlist.fqdn_listname, - 'admindb_url': mlist.script_url('admindb'), - }, mlist=mlist) - # This message should appear to come from the <list>-owner so as - # to avoid any useless bounce processing. - msg = Message.UserNotification( - mlist.owner_address, mlist.owner_address, - subject, text, mlist.preferred_language) - msg.send(mlist, tomoderators=True) - return request_id - - - -def handle_unsubscription(mlist, id, action, comment=None): - requestdb = config.db.requests.get_list_requests(mlist) - key, data = requestdb.get_request(id) - address = data['address'] - if action is Action.defer: - # Nothing to do. - return - elif action is Action.discard: - # Nothing to do except delete the request from the database. - pass - elif action is Action.reject: - key, data = requestdb.get_request(id) - _refuse(mlist, _('Unsubscription request'), address, - comment or _('[No reason given]')) - elif action is Action.accept: - key, data = requestdb.get_request(id) - try: - delete_member(mlist, address) - except errors.NotAMemberError: - # User has already been unsubscribed. - pass - slog.info('%s: deleted %s', mlist.fqdn_listname, address) - else: - raise AssertionError('Unexpected action: {0}'.format(action)) - # Delete the request from the database. - requestdb.delete_request(id) - - - -def _refuse(mlist, request, recip, comment, origmsg=None, lang=None): - # As this message is going to the requester, try to set the language to - # his/her language choice, if they are a member. Otherwise use the list's - # preferred language. - realname = mlist.real_name - if lang is None: - member = mlist.members.get_member(recip) - if member: - lang = member.preferred_language - text = Utils.maketext( - 'refuse.txt', - {'listname' : mlist.fqdn_listname, - 'request' : request, - 'reason' : comment, - 'adminaddr': mlist.owner_address, - }, lang=lang, mlist=mlist) - with i18n.using_language(lang): - # add in original message, but not wrap/filled - if origmsg: - text = NL.join( - [text, - '---------- ' + _('Original Message') + ' ----------', - str(origmsg) - ]) - subject = _('Request to mailing list "$realname" rejected') - msg = Message.UserNotification(recip, mlist.bounces_address, - subject, text, lang) - msg.send(mlist) diff --git a/mailman/app/notifications.py b/mailman/app/notifications.py deleted file mode 100644 index 9bef9998b..000000000 --- a/mailman/app/notifications.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright (C) 2007-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman 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 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman 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 -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Sending notifications.""" - -from __future__ import unicode_literals - -__metaclass__ = type -__all__ = [ - 'send_admin_subscription_notice', - 'send_goodbye_message', - 'send_welcome_message', - ] - - -from email.utils import formataddr -from lazr.config import as_boolean - -from mailman import Message -from mailman import Utils -from mailman import i18n -from mailman.config import config -from mailman.interfaces.member import DeliveryMode - - -_ = i18n._ - - - -def send_welcome_message(mlist, address, language, delivery_mode, text=''): - """Send a welcome message to a subscriber. - - Prepending to the standard welcome message template is the mailing list's - welcome message, if there is one. - - :param mlist: the mailing list - :type mlist: IMailingList - :param address: The address to respond to - :type address: string - :param language: the language of the response - :type language: string - :param delivery_mode: the type of delivery the subscriber is getting - :type delivery_mode: DeliveryMode - """ - if mlist.welcome_msg: - welcome = Utils.wrap(mlist.welcome_msg) + '\n' - else: - welcome = '' - # Find the IMember object which is subscribed to the mailing list, because - # from there, we can get the member's options url. - member = mlist.members.get_member(address) - options_url = member.options_url - # Get the text from the template. - text += Utils.maketext( - 'subscribeack.txt', { - 'real_name' : mlist.real_name, - 'posting_address' : mlist.fqdn_listname, - 'listinfo_url' : mlist.script_url('listinfo'), - 'optionsurl' : options_url, - 'request_address' : mlist.request_address, - 'welcome' : welcome, - }, lang=language, mlist=mlist) - if delivery_mode is not DeliveryMode.regular: - digmode = _(' (Digest mode)') - else: - digmode = '' - msg = Message.UserNotification( - address, mlist.request_address, - _('Welcome to the "$mlist.real_name" mailing list${digmode}'), - text, language) - msg['X-No-Archive'] = 'yes' - msg.send(mlist, verp=as_boolean(config.mta.verp_personalized_deliveries)) - - - -def send_goodbye_message(mlist, address, language): - """Send a goodbye message to a subscriber. - - Prepending to the standard goodbye message template is the mailing list's - goodbye message, if there is one. - - :param mlist: the mailing list - :type mlist: IMailingList - :param address: The address to respond to - :type address: string - :param language: the language of the response - :type language: string - """ - if mlist.goodbye_msg: - goodbye = Utils.wrap(mlist.goodbye_msg) + '\n' - else: - goodbye = '' - msg = Message.UserNotification( - address, mlist.bounces_address, - _('You have been unsubscribed from the $mlist.real_name mailing list'), - goodbye, language) - msg.send(mlist, verp=as_boolean(config.mta.verp_personalized_deliveries)) - - - -def send_admin_subscription_notice(mlist, address, full_name, language): - """Send the list administrators a subscription notice. - - :param mlist: the mailing list - :type mlist: IMailingList - :param address: the address being subscribed - :type address: string - :param full_name: the name of the subscriber - :type full_name: string - :param language: the language of the address's realname - :type language: string - """ - with i18n.using_language(mlist.preferred_language): - subject = _('$mlist.real_name subscription notification') - full_name = full_name.encode(Utils.GetCharSet(language), 'replace') - text = Utils.maketext( - 'adminsubscribeack.txt', - {'listname' : mlist.real_name, - 'member' : formataddr((full_name, address)), - }, mlist=mlist) - msg = Message.OwnerNotification(mlist, subject, text) - msg.send(mlist) diff --git a/mailman/app/registrar.py b/mailman/app/registrar.py deleted file mode 100644 index 6a2abeba9..000000000 --- a/mailman/app/registrar.py +++ /dev/null @@ -1,163 +0,0 @@ -# Copyright (C) 2007-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman 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 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman 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 -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Implementation of the IUserRegistrar interface.""" - -from __future__ import unicode_literals - -__metaclass__ = type -__all__ = [ - 'Registrar', - 'adapt_domain_to_registrar', - ] - - -import datetime - -from pkg_resources import resource_string -from zope.interface import implements - -from mailman.Message import UserNotification -from mailman.Utils import ValidateEmail -from mailman.config import config -from mailman.i18n import _ -from mailman.interfaces.domain import IDomain -from mailman.interfaces.member import MemberRole -from mailman.interfaces.pending import IPendable -from mailman.interfaces.registrar import IRegistrar - - - -class PendableRegistration(dict): - implements(IPendable) - PEND_KEY = 'registration' - - - -class Registrar: - implements(IRegistrar) - - def __init__(self, context): - self._context = context - - def register(self, address, real_name=None, mlist=None): - """See `IUserRegistrar`.""" - # First, do validation on the email address. If the address is - # invalid, it will raise an exception, otherwise it just returns. - ValidateEmail(address) - # Create a pendable for the registration. - pendable = PendableRegistration( - type=PendableRegistration.PEND_KEY, - address=address, - real_name=real_name) - if mlist is not None: - pendable['list_name'] = mlist.fqdn_listname - token = config.db.pendings.add(pendable) - # Set up some local variables for translation interpolation. - domain = IDomain(self._context) - domain_name = _(domain.email_host) - contact_address = domain.contact_address - confirm_url = domain.confirm_url(token) - confirm_address = domain.confirm_address(token) - email_address = address - # Calculate the message's Subject header. XXX Have to deal with - # translating this subject header properly. XXX Must deal with - # VERP_CONFIRMATIONS as well. - subject = 'confirm ' + token - # Send a verification email to the address. - text = _(resource_string('mailman.templates.en', 'verify.txt')) - msg = UserNotification(address, confirm_address, subject, text) - msg.send(mlist=None) - return token - - def confirm(self, token): - """See `IUserRegistrar`.""" - # For convenience - pendable = config.db.pendings.confirm(token) - if pendable is None: - return False - missing = object() - address = pendable.get('address', missing) - real_name = pendable.get('real_name', missing) - list_name = pendable.get('list_name', missing) - if pendable.get('type') != PendableRegistration.PEND_KEY: - # It seems like it would be very difficult to accurately guess - # tokens, or brute force an attack on the SHA1 hash, so we'll just - # throw the pendable away in that case. It's possible we'll need - # to repend the event or adjust the API to handle this case - # better, but for now, the simpler the better. - return False - # We are going to end up with an IAddress for the verified address - # and an IUser linked to this IAddress. See if any of these objects - # currently exist in our database. - usermgr = config.db.user_manager - addr = (usermgr.get_address(address) - if address is not missing else None) - user = (usermgr.get_user(address) - if address is not missing else None) - # If there is neither an address nor a user matching the confirmed - # record, then create the user, which will in turn create the address - # and link the two together - if addr is None: - assert user is None, 'How did we get a user but not an address?' - user = usermgr.create_user(address, real_name) - # Because the database changes haven't been flushed, we can't use - # IUserManager.get_address() to find the IAddress just created - # under the hood. Instead, iterate through the IUser's addresses, - # of which really there should be only one. - for addr in user.addresses: - if addr.address == address: - break - else: - raise AssertionError('Could not find expected IAddress') - elif user is None: - user = usermgr.create_user() - user.real_name = real_name - user.link(addr) - else: - # The IAddress and linked IUser already exist, so all we need to - # do is verify the address. - pass - addr.verified_on = datetime.datetime.now() - # If this registration is tied to a mailing list, subscribe the person - # to the list right now. - list_name = pendable.get('list_name') - if list_name is not None: - mlist = config.db.list_manager.get(list_name) - if mlist: - addr.subscribe(mlist, MemberRole.member) - return True - - def discard(self, token): - # Throw the record away. - config.db.pendings.confirm(token) - - - -def adapt_domain_to_registrar(iface, obj): - """Adapt `IDomain` to `IRegistrar`. - - :param iface: The interface to adapt to. - :type iface: `zope.interface.Interface` - :param obj: The object being adapted. - :type obj: `IDomain` - :return: An `IRegistrar` instance if adaptation succeeded or None if it - didn't. - """ - return (Registrar(obj) - if IDomain.providedBy(obj) and iface is IRegistrar - else None) diff --git a/mailman/app/replybot.py b/mailman/app/replybot.py deleted file mode 100644 index 0537f6645..000000000 --- a/mailman/app/replybot.py +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright (C) 2007-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman 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 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman 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 -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Application level auto-reply code.""" - -# XXX This should undergo a rewrite to move this functionality off of the -# mailing list. The reply governor should really apply site-wide per -# recipient (I think). - -from __future__ import unicode_literals - -__metaclass__ = type -__all__ = [ - 'autorespond_to_sender', - 'can_acknowledge', - ] - -import logging -import datetime - -from mailman import Utils -from mailman import i18n -from mailman.config import config - - -log = logging.getLogger('mailman.vette') -_ = i18n._ - - - -def autorespond_to_sender(mlist, sender, lang=None): - """Return True if Mailman should auto-respond to this sender. - - This is only consulted for messages sent to the -request address, or - for posting hold notifications, and serves only as a safety value for - mail loops with email 'bots. - """ - if lang is None: - lang = mlist.preferred_language - max_autoresponses_per_day = int(config.mta.max_autoresponses_per_day) - if max_autoresponses_per_day == 0: - # Unlimited. - return True - today = datetime.date.today() - info = mlist.hold_and_cmd_autoresponses.get(sender) - if info is None or info[0] <> today: - # This is the first time we've seen a -request/post-hold for this - # sender today. - mlist.hold_and_cmd_autoresponses[sender] = (today, 1) - return True - date, count = info - if count < 0: - # They've already hit the limit for today, and we've already notified - # them of this fact, so there's nothing more to do. - log.info('-request/hold autoresponse discarded for: %s', sender) - return False - if count >= max_autoresponses_per_day: - log.info('-request/hold autoresponse limit hit for: %s', sender) - mlist.hold_and_cmd_autoresponses[sender] = (today, -1) - # Send this notification message instead. - text = Utils.maketext( - 'nomoretoday.txt', - {'sender' : sender, - 'listname': mlist.fqdn_listname, - 'num' : count, - 'owneremail': mlist.owner_address, - }, - lang=lang) - with i18n.using_language(lang): - msg = Message.UserNotification( - sender, mlist.owner_address, - _('Last autoresponse notification for today'), - text, lang=lang) - msg.send(mlist) - return False - mlist.hold_and_cmd_autoresponses[sender] = (today, count + 1) - return True - - - -def can_acknowledge(msg): - """A boolean specifying whether this message can be acknowledged. - - There are several reasons why a message should not be acknowledged, mostly - related to competing standards or common practices. These include: - - * The message has a X-No-Ack header with any value - * The message has an X-Ack header with a 'no' value - * The message has a Precedence header - * The message has an Auto-Submitted header and that header does not have a - value of 'no' - * The message has an empty Return-Path header, e.g. <> - * The message has any RFC 2369 headers (i.e. List-* headers) - - :param msg: a Message object. - :return: Boolean specifying whether the message can be acknowledged or not - (which is different from whether it will be acknowledged). - """ - # I wrote it this way for clarity and consistency with the docstring. - for header in msg.keys(): - if header in ('x-no-ack', 'precedence'): - return False - if header.lower().startswith('list-'): - return False - if msg.get('x-ack', '').lower() == 'no': - return False - if msg.get('auto-submitted', 'no').lower() <> 'no': - return False - if msg.get('return-path') == '<>': - return False - return True |
