From 6bd19f91b98517af02a3dae766d49c1610967815 Mon Sep 17 00:00:00 2001 From: "jimpop@template.hostname" <> Date: Wed, 4 Nov 2015 21:09:42 +0000 Subject: Auto-Moderate Verbose Members --- Mailman/Defaults.py.in | 6 ++++++ Mailman/Gui/Privacy.py | 12 ++++++++++++ Mailman/Handlers/SpamDetect.py | 8 ++++++++ Mailman/Utils.py | 26 ++++++++++++++++++++++++++ Mailman/Version.py | 2 +- Mailman/versions.py | 3 +++ 6 files changed, 56 insertions(+), 1 deletion(-) diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index 0e376e1c..093ce1c8 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -1118,6 +1118,12 @@ DMARC_RESOLVER_TIMEOUT = seconds(3) # The total time to spend trying to get an answer to the question. DMARC_RESOLVER_LIFETIME = seconds(5) +# Should the list server auto-moderate members who post too frequently +# DEFAULT_MEMBER_VERBOSITY_INTERVAL = number of seconds to track posts +# DEFAULT_MEMBER_VERBOSITY_THRESHOLD = number of allowed posts per interval (0 to disable). +DEFAULT_MEMBER_VERBOSITY_INTERVAL = 300 +DEFAULT_MEMBER_VERBOSITY_THRESHOLD = 5 + # What domains should be considered equivalent when testing list membership # for posting/moderation. # If two poster addresses with the same local part but diff --git a/Mailman/Gui/Privacy.py b/Mailman/Gui/Privacy.py index 3af5d8ef..68f508af 100644 --- a/Mailman/Gui/Privacy.py +++ b/Mailman/Gui/Privacy.py @@ -229,6 +229,18 @@ class Privacy(GUIBase): membership management screens.""")), + ('member_verbosity_threshold', mm_cfg.Number, 5, 0, + _('Ceiling on acceptable number of member posts, per interval, before automatic moderation.'), + + _('''If a member posts this many times, within sender_verbosity_interval, + the member is automatically moderated. Use 0 to disable.''')), + + ('member_verbosity_interval', mm_cfg.Number, 5, 300, + _('Number of seconds to use in determining whether or not to automatically moderate a member.'), + + _('''If a member posts exceed member_verbosity_threshold, within sender_verbosity_interval, + the member is automatically moderated.''')), + ('member_moderation_action', mm_cfg.Radio, (_('Hold'), _('Reject'), _('Discard')), 0, _("""Action to take when a moderated member posts to the diff --git a/Mailman/Handlers/SpamDetect.py b/Mailman/Handlers/SpamDetect.py index d85cc6a6..6c10bb65 100644 --- a/Mailman/Handlers/SpamDetect.py +++ b/Mailman/Handlers/SpamDetect.py @@ -122,6 +122,12 @@ error, contact the mailing list owner at %(listowner)s.""")) raise Errors.RejectMessage, text elif mlist.dmarc_moderation_action == 4: raise Errors.DiscardMessage + + if Utils.IsVerboseMember(mlist, addr): + mlist.setMemberOption(addr, mm_cfg.Moderate, 1) + syslog('vette', '%s: Automatically Moderated %s for verbose postings.', + mlist.real_name, addr) + if msgdata.get('approved'): return # First do site hard coded header spam checks @@ -169,3 +175,5 @@ error, contact the mailing list owner at %(listowner)s.""")) hold_for_approval(mlist, msg, msgdata, HeaderMatchHold) if action == mm_cfg.ACCEPT: return + + diff --git a/Mailman/Utils.py b/Mailman/Utils.py index f22e45b4..516514ac 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -1246,6 +1246,32 @@ def IsDMARCProhibited(mlist, email): return False +# Check a known list in order to auto-moderate verbose members +recentMemberPostings = {}; +def IsVerboseMember(mlist, email): + + threshold = 5 if mlist.member_verbosity_threshold is None else mlist.member_verbosity_threshold + if threshold == 0: + return False + + interval = 5 if mlist.member_verbosity_interval is None else mlist.member_verbosity_interval + email = email.lower() + + t = time.time() + recentMemberPostings.setdefault(email,[]).append(t) + + syslog('vette', 'DEBUG: %s: Appended %f to recentMemberPostings[%s] (%d).', + mlist.real_name, t, email, len(recentMemberPostings[email])) + + for t in recentMemberPostings[email]: + if t < time.time() - float(interval): + recentMemberPostings[email].remove(t) + syslog('vette', 'DEBUG: %s: Removed %f from recentMemberPostings[%s] (%d).', + mlist.real_name, t, email, len(recentMemberPostings[email])) + + return len(recentMemberPostings[email]) >= threshold + + def check_eq_domains(email, domains_list): """The arguments are an email address and a string representing a list of lists in a form like 'a,b,c;1,2' representing [['a', 'b', diff --git a/Mailman/Version.py b/Mailman/Version.py index cdc8a043..910cd55d 100644 --- a/Mailman/Version.py +++ b/Mailman/Version.py @@ -37,7 +37,7 @@ HEX_VERSION = ((MAJOR_REV << 24) | (MINOR_REV << 16) | (MICRO_REV << 8) | (REL_LEVEL << 4) | (REL_SERIAL << 0)) # config.pck schema version number -DATA_FILE_VERSION = 108 +DATA_FILE_VERSION = 109 # qfile/*.db schema version number QFILE_SCHEMA_VERSION = 3 diff --git a/Mailman/versions.py b/Mailman/versions.py index da33dc10..d1f06543 100755 --- a/Mailman/versions.py +++ b/Mailman/versions.py @@ -497,6 +497,9 @@ def NewVars(l): add_only_if_missing('dmarc_moderation_notice', '') add_only_if_missing('dmarc_wrapped_message_text', mm_cfg.DEFAULT_DMARC_WRAPPED_MESSAGE_TEXT) + add_only_if_missing('member_verbosity_threshold', 0) + add_only_if_missing('member_verbosity_interval', + mm_cfg.DEFAULT_MEMBER_VERBOSITY_INTERVAL) add_only_if_missing('equivalent_domains', mm_cfg.DEFAULT_EQUIVALENT_DOMAINS) add_only_if_missing('new_member_options', -- cgit v1.2.3 From a777ae450ed966ca75f22c140fee938c5fd37996 Mon Sep 17 00:00:00 2001 From: "jimpop@template.hostname" <> Date: Wed, 4 Nov 2015 21:43:43 +0000 Subject: Removed 2 development debugging lines --- Mailman/Utils.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 516514ac..a4eb05da 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -1260,14 +1260,9 @@ def IsVerboseMember(mlist, email): t = time.time() recentMemberPostings.setdefault(email,[]).append(t) - syslog('vette', 'DEBUG: %s: Appended %f to recentMemberPostings[%s] (%d).', - mlist.real_name, t, email, len(recentMemberPostings[email])) - for t in recentMemberPostings[email]: if t < time.time() - float(interval): recentMemberPostings[email].remove(t) - syslog('vette', 'DEBUG: %s: Removed %f from recentMemberPostings[%s] (%d).', - mlist.real_name, t, email, len(recentMemberPostings[email])) return len(recentMemberPostings[email]) >= threshold -- cgit v1.2.3 From b8811f8fc2d9bd27d1963c000ddaf05d951b5bda Mon Sep 17 00:00:00 2001 From: "jimpop@template.hostname" <> Date: Wed, 4 Nov 2015 22:49:05 +0000 Subject: Improvements based on feedback from Mark Sapiro https://code.launchpad.net/~jimpop/mailman/mailman-auto-mod-verbose-members/+merge/276706/comments/699744 --- Mailman/Defaults.py.in | 2 +- Mailman/Handlers/SpamDetect.py | 2 +- Mailman/Utils.py | 8 +++----- Mailman/versions.py | 3 ++- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index 093ce1c8..80d8694c 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -1122,7 +1122,7 @@ DMARC_RESOLVER_LIFETIME = seconds(5) # DEFAULT_MEMBER_VERBOSITY_INTERVAL = number of seconds to track posts # DEFAULT_MEMBER_VERBOSITY_THRESHOLD = number of allowed posts per interval (0 to disable). DEFAULT_MEMBER_VERBOSITY_INTERVAL = 300 -DEFAULT_MEMBER_VERBOSITY_THRESHOLD = 5 +DEFAULT_MEMBER_VERBOSITY_THRESHOLD = 0 # What domains should be considered equivalent when testing list membership # for posting/moderation. diff --git a/Mailman/Handlers/SpamDetect.py b/Mailman/Handlers/SpamDetect.py index 6c10bb65..509e1e6e 100644 --- a/Mailman/Handlers/SpamDetect.py +++ b/Mailman/Handlers/SpamDetect.py @@ -123,7 +123,7 @@ error, contact the mailing list owner at %(listowner)s.""")) elif mlist.dmarc_moderation_action == 4: raise Errors.DiscardMessage - if Utils.IsVerboseMember(mlist, addr): + if mlist.member_verbosity_threshold > 0 and Utils.IsVerboseMember(mlist, addr): mlist.setMemberOption(addr, mm_cfg.Moderate, 1) syslog('vette', '%s: Automatically Moderated %s for verbose postings.', mlist.real_name, addr) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index a4eb05da..70ef34e8 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -1250,21 +1250,19 @@ def IsDMARCProhibited(mlist, email): recentMemberPostings = {}; def IsVerboseMember(mlist, email): - threshold = 5 if mlist.member_verbosity_threshold is None else mlist.member_verbosity_threshold - if threshold == 0: + if mlist.member_verbosity_threshold == 0: return False - interval = 5 if mlist.member_verbosity_interval is None else mlist.member_verbosity_interval email = email.lower() t = time.time() recentMemberPostings.setdefault(email,[]).append(t) for t in recentMemberPostings[email]: - if t < time.time() - float(interval): + if t < time.time() - float(mlist.member_verbosity_interval): recentMemberPostings[email].remove(t) - return len(recentMemberPostings[email]) >= threshold + return len(recentMemberPostings[email]) >= mlist.member_verbosity_threshold def check_eq_domains(email, domains_list): diff --git a/Mailman/versions.py b/Mailman/versions.py index d1f06543..d13be94f 100755 --- a/Mailman/versions.py +++ b/Mailman/versions.py @@ -497,7 +497,8 @@ def NewVars(l): add_only_if_missing('dmarc_moderation_notice', '') add_only_if_missing('dmarc_wrapped_message_text', mm_cfg.DEFAULT_DMARC_WRAPPED_MESSAGE_TEXT) - add_only_if_missing('member_verbosity_threshold', 0) + add_only_if_missing('member_verbosity_threshold', + mm_cfg.DEFAULT_MEMBER_VERBOSITY_THRESHOLD) add_only_if_missing('member_verbosity_interval', mm_cfg.DEFAULT_MEMBER_VERBOSITY_INTERVAL) add_only_if_missing('equivalent_domains', -- cgit v1.2.3