diff options
Diffstat (limited to 'modules/mm_bouncer.py')
| -rw-r--r-- | modules/mm_bouncer.py | 122 |
1 files changed, 96 insertions, 26 deletions
diff --git a/modules/mm_bouncer.py b/modules/mm_bouncer.py index 4ab2ec89e..5e3ebfe2a 100644 --- a/modules/mm_bouncer.py +++ b/modules/mm_bouncer.py @@ -27,7 +27,8 @@ class Bouncer: ('bounce_processing', mm_cfg.Toggle, ('No', 'Yes'), 0, 'Try to figure out error messages automatically? '), ('minimum_removal_date', mm_cfg.Number, 3, 0, - 'Minimum number of days an address has been bad before we consider nuking it'), + 'Minimum number of days an address has been non-fatally ' + 'bad before we take action'), ('minimum_post_count_before_bounce_action', mm_cfg.Number, 3, 0, 'Minimum number of posts to the list since members first ' 'bounce before we consider removing them from the list'), @@ -35,9 +36,11 @@ class Bouncer: "Maximum number of messages your list gets in an hour. " "(Yes, bounce detection finds this info useful)"), ('automatic_bounce_action', mm_cfg.Radio, - ("Do nothing", "Disable and notify me ", "Remove and notify me", - "Remove and don't notify me"), - 0, "Automatically remove addresses considered for removal, or alert you?") + ("Do nothing", + "Disable and notify me", + "Disable and DON'T notify me", + "Remove and notify me"), + 0, "Action when fatal or excessive bounces are detected.") ] def ClearBounceInfo(self, email): email = string.lower(email) @@ -54,7 +57,7 @@ class Bouncer: # What the last post ID was that we saw a bounce. self.bounce_info[string.lower(email)] = [now, self.post_id, self.post_id] - self.LogMsg("bounce", report + "first bounce") + self.LogMsg("bounce", report + "first") self.Save() return @@ -67,15 +70,15 @@ class Bouncer: # Should maybe keep track in see if people become stale entries # often... self.LogMsg("bounce", - report + "first fresh bounce on a stale addr") + report + "first fresh") self.bounce_info[addr] = [now, self.post_id, self.post_id] return self.bounce_info[addr][2] = self.post_id if ((self.post_id - inf[1] > self.minimum_post_count_before_bounce_action) and difference > self.minimum_removal_date * 24 * 60 * 60): - self.LogMsg("bounce", report + "they're Out of here...") - self.RemoveBouncingAddress(addr) + self.LogMsg("bounce", report + "exceeded limits") + self.HandleBouncingAddress(addr) return else: post_count = (self.minimum_post_count_before_bounce_action - @@ -93,40 +96,108 @@ class Bouncer: elif len(mm_utils.FindMatchingAddresses(addr, self.digest_members)): if self.volume > inf[1]: self.LogMsg("bounce", - "%s: First fresh bounce on a stale addr (D).", + "%s: first fresh (D)", self._internal_name) self.bounce_info[addr] = [now, self.volume, self.volume] return if difference > self.minimum_removal_date * 24 * 60 * 60: - self.LogMsg("bounce", "Seeya, digest-ee...") - self.RemoveBouncingAddress(addr) + self.LogMsg("bounce", "exceeded limits (D)") + self.HandleBouncingAddress(addr) return self.LogMsg("bounce", - "digester lucked out, he's still got time!") + "digester lucked out") else: self.LogMsg("bounce", - "Address %s wasn't a member of %s.", - addr, - self._internal_name) + "%s: address %s not a member.", + self._internal_name, + addr) - + def HandleBouncingAddress(self, addr): + """Disable or remove addr according to bounce_action setting.""" + if self.automatic_bounce_action == 0: + return + elif self.automatic_bounce_action == 1: + succeeded = self.DisableBouncingAddress(addr) + did = "disabled" + send = 1 + elif self.automatic_bounce_action == 2: + succeeded = self.DisableBouncingAddress(addr) + did = "disabled" + send = 0 + elif self.automatic_bounce_action == 3: + succeeded = self.RemoveBouncingAddress(addr) + did = "removed" + send = 1 + if send: + if succeeded != 1: + negative="not " + recipient = mm_cfg.MAILMAN_OWNER + else: + negative="" + recipient = self.GetAdminEmail() + text = ("This is a mailman maillist administrator notice.\n" + "\n\tMaillist:\t%s\n" + "\tMember:\t\t%s\n" + "\tAction:\t\tSubscription %s%s.\n" + "\tReason:\t\tExcessive or fatal bounces.\n" + % (self.real_name, addr, negative, did)) + if succeeded != 1: + text = text + "\tFailure reason:\t%s\n\n" % succeeded + else: + text = text + "\n" + if did == "disabled" and succeeded == 1: + text = text + ( + "You can reenable their subscription by visiting " + "their options page\n" + "(via %s) and using your\n" + "list admin password to authorize the option change.\n\n" + % self.GetScriptURL('listinfo')) + text = text + ("Questions? Contact the mailman site admin,\n%s" + % mm_cfg.MAILMAN_OWNER) + if negative: + negative = string.upper(negative) + self.SendTextToUser(subject = ("%s member %s %s%s due to bounces" + % (self.real_name, addr, + negative, did)), + recipient = recipient, + sender = mm_cfg.MAILMAN_OWNER, + errorsto = mm_cfg.MAILMAN_OWNER, + text = text) def DisableBouncingAddress(self, addr): + if not self.IsMember(addr): + reason = "User not found." + self.LogMsg("bounce", "%s: NOT disabled %s: %s", + self.real_name, addr, reason) + return reason try: self.SetUserOption(addr, mm_cfg.DisableDelivery, 1) - self.LogMsg("bounce", "%s: inhibited %s", self.real_name, addr) - # Send mail to the user... but we can't! + self.LogMsg("bounce", "%s: disabled %s", self.real_name, addr) + self.Save() + return 1 except mm_err.MMNoSuchUserError: + self.LogMsg("bounce", "%s: NOT disabled %s: %s", + self.real_name, addr, mm_err.MMNoSuchUserError) self.ClearBounceInfo(addr) - self.Save() + self.Save() + return mm_err.MMNoSuchUserError def RemoveBouncingAddress(self, addr): + if not self.IsMember(addr): + reason = "User not found." + self.LogMsg("bounce", "%s: NOT removed %s: %s", + self.real_name, addr, reason) + return reason try: self.DeleteMember(addr) self.LogMsg("bounce", "%s: removed %s", self.real_name, addr) - # Send mail to the user... but we can't! + self.Save() + return 1 except mm_err.MMNoSuchUserError: + self.LogMsg("bounce", "%s: NOT removed %s: %s", + self.real_name, addr, mm_err.MMNoSuchUserError) self.ClearBounceInfo(addr) - self.Save() + self.Save() + return mm_err.MMNoSuchUserError # Return 0 if we couldn't make any sense of it, 1 if we handled it. def ScanMessage(self, msg): @@ -191,14 +262,13 @@ class Bouncer: emails = string.split(email,',') for email_addr in emails: if email_addr not in did: - self.RemoveBouncingAddress( + self.HandleBouncingAddress( string.strip(email_addr)) did.append(email_addr) message_groked = 1 continue elif action == BOUNCE: emails = string.split(email,',') - print emails for email_addr in emails: self.RegisterBounce(email_addr) message_groked = 1 @@ -215,17 +285,17 @@ class Bouncer: continue if messy_pattern_3.match(line) <> -1 or messy_pattern_4.match(line) <> -1 or messy_pattern_5.match(line) <> -1: username = string.split(line)[1] - self.RemoveBouncingAddress('%s@%s' % (username, remote_host)) + self.HandleBouncingAddress('%s@%s' % (username, remote_host)) message_groked = 1 continue if messy_pattern_6.match(line) <> -1: username = string.split(string.strip(line))[0][:-1] - self.RemoveBouncingAddress('%s@%s' % (username, remote_host)) + self.HandleBouncingAddress('%s@%s' % (username, remote_host)) message_groked = 1 continue if messy_pattern_7.match(line) <> -1: username = string.split(string.strip(line))[0] - self.RemoveBouncingAddress('%s@%s' % (username, remote_host)) + self.HandleBouncingAddress('%s@%s' % (username, remote_host)) message_groked = 1 continue |
