aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xMailman/Defaults.py.in6
-rw-r--r--Mailman/Gui/Privacy.py12
-rw-r--r--Mailman/Handlers/SpamDetect.py8
-rw-r--r--Mailman/Utils.py26
-rw-r--r--Mailman/Version.py2
-rwxr-xr-xMailman/versions.py3
6 files changed, 56 insertions, 1 deletions
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):
<a href="%(adminurl)s/members">membership management
screens</a>.""")),
+ ('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',