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
--- /dev/null
+# 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)
--- /dev/null
+# 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 <em>%(safelistname)s</em>'))
+ 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(
+ _('''<p>There currently are no publicly-advertised
+ %(mailmanlink)s mailing lists on %(hostname)s.'''))
+ else:
+ welcome.append(
+ _('''<p>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.
+ <p>List administrators, you can visit '''),
+ Link(Utils.ScriptURL('admin'),
+ _('the list admin overview page')),
+ _(''' to find the management interface for your list.
+ <p>If you are having trouble using the lists, please contact '''),
+ Link('mailto:' + siteowner, siteowner),
+ '.<p>'))
+
+ 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('<hr>')
+ 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['<mm-digest-radio-button>'] = ""
+ replacements['<mm-undigest-radio-button>'] = ""
+ replacements['<mm-digest-question-start>'] = '<!-- '
+ replacements['<mm-digest-question-end>'] = ' -->'
+ else:
+ replacements['<mm-digest-radio-button>'] = mlist.FormatDigestButton()
+ replacements['<mm-undigest-radio-button>'] = \
+ mlist.FormatUndigestButton()
+ replacements['<mm-digest-question-start>'] = ''
+ replacements['<mm-digest-question-end>'] = ''
+ replacements['<mm-plain-digests-button>'] = \
+ mlist.FormatPlainDigestsButton()
+ replacements['<mm-mime-digests-button>'] = mlist.FormatMimeDigestsButton()
+ replacements['<mm-subscribe-box>'] = mlist.FormatBox('email', size=30)
+ replacements['<mm-subscribe-button>'] = mlist.FormatButton(
+ 'email-button', text=_('Subscribe'))
+ replacements['<mm-new-password-box>'] = mlist.FormatSecureBox('pw')
+ replacements['<mm-confirm-password>'] = mlist.FormatSecureBox('pw-conf')
+ replacements['<mm-subscribe-form-start>'] = 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['<mm-captcha-question>'] = captcha_question
+ replacements['<mm-captcha-box>'] = captcha_box
+ # fill form
+ replacements['<mm-subscribe-form-start>'] += (
+ '<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()
+ )
+ )
+ # Roster form substitutions
+ replacements['<mm-roster-form-start>'] = mlist.FormatFormStart('roster')
+ replacements['<mm-roster-option>'] = mlist.FormatRosterOptionForUser(lang)
+ # Options form substitutions
+ replacements['<mm-options-form-start>'] = mlist.FormatFormStart('options')
+ replacements['<mm-editing-options>'] = mlist.FormatEditingOption(lang)
+ replacements['<mm-info-button>'] = 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['<mm-displang-box>'] = displang
+ replacements['<mm-lang-form-start>'] = mlist.FormatFormStart('listinfo')
+ replacements['<mm-fullname-box>'] = mlist.FormatBox('fullname', size=30)
+
+ # Do the expansion.
+ doc.AddItem(mlist.ParseTags('listinfo.html', replacements, lang))
+ print doc.Format()
+
+
+
+
+if __name__ == "__main__":
+ main()
--- /dev/null
+# 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<p>'
+
+# 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 <em>%(safelistname)s</em>')))
+ # 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['<mm-results>'] = results
+ output = mlist.ParseTags('subscribe.html', replacements, lang)
+ doc.AddItem(output)
+ print doc.Format()
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!-- updated from Revision 1.1 -->
+<HTML>
+ <HEAD>
+ <TITLE><MM-List-Name> Infoseite</TITLE>
+
+ </HEAD>
+ <BODY BGCOLOR="#ffffff">
+
+ <P>
+ <TABLE BORDER="0" CELLSPACING="4" CELLPADDING="5">
+ <TR>
+ <TD COLSPAN="2" WIDTH="100%" BGCOLOR="#99CCFF" ALIGN="CENTER">
+ <B><FONT COLOR="#000000" SIZE="+1"><MM-List-Name> --
+ <MM-List-Description></FONT></B>
+ </TD>
+ </TR>
+ <tr>
+ <td colspan="2">
+ <p>
+ </td>
+ </tr>
+ <tr>
+ <TD COLSPAN="1" WIDTH="100%" BGCOLOR="#FFF0D0">
+ <B><FONT COLOR="#000000">Über <MM-List-Name></FONT></B>
+ </TD>
+ <TD COLSPAN="1" WIDTH="100%" BGCOLOR="#FFF0D0">
+ <MM-lang-form-start><MM-displang-box> <MM-list-langs>
+ <MM-form-end>
+ </TD>
+ </TR>
+ <tr>
+ <td colspan="2">
+ <P><MM-List-Info></P>
+ <p> Um frühere Nachrichten an diese Liste zu sehen,
+besuchen Sie bitte das <MM-Archive>Archiv der
+Liste <MM-List-Name></MM-Archive>. <MM-Restricted-List-Message>
+ </p>
+ </TD>
+ </TR>
+ <TR>
+ <TD COLSPAN="2" WIDTH="100%" BGCOLOR="#FFF0D0">
+ <B><FONT COLOR="#000000">Benutzung von <MM-List-Name></FONT></B>
+ </TD>
+ </TR>
+ <tr>
+ <td colspan="2">
+ Um eine Nachricht an alle Listenmitglieder zu senden, schicken Sie
+ diese an
+ <A HREF="mailto:<MM-Posting-Addr>"><MM-Posting-Addr></A>.
+
+ <p>Sie können im folgenden Abschnitt diese Liste abonnieren
+ oder ein bestehendes Abonnement ändern.
+ </td>
+ </tr>
+ <TR>
+ <TD COLSPAN="2" WIDTH="100%" BGCOLOR="#FFF0D0">
+ <B><FONT COLOR="#000000">Abonnieren von <MM-List-Name></FONT></B>
+ </TD>
+ </TR>
+ <tr>
+ <td colspan="2">
+ <P>
+ Abonnieren Sie <MM-List-Name>, indem Sie das folgende Formular
+ ausfüllen:
+ <MM-List-Subscription-Msg>
+ <ul>
+ <MM-Subscribe-Form-Start>
+ <TABLE BORDER="0" CELLSPACING="2" CELLPADDING="2"
+ WIDTH="70%">
+ <TR>
+ <TD BGCOLOR="#dddddd" WIDTH="55%">Ihre E-Mail-Adresse:</TD>
+ <TD WIDTH="33%"><MM-Subscribe-Box>
+ </TD>
+ <TD WIDTH="12%"> </TD></TR>
+ <tr>
+ <td bgcolor="#dddddd" width="55%">Ihr Name (optional):</td>
+ <td width="33%"><mm-fullname-box></td>
+ <TD WIDTH="12%"> </TD></TR>
+ <TR>
+ <TD COLSPAN="3"><FONT SIZE=-1>Sie können weiter unten ein Passwort
+ eingeben. Dieses Passwort bietet nur eine geringe Sicherheit,
+ sollte aber verhindern, dass andere Ihr Abonnement
+ manipulieren. <b>Verwenden Sie kein wertvolles Passwort</b>,
+ da es ab und zu an Sie geschickt wird und <b>im Klartext gespeichert</b> wird!
+
+ <br><br>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.
+ <MM-Reminder> </font></TD>
+ </TR>
+ <TR>
+ <TD BGCOLOR="#dddddd">Wählen Sie ein Passwort:</TD>
+ <TD><MM-New-Password-Box></TD>
+ <TD> </TD></TR>
+ <TR>
+ <TD BGCOLOR="#dddddd">Erneute Eingabe zur Bestätigung:</TD>
+ <TD><MM-Confirm-Password></TD>
+ <TD> </TD></TR>
+ <tr>
+ <TD BGCOLOR="#dddddd">Welche Sprache bevorzugen Sie zur
+ Benutzerführung?</TD>
+ <TD> <MM-list-langs></TD>
+ <TD> </TD></TR>
+ <mm-digest-question-start>
+ <tr>
+ <td>Möchten Sie die Listenmails gebündelt in Form einer täglichen
+ Zusammenfassung (digest) erhalten?
+ </td>
+ <td><MM-Undigest-Radio-Button>Nein
+ <MM-Digest-Radio-Button>Ja
+ </TD>
+ </tr>
+ <mm-digest-question-end>
+ <tr>
+ <TD BGCOLOR="#dddddd">Bitte beantworten Sie die folgende Frage, um zu beweisen, dass Sie kein Bot sind:
+ <mm-captcha-question>
+ </TD>
+ <TD><mm-captcha-box></TD>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <center><MM-Subscribe-Button></center>
+ </td>
+ </tr>
+ </TABLE>
+ <MM-Form-End>
+ </ul>
+ <TR>
+ <TD COLSPAN="2" WIDTH="100%" BGCOLOR="#FFF0D0">
+ <a name="subscribers">
+ <B><FONT COLOR="#000000">Abonnenten der Liste <MM-List-Name></FONT></B></a>
+ </TD>
+ </TR>
+ <tr>
+ <TD COLSPAN="2" WIDTH="100%">
+ <MM-Roster-Form-Start>
+ <MM-Roster-Option>
+ <MM-Form-End>
+ </TD>
+ </TR>
+ <TR>
+ <TD COLSPAN="2" WIDTH="100%" BGCOLOR="#FFF0D0">
+ <B><FONT COLOR="#000000">Austragen / Ändern einer Mailadresse</FONT></B>
+ </TD>
+ </TR>
+ <TD COLSPAN="2" WIDTH="100%">
+ <MM-Options-Form-Start>
+<br>
+ <MM-Editing-Options>
+<br>
+ <MM-Form-End>
+ </td>
+ </tr>
+ </table>
+<MM-Mailman-Footer>
+</BODY>
+</HTML>
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!-- $Revision: 5865 $ -->
+<HTML>
+ <HEAD>
+ <TITLE><MM-List-Name> Info Page</TITLE>
+
+ </HEAD>
+ <BODY BGCOLOR="#ffffff">
+
+ <P>
+ <TABLE BORDER="0" CELLSPACING="4" CELLPADDING="5">
+ <TR>
+ <TD COLSPAN="2" WIDTH="100%" BGCOLOR="#99CCFF" ALIGN="CENTER">
+ <B><FONT COLOR="#000000" SIZE="+1"><MM-List-Name> --
+ <MM-List-Description></FONT></B>
+ </TD>
+ </TR>
+ <tr>
+ <td colspan="2">
+ <p>
+ </td>
+ </tr>
+ <tr>
+ <TD COLSPAN="1" WIDTH="100%" BGCOLOR="#FFF0D0">
+ <B><FONT COLOR="#000000">About <MM-List-Name></FONT></B>
+ </TD>
+ <TD COLSPAN="1" WIDTH="100%" BGCOLOR="#FFF0D0">
+ <MM-lang-form-start><MM-displang-box> <MM-list-langs>
+ <MM-form-end>
+ </TD>
+ </TR>
+ <tr>
+ <td colspan="2">
+ <P><MM-List-Info></P>
+ <p> To see the collection of prior postings to the list,
+ visit the <MM-Archive><MM-List-Name>
+ Archives</MM-Archive>.
+ <MM-Restricted-List-Message>
+ </p>
+ </TD>
+ </TR>
+ <TR>
+ <TD COLSPAN="2" WIDTH="100%" BGCOLOR="#FFF0D0">
+ <B><FONT COLOR="#000000">Using <MM-List-Name></FONT></B>
+ </TD>
+ </TR>
+ <tr>
+ <td colspan="2">
+ To post a message to all the list members, send email to
+ <A HREF="mailto:<MM-Posting-Addr>"><MM-Posting-Addr></A>.
+
+ <p>You can subscribe to the list, or change your existing
+ subscription, in the sections below.
+ </td>
+ </tr>
+ <TR>
+ <TD COLSPAN="2" WIDTH="100%" BGCOLOR="#FFF0D0">
+ <B><FONT COLOR="#000000">Subscribing to <MM-List-Name></FONT></B>
+ </TD>
+ </TR>
+ <tr>
+ <td colspan="2">
+ <P>
+ Subscribe to <MM-List-Name> by filling out the following
+ form.
+ <MM-List-Subscription-Msg>
+ <ul>
+ <MM-Subscribe-Form-Start>
+ <TABLE BORDER="0" CELLSPACING="2" CELLPADDING="2"
+ WIDTH="70%">
+ <TR>
+ <TD BGCOLOR="#dddddd" WIDTH="55%">Your email address:</TD>
+ <TD WIDTH="33%"><MM-Subscribe-Box>
+ </TD>
+ <TD WIDTH="12%"> </TD></TR>
+ <tr>
+ <td bgcolor="#dddddd" width="55%">Your name (optional):</td>
+ <td width="33%"><mm-fullname-box></td>
+ <TD WIDTH="12%"> </TD></TR>
+ <TR>
+ <TD COLSPAN="3"><FONT SIZE=-1>You may enter a
+ privacy password below. This provides only mild security,
+ but should prevent others from messing with your
+ subscription. <b>Do not use a valuable password</b> as
+ it will occasionally be emailed back to you and <b>stored in cleartext</b>.
+
+ <br><br>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.
+ <MM-Reminder>
+ </font>
+ </TD>
+ </TR>
+ <TR>
+ <TD BGCOLOR="#dddddd">Pick a password:</TD>
+ <TD><MM-New-Password-Box></TD>
+ <TD> </TD></TR>
+ <TR>
+ <TD BGCOLOR="#dddddd">Reenter password to confirm:</TD>
+ <TD><MM-Confirm-Password></TD>
+ <TD> </TD></TR>
+ <tr>
+ <TD BGCOLOR="#dddddd">Which language do you prefer to display your messages?</TD>
+ <TD> <MM-list-langs></TD>
+ <TD> </TD></TR>
+ <mm-digest-question-start>
+ <tr>
+ <td>Would you like to receive list mail batched in a daily
+ digest?
+ </td>
+ <td><MM-Undigest-Radio-Button> No
+ <MM-Digest-Radio-Button> Yes
+ </TD>
+ </tr>
+ <mm-digest-question-end>
+ <tr>
+ <TD BGCOLOR="#dddddd">Please answer the following question to prove that you are not a bot:
+ <mm-captcha-question>
+ </TD>
+ <TD><mm-captcha-box></TD>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <center><MM-Subscribe-Button></center>
+ </td>
+ </tr>
+ </TABLE>
+ <MM-Form-End>
+ </ul>
+ <TR>
+ <TD COLSPAN="2" WIDTH="100%" BGCOLOR="#FFF0D0">
+ <a name="subscribers">
+ <B><FONT COLOR="#000000"><MM-List-Name> Subscribers</FONT></B></a>
+ </TD>
+ </TR>
+ <tr>
+ <TD COLSPAN="2" WIDTH="100%">
+ <MM-Roster-Form-Start>
+ <MM-Roster-Option>
+ <MM-Form-End>
+ <p>
+ <MM-Options-Form-Start>
+ <MM-Editing-Options>
+ <MM-Form-End>
+ </td>
+ </tr>
+ </table>
+<MM-Mailman-Footer>
+</BODY>
+</HTML>
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
# -*- python -*-
+# -*- coding: utf-8 -*-
# Copyright (C) 1998,1999,2000 by the Free Software Foundation, Inc.
#
# 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 %}
+]