Merge lp:~jimpop/mailman/mailman-auto-mod-verbose-members into lp:mailman/2.1

Proposed by Jim Popovitch
Status: Merged
Merged at revision: 1595
Proposed branch: lp:~jimpop/mailman/mailman-auto-mod-verbose-members
Merge into: lp:mailman/2.1
Diff against target: 120 lines (+50/-1)
6 files modified
Mailman/Defaults.py.in (+6/-0)
Mailman/Gui/Privacy.py (+12/-0)
Mailman/Handlers/SpamDetect.py (+8/-0)
Mailman/Utils.py (+19/-0)
Mailman/Version.py (+1/-1)
Mailman/versions.py (+4/-0)
To merge this branch: bzr merge lp:~jimpop/mailman/mailman-auto-mod-verbose-members
Reviewer Review Type Date Requested Status
Mark Sapiro Approve
Mark Shapiro Pending
Review via email: mp+276706@code.launchpad.net

Commit message

Auto-Moderate verbose members

Description of the change

This is a simple patch to enable the following two Privacy/Sender features:

  member_verbosity_threshold
     Ceiling on acceptable number of member posts, per interval, before automatic moderation.

  member_verbosity_interval
     Number of seconds to use in determining whether or not to automatically moderate a member.

The code tracks member posts in a simple python dictionary, and then checks the dictionary each time a post is processed via Mailman/Handlers/SpamDetect.py. If a member exceeds X posts in Y time, then the member is automatically moderated.

To post a comment you must log in.
1587. By <email address hidden>

Removed 2 development debugging lines

Revision history for this message
Mark Sapiro (msapiro) wrote :

It is not clear why you have

 add_only_if_missing('member_verbosity_threshold', 0)

in versions.py instead of

 add_only_if_missing('member_verbosity_threshold',
                     mm_cfg.DEFAULT_MEMBER_VERBOSITY_THRESHOLD)

Oversight perhaps. Also I think Defaults.py.in should probably have

DEFAULT_MEMBER_VERBOSITY_THRESHOLD = 0

Also, the first few lines of Utils.IsVerboseMember() seem unnecessary, and I'm not completely comfortable with recentMemberPostings being a global in Utils.py rather than a list attribute.

And, do you really see 5 posts from the same member in 5 minutes?

Finally, I'm curious if you ever saw <https://www.msapiro.net/scripts/PostLimit.py>. I guess it is very different in detail, but the goal may be similar.

I've been reluctant to actually implement that on my production server, although it has been suggested as a way to control one member (I'd rather have a conversation than hit him with a hammer, although hammers have their place.)

review: Needs Information
Revision history for this message
Jim Popovitch (jimpop) wrote :

Hi Mark, thank you. I have made the following improvements based on your feedback:

Mailman/versions.py:
    set mm_cfg.DEFAULT_MEMBER_VERBOSITY_THRESHOLD rather than hardcoded "0".

Mailman/Defaults.py.in:
    DEFAULT_MEMBER_VERBOSITY_THRESHOLD now defaults to 0

Mailman/Utils.py:
    Removed unnecessary value checks from the top of Utils.IsVerboseMember()

Mailman/Handlers/SpamDetect.py
    Added "mlist.member_verbosity_threshold > 0" test before call to Utils.IsVerboseMember()

I set recentMemberPostings as a global because I felt it was important to measure a members posting pattern across all lists, not a single list. Although I currently only moderated the user on the single impacted list, ideally I'd like to find a way to moderate the user on all subscribed lists.

NANOG recently had a problem of hundreds of spam posts in a matter of minutes. Search for "Fw: new messsage" on http://mailman.nanog.org/pipermail/nanog/2015-October/thread.html (DO NOT CLICK ON ANY OF THE EMAILED LINKS IN THOSE NANOG POSTS). There were so many posts it clogged their outbound queues for at least 1 day.

I have not seen PostLimit before, thanks. I honestly don't care which version makes it into Mailman, but there is clear evidence that spamvertised postings, using a known member address, is going to be a huge problem going forward.

1588. By <email address hidden>

Improvements based on feedback from Mark Sapiro
   https://code.launchpad.net/~jimpop/mailman/mailman-auto-mod-verbose-members/+merge/276706/comments/699744

Revision history for this message
Mark Sapiro (msapiro) wrote :

Thanks for your replies and changes. I now have a much clearer understanding of the issue that prompts this. I will probably do a little tweaking, but it looks good and I will get it in the next release.

It really has different goals from PostLimit which I did as a response to some request on Mailman-users.

Actually, we could have used something like this for a flood of bogus subscription requests a while back, but the addresses were predictable enough that I was able to handle it with a regexp in the new GLOBAL_BAN_LIST (and variable enough that matching on the address wouldn't work).

Anyway, You've done good work here. Thank you. I will get the feature in the next release (and probably aim for a release soon, but not before December as I a traveling the next 3 weeks)

review: Approve
Revision history for this message
Mark Sapiro (msapiro) wrote :

I have merged this branch and made some changes. The most significant is your code to drop old times from the recentMemberPostings dictionary is wrong. Deleting items from a list while processing 'for item in list' results in skipping items.

I also allow for different lists to have different member_verbosity_interval settings and process that I hope reasonably.

Finally, I don't attempt to moderate a non-member, and I look at more than the From: of the message to find a member address.

Revision history for this message
Jim Popovitch (jimpop) wrote :

Sounds good Mark, Thanks

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Mailman/Defaults.py.in'
--- Mailman/Defaults.py.in 2015-09-19 02:18:34 +0000
+++ Mailman/Defaults.py.in 2015-11-05 00:11:03 +0000
@@ -1118,6 +1118,12 @@
1118# The total time to spend trying to get an answer to the question.1118# The total time to spend trying to get an answer to the question.
1119DMARC_RESOLVER_LIFETIME = seconds(5)1119DMARC_RESOLVER_LIFETIME = seconds(5)
11201120
1121# Should the list server auto-moderate members who post too frequently
1122# DEFAULT_MEMBER_VERBOSITY_INTERVAL = number of seconds to track posts
1123# DEFAULT_MEMBER_VERBOSITY_THRESHOLD = number of allowed posts per interval (0 to disable).
1124DEFAULT_MEMBER_VERBOSITY_INTERVAL = 300
1125DEFAULT_MEMBER_VERBOSITY_THRESHOLD = 0
1126
1121# What domains should be considered equivalent when testing list membership1127# What domains should be considered equivalent when testing list membership
1122# for posting/moderation.1128# for posting/moderation.
1123# If two poster addresses with the same local part but1129# If two poster addresses with the same local part but
11241130
=== modified file 'Mailman/Gui/Privacy.py'
--- Mailman/Gui/Privacy.py 2015-02-04 04:48:25 +0000
+++ Mailman/Gui/Privacy.py 2015-11-05 00:11:03 +0000
@@ -229,6 +229,18 @@
229 <a href="%(adminurl)s/members">membership management229 <a href="%(adminurl)s/members">membership management
230 screens</a>.""")),230 screens</a>.""")),
231231
232 ('member_verbosity_threshold', mm_cfg.Number, 5, 0,
233 _('Ceiling on acceptable number of member posts, per interval, before automatic moderation.'),
234
235 _('''If a member posts this many times, within sender_verbosity_interval,
236 the member is automatically moderated. Use 0 to disable.''')),
237
238 ('member_verbosity_interval', mm_cfg.Number, 5, 300,
239 _('Number of seconds to use in determining whether or not to automatically moderate a member.'),
240
241 _('''If a member posts exceed member_verbosity_threshold, within sender_verbosity_interval,
242 the member is automatically moderated.''')),
243
232 ('member_moderation_action', mm_cfg.Radio,244 ('member_moderation_action', mm_cfg.Radio,
233 (_('Hold'), _('Reject'), _('Discard')), 0,245 (_('Hold'), _('Reject'), _('Discard')), 0,
234 _("""Action to take when a moderated member posts to the246 _("""Action to take when a moderated member posts to the
235247
=== modified file 'Mailman/Handlers/SpamDetect.py'
--- Mailman/Handlers/SpamDetect.py 2015-03-02 21:50:24 +0000
+++ Mailman/Handlers/SpamDetect.py 2015-11-05 00:11:03 +0000
@@ -122,6 +122,12 @@
122 raise Errors.RejectMessage, text122 raise Errors.RejectMessage, text
123 elif mlist.dmarc_moderation_action == 4:123 elif mlist.dmarc_moderation_action == 4:
124 raise Errors.DiscardMessage124 raise Errors.DiscardMessage
125
126 if mlist.member_verbosity_threshold > 0 and Utils.IsVerboseMember(mlist, addr):
127 mlist.setMemberOption(addr, mm_cfg.Moderate, 1)
128 syslog('vette', '%s: Automatically Moderated %s for verbose postings.',
129 mlist.real_name, addr)
130
125 if msgdata.get('approved'):131 if msgdata.get('approved'):
126 return132 return
127 # First do site hard coded header spam checks133 # First do site hard coded header spam checks
@@ -169,3 +175,5 @@
169 hold_for_approval(mlist, msg, msgdata, HeaderMatchHold)175 hold_for_approval(mlist, msg, msgdata, HeaderMatchHold)
170 if action == mm_cfg.ACCEPT:176 if action == mm_cfg.ACCEPT:
171 return177 return
178
179
172180
=== modified file 'Mailman/Utils.py'
--- Mailman/Utils.py 2015-09-17 00:39:34 +0000
+++ Mailman/Utils.py 2015-11-05 00:11:03 +0000
@@ -1246,6 +1246,25 @@
1246 return False1246 return False
12471247
12481248
1249# Check a known list in order to auto-moderate verbose members
1250recentMemberPostings = {};
1251def IsVerboseMember(mlist, email):
1252
1253 if mlist.member_verbosity_threshold == 0:
1254 return False
1255
1256 email = email.lower()
1257
1258 t = time.time()
1259 recentMemberPostings.setdefault(email,[]).append(t)
1260
1261 for t in recentMemberPostings[email]:
1262 if t < time.time() - float(mlist.member_verbosity_interval):
1263 recentMemberPostings[email].remove(t)
1264
1265 return len(recentMemberPostings[email]) >= mlist.member_verbosity_threshold
1266
1267
1249def check_eq_domains(email, domains_list):1268def check_eq_domains(email, domains_list):
1250 """The arguments are an email address and a string representing a1269 """The arguments are an email address and a string representing a
1251 list of lists in a form like 'a,b,c;1,2' representing [['a', 'b',1270 list of lists in a form like 'a,b,c;1,2' representing [['a', 'b',
12521271
=== modified file 'Mailman/Version.py'
--- Mailman/Version.py 2015-03-31 17:18:39 +0000
+++ Mailman/Version.py 2015-11-05 00:11:03 +0000
@@ -37,7 +37,7 @@
37 (REL_LEVEL << 4) | (REL_SERIAL << 0))37 (REL_LEVEL << 4) | (REL_SERIAL << 0))
3838
39# config.pck schema version number39# config.pck schema version number
40DATA_FILE_VERSION = 10840DATA_FILE_VERSION = 109
4141
42# qfile/*.db schema version number42# qfile/*.db schema version number
43QFILE_SCHEMA_VERSION = 343QFILE_SCHEMA_VERSION = 3
4444
=== modified file 'Mailman/versions.py'
--- Mailman/versions.py 2015-02-08 07:54:26 +0000
+++ Mailman/versions.py 2015-11-05 00:11:03 +0000
@@ -497,6 +497,10 @@
497 add_only_if_missing('dmarc_moderation_notice', '')497 add_only_if_missing('dmarc_moderation_notice', '')
498 add_only_if_missing('dmarc_wrapped_message_text',498 add_only_if_missing('dmarc_wrapped_message_text',
499 mm_cfg.DEFAULT_DMARC_WRAPPED_MESSAGE_TEXT)499 mm_cfg.DEFAULT_DMARC_WRAPPED_MESSAGE_TEXT)
500 add_only_if_missing('member_verbosity_threshold',
501 mm_cfg.DEFAULT_MEMBER_VERBOSITY_THRESHOLD)
502 add_only_if_missing('member_verbosity_interval',
503 mm_cfg.DEFAULT_MEMBER_VERBOSITY_INTERVAL)
500 add_only_if_missing('equivalent_domains', 504 add_only_if_missing('equivalent_domains',
501 mm_cfg.DEFAULT_EQUIVALENT_DOMAINS)505 mm_cfg.DEFAULT_EQUIVALENT_DOMAINS)
502 add_only_if_missing('new_member_options',506 add_only_if_missing('new_member_options',