summaryrefslogtreecommitdiff
path: root/Mailman/Bouncer.py
diff options
context:
space:
mode:
Diffstat (limited to 'Mailman/Bouncer.py')
-rw-r--r--Mailman/Bouncer.py250
1 files changed, 0 insertions, 250 deletions
diff --git a/Mailman/Bouncer.py b/Mailman/Bouncer.py
deleted file mode 100644
index b576cd210..000000000
--- a/Mailman/Bouncer.py
+++ /dev/null
@@ -1,250 +0,0 @@
-# Copyright (C) 1998-2008 by the Free Software Foundation, Inc.
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""Handle delivery bounces."""
-
-import sys
-import time
-import logging
-
-from email.MIMEMessage import MIMEMessage
-from email.MIMEText import MIMEText
-
-from Mailman import Defaults
-from Mailman import Message
-from Mailman import Utils
-from Mailman import i18n
-from Mailman.configuration import config
-from Mailman.interfaces import DeliveryStatus
-
-EMPTYSTRING = ''
-
-# This constant is supposed to represent the day containing the first midnight
-# after the epoch. We'll add (0,)*6 to this tuple to get a value appropriate
-# for time.mktime().
-ZEROHOUR_PLUSONEDAY = time.localtime(60 * 60 * 24)[:3]
-
-def _(s): return s
-
-REASONS = {
- DeliveryStatus.by_bounces : _('due to excessive bounces'),
- DeliveryStatus.by_user : _('by yourself'),
- DeliveryStatus.by_moderator : _('by the list administrator'),
- DeliveryStatus.unknown : _('for unknown reasons'),
- }
-
-_ = i18n._
-
-log = logging.getLogger('mailman.bounce')
-slog = logging.getLogger('mailman.subscribe')
-
-
-
-class _BounceInfo:
- def __init__(self, member, score, date, noticesleft):
- self.member = member
- self.cookie = None
- self.reset(score, date, noticesleft)
-
- def reset(self, score, date, noticesleft):
- self.score = score
- self.date = date
- self.noticesleft = noticesleft
- self.lastnotice = ZEROHOUR_PLUSONEDAY
-
- def __repr__(self):
- # For debugging
- return """\
-<bounce info for member %(member)s
- current score: %(score)s
- last bounce date: %(date)s
- email notices left: %(noticesleft)s
- last notice date: %(lastnotice)s
- confirmation cookie: %(cookie)s
- >""" % self.__dict__
-
-
-
-class Bouncer:
- def registerBounce(self, member, msg, weight=1.0, day=None):
- if not self.isMember(member):
- return
- info = self.getBounceInfo(member)
- if day is None:
- # Use today's date
- day = time.localtime()[:3]
- if not isinstance(info, _BounceInfo):
- # This is the first bounce we've seen from this member
- info = _BounceInfo(member, weight, day,
- self.bounce_you_are_disabled_warnings)
- self.setBounceInfo(member, info)
- log.info('%s: %s bounce score: %s', self.internal_name(),
- member, info.score)
- # Continue to the check phase below
- elif self.getDeliveryStatus(member) <> DeliveryStatus.enabled:
- # The user is already disabled, so we can just ignore subsequent
- # bounces. These are likely due to residual messages that were
- # sent before disabling the member, but took a while to bounce.
- log.info('%s: %s residual bounce received',
- self.internal_name(), member)
- return
- elif info.date == day:
- # We've already scored any bounces for this day, so ignore it.
- log.info('%s: %s already scored a bounce for date %s',
- self.internal_name(), member,
- time.strftime('%d-%b-%Y', day + (0,0,0,0,1,0)))
- # Continue to check phase below
- else:
- # See if this member's bounce information is stale.
- now = Utils.midnight(day)
- lastbounce = Utils.midnight(info.date)
- if lastbounce + self.bounce_info_stale_after < now:
- # Information is stale, so simply reset it
- info.reset(weight, day, self.bounce_you_are_disabled_warnings)
- log.info('%s: %s has stale bounce info, resetting',
- self.internal_name(), member)
- else:
- # Nope, the information isn't stale, so add to the bounce
- # score and take any necessary action.
- info.score += weight
- info.date = day
- log.info('%s: %s current bounce score: %s',
- self.internal_name(), member, info.score)
- # Continue to the check phase below
- #
- # Now that we've adjusted the bounce score for this bounce, let's
- # check to see if the disable-by-bounce threshold has been reached.
- if info.score >= self.bounce_score_threshold:
- if config.VERP_PROBES:
- log.info('sending %s list probe to: %s (score %s >= %s)',
- self.internal_name(), member, info.score,
- self.bounce_score_threshold)
- self.sendProbe(member, msg)
- info.reset(0, info.date, info.noticesleft)
- else:
- self.disableBouncingMember(member, info, msg)
-
- def disableBouncingMember(self, member, info, msg):
- # Initialize their confirmation cookie. If we do it when we get the
- # first bounce, it'll expire by the time we get the disabling bounce.
- cookie = self.pend_new(Pending.RE_ENABLE, self.internal_name(), member)
- info.cookie = cookie
- # Disable them
- if config.VERP_PROBES:
- log.info('%s: %s disabling due to probe bounce received',
- self.internal_name(), member)
- else:
- log.info('%s: %s disabling due to bounce score %s >= %s',
- self.internal_name(), member,
- info.score, self.bounce_score_threshold)
- self.setDeliveryStatus(member, DeliveryStatus.by_bounces)
- self.sendNextNotification(member)
- if self.bounce_notify_owner_on_disable:
- self.__sendAdminBounceNotice(member, msg)
-
- def __sendAdminBounceNotice(self, member, msg):
- # BAW: This is a bit kludgey, but we're not providing as much
- # information in the new admin bounce notices as we used to (some of
- # it was of dubious value). However, we'll provide empty, strange, or
- # meaningless strings for the unused %()s fields so that the language
- # translators don't have to provide new templates.
- text = Utils.maketext(
- 'bounce.txt',
- {'listname' : self.real_name,
- 'addr' : member,
- 'negative' : '',
- 'did' : _('disabled'),
- 'but' : '',
- 'reenable' : '',
- 'owneraddr': self.no_reply_address,
- }, mlist=self)
- subject = _('Bounce action notification')
- umsg = Message.UserNotification(self.GetOwnerEmail(),
- self.no_reply_address,
- subject,
- lang=self.preferred_language)
- # BAW: Be sure you set the type before trying to attach, or you'll get
- # a MultipartConversionError.
- umsg.set_type('multipart/mixed')
- umsg.attach(
- MIMEText(text, _charset=Utils.GetCharSet(self.preferred_language)))
- if isinstance(msg, str):
- umsg.attach(MIMEText(msg))
- else:
- umsg.attach(MIMEMessage(msg))
- umsg.send(self)
-
- def sendNextNotification(self, member):
- info = self.getBounceInfo(member)
- if info is None:
- return
- reason = self.getDeliveryStatus(member)
- if info.noticesleft <= 0:
- # BAW: Remove them now, with a notification message
- self.ApprovedDeleteMember(
- member, 'disabled address',
- admin_notif=self.bounce_notify_owner_on_removal,
- userack=1)
- # Expunge the pending cookie for the user. We throw away the
- # returned data.
- self.pend_confirm(info.cookie)
- if reason == DeliveryStatus.by_bounces:
- log.info('%s: %s deleted after exhausting notices',
- self.internal_name(), member)
- slog.info('%s: %s auto-unsubscribed [reason: %s]',
- self.internal_name(), member,
- {DeliveryStatus.by_bounces: 'BYBOUNCE',
- DeliveryStatus.by_user: 'BYUSER',
- DeliveryStatus.by_moderator: 'BYADMIN',
- DeliveryStatus.unknown: 'UNKNOWN'}.get(
- reason, 'invalid value'))
- return
- # Send the next notification
- confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1),
- info.cookie)
- optionsurl = self.GetOptionsURL(member, absolute=1)
- reqaddr = self.GetRequestEmail()
- lang = self.getMemberLanguage(member)
- txtreason = REASONS.get(reason)
- if txtreason is None:
- txtreason = _('for unknown reasons')
- else:
- txtreason = _(txtreason)
- # Give a little bit more detail on bounce disables
- if reason == DeliveryStatus.by_bounces:
- date = time.strftime('%d-%b-%Y',
- time.localtime(Utils.midnight(info.date)))
- extra = _(' The last bounce received from you was dated %(date)s')
- txtreason += extra
- text = Utils.maketext(
- 'disabled.txt',
- {'listname' : self.real_name,
- 'noticesleft': info.noticesleft,
- 'confirmurl' : confirmurl,
- 'optionsurl' : optionsurl,
- 'password' : self.getMemberPassword(member),
- 'owneraddr' : self.GetOwnerEmail(),
- 'reason' : txtreason,
- }, lang=lang, mlist=self)
- msg = Message.UserNotification(member, reqaddr, text=text, lang=lang)
- # BAW: See the comment in MailList.py ChangeMemberAddress() for why we
- # set the Subject this way.
- del msg['subject']
- msg['Subject'] = 'confirm ' + info.cookie
- msg.send(self)
- info.noticesleft -= 1
- info.lastnotice = time.localtime()[:3]