Ralf Jung has proposed merging lp:~ralfjung-e/mailman/2.1 into lp:mailman/2.1.
Commit message:
Implement a simple CAPTCHA scheme based on questions and answers configured by
the site admin.
Fixes https://bugs.launchpad.net/mailman/+bug/1774826.
I have manually edited listinfo.html for a few languages for testing; is there
a way to automate that or do I have to manually do that for all languages?
Requested reviews:
Mailman Coders (mailman-coders)
For more details, see:
https://code.launchpad.net/~ralfjung-e/mailman/2.1/+merge/368614
--
Your team Mailman Coders is requested to review the proposed merge of
lp:~ralfjung-e/mailman/2.1 into lp:mailman/2.1.
=== modified file 'Mailman/Cgi/listinfo.py'
--- Mailman/Cgi/listinfo.py 2018-06-21 16:23:09 +0000
+++ Mailman/Cgi/listinfo.py 2019-06-10 15:36:27 +0000
@@ -216,10 +216,25 @@
# drop one : resulting in an invalid format, but it's only
# for our hash so it doesn't matter.
remote = remote.rsplit(':', 1)[0]
+ # render CAPTCHA, if configured
+ if isinstance(mm_cfg.CAPTCHAS, dict):
+ (captcha_question, captcha_box, captcha_idx) = \
+ Utils.captcha_display(mlist, lang, mm_cfg.CAPTCHAS)
+ pre_question = _(
+ '''Please answer the following question to prove that
+ you are not a bot:'''
+ )
+ replacements['<mm-captcha-ui>'] = (
+ """<tr><td BGCOLOR="#dddddd">%s<br>%s</td><td>%s</td></tr>"""
+ % (pre_question, captcha_question, captcha_box))
+ else:
+ captcha_idx = 0 # just to have something to include in the hash below
+ # fill form
replacements['<mm-subscribe-form-start>'] += (
- '<input type="hidden" name="sub_form_token" value="%s:%s">\n'
- % (now, Utils.sha_new(mm_cfg.SUBSCRIBE_FORM_SECRET + ":" +
+ '<input type="hidden" name="sub_form_token" value="%s:%s:%s">\n'
+ % (now, captcha_idx, Utils.sha_new(mm_cfg.SUBSCRIBE_FORM_SECRET + ":" +
now + ":" +
+ captcha_idx + ":" +
mlist.internal_name() + ":" +
remote
).hexdigest()
=== modified file 'Mailman/Cgi/subscribe.py'
--- Mailman/Cgi/subscribe.py 2018-06-17 23:47:34 +0000
+++ Mailman/Cgi/subscribe.py 2019-06-10 15:36:27 +0000
@@ -168,13 +168,14 @@
# for our hash so it doesn't matter.
remote1 = remote.rsplit(':', 1)[0]
try:
- ftime, fhash = cgidata.getfirst('sub_form_token', '').split(':')
+ ftime, fcaptcha_idx, fhash = cgidata.getfirst('sub_form_token', '').split(':')
then = int(ftime)
except ValueError:
- ftime = fhash = ''
+ ftime = fcaptcha_idx = fhash = ''
then = 0
token = Utils.sha_new(mm_cfg.SUBSCRIBE_FORM_SECRET + ":" +
ftime + ":" +
+ fcaptcha_idx + ":" +
mlist.internal_name() + ":" +
remote1).hexdigest()
if ftime and now - then > mm_cfg.FORM_LIFETIME:
@@ -189,6 +190,11 @@
results.append(
_('There was no hidden token in your submission or it was corrupted.'))
results.append(_('You must GET the form before submitting it.'))
+ # Check captcha
+ if isinstance(mm_cfg.CAPTCHAS, dict):
+ captcha_answer = cgidata.getvalue('captcha_answer', '')
+ if not Utils.captcha_verify(fcaptcha_idx, captcha_answer, mm_cfg.CAPTCHAS):
+ results.append(_('This was not the right answer to the CAPTCHA question.'))
# Was an attempt made to subscribe the list to itself?
if email == mlist.GetListEmail():
syslog('mischief', 'Attempt to self subscribe %s: %s', email, remote)
=== modified file 'Mailman/Defaults.py.in'
--- Mailman/Defaults.py.in 2019-05-22 00:10:40 +0000
+++ Mailman/Defaults.py.in 2019-06-10 15:36:27 +0000
@@ -131,6 +131,22 @@
# test.
SUBSCRIBE_FORM_MIN_TIME = seconds(5)
+# Use a custom question-answer CAPTCHA to protect against subscription spam.
+# Has no effect unless SUBSCRIBE_FORM_SECRET is set.
+# Should be set to a dict mapping language keys to a list of pairs
+# of questions and regexes for the answers, e.g.
+# CAPTCHAS = {
+# 'en': [
+# ('What is two times six?', '(12|twelve)'),
+# ],
+# 'de': [
+# ('Was ist 3 mal 6?', '(18|achtzehn)'),
+# ],
+# }
+# The regular expression must match the full string, i.e., it is implicitly
+# acting as if it had "^" in the beginning and "$" at the end.
+CAPTCHAS = None
+
# Use Google reCAPTCHA to protect the subscription form from spam bots. The
# following must be set to a pair of keys issued by the reCAPTCHA service at
# https://www.google.com/recaptcha/admin
=== modified file 'Mailman/Utils.py'
--- Mailman/Utils.py 2019-03-02 02:24:14 +0000
+++ Mailman/Utils.py 2019-06-10 15:36:27 +0000
@@ -1576,3 +1576,32 @@
if not re.search(r'127\.0\.1\.255$', text, re.MULTILINE):
return True
return False
+
+
+def captcha_display(mlist, lang, captchas):
+ """Returns a CAPTCHA question, the HTML for the answer box, and
+ the data to be put into the CSRF token"""
+ if not lang in captchas:
+ lang = 'en'
+ captchas = captchas[lang]
+ idx = random.randrange(len(captchas))
+ question = captchas[idx][0]
+ box_html = mlist.FormatBox('captcha_answer', size=30)
+ # Remember to encode the language in the index so that we can get it out again!
+ return (websafe(question), box_html, lang + "-" + str(idx))
+
+def captcha_verify(idx, given_answer, captchas):
+ try:
+ (lang, idx) = idx.split("-")
+ idx = int(idx)
+ except ValueError:
+ return False
+ if not lang in captchas:
+ return False
+ captchas = captchas[lang]
+ if not idx in range(len(captchas)):
+ return False
+ # Check the given answer.
+ # We append a `$` to emulate `re.fullmatch`.
+ correct_answer_pattern = captchas[idx][1] + "$"
+ return re.match(correct_answer_pattern, given_answer)
=== modified file 'templates/de/listinfo.html'
--- templates/de/listinfo.html 2018-01-29 12:58:42 +0000
+++ templates/de/listinfo.html 2019-06-10 15:36:27 +0000
@@ -115,6 +115,7 @@
</tr>
<mm-digest-question-end>
<mm-recaptcha-ui>
+ <mm-captcha-ui>
<tr>
<td colspan="3">
<center><MM-Subscribe-Button></center>
=== modified file 'templates/en/listinfo.html'
--- templates/en/listinfo.html 2018-01-29 12:58:42 +0000
+++ templates/en/listinfo.html 2019-06-10 15:36:27 +0000
@@ -116,6 +116,7 @@
</tr>
<mm-digest-question-end>
<mm-recaptcha-ui>
+ <mm-captcha-ui>
<tr>
<td colspan="3">
<center><MM-Subscribe-Button></center>
=== modified file 'templates/fr/listinfo.html'
--- templates/fr/listinfo.html 2018-01-29 12:58:42 +0000
+++ templates/fr/listinfo.html 2019-06-10 15:36:27 +0000
@@ -119,6 +119,7 @@
</tr>
<mm-digest-question-end>
<mm-recaptcha-ui>
+ <mm-captcha-ui>
<tr>
<td colspan="3">
<center><MM-Subscribe-Button></center>
_______________________________________________
Mailman-coders mailing list
[email protected]
https://mail.python.org/mailman/listinfo/mailman-coders