summaryrefslogtreecommitdiff
path: root/mailman/app
diff options
context:
space:
mode:
authorBarry Warsaw2009-01-25 13:01:41 -0500
committerBarry Warsaw2009-01-25 13:01:41 -0500
commiteefd06f1b88b8ecbb23a9013cd223b72ca85c20d (patch)
tree72c947fe16fce0e07e996ee74020b26585d7e846 /mailman/app
parent07871212f74498abd56bef3919bf3e029eb8b930 (diff)
downloadmailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.gz
mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.zst
mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.zip
Diffstat (limited to 'mailman/app')
-rw-r--r--mailman/app/__init__.py0
-rw-r--r--mailman/app/bounces.py63
-rw-r--r--mailman/app/commands.py44
-rw-r--r--mailman/app/lifecycle.py114
-rw-r--r--mailman/app/membership.py137
-rw-r--r--mailman/app/moderator.py351
-rw-r--r--mailman/app/notifications.py136
-rw-r--r--mailman/app/registrar.py163
-rw-r--r--mailman/app/replybot.py125
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