def FormatDisabledNotice(self, user): status = self.getDeliveryStatus(user) reason = None info = self.getBounceInfo(user) if status == MemberAdaptor.BYUSER: reason = _('; it was disabled by you') elif status == MemberAdaptor.BYADMIN: reason = _('; it was disabled by the list administrator') elif status == MemberAdaptor.BYBOUNCE: date = time.strftime('%d-%b-%Y', time.localtime(Utils.midnight(info.date))) reason = _('''; it was disabled due to excessive bounces. The last bounce was received on %(date)s''') elif status == MemberAdaptor.UNKNOWN: reason = _('; it was disabled for unknown reasons') if reason: note = FontSize( '+1', _('Note: your list delivery is currently disabled%(reason)s.') ).Format() link = Link('#disable', _('Mail delivery')).Format() mailto = Link('mailto:' + self.GetOwnerEmail(), _('the list administrator')).Format() return _('''<p>%(note)s <p>You may have disabled list delivery intentionally, or it may have been triggered by bounces from your email address. In either case, to re-enable delivery, change the %(link)s option below. Contact %(mailto)s if you have any questions or need assistance.''') elif info and info.score > 0: # Provide information about their current bounce score. We know # their membership is currently enabled. score = info.score total = self.bounce_score_threshold return _('''<p>We have received some recent bounces from your address. Your current <em>bounce score</em> is %(score)s out of a maximum of %(total)s. Please double check that your subscribed address is correct and that there are no problems with delivery to this address. Your bounce score will be automatically reset if the problems are corrected soon.''') else: return ''
def FormatDisabledNotice(self, user): status = self.getDeliveryStatus(user) reason = None info = self.getBounceInfo(user) if status == MemberAdaptor.BYUSER: reason = _('; it was disabled by you') elif status == MemberAdaptor.BYADMIN: reason = _('; it was disabled by the list administrator') elif status == MemberAdaptor.BYBOUNCE: date = time.strftime('%d-%b-%Y', time.localtime(Utils.midnight(info.date))) reason = _('''; it was disabled due to excessive bounces. The last bounce was received on %(date)s''') elif status == MemberAdaptor.UNKNOWN: reason = _('; it was disabled for unknown reasons') if reason: note = FontSize('+1', _( 'Note: your list delivery is currently disabled%(reason)s.' )).Format() link = Link('#disable', _('Mail delivery')).Format() mailto = Link('mailto:' + self.GetOwnerEmail(), _('the list administrator')).Format() return _('''<p>%(note)s <p>You may have disabled list delivery intentionally, or it may have been triggered by bounces from your email address. In either case, to re-enable delivery, change the %(link)s option below. Contact %(mailto)s if you have any questions or need assistance.''') elif info and info.score > 0: # Provide information about their current bounce score. We know # their membership is currently enabled. score = info.score total = self.bounce_score_threshold return _('''<p>We have received some recent bounces from your address. Your current <em>bounce score</em> is %(score)s out of a maximum of %(total)s. Please double check that your subscribed address is correct and that there are no problems with delivery to this address. Your bounce score will be automatically reset if the problems are corrected soon.''') else: return ''
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 == MemberAdaptor.BYBOUNCE: syslog('bounce', '%s: %s deleted after exhausting notices', self.internal_name(), member) syslog( 'subscribe', '%s: %s auto-unsubscribed [reason: %s]', self.internal_name(), member, { MemberAdaptor.BYBOUNCE: 'BYBOUNCE', MemberAdaptor.BYUSER: '******', MemberAdaptor.BYADMIN: 'BYADMIN', MemberAdaptor.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 == MemberAdaptor.BYBOUNCE: 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 # Send without Precedence: bulk. Bug #808821. msg.send(self, noprecedence=True) info.noticesleft -= 1 info.lastnotice = time.localtime()[:3] # In case the MemberAdaptor stores bounce info externally to # the list, we need to tell it to update self.setBounceInfo(member, info)
def registerBounce(self, member, msg, weight=1.0, day=None, sibling=False): if not self.isMember(member): # check regular_include_lists, only one level if not self.regular_include_lists or sibling: return from Mailman.MailList import MailList for listaddr in self.regular_include_lists: listname, hostname = listaddr.split('@') listname = listname.lower() if listname == self.internal_name(): syslog('error', 'Bouncer: %s: Include list self reference', listname) continue try: siblist = None try: siblist = MailList(listname) except MMUnknownListError: syslog('error', 'Bouncer: %s: Include list "%s" not found.', self.real_name, listname) continue siblist.registerBounce(member, msg, weight, day, sibling=True) siblist.Save() finally: if siblist and siblist.Locked(): siblist.Unlock() return info = self.getBounceInfo(member) first_today = True 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) # setBounceInfo is now called below after check phase. syslog('bounce', '%s: %s bounce score: %s', self.internal_name(), member, info.score) # Continue to the check phase below elif self.getDeliveryStatus(member) <> MemberAdaptor.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. syslog('bounce', '%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. first_today = False syslog('bounce', '%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) syslog('bounce', '%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 syslog('bounce', '%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 mm_cfg.VERP_PROBES: syslog('bounce', '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) elif self.bounce_notify_owner_on_bounce_increment and first_today: self.__sendAdminBounceNotice(member, msg, did=_('bounce score incremented')) # We've set/changed bounce info above. We now need to tell the # MemberAdaptor to set/update it. We do it here in case the # MemberAdaptor stores bounce info externally to the list object to # be sure updated information is stored, but we have to be sure the # member wasn't removed. if self.isMember(member): self.setBounceInfo(member, info)
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 == MemberAdaptor.BYBOUNCE: syslog("bounce", "%s: %s deleted after exhausting notices", self.internal_name(), member) syslog( "subscribe", "%s: %s auto-unsubscribed [reason: %s]", self.internal_name(), member, { MemberAdaptor.BYBOUNCE: "BYBOUNCE", MemberAdaptor.BYUSER: "******", MemberAdaptor.BYADMIN: "BYADMIN", MemberAdaptor.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 == MemberAdaptor.BYBOUNCE: 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 # Send without Precedence: bulk. Bug #808821. msg.send(self, noprecedence=True) info.noticesleft -= 1 info.lastnotice = time.localtime()[:3] # In case the MemberAdaptor stores bounce info externally to # the list, we need to tell it to update self.setBounceInfo(member, info)
def registerBounce(self, member, msg, weight=1.0, day=None, sibling=False): if not self.isMember(member): # check regular_include_lists, only one level if not self.regular_include_lists or sibling: return from Mailman.MailList import MailList for listaddr in self.regular_include_lists: listname, hostname = listaddr.split("@") listname = listname.lower() if listname == self.internal_name(): syslog("error", "Bouncer: %s: Include list self reference", listname) continue try: siblist = None try: siblist = MailList(listname) except MMUnknownListError: syslog("error", 'Bouncer: %s: Include list "%s" not found.', self.real_name, listname) continue siblist.registerBounce(member, msg, weight, day, sibling=True) siblist.Save() finally: if siblist and siblist.Locked(): siblist.Unlock() 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) # setBounceInfo is now called below after check phase. syslog("bounce", "%s: %s bounce score: %s", self.internal_name(), member, info.score) # Continue to the check phase below elif self.getDeliveryStatus(member) <> MemberAdaptor.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. syslog("bounce", "%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. syslog( "bounce", "%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) syslog("bounce", "%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 syslog("bounce", "%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 mm_cfg.VERP_PROBES: syslog( "bounce", "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) # We've set/changed bounce info above. We now need to tell the # MemberAdaptor to set/update it. We do it here in case the # MemberAdaptor stores bounce info externally to the list object to # be sure updated information is stored, but we have to be sure the # member wasn't removed. if self.isMember(member): self.setBounceInfo(member, info)
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 == MemberAdaptor.BYBOUNCE: syslog('bounce', '%s: %s deleted after exhausting notices', self.internal_name(), member) syslog('subscribe', '%s: %s auto-unsubscribed [reason: %s]', self.internal_name(), member, {MemberAdaptor.BYBOUNCE: 'BYBOUNCE', MemberAdaptor.BYUSER: '******', MemberAdaptor.BYADMIN: 'BYADMIN', MemberAdaptor.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 == MemberAdaptor.BYBOUNCE: 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] # In case the MemberAdaptor stores bounce info externally to # the list, we need to tell it to update self.setBounceInfo(member, info)
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) # setBounceInfo is now called below after check phase. syslog('bounce', '%s: %s bounce score: %s', self.internal_name(), member, info.score) # Continue to the check phase below elif self.getDeliveryStatus(member) <> MemberAdaptor.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. syslog('bounce', '%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. syslog('bounce', '%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) syslog('bounce', '%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 syslog('bounce', '%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 mm_cfg.VERP_PROBES: syslog('bounce', '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) # We've set/changed bounce info above. We now need to tell the # MemberAdaptor to set/update it. We do it here in case the # MemberAdaptor stores bounce info externally to the list object to # be sure updated information is stored, but we have to be sure the # member wasn't removed. if self.isMember(member): self.setBounceInfo(member, info)