From ca9b60e34617f0f9ddbb5cdaa59c843aedc4b7f5 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss <erdnaxe@crans.org> Date: Sun, 17 May 2020 08:30:26 +0200 Subject: [PATCH 1/6] Clean at and monit --- clean_servers.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clean_servers.yml b/clean_servers.yml index 218948f2..4f727380 100755 --- a/clean_servers.yml +++ b/clean_servers.yml @@ -9,6 +9,7 @@ apt: state: absent name: + - at - arpwatch # old sniffing - collectd - collectd-utils # old monitoring @@ -28,6 +29,7 @@ - monitoring-plugins-standard - monitoring-plugins-basic - monitoring-plugins-common + - monit - libmonitoring-plugin-perl - snmp - nagios-plugins-contrib From 41e941034e0a7efc5cbd6134094eb6aaf11be378 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss <erdnaxe@crans.org> Date: Sun, 17 May 2020 08:32:29 +0200 Subject: [PATCH 2/6] [reverseproxy] Do not install nginx certbot --- roles/nginx-reverseproxy/tasks/main.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/roles/nginx-reverseproxy/tasks/main.yml b/roles/nginx-reverseproxy/tasks/main.yml index 5a0e298f..b1e39458 100644 --- a/roles/nginx-reverseproxy/tasks/main.yml +++ b/roles/nginx-reverseproxy/tasks/main.yml @@ -2,9 +2,7 @@ - name: Install NGINX apt: update_cache: true - name: - - nginx - - python3-certbot-nginx # for options-ssl-nginx.conf + name: nginx register: apt_result retries: 3 until: apt_result is succeeded From 00c5769d6e930294b6a76551db46e1d55b195bf2 Mon Sep 17 00:00:00 2001 From: Bombar Maxime <bombar@crans.org> Date: Sun, 17 May 2020 09:05:32 +0200 Subject: [PATCH 3/6] [clean_servers] Clean up nagios. --- clean_servers.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clean_servers.yml b/clean_servers.yml index 4f727380..0b6b7fd0 100755 --- a/clean_servers.yml +++ b/clean_servers.yml @@ -85,9 +85,12 @@ - /etc/munin - /etc/icinga2 - /etc/init.d/bcfg2 + - /etc/nagios + - /etc/nagios-plugins - /etc/nut - /etc/nginx/sites-enabled/status - /etc/nginx/sites-available/status + - /etc/pnp4nagios - /var/local/aptdater - /etc/apt-dater-host.conf - /etc/sudoers.d/apt-dater-host From 79f4d274b07f80f585dfcac8bb41fc77980a9aa7 Mon Sep 17 00:00:00 2001 From: Bombar Maxime <bombar@crans.org> Date: Sun, 17 May 2020 09:05:53 +0200 Subject: [PATCH 4/6] Wildcard certificate on redisdead --- certbot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot.yml b/certbot.yml index 6a6a3eb5..80f49ebc 100755 --- a/certbot.yml +++ b/certbot.yml @@ -2,7 +2,7 @@ --- # Temporary # Wildcard certificate for MX servers -- hosts: titanic.adm.crans.org +- hosts: titanic.adm.crans.org, redisdead.adm.crans.org vars: certbot: dns_rfc2136_name: certbot_challenge. From e585efb9afdba643ebc577d3d2acce575ffe7c10 Mon Sep 17 00:00:00 2001 From: Bombar Maxime <bombar@crans.org> Date: Sun, 17 May 2020 09:06:20 +0200 Subject: [PATCH 5/6] Add apt-file to common tools --- roles/common-tools/tasks/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/roles/common-tools/tasks/main.yml b/roles/common-tools/tasks/main.yml index 70488e80..b92fea69 100644 --- a/roles/common-tools/tasks/main.yml +++ b/roles/common-tools/tasks/main.yml @@ -4,6 +4,7 @@ update_cache: true install_recommends: false name: + - apt-file - sudo - molly-guard # prevent reboot - ntp # network time sync From 4ebcfa287a2cbc8c0dfbf50cfc8261f8bc73839e Mon Sep 17 00:00:00 2001 From: Bombar Maxime <bombar@crans.org> Date: Sun, 17 May 2020 11:09:23 +0200 Subject: [PATCH 6/6] Huge clean up in mailman configuration --- logos/crans.png | Bin 0 -> 10618 bytes mailman.yml | 23 + roles/mailman/handlers/main.yml | 5 + roles/mailman/tasks/main.yml | 39 + .../mailman/templates/mailman/create.html.j2 | 13 + roles/mailman/templates/mailman/mm_cfg.py.j2 | 226 ++++++ .../templates/update-motd.d/05-mailman.j2 | 3 + .../usr/lib/mailman/Mailman/htmlformat.py.j2 | 742 ++++++++++++++++++ roles/nginx-mailman/handlers/main.yml | 5 + roles/nginx-mailman/tasks/main.yml | 43 + .../templates/nginx/mailman_passwd.j2 | 2 + .../nginx/sites-available/mailman.j2 | 94 +++ .../nginx/snippets/fastcgi-mailman.conf.j2 | 18 + .../nginx/snippets/fastcgi-mailman.conf.j2~ | 18 + .../nginx/snippets/options-ssl.conf.j2 | 17 + .../templates/update-motd.d/05-service.j2 | 3 + .../templates/var/www/custom_401.html.j2 | 18 + .../templates/var/www/robots.txt.j2 | 4 + 18 files changed, 1273 insertions(+) create mode 100644 logos/crans.png create mode 100755 mailman.yml create mode 100644 roles/mailman/handlers/main.yml create mode 100644 roles/mailman/tasks/main.yml create mode 100644 roles/mailman/templates/mailman/create.html.j2 create mode 100644 roles/mailman/templates/mailman/mm_cfg.py.j2 create mode 100755 roles/mailman/templates/update-motd.d/05-mailman.j2 create mode 100644 roles/mailman/templates/usr/lib/mailman/Mailman/htmlformat.py.j2 create mode 100644 roles/nginx-mailman/handlers/main.yml create mode 100644 roles/nginx-mailman/tasks/main.yml create mode 100644 roles/nginx-mailman/templates/nginx/mailman_passwd.j2 create mode 100644 roles/nginx-mailman/templates/nginx/sites-available/mailman.j2 create mode 100644 roles/nginx-mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2 create mode 100644 roles/nginx-mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2~ create mode 100644 roles/nginx-mailman/templates/nginx/snippets/options-ssl.conf.j2 create mode 100755 roles/nginx-mailman/templates/update-motd.d/05-service.j2 create mode 100644 roles/nginx-mailman/templates/var/www/custom_401.html.j2 create mode 100644 roles/nginx-mailman/templates/var/www/robots.txt.j2 diff --git a/logos/crans.png b/logos/crans.png new file mode 100644 index 0000000000000000000000000000000000000000..9c5e281a69694f2aed73c8466229c3dcb96b9609 GIT binary patch literal 10618 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 UD3FYmR{#J207*qoM6N<$g4PgwlmGw# literal 0 HcmV?d00001 diff --git a/mailman.yml b/mailman.yml new file mode 100755 index 00000000..2ce0e772 --- /dev/null +++ b/mailman.yml @@ -0,0 +1,23 @@ +#!/usr/bin/env ansible-playbook +# Mailman playbook +--- +- hosts: redisdead.adm.crans.org + vars: + mailman: + site_list: "nounou" + default_url: "https://lists.crans.org/" + default_host: "lists.crans.org" + default_language: "fr" + auth_basic: | + "On n'aime pas les spambots, donc on a mis un mot de passe. Le login est Stop et le mot de passe est Spam."; + spamassassin: "SpamAssassin_crans" + smtphost: "smtp.adm.crans.org" + mynetworks: ['138.231.0.0/16', '185.230.76.0/22', '2a0c:700:0::/40'] + nginx: + ssl: + cert: /etc/letsencrypt/live/crans.org/fullchain.pem + key: /etc/letsencrypt/live/crans.org/privkey.pem + trusted_cert: /etc/letsencrypt/live/crans.org/chain.pem + roles: + - mailman + - nginx-mailman diff --git a/roles/mailman/handlers/main.yml b/roles/mailman/handlers/main.yml new file mode 100644 index 00000000..77550456 --- /dev/null +++ b/roles/mailman/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Reload mailman + systemd: + name: mailman + state: reloaded diff --git a/roles/mailman/tasks/main.yml b/roles/mailman/tasks/main.yml new file mode 100644 index 00000000..53ae09de --- /dev/null +++ b/roles/mailman/tasks/main.yml @@ -0,0 +1,39 @@ +--- +- name: Install mailman and SpamAssassin + apt: + update_cache: true + name: + - mailman + - spamassassin + register: apt_result + retries: 3 + until: apt_result is succeeded + +- name: Deploy mailman config + template: + src: "mailman/{{ item }}.j2" + dest: "/etc/mailman/{{ item }}" + mode: 0755 + loop: + - mm_cfg.py + - create.html + notify: Reload mailman + +# Fanciness +- name: Deploy crans logo + copy: + src: ../../../logos/crans.png + dest: /usr/share/images/mailman/crans.png + +- name: Deploy crans logo + template: + src: usr/lib/mailman/Mailman/htmlformat.py.j2 + dest: /usr/lib/mailman/Mailman/htmlformat.py + mode: 0755 + notify: Reload mailman + +- name: Indicate role in motd + template: + src: update-motd.d/05-mailman.j2 + dest: /etc/update-motd.d/05-mailman + mode: 0755 diff --git a/roles/mailman/templates/mailman/create.html.j2 b/roles/mailman/templates/mailman/create.html.j2 new file mode 100644 index 00000000..68236402 --- /dev/null +++ b/roles/mailman/templates/mailman/create.html.j2 @@ -0,0 +1,13 @@ +{{ ansible_header | comment('xml') }} + +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> +<title>Creation de mailing list</title> +</head> + +<body> +<h1>Creation de mailing list</h1> +Il faut s'adresser a nounou arobase crans point org. +</body> +</html> diff --git a/roles/mailman/templates/mailman/mm_cfg.py.j2 b/roles/mailman/templates/mailman/mm_cfg.py.j2 new file mode 100644 index 00000000..25f82461 --- /dev/null +++ b/roles/mailman/templates/mailman/mm_cfg.py.j2 @@ -0,0 +1,226 @@ +{{ ansible_header | comment }} +# -*- python -*- + +# Copyright (C) 1998,1999,2000 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 + + +"""This is the module which takes your site-specific settings. + +From a raw distribution it should be copied to mm_cfg.py. If you +already have an mm_cfg.py, be careful to add in only the new settings +you want. The complete set of distributed defaults, with annotation, +are in ./Defaults. In mm_cfg, override only those you want to +change, after the + + from Defaults import * + +line (see below). + +Note that these are just default settings - many can be overridden via the +admin and user interfaces on a per-list or per-user basis. + +Note also that some of the settings are resolved against the active list +setting by using the value as a format string against the +list-instance-object's dictionary - see the distributed value of +DEFAULT_MSG_FOOTER for an example.""" + + +####################################################### +# Here's where we get the distributed defaults. # + +from Defaults import * + + +##### +# General system-wide defaults +##### + +# Should image logos be used? Set this to 0 to disable image logos from "our +# sponsors" and just use textual links instead (this will also disable the +# shortcut "favicon"). Otherwise, this should contain the URL base path to +# the logo images (and must contain the trailing slash).. If you want to +# disable Mailman's logo footer altogther, hack +# Mailman/htmlformat.py:MailmanLogo(), which also contains the hardcoded links +# and image names. +IMAGE_LOGOS = '/images/mailman/' + +#------------------------------------------------------------- +# The name of the list Mailman uses to send password reminders +# and similar. Don't change if you want mailman-owner to be +# a valid local part. +MAILMAN_SITE_LIST = '{{ mailman.site_list }}' + +DEFAULT_URL= '{{ mailman.default_url }}' +DEFAULT_URL_PATTERN = 'https://%s/' +add_virtualhost(DEFAULT_URL_HOST, DEFAULT_EMAIL_HOST) + +#------------------------------------------------------------- +# Default domain for email addresses of newly created MLs +DEFAULT_EMAIL_HOST = '{{ mailman.default_host }}' +#------------------------------------------------------------- +# Default host for web interface of newly created MLs +DEFAULT_URL_HOST = '{{ mailman.default_host }}' +#------------------------------------------------------------- +# Required when setting any of its arguments. +add_virtualhost(DEFAULT_URL_HOST, DEFAULT_EMAIL_HOST) + +#------------------------------------------------------------- +# Do we send monthly reminders? +DEFAULT_SEND_REMINDERS = No + +# Normally when a site administrator authenticates to a web page with the site +# password, they get a cookie which authorizes them as the list admin. It +# makes me nervous to hand out site auth cookies because if this cookie is +# cracked or intercepted, the intruder will have access to every list on the +# site. OTOH, it's dang handy to not have to re-authenticate to every list on +# the site. Set this value to Yes to allow site admin cookies. +ALLOW_SITE_ADMIN_COOKIES = Yes + +##### +# Archive defaults +##### + +PUBLIC_ARCHIVE_URL = '{{ mailman.default_url }}archives/%(listname)s' + +# Are archives on or off by default? +DEFAULT_ARCHIVE = Off + +# Are archives public or private by default? +# 0=public, 1=private +DEFAULT_ARCHIVE_PRIVATE = 1 + +# Pipermail assumes that messages bodies contain US-ASCII text. +# Change this option to define a different character set to be used as +# the default character set for the archive. The term "character set" +# is used in MIME to refer to a method of converting a sequence of +# octets into a sequence of characters. If you change the default +# charset, you might need to add it to VERBATIM_ENCODING below. +DEFAULT_CHARSET = 'utf-8' + +# Most character set encodings require special HTML entity characters to be +# quoted, otherwise they won't look right in the Pipermail archives. However +# some character sets must not quote these characters so that they can be +# rendered properly in the browsers. The primary issue is multi-byte +# encodings where the octet 0x26 does not always represent the & character. +# This variable contains a list of such characters sets which are not +# HTML-quoted in the archives. +VERBATIM_ENCODING = ['utf-8'] + +##### +# General defaults +##### + +# The default language for this server. Whenever we can't figure out the list +# context or user context, we'll fall back to using this language. See +# LC_DESCRIPTIONS below for legal values. +DEFAULT_SERVER_LANGUAGE = '{{ mailman.default_language }}' + +# How many members to display at a time on the admin cgi to unsubscribe them +# or change their options? +DEFAULT_ADMIN_MEMBER_CHUNKSIZE = 50 + +# set this variable to Yes to allow list owners to delete their own mailing +# lists. You may not want to give them this power, in which case, setting +# this variable to No instead requires list removal to be done by the site +# administrator, via the command line script bin/rmlist. +#OWNERS_CAN_DELETE_THEIR_OWN_LISTS = No + +# Set this variable to Yes to allow list owners to set the "personalized" +# flags on their mailing lists. Turning these on tells Mailman to send +# separate email messages to each user instead of batching them together for +# delivery to the MTA. This gives each member a more personalized message, +# but can have a heavy impact on the performance of your system. +#OWNERS_CAN_ENABLE_PERSONALIZATION = No + +##### +# List defaults. NOTE: Changing these values does NOT change the +# configuration of an existing list. It only defines the default for new +# lists you subsequently create. +##### + +# Should a list, by default be advertised? What is the default maximum number +# of explicit recipients allowed? What is the default maximum message size +# allowed? +DEFAULT_LIST_ADVERTISED = Yes + +# {header-name: regexp} spam filtering - we include some for example sake. +DEFAULT_BOUNCE_MATCHING_HEADERS = """ +# Les lignes commencant par # sont des commentairtes. +#from: .*-owner@yahoogroups.com +#from: .*@uplinkpro.com +#from: .*@coolstats.comic.com +#from: .*@trafficmagnet.com +#from: .*@hotmail.com +#X-Reject: 450 +#X-Reject: 554 +""" + +# Mailman can be configured to strip any existing Reply-To: header, or simply +# extend any existing Reply-To: with one based on the above setting. +DEFAULT_FIRST_STRIP_REPLY_TO = Yes + +# SUBSCRIBE POLICY +# 0 - open list (only when ALLOW_OPEN_SUBSCRIBE is set to 1) ** +# 1 - confirmation required for subscribes +# 2 - admin approval required for subscribes +# 3 - both confirmation and admin approval required +# +# ** please do not choose option 0 if you are not allowing open +# subscribes (next variable) +DEFAULT_SUBSCRIBE_POLICY = 3 + +# Is the list owner notified of subscribes/unsubscribes? +DEFAULT_ADMIN_NOTIFY_MCHANGES = Yes + +# Do we send monthly reminders? +DEFAULT_SEND_REMINDERS = No + +# What should happen to non-member posts which do not match explicit +# non-member actions? +# 0 = Accept +# 1 = Hold +# 2 = Reject +# 3 = Discard +DEFAULT_GENERIC_NONMEMBER_ACTION = 1 + +# Use spamassassin automatically +GLOBAL_PIPELINE.insert(5, '{{ spamassassin }}') +# Discard messages with score higher than ... +SPAMASSASSIN_DISCARD_SCORE = 8 +# Hold in moderation messages with score higher than ... +SPAMASSASSIN_HOLD_SCORE = 2.1 + +# Add SpamAssassin administration interface on gui +# To make it work, you need to edit Gui/__init__.py +# with +# from SpamAssassin import SpamAssassin +ADMIN_CATEGORIES.append("spamassassin") + +# Add header to keep +PLAIN_DIGEST_KEEP_HEADERS.append('X-Spam-Score') + +# configure MTA +MTA = 'Postfix' +SMTPHOST = '{{ smtphost }}' +SMTP_MAX_RCPTS = 50 + + +POSTFIX_STYLE_VIRTUAL_DOMAINS = ["{{ mailman.default_host }}"] + +# Note - if you're looking for something that is imported from mm_cfg, but you +# didn't find it above, it's probably in /usr/lib/mailman/Mailman/Defaults.py. diff --git a/roles/mailman/templates/update-motd.d/05-mailman.j2 b/roles/mailman/templates/update-motd.d/05-mailman.j2 new file mode 100755 index 00000000..d3fee0db --- /dev/null +++ b/roles/mailman/templates/update-motd.d/05-mailman.j2 @@ -0,0 +1,3 @@ +#!/usr/bin/tail +14 +{{ ansible_header | comment }} +[0m> [38;5;82mMailman[0m a été déployé sur cette machine. Voir [38;5;6m/etc/mailman/[0m et [38;5;6m/var/lib/mailman/[0m. diff --git a/roles/mailman/templates/usr/lib/mailman/Mailman/htmlformat.py.j2 b/roles/mailman/templates/usr/lib/mailman/Mailman/htmlformat.py.j2 new file mode 100644 index 00000000..146f9576 --- /dev/null +++ b/roles/mailman/templates/usr/lib/mailman/Mailman/htmlformat.py.j2 @@ -0,0 +1,742 @@ +{{ ansible_header | comment }} +# Copyright (C) 1998-2018 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. + + +"""Library for program-based construction of an HTML documents. + +Encapsulate HTML formatting directives in classes that act as containers +for python and, recursively, for nested HTML formatting objects. +""" + + +# Eventually could abstract down to HtmlItem, which outputs an arbitrary html +# object given start / end tags, valid options, and a value. Ug, objects +# shouldn't be adding their own newlines. The next object should. + + +import types + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman.i18n import _, get_translation + +from Mailman.CSRFcheck import csrf_token + +SPACE = ' ' +EMPTYSTRING = '' +NL = '\n' + + + +# Format an arbitrary object. +def HTMLFormatObject(item, indent): + "Return a presentation of an object, invoking their Format method if any." + if type(item) == type(''): + return item + elif not hasattr(item, "Format"): + return `item` + else: + return item.Format(indent) + +def CaseInsensitiveKeyedDict(d): + result = {} + for (k,v) in d.items(): + result[k.lower()] = v + return result + +# Given references to two dictionaries, copy the second dictionary into the +# first one. +def DictMerge(destination, fresh_dict): + for (key, value) in fresh_dict.items(): + destination[key] = value + +class Table: + def __init__(self, **table_opts): + self.cells = [] + self.cell_info = {} + self.row_info = {} + self.opts = table_opts + + def AddOptions(self, opts): + DictMerge(self.opts, opts) + + # Sets all of the cells. It writes over whatever cells you had there + # previously. + + def SetAllCells(self, cells): + self.cells = cells + + # Add a new blank row at the end + def NewRow(self): + self.cells.append([]) + + # Add a new blank cell at the end + def NewCell(self): + self.cells[-1].append('') + + def AddRow(self, row): + self.cells.append(row) + + def AddCell(self, cell): + self.cells[-1].append(cell) + + def AddCellInfo(self, row, col, **kws): + kws = CaseInsensitiveKeyedDict(kws) + if not self.cell_info.has_key(row): + self.cell_info[row] = { col : kws } + elif self.cell_info[row].has_key(col): + DictMerge(self.cell_info[row], kws) + else: + self.cell_info[row][col] = kws + + def AddRowInfo(self, row, **kws): + kws = CaseInsensitiveKeyedDict(kws) + if not self.row_info.has_key(row): + self.row_info[row] = kws + else: + DictMerge(self.row_info[row], kws) + + # What's the index for the row we just put in? + def GetCurrentRowIndex(self): + return len(self.cells)-1 + + # What's the index for the col we just put in? + def GetCurrentCellIndex(self): + return len(self.cells[-1])-1 + + def ExtractCellInfo(self, info): + valid_mods = ['align', 'valign', 'nowrap', 'rowspan', 'colspan', + 'bgcolor'] + output = '' + + for (key, val) in info.items(): + if not key in valid_mods: + continue + if key == 'nowrap': + output = output + ' NOWRAP' + continue + else: + output = output + ' %s="%s"' % (key.upper(), val) + + return output + + def ExtractRowInfo(self, info): + valid_mods = ['align', 'valign', 'bgcolor'] + output = '' + + for (key, val) in info.items(): + if not key in valid_mods: + continue + output = output + ' %s="%s"' % (key.upper(), val) + + return output + + def ExtractTableInfo(self, info): + valid_mods = ['align', 'width', 'border', 'cellspacing', 'cellpadding', + 'bgcolor'] + + output = '' + + for (key, val) in info.items(): + if not key in valid_mods: + continue + if key == 'border' and val == None: + output = output + ' BORDER' + continue + else: + output = output + ' %s="%s"' % (key.upper(), val) + + return output + + def FormatCell(self, row, col, indent): + try: + my_info = self.cell_info[row][col] + except: + my_info = None + + output = '\n' + ' '*indent + '<td' + if my_info: + output = output + self.ExtractCellInfo(my_info) + item = self.cells[row][col] + item_format = HTMLFormatObject(item, indent+4) + output = '%s>%s</td>' % (output, item_format) + return output + + def FormatRow(self, row, indent): + try: + my_info = self.row_info[row] + except: + my_info = None + + output = '\n' + ' '*indent + '<tr' + if my_info: + output = output + self.ExtractRowInfo(my_info) + output = output + '>' + + for i in range(len(self.cells[row])): + output = output + self.FormatCell(row, i, indent + 2) + + output = output + '\n' + ' '*indent + '</tr>' + + return output + + def Format(self, indent=0): + output = '\n' + ' '*indent + '<table' + output = output + self.ExtractTableInfo(self.opts) + output = output + '>' + + for i in range(len(self.cells)): + output = output + self.FormatRow(i, indent + 2) + + output = output + '\n' + ' '*indent + '</table>\n' + + return output + + +class Link: + def __init__(self, href, text, target=None): + self.href = href + self.text = text + self.target = target + + def Format(self, indent=0): + texpr = "" + if self.target != None: + texpr = ' target="%s"' % self.target + return '<a href="%s"%s>%s</a>' % (HTMLFormatObject(self.href, indent), + texpr, + HTMLFormatObject(self.text, indent)) + +class FontSize: + """FontSize is being deprecated - use FontAttr(..., size="...") instead.""" + def __init__(self, size, *items): + self.items = list(items) + self.size = size + + def Format(self, indent=0): + output = '<font size="%s">' % self.size + for item in self.items: + output = output + HTMLFormatObject(item, indent) + output = output + '</font>' + return output + +class FontAttr: + """Present arbitrary font attributes.""" + def __init__(self, *items, **kw): + self.items = list(items) + self.attrs = kw + + def Format(self, indent=0): + seq = [] + for k, v in self.attrs.items(): + seq.append('%s="%s"' % (k, v)) + output = '<font %s>' % SPACE.join(seq) + for item in self.items: + output = output + HTMLFormatObject(item, indent) + output = output + '</font>' + return output + + +class Container: + def __init__(self, *items): + if not items: + self.items = [] + else: + self.items = items + + def AddItem(self, obj): + self.items.append(obj) + + def Format(self, indent=0): + output = [] + for item in self.items: + output.append(HTMLFormatObject(item, indent)) + return EMPTYSTRING.join(output) + + +class Label(Container): + align = 'right' + + def __init__(self, *items): + Container.__init__(self, *items) + + def Format(self, indent=0): + return ('<div align="%s">' % self.align) + \ + Container.Format(self, indent) + \ + '</div>' + + +# My own standard document template. YMMV. +# something more abstract would be more work to use... + +class Document(Container): + title = None + language = None + bgcolor = mm_cfg.WEB_BG_COLOR + suppress_head = 0 + + def set_language(self, lang=None): + self.language = lang + + def set_bgcolor(self, color): + self.bgcolor = color + + def SetTitle(self, title): + self.title = title + + def Format(self, indent=0, **kws): + charset = 'us-ascii' + if self.language and Utils.IsLanguage(self.language): + charset = Utils.GetCharSet(self.language) + output = ['Content-Type: text/html; charset=%s' % charset] + output.append('Cache-control: no-cache\n') + if not self.suppress_head: + kws.setdefault('bgcolor', self.bgcolor) + tab = ' ' * indent + output.extend([tab, + '<HTML>', + '<HEAD>' + ]) + if mm_cfg.IMAGE_LOGOS: + output.append('<LINK REL="SHORTCUT ICON" HREF="%s">' % + (mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON)) + # Hit all the bases + output.append('<META http-equiv="Content-Type" ' + 'content="text/html; charset=%s">' % charset) + if self.title: + output.append('%s<TITLE>%s</TITLE>' % (tab, self.title)) + # Add CSS to visually hide some labeling text but allow screen + # readers to read it. + output.append("""\ +<style type="text/css"> + div.hidden + {position:absolute; + left:-10000px; + top:auto; + width:1px; + height:1px; + overflow:hidden;} +</style> +""") + if mm_cfg.WEB_HEAD_ADD: + output.append(mm_cfg.WEB_HEAD_ADD) + output.append('%s</HEAD>' % tab) + quals = [] + # Default link colors + if mm_cfg.WEB_VLINK_COLOR: + kws.setdefault('vlink', mm_cfg.WEB_VLINK_COLOR) + if mm_cfg.WEB_ALINK_COLOR: + kws.setdefault('alink', mm_cfg.WEB_ALINK_COLOR) + if mm_cfg.WEB_LINK_COLOR: + kws.setdefault('link', mm_cfg.WEB_LINK_COLOR) + for k, v in kws.items(): + quals.append('%s="%s"' % (k, v)) + output.append('%s<BODY %s' % (tab, SPACE.join(quals))) + # Language direction + direction = Utils.GetDirection(self.language) + output.append('dir="%s">' % direction) + # Always do this... + output.append(Container.Format(self, indent)) + if not self.suppress_head: + output.append('%s</BODY>' % tab) + output.append('%s</HTML>' % tab) + return NL.join(output) + + def addError(self, errmsg, tag=None): + if tag is None: + tag = _('Error: ') + self.AddItem(Header(3, Bold(FontAttr( + _(tag), color=mm_cfg.WEB_ERROR_COLOR, size='+2')).Format() + + Italic(errmsg).Format())) + + +class HeadlessDocument(Document): + """Document without head section, for templates that provide their own.""" + suppress_head = 1 + + +class StdContainer(Container): + def Format(self, indent=0): + # If I don't start a new I ignore indent + output = '<%s>' % self.tag + output = output + Container.Format(self, indent) + output = '%s</%s>' % (output, self.tag) + return output + + +class QuotedContainer(Container): + def Format(self, indent=0): + # If I don't start a new I ignore indent + output = '<%s>%s</%s>' % ( + self.tag, + Utils.websafe(Container.Format(self, indent)), + self.tag) + return output + +class Header(StdContainer): + def __init__(self, num, *items): + self.items = items + self.tag = 'h%d' % num + +class Address(StdContainer): + tag = 'address' + +class Underline(StdContainer): + tag = 'u' + +class Bold(StdContainer): + tag = 'strong' + +class Italic(StdContainer): + tag = 'em' + +class Preformatted(QuotedContainer): + tag = 'pre' + +class Subscript(StdContainer): + tag = 'sub' + +class Superscript(StdContainer): + tag = 'sup' + +class Strikeout(StdContainer): + tag = 'strike' + +class Center(StdContainer): + tag = 'center' + +class Form(Container): + def __init__(self, action='', method='POST', encoding=None, + mlist=None, contexts=None, user=None, *items): + apply(Container.__init__, (self,) + items) + self.action = action + self.method = method + self.encoding = encoding + self.mlist = mlist + self.contexts = contexts + self.user = user + + def set_action(self, action): + self.action = action + + def Format(self, indent=0): + spaces = ' ' * indent + encoding = '' + if self.encoding: + encoding = 'enctype="%s"' % self.encoding + output = '\n%s<FORM action="%s" method="%s" %s>\n' % ( + spaces, self.action, self.method, encoding) + if self.mlist: + output = output + \ + '<input type="hidden" name="csrf_token" value="%s">\n' \ + % csrf_token(self.mlist, self.contexts, self.user) + output = output + Container.Format(self, indent+2) + output = '%s\n%s</FORM>\n' % (output, spaces) + return output + + +class InputObj: + def __init__(self, name, ty, value, checked, **kws): + self.name = name + self.type = ty + self.value = value + self.checked = checked + self.kws = kws + + def Format(self, indent=0): + charset = get_translation().charset() or 'us-ascii' + output = ['<INPUT name="%s" type="%s" value="%s"' % + (self.name, self.type, self.value)] + for item in self.kws.items(): + output.append('%s="%s"' % item) + if self.checked: + output.append('CHECKED') + output.append('>') + ret = SPACE.join(output) + if self.type == 'TEXT' and isinstance(ret, unicode): + ret = ret.encode(charset, 'xmlcharrefreplace') + return ret + + +class SubmitButton(InputObj): + def __init__(self, name, button_text): + InputObj.__init__(self, name, "SUBMIT", button_text, checked=0) + +class PasswordBox(InputObj): + def __init__(self, name, value='', size=mm_cfg.TEXTFIELDWIDTH): + InputObj.__init__(self, name, "PASSWORD", value, checked=0, size=size) + +class TextBox(InputObj): + def __init__(self, name, value='', size=mm_cfg.TEXTFIELDWIDTH): + if isinstance(value, str): + safevalue = Utils.websafe(value) + else: + safevalue = value + InputObj.__init__(self, name, "TEXT", safevalue, checked=0, size=size) + +class Hidden(InputObj): + def __init__(self, name, value=''): + InputObj.__init__(self, name, 'HIDDEN', value, checked=0) + +class TextArea: + def __init__(self, name, text='', rows=None, cols=None, wrap='soft', + readonly=0): + if isinstance(text, str): + # Double escape HTML entities in non-readonly areas. + doubleescape = not readonly + safetext = Utils.websafe(text, doubleescape) + else: + safetext = text + self.name = name + self.text = safetext + self.rows = rows + self.cols = cols + self.wrap = wrap + self.readonly = readonly + + def Format(self, indent=0): + charset = get_translation().charset() or 'us-ascii' + output = '<TEXTAREA NAME=%s' % self.name + if self.rows: + output += ' ROWS=%s' % self.rows + if self.cols: + output += ' COLS=%s' % self.cols + if self.wrap: + output += ' WRAP=%s' % self.wrap + if self.readonly: + output += ' READONLY' + output += '>%s</TEXTAREA>' % self.text + if isinstance(output, unicode): + output = output.encode(charset, 'xmlcharrefreplace') + return output + +class FileUpload(InputObj): + def __init__(self, name, rows=None, cols=None, **kws): + apply(InputObj.__init__, (self, name, 'FILE', '', 0), kws) + +class RadioButton(InputObj): + def __init__(self, name, value, checked=0, **kws): + apply(InputObj.__init__, (self, name, 'RADIO', value, checked), kws) + +class CheckBox(InputObj): + def __init__(self, name, value, checked=0, **kws): + apply(InputObj.__init__, (self, name, "CHECKBOX", value, checked), kws) + +class VerticalSpacer: + def __init__(self, size=10): + self.size = size + def Format(self, indent=0): + output = '<spacer type="vertical" height="%d">' % self.size + return output + +class WidgetArray: + Widget = None + + def __init__(self, name, button_names, checked, horizontal, values): + self.name = name + self.button_names = button_names + self.checked = checked + self.horizontal = horizontal + self.values = values + assert len(values) == len(button_names) + # Don't assert `checked' because for RadioButtons it is a scalar while + # for CheckedBoxes it is a vector. Subclasses will assert length. + + def ischecked(self, i): + raise NotImplemented + + def Format(self, indent=0): + t = Table(cellspacing=5) + items = [] + for i, name, value in zip(range(len(self.button_names)), + self.button_names, + self.values): + ischecked = (self.ischecked(i)) + item = ('<label>' + + self.Widget(self.name, value, ischecked).Format() + + name + '</label>') + items.append(item) + if not self.horizontal: + t.AddRow(items) + items = [] + if self.horizontal: + t.AddRow(items) + return t.Format(indent) + +class RadioButtonArray(WidgetArray): + Widget = RadioButton + + def __init__(self, name, button_names, checked=None, horizontal=1, + values=None): + if values is None: + values = range(len(button_names)) + # BAW: assert checked is a scalar... + WidgetArray.__init__(self, name, button_names, checked, horizontal, + values) + + def ischecked(self, i): + return self.checked == i + +class CheckBoxArray(WidgetArray): + Widget = CheckBox + + def __init__(self, name, button_names, checked=None, horizontal=0, + values=None): + if checked is None: + checked = [0] * len(button_names) + else: + assert len(checked) == len(button_names) + if values is None: + values = range(len(button_names)) + WidgetArray.__init__(self, name, button_names, checked, horizontal, + values) + + def ischecked(self, i): + return self.checked[i] + +class UnorderedList(Container): + def Format(self, indent=0): + spaces = ' ' * indent + output = '\n%s<ul>\n' % spaces + for item in self.items: + output = output + '%s<li>%s\n' % \ + (spaces, HTMLFormatObject(item, indent + 2)) + output = output + '%s</ul>\n' % spaces + return output + +class OrderedList(Container): + def Format(self, indent=0): + spaces = ' ' * indent + output = '\n%s<ol>\n' % spaces + for item in self.items: + output = output + '%s<li>%s\n' % \ + (spaces, HTMLFormatObject(item, indent + 2)) + output = output + '%s</ol>\n' % spaces + return output + +class DefinitionList(Container): + def Format(self, indent=0): + spaces = ' ' * indent + output = '\n%s<dl>\n' % spaces + for dt, dd in self.items: + output = output + '%s<dt>%s\n<dd>%s\n' % \ + (spaces, HTMLFormatObject(dt, indent+2), + HTMLFormatObject(dd, indent+2)) + output = output + '%s</dl>\n' % spaces + return output + + + +# Logo constants +# +# These are the URLs which the image logos link to. The Mailman home page now +# points at the gnu.org site instead of the www.list.org mirror. +# +from mm_cfg import MAILMAN_URL +PYTHON_URL = 'http://www.python.org/' +GNU_URL = 'http://www.gnu.org/' +CRANS_URL = 'http://www.crans.org/' + +# The names of the image logo files. These are concatentated onto +# mm_cfg.IMAGE_LOGOS (not urljoined). +DELIVERED_BY = 'mailman.jpg' +PYTHON_POWERED = 'PythonPowered.png' +GNU_HEAD = 'gnu-head-tiny.jpg' +CRANS_LOGO = 'crans.png' + + +def MailmanLogo(): + t = Table(border=0, width='100%') + + version = mm_cfg.VERSION + mmlink = _("Delivered by Mailman") + pylink = _("Python Powered") + gnulink = _("GNU's Not Unix") + cranslink = _("CRANS") + if mm_cfg.SITE_LINK: + sitelink = mm_cfg.SITE_TEXT + + if mm_cfg.IMAGE_LOGOS: + def logo(file, alt, base=mm_cfg.IMAGE_LOGOS): + return '<img src="%s" alt="%s" border="0" />' % \ + (base + file, alt) + mmlink = logo(DELIVERED_BY, mmlink) + pylink = logo(PYTHON_POWERED, pylink) + gnulink = logo(GNU_HEAD, gnulink) + cranslink = logo(CRANS_LOGO, cranslink) + if mm_cfg.SITE_LINK: + sitelink = logo(mm_cfg.SITE_LOGO, sitelink, "") + + mmlink = Link(MAILMAN_URL, mmlink + _('<br>version %(version)s')) + pylink = Link(PYTHON_URL, pylink) + gnulink = Link(GNU_URL, gnulink) + cranslink = Link(CRANS_URL, cranslink) + links = [mmlink, pylink, gnulink, cranslink] + if mm_cfg.SITE_LINK: + if mm_cfg.SITE_URL: + sitelink = Link(mm_cfg.SITE_URL, sitelink) + links.append(sitelink) + t.AddRow(links) + return t + + +class SelectOptions: + def __init__(self, varname, values, legend, + selected=0, size=1, multiple=None): + self.varname = varname + self.values = values + self.legend = legend + self.size = size + self.multiple = multiple + # we convert any type to tuple, commas are needed + if not multiple: + if type(selected) == types.IntType: + self.selected = (selected,) + elif type(selected) == types.TupleType: + self.selected = (selected[0],) + elif type(selected) == types.ListType: + self.selected = (selected[0],) + else: + self.selected = (0,) + + def Format(self, indent=0): + spaces = " " * indent + items = min( len(self.values), len(self.legend) ) + + # jcrey: If there is no argument, we return nothing to avoid errors + if items == 0: + return "" + + text = "\n" + spaces + "<Select name=\"%s\"" % self.varname + if self.size > 1: + text = text + " size=%d" % self.size + if self.multiple: + text = text + " multiple" + text = text + ">\n" + + for i in range(items): + if i in self.selected: + checked = " Selected" + else: + checked = "" + + opt = " <option value=\"%s\"%s> %s </option>" % ( + self.values[i], checked, self.legend[i]) + text = text + spaces + opt + "\n" + + return text + spaces + '</Select>' diff --git a/roles/nginx-mailman/handlers/main.yml b/roles/nginx-mailman/handlers/main.yml new file mode 100644 index 00000000..6dfcdd76 --- /dev/null +++ b/roles/nginx-mailman/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Reload nginx + systemd: + name: nginx + state: reloaded diff --git a/roles/nginx-mailman/tasks/main.yml b/roles/nginx-mailman/tasks/main.yml new file mode 100644 index 00000000..e2036b6b --- /dev/null +++ b/roles/nginx-mailman/tasks/main.yml @@ -0,0 +1,43 @@ +--- +- name: Install NGINX + apt: + update_cache: true + name: + - nginx + register: apt_result + retries: 3 + until: apt_result is succeeded + +- name: Copy configuration files + template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + loop: + - src: nginx/sites-available/mailman.j2 + dest: /etc/nginx/sites-available/mailman + - src: nginx/mailman_passwd.j2 + dest: /etc/nginx/mailman_passwd + - src: nginx/snippets/fastcgi-mailman.conf.j2 + dest: /etc/nginx/snippets/fastcgi-mailman.conf + - src: nginx/snippets/options-ssl.conf.j2 + dest: /etc/nginx/snippets/options-ssl.conf + - src: var/www/robots.txt.j2 + dest: /var/www/robots.txt + - src: var/www/custom_401.html.j2 + dest: /var/www/custom_401.html + notify: Reload nginx + +- name: Enable mailman + file: + src: /etc/nginx/sites-available/mailman + dest: /etc/nginx/sites-enabled/mailman + state: link + force: true + when: not ansible_check_mode + notify: Reload nginx + +- name: Indicate role in motd + template: + src: update-motd.d/05-service.j2 + dest: /etc/update-motd.d/05-nginx-mailman + mode: 0755 diff --git a/roles/nginx-mailman/templates/nginx/mailman_passwd.j2 b/roles/nginx-mailman/templates/nginx/mailman_passwd.j2 new file mode 100644 index 00000000..741d52d9 --- /dev/null +++ b/roles/nginx-mailman/templates/nginx/mailman_passwd.j2 @@ -0,0 +1,2 @@ +{{ ansible_header | comment }} +Stop:$apr1$NXaV5H7Q$J3ora3Jo5h775Y1nm93PN1 diff --git a/roles/nginx-mailman/templates/nginx/sites-available/mailman.j2 b/roles/nginx-mailman/templates/nginx/sites-available/mailman.j2 new file mode 100644 index 00000000..ba13c111 --- /dev/null +++ b/roles/nginx-mailman/templates/nginx/sites-available/mailman.j2 @@ -0,0 +1,94 @@ +{{ ansible_header | comment }} +server { + listen 80 default; + listen [::]:80 default; + + server_name _; + + location / { + return 302 https://{{ mailman.default_host }}$request_uri; + } +} + +# Redirect everybody to mailing lists +server { + listen 443 default_server ssl; + listen [::]:443 default_server ssl; + server_name _; + + include "/etc/nginx/snippets/options-ssl.conf"; + + location / { + return 302 https://{{ mailman.default_host }}$request_uri; + } +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name {{ mailman.default_host }}; + + include "/etc/nginx/snippets/options-ssl.conf"; + + root /usr/lib/cgi-bin/mailman/; + index index.htm index.html; + + location /error/ { + internal; + alias /var/www/; + } + + location /create { + default_type text/html; + alias /etc/mailman/create.html; + } + + location ~ ^/$ { + return 302 https://{{ mailman.default_host }}/listinfo; + } + + location / { + include "/etc/nginx/snippets/fastcgi-mailman.conf"; + } + + location ~ ^/listinfo { + satisfy any; + include "/etc/nginx/snippets/fastcgi-mailman.conf"; + + {% for net in mynetworks -%} + allow {{ net }}; + {% endfor -%} + deny all; + + auth_basic {{ mailman.auth_basic }} + auth_basic_user_file /etc/nginx/mailman_passwd; + + error_page 401 /error/custom_401.html; + } + + location ~ ^/admin { + satisfy any; + + include "/etc/nginx/snippets/fastcgi-mailman.conf"; + + {% for net in mynetworks -%} + allow {{ net }}; + {% endfor -%} + deny all; + + auth_basic {{ mailman.auth_basic }} + auth_basic_user_file /etc/nginx/mailman_passwd; + error_page 401 /error/custom_401.html; + } + + + location /images/mailman { alias /usr/share/images/mailman;} + + location /robots.txt { alias /var/www/robots.txt;} + + location /archives { + alias /var/lib/mailman/archives/public; + autoindex on; + } + +} diff --git a/roles/nginx-mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2 b/roles/nginx-mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2 new file mode 100644 index 00000000..d3215c7f --- /dev/null +++ b/roles/nginx-mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2 @@ -0,0 +1,18 @@ +{{ ansible_header | comment }} + +# regex to split $uri to $fastcgi_script_name and $fastcgi_path +fastcgi_split_path_info (^/[^/]*)(.*)$; + +# check that the PHP script exists before passing it +try_files $fastcgi_script_name =404; + +# Bypass the fact that try_files resets $fastcgi_path_info +# see: http://trac.nginx.org/nginx/ticket/321 +set $path_info $fastcgi_path_info; +fastcgi_param PATH_INFO $path_info; + +# Let NGINX handle errors +fastcgi_intercept_errors on; + +include /etc/nginx/fastcgi.conf; +fastcgi_pass unix:/var/run/fcgiwrap.socket; diff --git a/roles/nginx-mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2~ b/roles/nginx-mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2~ new file mode 100644 index 00000000..3ce2f923 --- /dev/null +++ b/roles/nginx-mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2~ @@ -0,0 +1,18 @@ +{{ ansible_header | comment }} + +# regex to split $uri to $fastcgi_script_name and $fastcgi_path +fastcgi_split_path_info (^/[^/]*)(.*)$; + +# check that the PHP script exists before passing it +try_files $fastcgi_script_name =404; + +# Bypass the fact that try_files resets $fastcgi_path_info +# see: http://trac.nginx.org/nginx/ticket/321 +set $path_info $fastcgi_path_info; +fastcgi_param PATH_INFO $path_info; + +# Let NGINX handle errors +fastcgi_intercept_errors on; + +include /etc/nginx/fastcgi.conf; +fastcgi_pass unix:/var/run/fcgiwrap.socket; \ No newline at end of file diff --git a/roles/nginx-mailman/templates/nginx/snippets/options-ssl.conf.j2 b/roles/nginx-mailman/templates/nginx/snippets/options-ssl.conf.j2 new file mode 100644 index 00000000..79d75395 --- /dev/null +++ b/roles/nginx-mailman/templates/nginx/snippets/options-ssl.conf.j2 @@ -0,0 +1,17 @@ +{{ ansible_header | comment }} + +ssl_certificate {{ nginx.ssl.cert }}; +ssl_certificate_key {{ nginx.ssl.key }}; +ssl_session_timeout 1d; +ssl_session_cache shared:MozSSL:10m; +ssl_session_tickets off; +ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; +ssl_protocols TLSv1.2 TLSv1.3; + +ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; +ssl_prefer_server_ciphers off; + +# Enable OCSP Stapling, point to certificate chain +ssl_stapling on; +ssl_stapling_verify on; +ssl_trusted_certificate {{ nginx.ssl.trusted_cert }}; diff --git a/roles/nginx-mailman/templates/update-motd.d/05-service.j2 b/roles/nginx-mailman/templates/update-motd.d/05-service.j2 new file mode 100755 index 00000000..82373d0b --- /dev/null +++ b/roles/nginx-mailman/templates/update-motd.d/05-service.j2 @@ -0,0 +1,3 @@ +#!/usr/bin/tail +14 +{{ ansible_header | comment }} +[0m> [38;5;82mNGINX[0m a été déployé sur cette machine. Voir [38;5;6m/etc/nginx/[0m. diff --git a/roles/nginx-mailman/templates/var/www/custom_401.html.j2 b/roles/nginx-mailman/templates/var/www/custom_401.html.j2 new file mode 100644 index 00000000..93fc38ac --- /dev/null +++ b/roles/nginx-mailman/templates/var/www/custom_401.html.j2 @@ -0,0 +1,18 @@ +{{ ansible_header | comment('xml') }} + +<html> +<head> + <title>Accès refusé</title> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> +</head> +<body> + <h1>Accès refusé</h1> + <p> + Pour éviter le scan des adresses de diffusions par un robot, cette page demande un identifiant et mot de passe. + </p> + <ul> + <li>Identifiant : <em>Stop</em></li> + <li>Mot de passe : <em>Spam</em></li> + </ul> +</body> +</html> diff --git a/roles/nginx-mailman/templates/var/www/robots.txt.j2 b/roles/nginx-mailman/templates/var/www/robots.txt.j2 new file mode 100644 index 00000000..3fbaed74 --- /dev/null +++ b/roles/nginx-mailman/templates/var/www/robots.txt.j2 @@ -0,0 +1,4 @@ +{{ ansible_header | comment }} + +User-agent: * +Disallow: /