From: Ralf Jung Date: Sun, 3 Jun 2018 18:37:35 +0000 (+0200) Subject: patch mailman to add a simple question-and-answer CAPTCHA X-Git-Url: https://git.ralfj.de/ansible.git/commitdiff_plain/e950b50ac5f0ac5f5418554f260ab7916ea116bc?ds=sidebyside;hp=937b170594be82e500ae726dc47de8ca9ef3dfcf patch mailman to add a simple question-and-answer CAPTCHA --- diff --git a/roles/base/tasks/main.yml b/roles/base/tasks/main.yml index 0fefd93..d1c108c 100644 --- a/roles/base/tasks/main.yml +++ b/roles/base/tasks/main.yml @@ -3,7 +3,7 @@ when: not (ansible_distribution == "Debian" and ansible_lsb.major_release|int >= 9) command: "false" - name: detect if we have backports in the sources.list - command: fgrep backports /etc/apt/sources.list + command: 'fgrep backports /etc/apt/sources.list' register: backports failed_when: backports.rc == 2 changed_when: False diff --git a/roles/email/files/mailman-patched/Captcha.py b/roles/email/files/mailman-patched/Captcha.py new file mode 100644 index 0000000..42f75f0 --- /dev/null +++ b/roles/email/files/mailman-patched/Captcha.py @@ -0,0 +1,39 @@ +# Copyright (C) 2018 by Ralf Jung +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +import random +from Mailman import Utils + +def displayhtml(mlist, captchas): + """Returns a CAPTCHA question, the HTML for the answer box, and + the data to be put into the CSRF token""" + idx = random.randrange(len(captchas)) + question = captchas[idx][0] + box_html = mlist.FormatBox('captcha_answer', size=30) + return (Utils.websafe(question), box_html, str(idx)) + +def verify(idx, given_answer, captchas): + try: + idx = int(idx) + except ValueError: + return False + if not idx in range(len(captchas)): + return False + # Chec the given answer + correct_answers = captchas[idx][1] + given_answer = given_answer.strip().lower() + return given_answer in map(lambda x: x.strip().lower(), correct_answers) diff --git a/roles/email/files/mailman-patched/Cgi/listinfo.py b/roles/email/files/mailman-patched/Cgi/listinfo.py new file mode 100644 index 0000000..0ce6393 --- /dev/null +++ b/roles/email/files/mailman-patched/Cgi/listinfo.py @@ -0,0 +1,265 @@ +# Copyright (C) 1998-2016 by the Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Produce listinfo page, primary web entry-point to mailing lists. +""" + +# No lock needed in this script, because we don't change data. + +import os +import cgi +import time + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import Captcha # MAILMAN_CAPTCHA_PATCHED +from Mailman import MailList +from Mailman import Errors +from Mailman import i18n +from Mailman.htmlformat import * +from Mailman.Logging.Syslog import syslog + +# Set up i18n +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + + + +def main(): + parts = Utils.GetPathPieces() + if not parts: + listinfo_overview() + return + + listname = parts[0].lower() + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + # Send this with a 404 status. + print 'Status: 404 Not Found' + listinfo_overview(_('No such list %(safelistname)s')) + syslog('error', 'listinfo: No such list "%s": %s', listname, e) + return + + # See if the user want to see this page in other language + cgidata = cgi.FieldStorage() + try: + language = cgidata.getvalue('language') + except TypeError: + # Someone crafted a POST with a bad Content-Type:. + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + # Send this with a 400 status. + print 'Status: 400 Bad Request' + print doc.Format() + return + + if not Utils.IsLanguage(language): + language = mlist.preferred_language + i18n.set_language(language) + list_listinfo(mlist, language) + + + + +def listinfo_overview(msg=''): + # Present the general listinfo overview + hostname = Utils.get_domain() + # Set up the document and assign it the correct language. The only one we + # know about at the moment is the server's default. + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + legend = _("%(hostname)s Mailing Lists") + doc.SetTitle(legend) + + table = Table(border=0, width="100%") + table.AddRow([Center(Header(2, legend))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + + # Skip any mailing lists that isn't advertised. + advertised = [] + listnames = Utils.list_names() + listnames.sort() + + for name in listnames: + try: + mlist = MailList.MailList(name, lock=0) + except Errors.MMUnknownListError: + # The list could have been deleted by another process. + continue + if mlist.advertised: + if mm_cfg.VIRTUAL_HOST_OVERVIEW and ( + mlist.web_page_url.find('/%s/' % hostname) == -1 and + mlist.web_page_url.find('/%s:' % hostname) == -1): + # List is for different identity of this host - skip it. + continue + else: + advertised.append((mlist.GetScriptURL('listinfo'), + mlist.real_name, + Utils.websafe(mlist.description))) + if msg: + greeting = FontAttr(msg, color="ff5060", size="+1") + else: + greeting = FontAttr(_('Welcome!'), size='+2') + + welcome = [greeting] + mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format() + if not advertised: + welcome.extend( + _('''

There currently are no publicly-advertised + %(mailmanlink)s mailing lists on %(hostname)s.''')) + else: + welcome.append( + _('''

Below is a listing of all the public mailing lists on + %(hostname)s. Click on a list name to get more information about + the list, or to subscribe, unsubscribe, and change the preferences + on your subscription.''')) + + # set up some local variables + adj = msg and _('right') or '' + siteowner = Utils.get_site_email() + welcome.extend( + (_(''' To visit the general information page for an unadvertised list, + open a URL similar to this one, but with a '/' and the %(adj)s + list name appended. +

List administrators, you can visit '''), + Link(Utils.ScriptURL('admin'), + _('the list admin overview page')), + _(''' to find the management interface for your list. +

If you are having trouble using the lists, please contact '''), + Link('mailto:' + siteowner, siteowner), + '.

')) + + table.AddRow([apply(Container, welcome)]) + table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2) + + if advertised: + table.AddRow([' ', ' ']) + table.AddRow([Bold(FontAttr(_('List'), size='+2')), + Bold(FontAttr(_('Description'), size='+2')) + ]) + highlight = 1 + for url, real_name, description in advertised: + table.AddRow( + [Link(url, Bold(real_name)), + description or Italic(_('[no description available]'))]) + if highlight and mm_cfg.WEB_HIGHLIGHT_COLOR: + table.AddRowInfo(table.GetCurrentRowIndex(), + bgcolor=mm_cfg.WEB_HIGHLIGHT_COLOR) + highlight = not highlight + + doc.AddItem(table) + doc.AddItem('


') + doc.AddItem(MailmanLogo()) + print doc.Format() + + + + +def list_listinfo(mlist, lang): + # Generate list specific listinfo + doc = HeadlessDocument() + doc.set_language(lang) + + replacements = mlist.GetStandardReplacements(lang) + + if not mlist.digestable or not mlist.nondigestable: + replacements[''] = "" + replacements[''] = "" + replacements[''] = '' + else: + replacements[''] = mlist.FormatDigestButton() + replacements[''] = \ + mlist.FormatUndigestButton() + replacements[''] = '' + replacements[''] = '' + replacements[''] = \ + mlist.FormatPlainDigestsButton() + replacements[''] = mlist.FormatMimeDigestsButton() + replacements[''] = mlist.FormatBox('email', size=30) + replacements[''] = mlist.FormatButton( + 'email-button', text=_('Subscribe')) + replacements[''] = mlist.FormatSecureBox('pw') + replacements[''] = mlist.FormatSecureBox('pw-conf') + replacements[''] = mlist.FormatFormStart( + 'subscribe') + if mm_cfg.SUBSCRIBE_FORM_SECRET: + now = str(int(time.time())) + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'w.x.y.z'))) + # Try to accept a range in case of load balancers, etc. (LP: #1447445) + if remote.find('.') >= 0: + # ipv4 - drop last octet + remote = remote.rsplit('.', 1)[0] + else: + # ipv6 - drop last 16 (could end with :: in which case we just + # drop one : resulting in an invalid format, but it's only + # for our hash so it doesn't matter. + remote = remote.rsplit(':', 1)[0] + # get CAPTCHA data + (captcha_question, captcha_box, captcha_idx) = Captcha.displayhtml(mlist, mm_cfg.CAPTCHAS) + replacements[''] = captcha_question + replacements[''] = captcha_box + # fill form + replacements[''] += ( + '\n' + % (now, captcha_idx, Utils.sha_new(mm_cfg.SUBSCRIBE_FORM_SECRET + + now + + captcha_idx + + mlist.internal_name() + + remote + ).hexdigest() + ) + ) + # Roster form substitutions + replacements[''] = mlist.FormatFormStart('roster') + replacements[''] = mlist.FormatRosterOptionForUser(lang) + # Options form substitutions + replacements[''] = mlist.FormatFormStart('options') + replacements[''] = mlist.FormatEditingOption(lang) + replacements[''] = SubmitButton('UserOptions', + _('Edit Options')).Format() + # If only one language is enabled for this mailing list, omit the choice + # buttons. + if len(mlist.GetAvailableLanguages()) == 1: + displang = '' + else: + displang = mlist.FormatButton('displang-button', + text = _("View this page in")) + replacements[''] = displang + replacements[''] = mlist.FormatFormStart('listinfo') + replacements[''] = mlist.FormatBox('fullname', size=30) + + # Do the expansion. + doc.AddItem(mlist.ParseTags('listinfo.html', replacements, lang)) + print doc.Format() + + + + +if __name__ == "__main__": + main() diff --git a/roles/email/files/mailman-patched/Cgi/subscribe.py b/roles/email/files/mailman-patched/Cgi/subscribe.py new file mode 100644 index 0000000..153286d --- /dev/null +++ b/roles/email/files/mailman-patched/Cgi/subscribe.py @@ -0,0 +1,333 @@ +# Copyright (C) 1998-2016 by the Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Process subscription or roster requests from listinfo form.""" + +import sys +import os +import cgi +import time +import signal + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import Captcha # MAILMAN_CAPTCHA_PATCHED +from Mailman import MailList +from Mailman import Errors +from Mailman import i18n +from Mailman import Message +from Mailman.UserDesc import UserDesc +from Mailman.htmlformat import * +from Mailman.Logging.Syslog import syslog + +SLASH = '/' +ERRORSEP = '\n\n

' + +# Set up i18n +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + + + +def main(): + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + parts = Utils.GetPathPieces() + if not parts: + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Invalid options to CGI script'))) + print doc.Format() + return + + listname = parts[0].lower() + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('No such list %(safelistname)s'))) + # Send this with a 404 status. + print 'Status: 404 Not Found' + print doc.Format() + syslog('error', 'subscribe: No such list "%s": %s\n', listname, e) + return + + # See if the form data has a preferred language set, in which case, use it + # for the results. If not, use the list's preferred language. + cgidata = cgi.FieldStorage() + try: + language = cgidata.getvalue('language', '') + except TypeError: + # Someone crafted a POST with a bad Content-Type:. + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + # Send this with a 400 status. + print 'Status: 400 Bad Request' + print doc.Format() + return + if not Utils.IsLanguage(language): + language = mlist.preferred_language + i18n.set_language(language) + doc.set_language(language) + + # We need a signal handler to catch the SIGTERM that can come from Apache + # when the user hits the browser's STOP button. See the comment in + # admin.py for details. + # + # BAW: Strictly speaking, the list should not need to be locked just to + # read the request database. However the request database asserts that + # the list is locked in order to load it and it's not worth complicating + # that logic. + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + mlist.Unlock() + # ...and ensure we exit, otherwise race conditions could cause us to + # enter MailList.Save() while we're in the unlocked state, and that + # could be bad! + sys.exit(0) + + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) + + process_form(mlist, doc, cgidata, language) + mlist.Save() + finally: + mlist.Unlock() + + + + +def process_form(mlist, doc, cgidata, lang): + listowner = mlist.GetOwnerEmail() + realname = mlist.real_name + results = [] + + # The email address being subscribed, required + email = cgidata.getvalue('email', '').strip() + if not email: + results.append(_('You must supply a valid email address.')) + + fullname = cgidata.getvalue('fullname', '') + # Canonicalize the full name + fullname = Utils.canonstr(fullname, lang) + # Who was doing the subscribing? + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + # Are we checking the hidden data? + if mm_cfg.SUBSCRIBE_FORM_SECRET: + now = int(time.time()) + # Try to accept a range in case of load balancers, etc. (LP: #1447445) + if remote.find('.') >= 0: + # ipv4 - drop last octet + remote1 = remote.rsplit('.', 1)[0] + else: + # ipv6 - drop last 16 (could end with :: in which case we just + # drop one : resulting in an invalid format, but it's only + # for our hash so it doesn't matter. + remote1 = remote.rsplit(':', 1)[0] + try: + ftime, fcaptcha_idx, fhash = cgidata.getvalue('sub_form_token', '').split(':') + then = int(ftime) + except ValueError: + 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: + results.append(_('The form is too old. Please GET it again.')) + if ftime and now - then < mm_cfg.SUBSCRIBE_FORM_MIN_TIME: + results.append( + _('Please take a few seconds to fill out the form before submitting it.')) + if ftime and token != fhash: + results.append( + _("The hidden token didn't match. Did your IP change?")) + if not ftime: + 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 + captcha_answer = cgidata.getvalue('captcha_answer', '') + if not 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) + results.append(_('You may not subscribe a list to itself!')) + # If the user did not supply a password, generate one for him + password = cgidata.getvalue('pw', '').strip() + confirmed = cgidata.getvalue('pw-conf', '').strip() + + if not password and not confirmed: + password = Utils.MakeRandomPassword() + elif not password or not confirmed: + results.append(_('If you supply a password, you must confirm it.')) + elif password <> confirmed: + results.append(_('Your passwords did not match.')) + + # Get the digest option for the subscription. + digestflag = cgidata.getvalue('digest') + if digestflag: + try: + digest = int(digestflag) + except ValueError: + digest = 0 + else: + digest = mlist.digest_is_default + + # Sanity check based on list configuration. BAW: It's actually bogus that + # the page allows you to set the digest flag if you don't really get the + # choice. :/ + if not mlist.digestable: + digest = 0 + elif not mlist.nondigestable: + digest = 1 + + if results: + print_results(mlist, ERRORSEP.join(results), doc, lang) + return + + # If this list has private rosters, we have to be careful about the + # message that gets printed, otherwise the subscription process can be + # used to mine for list members. It may be inefficient, but it's still + # possible, and that kind of defeats the purpose of private rosters. + # We'll use this string for all successful or unsuccessful subscription + # results. + if mlist.private_roster == 0: + # Public rosters + privacy_results = '' + else: + privacy_results = _("""\ +Your subscription request has been received, and will soon be acted upon. +Depending on the configuration of this mailing list, your subscription request +may have to be first confirmed by you via email, or approved by the list +moderator. If confirmation is required, you will soon get a confirmation +email which contains further instructions.""") + + try: + userdesc = UserDesc(email, fullname, password, digest, lang) + mlist.AddMember(userdesc, remote) + results = '' + # Check for all the errors that mlist.AddMember can throw options on the + # web page for this cgi + except Errors.MembershipIsBanned: + results = _("""The email address you supplied is banned from this + mailing list. If you think this restriction is erroneous, please + contact the list owners at %(listowner)s.""") + except Errors.MMBadEmailError: + results = _("""\ +The email address you supplied is not valid. (E.g. it must contain an +`@'.)""") + except Errors.MMHostileAddress: + results = _("""\ +Your subscription is not allowed because the email address you gave is +insecure.""") + except Errors.MMSubscribeNeedsConfirmation: + # Results string depends on whether we have private rosters or not + if privacy_results: + results = privacy_results + else: + results = _("""\ +Confirmation from your email address is required, to prevent anyone from +subscribing you without permission. Instructions are being sent to you at +%(email)s. Please note your subscription will not start until you confirm +your subscription.""") + except Errors.MMNeedApproval, x: + # Results string depends on whether we have private rosters or not + if privacy_results: + results = privacy_results + else: + # We need to interpolate into x.__str__() + x = _(str(x)) + results = _("""\ +Your subscription request was deferred because %(x)s. Your request has been +forwarded to the list moderator. You will receive email informing you of the +moderator's decision when they get to your request.""") + except Errors.MMAlreadyAMember: + # Results string depends on whether we have private rosters or not + if not privacy_results: + results = _('You are already subscribed.') + else: + results = privacy_results + # This could be a membership probe. For safety, let the user know + # a probe occurred. BAW: should we inform the list moderator? + listaddr = mlist.GetListEmail() + # Set the language for this email message to the member's language. + mlang = mlist.getMemberLanguage(email) + otrans = i18n.get_translation() + i18n.set_language(mlang) + try: + msg = Message.UserNotification( + mlist.getMemberCPAddress(email), + mlist.GetBouncesEmail(), + _('Mailman privacy alert'), + _("""\ +An attempt was made to subscribe your address to the mailing list +%(listaddr)s. You are already subscribed to this mailing list. + +Note that the list membership is not public, so it is possible that a bad +person was trying to probe the list for its membership. This would be a +privacy violation if we let them do this, but we didn't. + +If you submitted the subscription request and forgot that you were already +subscribed to the list, then you can ignore this message. If you suspect that +an attempt is being made to covertly discover whether you are a member of this +list, and you are worried about your privacy, then feel free to send a message +to the list administrator at %(listowner)s. +"""), lang=mlang) + finally: + i18n.set_translation(otrans) + msg.send(mlist) + # These shouldn't happen unless someone's tampering with the form + except Errors.MMCantDigestError: + results = _('This list does not support digest delivery.') + except Errors.MMMustDigestError: + results = _('This list only supports digest delivery.') + else: + # Everything's cool. Our return string actually depends on whether + # this list has private rosters or not + if privacy_results: + results = privacy_results + else: + results = _("""\ +You have been successfully subscribed to the %(realname)s mailing list.""") + # Show the results + print_results(mlist, results, doc, lang) + + + + +def print_results(mlist, results, doc, lang): + # The bulk of the document will come from the options.html template, which + # includes its own html armor (head tags, etc.). Suppress the head that + # Document() derived pages get automatically. + doc.suppress_head = 1 + + replacements = mlist.GetStandardReplacements(lang) + replacements[''] = results + output = mlist.ParseTags('subscribe.html', replacements, lang) + doc.AddItem(output) + print doc.Format() diff --git a/roles/email/files/mailman-patched/de/listinfo.html b/roles/email/files/mailman-patched/de/listinfo.html new file mode 100644 index 0000000..2657d0c --- /dev/null +++ b/roles/email/files/mailman-patched/de/listinfo.html @@ -0,0 +1,160 @@ + + + + + <MM-List-Name> Infoseite + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ -- + +
+

  +

+ Über + + + +
+

+

Um frühere Nachrichten an diese Liste zu sehen, +besuchen Sie bitte das Archiv der +Liste . +

+
+ Benutzung von +
+ Um eine Nachricht an alle Listenmitglieder zu senden, schicken Sie + diese an + . + +

Sie können im folgenden Abschnitt diese Liste abonnieren + oder ein bestehendes Abonnement ändern. +

+ Abonnieren von +
+

+ Abonnieren Sie , indem Sie das folgende Formular + ausfüllen: + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Ihre E-Mail-Adresse: +  
    Ihr Name (optional): 
    Sie können weiter unten ein Passwort + eingeben. Dieses Passwort bietet nur eine geringe Sicherheit, + sollte aber verhindern, dass andere Ihr Abonnement + manipulieren. Verwenden Sie kein wertvolles Passwort, + da es ab und zu an Sie geschickt wird und im Klartext gespeichert wird! + +

    Wenn Sie kein Passwort eingeben, wird für Sie ein + Zufallspasswort generiert und Ihnen zugeschickt, sobald Sie Ihr + Abonnement bestätigt haben. Sie können sich Ihr Passwort + jederzeit per E-Mail zuschicken lassen, wenn Sie weiter unten + die Seite zum ändern Ihrer persönlichen Einstellungen aufrufen. +
    Wählen Sie ein Passwort: 
    Erneute Eingabe zur Bestätigung: 
    Welche Sprache bevorzugen Sie zur + Benutzerführung?  
    Möchten Sie die Listenmails gebündelt in Form einer täglichen + Zusammenfassung (digest) erhalten? + Nein + Ja +
    Bitte beantworten Sie die folgende Frage, um zu beweisen, dass Sie kein Bot sind: + +
    +
    +
    + +
+
+ + Abonnenten der Liste +
+ + + +
+ Austragen / Ändern einer Mailadresse +
+ +
+ +
+ +
+ + + diff --git a/roles/email/files/mailman-patched/en/listinfo.html b/roles/email/files/mailman-patched/en/listinfo.html new file mode 100644 index 0000000..cf0eca6 --- /dev/null +++ b/roles/email/files/mailman-patched/en/listinfo.html @@ -0,0 +1,152 @@ + + + + + <MM-List-Name> Info Page + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ -- + +
+

  +

+ About + + + +
+

+

To see the collection of prior postings to the list, + visit the + Archives. + +

+
+ Using +
+ To post a message to all the list members, send email to + . + +

You can subscribe to the list, or change your existing + subscription, in the sections below. +

+ Subscribing to +
+

+ Subscribe to by filling out the following + form. + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Your email address: +  
    Your name (optional): 
    You may enter a + privacy password below. This provides only mild security, + but should prevent others from messing with your + subscription. Do not use a valuable password as + it will occasionally be emailed back to you and stored in cleartext. + +

    If you choose not to enter a password, one will be + automatically generated for you, and it will be sent to + you once you've confirmed your subscription. You can + always request a mail-back of your password when you edit + your personal options. + +
    +
    Pick a password: 
    Reenter password to confirm: 
    Which language do you prefer to display your messages?  
    Would you like to receive list mail batched in a daily + digest? + No + Yes +
    Please answer the following question to prove that you are not a bot: + +
    +
    +
    + +
+
+ + Subscribers +
+ + + +

+ + + +

+ + + diff --git a/roles/email/tasks/mailman.yml b/roles/email/tasks/mailman.yml index 7f4aaea..b9c177e 100644 --- a/roles/email/tasks/mailman.yml +++ b/roles/email/tasks/mailman.yml @@ -10,3 +10,32 @@ dest: /etc/cron.daily/mailman-check src: files/mailman-check mode: u=rwx,g=rx,o=rx +# the CAPTCHA patch (and some template modifications) +- name: check if the files are already patched + shell: 'fgrep MAILMAN_CAPTCHA_PATCHED Cgi/listinfo.py && fgrep MAILMAN_CAPTCHA_PATCHED Cgi/subscribe.py' + args: + chdir: /usr/lib/mailman/Mailman + changed_when: False + register: mailman_patched +- name: check if all the files have the right checksums to be patched + shell: 'echo "{{item}}" | sha256sum -c' + loop: + - "26b4cbb7c5bde8badf741e31975235e74abb932037d77d862cf00b412726c2c2 /usr/lib/mailman/Mailman/Cgi/listinfo.py" + - "cbef3d8cb6b65e4c9b2462f8627966d55dd52caa2e626c87241c4f8d47477dc7 /usr/lib/mailman/Mailman/Cgi/subscribe.py" + changed_when: False + when: mailman_patched.rc != 0 +- name: install patched python files + copy: + dest: /usr/lib/mailman/Mailman/{{item}} + src: files/mailman-patched/{{item}} + loop: + - Cgi/listinfo.py + - Cgi/subscribe.py + - Captcha.py +- name: install patched templates + copy: + dest: /etc/mailman/{{item}} + src: files/mailman-patched/{{item}} + loop: + - de/listinfo.html + - en/listinfo.html diff --git a/roles/email/templates/mm_cfg.py b/roles/email/templates/mm_cfg.py index 9464272..89dd0d8 100644 --- a/roles/email/templates/mm_cfg.py +++ b/roles/email/templates/mm_cfg.py @@ -1,4 +1,5 @@ # -*- python -*- +# -*- coding: utf-8 -*- # Copyright (C) 1998,1999,2000 by the Free Software Foundation, Inc. # @@ -131,3 +132,12 @@ DEFAULT_DMARC_MODERATION_ACTION = 1 # Munge From # Spammer protection SUBSCRIBE_FORM_SECRET = "{{postfix.mailman.form_secret}}" +CAPTCHAS = [ +{% for item in postfix.mailman.captcha %} + ('{{item.question}}', [ + {% for answer in item.answers %} + '{{answer}}', + {% endfor %} + ]), +{% endfor %} +]