ignore more SSH errors master
authorRalf Jung <post@ralfj.de>
Sun, 14 Jan 2024 09:13:21 +0000 (10:13 +0100)
committerRalf Jung <post@ralfj.de>
Sun, 14 Jan 2024 09:13:21 +0000 (10:13 +0100)
42 files changed:
.gitignore
ansible.cfg
do-update [deleted file]
host_vars/template.yml
roles/apache/files/log-anon
roles/apache/tasks/main.yml
roles/apache/templates/000-default.conf
roles/apache/templates/caching.conf [new file with mode: 0644]
roles/apache/templates/security.conf
roles/apache/templates/ssl.conf
roles/base/files/needrestart.conf [new file with mode: 0644]
roles/base/tasks/main.yml
roles/email/files/mailman-check
roles/email/files/mailman-patched/Cgi/listinfo.py
roles/email/files/mailman-patched/Cgi/subscribe.py
roles/email/tasks/dovecot.yml
roles/email/tasks/mailman.yml
roles/email/tasks/opendkim.yml
roles/email/tasks/postfix.yml
roles/email/templates/changepw
roles/email/templates/dovecot/conf.d/10-auth.conf
roles/email/templates/dovecot/conf.d/10-mail.conf
roles/email/templates/dovecot/conf.d/10-ssl.conf
roles/email/templates/dovecot/conf.d/15-lda.conf
roles/email/templates/dovecot/dovecot-sql.conf.ext
roles/email/templates/dovecot/quota-warning.sh
roles/email/templates/main.cf
roles/email/templates/mm_cfg.py
roles/email/templates/newmail/newmail
roles/email/templates/opendkim.conf
roles/email/templates/opendkim.env
roles/email/templates/postscreen_access.cidr [new file with mode: 0644]
roles/email/templates/sender_transport_map [new file with mode: 0644]
roles/etherpad/tasks/main.yml
roles/etherpad/templates/settings.json
roles/journalwatch/templates/patterns
roles/prosody/handlers/main.yml
roles/prosody/tasks/main.yml
roles/prosody/templates/prosody.cfg.lua
roles/prosody/templates/turnserver.conf [new file with mode: 0644]
roles/unbound/tasks/main.yml
roles/unbound/templates/fix-dns

index 2bf7c68f3e21894d40a3836cbb0b29763a48abe1..545a8385791e24e37e1a045418d5c40d4a5f59d1 100644 (file)
@@ -1,4 +1,3 @@
 *.retry
 hosts
 host_vars
-hosts
index d6bbf37119942f4f474a1c69e7e3ec2cb06c0b7e..dfba7c46c31c729828060ba98eb35025255222bf 100644 (file)
@@ -1,7 +1,7 @@
 [defaults]
 inventory = hosts
 stdout_callback = debug
-hash_behaviour = merge
+interpreter_python = auto_silent
 
 [diff]
 always = True
diff --git a/do-update b/do-update
deleted file mode 100755 (executable)
index e9edd48..0000000
--- a/do-update
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-cd "$(dirname "$(readlink -e "$0")")"
-for HOST in "$@"; do
-  echo "Playing on $HOST"
-  ansible-playbook upgrade.yml -l "$HOST"
-  read -p "Do you want me to reboot $HOST? (y/N) " ANSWER
-  if [[ "$ANSWER" == y* ]]; then
-    ansible "$HOST" -a "shutdown -r +1"
-  fi
-  echo
-done
index 142e408e05fb8246567e8687dec221b77762a7c4..941796b40256156fc178ce1be80ab22e979661e3 100644 (file)
@@ -51,6 +51,7 @@ postfix:
   # optional: install and configure mailman
   mailman:
     default_host: lists.example.org
+    smtp_outgoing: smtp2
     default_lang: en # if you change this, also run "dpkg-reconfigure mailman" to enable more languages
     domains: # the file /etc/postfix/virtual_alias_map can be used to configure aliases
     - lists.example.org
@@ -91,5 +92,6 @@ bind:
 etherpad:
   domain: pad.example.org
   default_text: "Welcome to Etherpad!\\n\\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\\n\\nGet involved with Etherpad at http:\/\/etherpad.org\\n"
+  # Also see <https://github.com/ether/etherpad-lite/issues/3959> for some database configuration advice!
   mysql_password: $RANDOM
   admin_password: $RANDOM
index 32b6c1e58b61d417495b463b82a37bec71a2fd64..0b8466832f1b8e21494fbe2b37c600a5e1061f06 100644 (file)
@@ -1,9 +1,9 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 import sys, re
 from netaddr import IPAddress, AddrFormatError
 
 if len(sys.argv) != 2:
-       print >>sys.stderr, "Usage: %s filename" % sys.argv[0]
+       print("Usage: %s filename" % sys.argv[0], file=sys.stderr)
        sys.exit(1)
 
 log = open(sys.argv[1], "a")
index fee26efff5be7b765f1e34d53fd537d57f45ade8..8d1382c179fc857faaa5d0d36fd6acb615f6acac 100644 (file)
@@ -1,5 +1,5 @@
 - name: install apache
-  apt: name=apache2,python-netaddr state=latest
+  apt: name=apache2,python3-netaddr state=latest
 - name: enable apache
   service: name=apache2 enabled=yes
 # apache config
@@ -35,6 +35,7 @@
   - php5.conf
   - security.conf
   - defaults.conf
+  - caching.conf
   notify: apache
 - name: enable config files
   command: a2enconf {{ item }}
@@ -44,6 +45,7 @@
   - ssl
   - security
   - defaults
+  - caching
   notify: apache
 - name: disable config files
   command: a2disconf {{ item }}
index 0d68ad61f1da6c9b397db3f119599507670aafe7..5e02d0b3af4c02555d0659c4e21039b92fa9063a 100644 (file)
@@ -1,14 +1,8 @@
-# redirect all undefined virtual hosts to {{ apache.default_host }}
+# Default host: serve nothing.
 <VirtualHost *:80>
-{% if 'letsencrypt' in group_names %}
-    Redirect temp / https://{{ apache.default_host }}/
-{% else %}
-    Redirect temp / http://{{ apache.default_host }}/
-{% endif %}
 </VirtualHost>
 {% if 'letsencrypt' in group_names %}
 <VirtualHost *:443>
     Use SSL letsencrypt/live
-    Redirect temp / https://{{ apache.default_host }}/
 </VirtualHost>
 {% endif %}
diff --git a/roles/apache/templates/caching.conf b/roles/apache/templates/caching.conf
new file mode 100644 (file)
index 0000000..7880aec
--- /dev/null
@@ -0,0 +1,7 @@
+<FilesMatch "\.(gif|jpe?g|png|ico|js|css)$">
+    Header set Cache-Control "max-age=86400, public"
+</FilesMatch>
+
+<FilesMatch "\.html?$">
+    Header set Cache-Control "no-cache"
+</FilesMatch>
index adefdd8a77ba8de51ba56d49fed068c584abcc6f..9334c367c889e83453b38fb12c0902e4206e96a0 100644 (file)
@@ -59,7 +59,7 @@ Header set X-Content-Type-Options: "nosniff"
 # site as frames. This defends against clickjacking attacks.
 # Requires mod_headers to be enabled.
 #
-Header set X-Frame-Options: "sameorigin"
+Header add Content-Security-Policy "frame-ancestors 'self'"
 
 
 # vim: syntax=apache ts=4 sw=4 sts=4 sr noet
index bb807463db16fb510516f38a22d29ba4787b69ed..5fa87c39c424e28bb2f9f17fce0a06687c1072b5 100644 (file)
@@ -17,7 +17,7 @@
     Header unset Strict-Transport-Security
     Header set Strict-Transport-Security "max-age=864000"
     # Make sure we load everything via HTTPS
-    Header set Content-Security-Policy "upgrade-insecure-requests"
+    Header add Content-Security-Policy "upgrade-insecure-requests"
 
     #########################################################
     # SSL configuration below ###############################
     SSLCipherSuite 'kEECDH+AESGCM:kEDH+AESGCM:kEECDH:kEDH:AESGCM:ALL:!3DES:!EXPORT:!LOW:!MEDIUM:!aNULL:!eNULL'
     SSLHonorCipherOrder     on
 
-    #   Certificate, DH parameters and key
-    SSLCertificateFile    /etc/ssl/mycerts/$cert.crt+dh
+    # DH parameters
+    SSLOpenSSLConfCmd DHParameters "/etc/ssl/dh2048.pem"
+
+    #   Certificate and key
+    SSLCertificateFile    /etc/ssl/mycerts/$cert.crt
     SSLCertificateKeyFile /etc/ssl/private/$cert.key
 
     #   Server Certificate Chain:
@@ -44,7 +47,7 @@
     #   the referenced file can be the same as SSLCertificateFile
     #   when the CA certificates are directly appended to the server
     #   certificate for convinience.
-    SSLCertificateChainFile /etc/ssl/mycerts/$cert.chain
+    SSLCertificateChainFile /etc/ssl/mycerts/$cert.crt
 
     #   Certificate Authority (CA):
     #   Set the CA certificate verification path where to find CA
diff --git a/roles/base/files/needrestart.conf b/roles/base/files/needrestart.conf
new file mode 100644 (file)
index 0000000..b2e1f1a
--- /dev/null
@@ -0,0 +1,2 @@
+# Restart when unattended
+$nrconf{restart} = 'a'
index 0757dc7c6c6e5278fdce77e4666968dc336c961c..4b4e4cd42117cf22160cfc9be3e967de2a60c7ba 100644 (file)
@@ -13,9 +13,7 @@
 - name: get rid of packages we do not want
   apt: name=exim4-base,rpcbind,procmail,fetchmail state=absent autoremove=yes
 - name: install some basic tools
-  apt: name=nano,aptitude,rsync,git,mercurial,curl,apt-transport-https,psmisc,dnsutils,tree,htop,acl,libpam-systemd state=latest
-- name: install needrestart (from backports)
-  apt: name=needrestart state=latest default_release={{ansible_distribution_release}}-backports
+  apt: name=nano,aptitude,rsync,git,mercurial,curl,apt-transport-https,psmisc,dnsutils,tree,htop,acl,libpam-systemd,needrestart,reboot-notifier,debian-security-support state=latest
 # configuration
 - name: configure root shell
   copy:
@@ -31,3 +29,7 @@
     dest: /etc/systemd/timesyncd.conf
     src: files/timesyncd.conf
   notify: timesyncd
+- name: configure needrestart
+  copy:
+    dest: /etc/needrestart/conf.d/restart.conf
+    src: files/needrestart.conf
index 0a5d9deba46979faf8dc035ecdf27d3e2c939b17..09d2d0026228e6ffb001d8c90e0f9dd1830b9de3 100755 (executable)
@@ -10,5 +10,11 @@ if not ((mlist.dmarc_moderation_action in (1, 2) and mlist.dmarc_quarantine_mode
     print "List",mlist.real_name,"by",(', '.join(mlist.owner)),"is not configured to deal with DMARC."
 if mlist.reply_goes_to_list != 0 and not mlist.first_strip_reply_to:
     print "List",mlist.real_name,"by",(', '.join(mlist.owner)),"provides an inconsistent Reply-To treatment."
+if mlist.generic_nonmember_action == 2:
+    print "List",mlist.real_name,"by",(', '.join(mlist.owner)),"is prone to backscatter spam due to reject notifications"
+if mlist.generic_nonmember_action == 1 and mlist.respond_to_post_requests:
+    print "List",mlist.real_name,"by",(', '.join(mlist.owner)),"is prone to backscatter spam due to hold notifications"
+if mlist.bounce_unrecognized_goes_to_list_owner:
+    print "List",mlist.real_name,"by",(', '.join(mlist.owner)),"may spam the owner with unrecognized bounce notifications"
 EOF
 done
index b46bab149c87d83f2b81bb7d2e713dd3b1d70398..0e38d03c71143b48e08b72a497b6e11a711d89fb 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2016 by the Free Software Foundation, Inc.
+# 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
@@ -38,8 +38,7 @@ _ = i18n._
 i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
 
 
-
-
+\f
 def main():
     parts = Utils.GetPathPieces()
     if not parts:
@@ -61,7 +60,7 @@ def main():
     # See if the user want to see this page in other language
     cgidata = cgi.FieldStorage()
     try:
-        language = cgidata.getvalue('language')
+        language = cgidata.getfirst('language')
     except TypeError:
         # Someone crafted a POST with a bad Content-Type:.
         doc = Document()
@@ -79,8 +78,7 @@ def main():
     list_listinfo(mlist, language)
 
 
-
-
+\f
 def listinfo_overview(msg=''):
     # Present the general listinfo overview
     hostname = Utils.get_domain()
@@ -117,7 +115,7 @@ def listinfo_overview(msg=''):
             else:
                 advertised.append((mlist.GetScriptURL('listinfo'),
                                    mlist.real_name,
-                                   Utils.websafe(mlist.description)))
+                                   Utils.websafe(mlist.GetDescription())))
     if msg:
         greeting = FontAttr(msg, color="ff5060", size="+1")
     else:
@@ -175,8 +173,7 @@ def listinfo_overview(msg=''):
     print doc.Format()
 
 
-
-
+\f
 def list_listinfo(mlist, lang):
     # Generate list specific listinfo
     doc = HeadlessDocument()
@@ -227,10 +224,10 @@ def list_listinfo(mlist, lang):
         # 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() +
+                % (now, captcha_idx, Utils.sha_new(mm_cfg.SUBSCRIBE_FORM_SECRET + ":" +
+                          now + ":" +
+                          captcha_idx + ":" +
+                          mlist.internal_name() + ":" +
                           remote
                           ).hexdigest()
                     )
@@ -253,13 +250,25 @@ def list_listinfo(mlist, lang):
     replacements['<mm-displang-box>'] = displang
     replacements['<mm-lang-form-start>'] = mlist.FormatFormStart('listinfo')
     replacements['<mm-fullname-box>'] = mlist.FormatBox('fullname', size=30)
+    # If reCAPTCHA is enabled, display its user interface
+    if mm_cfg.RECAPTCHA_SITE_KEY:
+        noscript = _('This form requires JavaScript.')
+        replacements['<mm-recaptcha-ui>'] = (
+            """<tr><td>&nbsp;</td><td>
+            <noscript>%s</noscript>
+            <script src="https://www.google.com/recaptcha/api.js?hl=%s">
+            </script>
+            <div class="g-recaptcha" data-sitekey="%s"></div>
+            </td></tr>"""
+            % (noscript, lang, mm_cfg.RECAPTCHA_SITE_KEY))
+    else:
+        replacements['<mm-recaptcha-ui>'] = ''
 
     # Do the expansion.
     doc.AddItem(mlist.ParseTags('listinfo.html', replacements, lang))
     print doc.Format()
 
 
-
-
+\f
 if __name__ == "__main__":
     main()
index 153286dfb13865e38208dbf9232139cfde35dc3e..ea4ef9499014d17cdd6df4b6cb520b494617764b 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2016 by the Free Software Foundation, Inc.
+# 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
@@ -22,6 +22,9 @@ import os
 import cgi
 import time
 import signal
+import urllib
+import urllib2
+import json
 
 from Mailman import mm_cfg
 from Mailman import Utils
@@ -36,14 +39,14 @@ from Mailman.Logging.Syslog import syslog
 
 SLASH = '/'
 ERRORSEP = '\n\n<p>'
+COMMASPACE = ', '
 
 # Set up i18n
 _ = i18n._
 i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
 
 
-
-
+\f
 def main():
     doc = Document()
     doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
@@ -73,7 +76,7 @@ def main():
     # for the results.  If not, use the list's preferred language.
     cgidata = cgi.FieldStorage()
     try:
-        language = cgidata.getvalue('language', '')
+        language = cgidata.getfirst('language', '')
     except TypeError:
         # Someone crafted a POST with a bad Content-Type:.
         doc.AddItem(Header(2, _("Error")))
@@ -114,19 +117,18 @@ def main():
         mlist.Unlock()
 
 
-
-
+\f
 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()
+    email = cgidata.getfirst('email', '').strip()
     if not email:
         results.append(_('You must supply a valid email address.'))
 
-    fullname = cgidata.getvalue('fullname', '')
+    fullname = cgidata.getfirst('fullname', '')
     # Canonicalize the full name
     fullname = Utils.canonstr(fullname, lang)
     # Who was doing the subscribing?
@@ -134,6 +136,26 @@ def process_form(mlist, doc, cgidata, lang):
              os.environ.get('HTTP_X_FORWARDED_FOR',
              os.environ.get('REMOTE_ADDR',
                             'unidentified origin')))
+
+    # Check reCAPTCHA submission, if enabled
+    if mm_cfg.RECAPTCHA_SECRET_KEY:
+        request = urllib2.Request(
+            url = 'https://www.google.com/recaptcha/api/siteverify',
+            data = urllib.urlencode({
+                'secret': mm_cfg.RECAPTCHA_SECRET_KEY,
+                'response': cgidata.getvalue('g-recaptcha-response', ''),
+                'remoteip': remote}))
+        try:
+            httpresp = urllib2.urlopen(request)
+            captcha_response = json.load(httpresp)
+            httpresp.close()
+            if not captcha_response['success']:
+                e_codes = COMMASPACE.join(captcha_response['error-codes'])
+                results.append(_('reCAPTCHA validation failed: %(e_codes)s'))
+        except urllib2.URLError, e:
+            e_reason = e.reason
+            results.append(_('reCAPTCHA could not be validated: %(e_reason)s'))
+
     # Are we checking the hidden data?
     if mm_cfg.SUBSCRIBE_FORM_SECRET:
         now = int(time.time())
@@ -147,15 +169,15 @@ def process_form(mlist, doc, cgidata, lang):
             #        for our hash so it doesn't matter.
             remote1 = remote.rsplit(':', 1)[0]
         try:
-            ftime, fcaptcha_idx, fhash = cgidata.getvalue('sub_form_token', '').split(':')
+            ftime, fcaptcha_idx, fhash = cgidata.getfirst('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() +
+        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.'))
@@ -169,17 +191,17 @@ def process_form(mlist, doc, cgidata, lang):
             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.'))
+    # 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()
+    password = cgidata.getfirst('pw', '').strip()
+    confirmed = cgidata.getfirst('pw-conf', '').strip()
 
     if not password and not confirmed:
         password = Utils.MakeRandomPassword()
@@ -189,11 +211,11 @@ def process_form(mlist, doc, cgidata, lang):
         results.append(_('Your passwords did not match.'))
 
     # Get the digest option for the subscription.
-    digestflag = cgidata.getvalue('digest')
+    digestflag = cgidata.getfirst('digest')
     if digestflag:
         try:
             digest = int(digestflag)
-        except ValueError:
+        except (TypeError, ValueError):
             digest = 0
     else:
         digest = mlist.digest_is_default
@@ -318,8 +340,7 @@ You have been successfully subscribed to the %(realname)s mailing list.""")
     print_results(mlist, results, doc, lang)
 
 
-
-
+\f
 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
index 6e1b12a3a7c2236426afd9fadf3575ef2afd1c88..1766ede7caa9f4fefbe9b6167a0c69e5d5f3db6a 100644 (file)
@@ -1,5 +1,5 @@
 - name: install dovecot
-  apt: name=dovecot-imapd,dovecot-lmtpd,dovecot-mysql,dovecot-pop3d,dovecot-sieve,dovecot-managesieved state=latest
+  apt: name=dovecot-imapd,dovecot-lmtpd,dovecot-mysql,dovecot-pop3d,dovecot-sieve,dovecot-managesieved,python3-mysqldb state=latest
 - name: enable dovecot
   service: name=dovecot enabled=yes
 # configuration
@@ -22,7 +22,7 @@
   template:
     dest: /etc/dovecot/{{ item }}
     src: templates/dovecot/{{ item }}
-    mode: u=rw,g=r,o=
+    mode: u=rw,g=r,o=r # changepw needs read access
     group: dovecot
   loop:
   - conf.d/10-auth.conf
   - conf.d/20-lmtp.conf
   - conf.d/90-quota.conf
   - conf.d/auth-sql.conf.ext
+- name: configure dovecot secrets
+  notify: dovecot
+  template:
+    dest: /etc/dovecot/{{ item }}
+    src: templates/dovecot/{{ item }}
+    mode: u=rw,g=r,o=
+    group: dovecot
+  loop:
   - dovecot-sql.conf.ext
 - name: install quota notification script
   template:
index 5d1288fdee2f8c9244e502cd6aa6809d5dcb9cab..cba451906de89d1ef3d7453012a99c960aec2022 100644 (file)
@@ -21,8 +21,8 @@
 - 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"
+    - "621368ef38d991be46e4537b1d5444276579cd60cc721a749d500dd0b98efe27  /usr/lib/mailman/Mailman/Cgi/listinfo.py"
+    - "c6e46afe1c016d6853c8397916a6f6fd88c6cea71ae890ef3680617f1f8f7c9a  /usr/lib/mailman/Mailman/Cgi/subscribe.py"
   changed_when: False
   when: mailman_patched.rc != 0
 - name: install patched python files
@@ -33,6 +33,7 @@
   - Cgi/listinfo.py
   - Cgi/subscribe.py
   - Captcha.py
+  when: mailman_patched.rc != 0
 - name: install patched templates
   copy:
     dest: /etc/mailman/{{item}}
index 8da35b37301a1526edd4b44984fe88e0a50f190e..5b76b02012e7aa5ff3ce22d11ce786bc6c88f821 100644 (file)
@@ -20,7 +20,6 @@
   become_user: opendkim
   args:
     creates: /etc/opendkim/{{ item }}/mail.private
-    warn: False
   loop: "{{ postfix.opendkim.private_keys }}"
 - name: generate opendkim tables
   template:
index adb054c8fa1c1b8c5d8258804f7908eba78c070f..d59144d9b88f3f59c4d675694b4d536c7bad7291 100644 (file)
@@ -13,6 +13,7 @@
   loop:
   - main.cf
   - master.cf
+  - postscreen_access.cidr
   notify: postfix
 - name: install postfix mysql config
   when: postfix.dovecot is defined
   when: virtual_alias_map.changed
   command: postmap /etc/postfix/virtual_alias_map
   notify: postfix
-- name: create empty sender_transport_map
+- name: create sender_transport_map
   when: postfix.smtp_outgoing is defined
   register: sender_transport_map
-  copy:
+  template:
     dest: /etc/postfix/sender_transport_map
-    content: ""
-    force: no
+    src: templates/sender_transport_map
 - name: postmap sender_transport_map
   when: sender_transport_map.changed
   command: postmap /etc/postfix/sender_transport_map
@@ -76,8 +76,6 @@
   command: postmap /etc/postfix/transport_map
   notify: postfix
 # cronjobs
-- name: delete old local-mail cronjob
-  file: path=/etc/cron.daily/local-mail state=absent
 - name: install check-for-local-mail cronjob
   copy:
     dest: /etc/cron.daily/check-for-local-mail
index 96eef60078492b36fdc1ad3451e7b8793b0811db..7a1b6003ffbd44176d69402300aa46f46d7aeea9 100644 (file)
@@ -1,5 +1,5 @@
-#!/usr/bin/env python2
-from __future__ import print_function
+#!/usr/bin/env python3
+
 import os, cgi, MySQLdb, subprocess, time
 
 # settings
index 7c814e08187d33ae648f813b0277bf9316c766b4..a6634cf524f70aa69841cf90ae628e2f8d9f7940 100644 (file)
@@ -124,5 +124,4 @@ auth_mechanisms = plain login
 #!include auth-ldap.conf.ext
 #!include auth-passwdfile.conf.ext
 #!include auth-checkpassword.conf.ext
-#!include auth-vpopmail.conf.ext
 #!include auth-static.conf.ext
index d9f97afd80f617fcc4eb5fd1a0233eaae03cddf0..db650c661b0e21be9a7c695f3bc37e957c397100 100644 (file)
@@ -27,7 +27,7 @@
 #
 # <doc/wiki/MailLocation.txt>
 #
-mail_location = maildir:/var/vmail/%u
+mail_location = maildir:~/mail
 
 # If you need to set multiple mailbox locations or want to change default
 # namespace settings, you can do it by defining namespace sections.
index 066f3722071b64ae073cd446cb7a9802e63fe8fc..ae6d35400b22b89d3a0e1457cfeddca45cb30d36 100644 (file)
@@ -9,7 +9,7 @@ ssl = required
 # dropping root privileges, so keep the key file unreadable by anyone but
 # root. Included doc/mkcert.sh can be used to easily generate self-signed
 # certificate, just make sure to update the domains in dovecot-openssl.cnf
-ssl_cert = </etc/ssl/mycerts/letsencrypt/live.crt+chain
+ssl_cert = </etc/ssl/mycerts/letsencrypt/live.crt
 ssl_key = </etc/ssl/private/letsencrypt/live.key
 
 # If key file is password protected, give the password here. Alternatively
@@ -42,11 +42,8 @@ ssl_key = </etc/ssl/private/letsencrypt/live.key
 # auth_ssl_username_from_cert=yes.
 #ssl_cert_username_field = commonName
 
-# DH parameters length to use.
-ssl_dh_parameters_length = 2048
-
-# SSL protocols to use
-ssl_protocols = !SSLv3
+# DH parameters to use
+ssl_dh=</etc/ssl/dh2048.pem
 
 # SSL ciphers to use
 ssl_cipher_list = ALL:!EXPORT:!LOW:!MEDIUM:!kRSA:!kDH:!kECDH:!3DES
index 6da44d03c02b3c4da79b8eefc2328a3be6816484..ab3b8587a399a08f7dbd96db5469172c78f97883 100644 (file)
@@ -29,7 +29,7 @@ postmaster_address = {{postfix.postmaster}}
 #rejection_reason = Your message to <%t> was automatically rejected:%n%r
 
 # Delimiter character between local-part and detail in email address.
-#recipient_delimiter = +
+recipient_delimiter = {{ postfix.recipient_delimiter | default("+") }}
 
 # Header where the original recipient address (SMTP's RCPT TO: address) is taken
 # from if not available elsewhere. With dovecot-lda -a parameter overrides this.
index 1aca7d9915733709b01051a911098bb6ce70db54..87f40ff5a47c851f24de687604f2b5516f34a75a 100644 (file)
@@ -127,7 +127,8 @@ default_pass_scheme = SSHA
 #user_query = \
 #  SELECT home, uid, gid \
 #  FROM users WHERE username = '%n' AND domain = '%d'
-user_query = SELECT '/var/vmail/%u' as home, 'maildir:/var/vmail/%u' as mail, {{postfix.dovecot.vmail_uid}} AS uid, {{postfix.dovecot.mail_gid}} AS gid FROM users WHERE username = '%u' AND active = '1'
+user_query = SELECT '/var/vmail/%u' as home \
+    FROM users WHERE username = '%u' AND active = '1'
 
 # If you wish to avoid two SQL lookups (passdb + userdb), you can use
 # userdb prefetch instead of userdb sql in dovecot.conf. In that case you'll
@@ -137,7 +138,7 @@ user_query = SELECT '/var/vmail/%u' as home, 'maildir:/var/vmail/%u' as mail, {{
 #  SELECT userid AS user, password, \
 #    home AS userdb_home, uid AS userdb_uid, gid AS userdb_gid \
 #  FROM users WHERE userid = '%u'
-password_query = SELECT username as user, password, '/var/vmail/%u' as userdb_home, 'maildir:/var/vmail/%u' as userdb_mail, {{postfix.dovecot.vmail_uid}} as userdb_uid, {{postfix.dovecot.mail_gid}} as userdb_gid \
+password_query = SELECT username as user, password as password, '/var/vmail/%u' as userdb_home \
     FROM users WHERE username = '%u' AND active = '1'
 
 # Query to get a list of all usernames.
index f834d1c45c0e2b74b1431dd6e28242d75f7ab941..96951b5c4c6df01c00b233193be516d9001a9896 100644 (file)
@@ -1,7 +1,8 @@
-#!/bin/sh
+#!/bin/bash
 set -e
 
 PERCENT=$1
+USER=$2
 FROM="{{postfix.postmaster}}"
 
 msg="From: $FROM
@@ -14,6 +15,6 @@ Dein Posteingang ist zu $PERCENT% voll. Bitte räume etwas auf!
 
 Your mailbox is now $PERCENT% full. Please clean it up a bit!"
 
-echo -e "$msg" | /usr/sbin/sendmail -f "$FROM" "$USER" "$FROM"
+echo "$msg" | /usr/sbin/sendmail -f "$FROM" "$USER" "$FROM"
 
 exit 0
index a03d62764273be553f77bb2319e1e6670a4075a5..2d254f0c5219502fcb9ee091e9b2c4a364b21229 100644 (file)
@@ -13,7 +13,7 @@ local_recipient_maps = $alias_maps
 
 {% if 'letsencrypt' in group_names %}
 # TLS server parameters
-smtpd_tls_cert_file=/etc/ssl/mycerts/letsencrypt/live.crt+chain
+smtpd_tls_cert_file=/etc/ssl/mycerts/letsencrypt/live.crt
 smtpd_tls_key_file=/etc/ssl/private/letsencrypt/live.key
 smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
 smtpd_tls_security_level = may
@@ -39,14 +39,17 @@ smtp_tls_security_level = dane
 postscreen_dnsbl_threshold = 3
 postscreen_dnsbl_whitelist_threshold = -2
 postscreen_dnsbl_sites =
-       ix.dnsbl.manitu.net*2 sbl-xbl.spamhaus.org*2
-       bl.spamcop.net dnsbl.sorbs.net bl.mailspike.net
+       ix.dnsbl.manitu.net*2 sbl-xbl.spamhaus.org*3 truncate.gbudb.net*2
+       bl.spamcop.net bl.mailspike.net
        swl.spamhaus.org*-2 list.dnswl.org=127.0.[0..255].[0..254]*-2
 postscreen_greet_action = enforce
 postscreen_dnsbl_action = enforce
 postscreen_pipelining_enable = yes
 postscreen_non_smtp_command_enable = yes
 postscreen_bare_newline_enable = yes
+postscreen_access_list = permit_mynetworks,
+       cidr:$config_directory/postscreen_access.cidr
+postscreen_blacklist_action = enforce
 {% endif %}
 
 # control relay access
@@ -62,6 +65,11 @@ smtpd_recipient_restrictions = permit_mynetworks, permit_tls_clientcerts,
     reject_non_fqdn_recipient,
     reject_non_fqdn_sender,
 
+# SMTP smuggling protection
+# <https://www.postfix.org/smtp-smuggling.html>
+smtpd_data_restrictions = reject_unauth_pipelining
+smtpd_discard_ehlo_keywords = chunking
+
 {% if postfix.relay_host is defined %}
 # Relay everything
 default_transport = smtp:{{ postfix.relay_host }}
@@ -137,6 +145,6 @@ non_smtpd_milters = $smtpd_milters
 # misc
 smtpd_delay_reject = yes
 disable_vrfy_command = yes
-recipient_delimiter = +
+recipient_delimiter = {{ postfix.recipient_delimiter | default("+") }}
 delay_warning_time = 4h
-message_size_limit = 21384000
+message_size_limit = 30100100
index 89dd0d87bbf784bccde4edbc9343ccd75a3ce8ac..2b9c4518345ee480035caeeeeebfa6b0e2018852 100644 (file)
@@ -121,6 +121,7 @@ DEB_LISTMASTER = '{{postfix.postmaster}}'
 DEFAULT_ARCHIVE = Off
 DEFAULT_MAX_MESSAGE_SIZE = 1024           # KB
 DEFAULT_ADMIN_MEMBER_CHUNKSIZE = 100
+DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER = No
 
 # footer
 DEFAULT_MSG_FOOTER = """{{postfix.mailman.default_footer}}"""
index 86a1d99de21dc73e0f33d8aac0883327d49f11fc..4f3785909c30159c4b91b8915aabd31b5b8a4e2b 100755 (executable)
@@ -1,8 +1,8 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
-from __future__ import print_function
+
 import sys, os, random, MySQLdb, subprocess, time, string, argparse
-import urllib, smtplib, email.mime.text, email.utils
+import smtplib, email.mime.text, email.utils
 from settings import *
 from templates import *
 
index 6fccfc29393a1fea1eaca2f1d93712d8c3ce5019..b603f8736357b7905ca5a6c329ef10988a884feb 100644 (file)
@@ -36,3 +36,8 @@ OversignHeaders               From
 ## at http://unbound.net for the expected format of this file.
 
 TrustAnchorFile       /usr/share/dns/root.key
+
+
+# Path must match postfix main.cf
+Socket      local:/var/spool/postfix/opendkim/sock
+PidFile     /var/spool/postfix/opendkim/opendkim.pid
index 02fadef320f7cadd90d1681df3011f0c55691f2b..f56a2b9aac9b03c352e05b7e837c1746a5ab57ef 100644 (file)
@@ -1,5 +1,8 @@
 # Command-line options specified here will override the contents of
 # /etc/opendkim.conf. See opendkim(8) for a complete list of options.
+# RJ: This might seem redundant with the opendkim.conf settings,
+# but the script in /lib/opendkim/opendkim.service.generate also helps by
+# generating tmpfiles.d/opendkim.conf so replacing it seems like a hassle.
 #DAEMON_OPTS=""
 
 RUNDIR=/var/spool/postfix/opendkim
diff --git a/roles/email/templates/postscreen_access.cidr b/roles/email/templates/postscreen_access.cidr
new file mode 100644 (file)
index 0000000..91acb4c
--- /dev/null
@@ -0,0 +1,23 @@
+# Google thinks they are better than everyone else, and don't need to be compatible with greylisting.
+209.85.128.0/17 permit
+# Same for Microsoft.
+40.96.0.0/13 permit
+40.104.0.0/15 permit
+52.96.0.0/14 permit
+2603:1000::/24 permit
+# And Sparkpost.
+156.70.4.0/23 permit
+156.70.2.0/23 permit
+147.253.208.0/20 permit
+
+# Some hosts that send bounces to the wrong guy (i.e., me)
+192.241.146.138 reject please check mail server config, your server is sending bounce spam
+209.97.143.86 reject please check mail server config, your server is sending bounce spam
+209.97.132.56 reject please check mail server config, your server is sending bounce spam
+188.166.20.128 reject please check mail server config, your server is sending bounce spam
+159.203.188.91 reject please check mail server config, your server is sending bounce spam
+159.203.190.197 reject please check mail server config, your server is sending bounce spam
+159.65.138.221 reject please check mail server config, your server is sending bounce spam
+128.199.206.172 reject please check mail server config, your server is sending bounce spam
+142.93.223.22 reject please check mail server config, your server is sending bounce spam
+59.106.209.178 reject please check mail server config, your server is sending bounce spam
diff --git a/roles/email/templates/sender_transport_map b/roles/email/templates/sender_transport_map
new file mode 100644 (file)
index 0000000..e59fb7c
--- /dev/null
@@ -0,0 +1,6 @@
+# default is {{ postfix.default_smtp_outgoing }}
+{% if postfix.mailman is defined and postfix.mailman.smtp_outgoing is defined %}
+{% for item in postfix.mailman.domains %}
+@{{item}}              {{ postfix.mailman.smtp_outgoing }}
+{% endfor %}
+{% endif %}
index 790e8cdd6c049f24eff2eab2d599f68cb2999df6..6cc9c3ebf33f685a2e4e7cc6914bfd0c7a2916da 100644 (file)
@@ -16,7 +16,7 @@
   git:
     dest: /srv/{{etherpad.domain}}/etherpad-lite
     repo: 'https://github.com/ether/etherpad-lite.git'
-    version: '1.7.5'
+    version: '1.8.18'
     force: yes
 - name: create etherpad service file
   register: etherpad_service
index 74bb3c67e2f80a05cdbdcb26fe4052f8f3d0d4a9..61a324eb307ba74f4f5a9f3304a67b292f21ccdb 100644 (file)
@@ -4,6 +4,8 @@
   Please edit settings.json, not settings.json.template
 */
 {
+  "skinName": "colibris",
+
   //IP and port which etherpad should bind at
   "ip": "127.0.0.1",
   "port" : 9001,
@@ -15,7 +17,8 @@
                     "user"    : "etherpad",
                     "host"    : "localhost",
                     "password": "{{etherpad.mysql_password}}",
-                    "database": "etherpad"
+                    "database": "etherpad",
+                    "charset" : "utf8mb4"
                   },
 
   //the default text of a pad
index 42d698162897839c84bbe7e335405bcf5358e5ba..db20d2da7b07c33e403fe33a6ff07f272bedab1a 100644 (file)
@@ -51,33 +51,56 @@ Failed to allocate manager object: Permission denied
 
 _SYSTEMD_UNIT = init.scope
 user@\d+\.service: Killing process \d+ \(kill\) with signal SIGKILL\.
+[^: ]+: Consumed [\d.]+s CPU time\.
 {% if journalwatch is defined and journalwatch.strato_broken | default(False) %}
 Failed to set devices.allow on /system.slice/[a-z-]+.service: Operation not permitted
 {% endif %}
 
 SYSLOG_IDENTIFIER = sudo
-\s*[_\w.-]+ : TTY=(unknown|console|(pts/|ttyp?|vc/)\d+) ; PWD=[^;]+ ; USER=[._\w-]+ ; COMMAND=.*
+\s*[._\w-]+ : (TTY=(unknown|console|(pts/|ttyp?|vc/)\d+) ; )?PWD=[^;]+ ; USER=[._\w-]+ ; COMMAND=.*
+
+SYSLOG_IDENTIFIER = su
+\(to [._\w-]+\) [._\w-]+ on none
 
 _SYSTEMD_UNIT = postfix@-.service
-warning: hostname [^\s]+ does not resolve to address [\da-fA-F.:]+(: Name or service not known)?
+warning: hostname [\w._-]+ does not resolve to address [\da-fA-F.:]+(: .*)?
 warning: [._\w-]+\[[\da-fA-F.:]+\]: SASL (LOGIN|PLAIN) authentication failed:.*
-warning: non-SMTP command from \w+\[[\da-fA-F.:]+\]: .*
+warning: non-SMTP command from [._\w-]+\[[\da-fA-F.:]+\]: .*
 warning: TLS library problem: error:[0-9A-F]+:SSL routines:\w+:(no shared cipher|decryption failed or bad record mac|unknown protocol|version too low):[\w./]+:\d+:
+warning: dnsblog reply timeout \d+s for .*
+warning: dnsblog_query: lookup error for DNS query .*: Host or domain name not found. Name service error .*
+warning: ciphertext read/write timeout for \[[\da-fA-F.:]+\]:\d+
+warning: getpeername: Transport endpoint is not connected -- dropping this connection
 {% if journalwatch is defined and journalwatch.postfix_slow | default(False) %}
 warning: psc_cache_update: btree:/var/lib/postfix/[a-z_]+ (update|lookup) average delay is \d\d\d ms
 {% endif %}
+{% if journalwatch is defined and journalwatch.strato_broken | default(False) %}
+warning: dnsblog reply timeout [0-9]+s for [\w._-]+
+warning: dnsblog_query: lookup error for DNS query .*
+{% endif %}
 
 _SYSTEMD_UNIT = dovecot.service
 auth: Warning: auth client \d+ disconnected with \d+ pending requests: (EOF|Connection reset by peer)
+auth: Warning: Event 0x[\da-fA-F]+ leaked \(parent=\(nil\)\): auth-client-connection.c:\d+
 
 SYSLOG_IDENTIFIER = sshd
 error: Received disconnect from [\da-fA-F.:]+ port \d+:\d+: .*
-error: maximum authentication attempts exceeded for (invalid user \w*|\w+) from [\da-fA-F.:]+ port \d+ ssh2 \[preauth\]
+error: maximum authentication attempts exceeded for (invalid user [\w_-]*|[\w_-]+) from [\da-fA-F.:]+ port \d+ ssh2 \[preauth\]
+error: (kex_exchange_identification|send_error|kex_protocol_error|kex protocol error|Bad remote protocol version identification|Protocol major versions differ|beginning MaxStartups throttling).*
 fatal: ssh_packet_get_string: string is too large \[preauth\]
+(error|fatal): userauth_pubkey: (parse request failed:|could not parse key:|cannot decode key:) .*
+fatal: monitor_read: unpermitted request .*
+error: key_from_blob: invalid format \[preauth\]
 
-_SYSTEMD_UNIT = bind9.service
-client [\da-fA-F.:]+#\d+( \([\w.-]+\))?: (zone transfer '[\w.-]+/AXFR/IN' denied|message parsing failed: (bad compression pointer|bad label type|unexpected end of input))
+_SYSTEMD_UNIT = named.service
+client (@0x[a-f0-9]+ )?[\da-fA-F.:]+#\d+( \([\w.-]+\))?: (zone transfer '[\w.-]+/AXFR/IN' denied|message parsing failed: (bad compression pointer|bad label type|unexpected end of input))
 
 _SYSTEMD_UNIT = opendkim.service
 [A-Z0-9]+: (bad signature data|failed to parse [Aa]uthentication-[Rr]esults: header field)
-[A-Z0-9]+: key retrieval failed \(s=[\w._-]+, d=[\w._-]+\)(: '[\w._-]+' record not found)?
+[A-Z0-9]+: key retrieval failed \(s=[\w._-]+, d=[\w._-]+\)(: '[\w._-]+' (record not found|query timed out))?
+
+_SYSTEMD_SLICE=system-openvpn.slice
+(client/)?[0-9a-f.:]+ (peer info: .*|VERIFY OK: .*|Outgoing Data Channel: .*|Incoming Data Channel: .*|Control Channel: .*|TLS: .*|\[client\] .*|MULTI(_sva)?: .*|SIGUSR1.*|PUSH: .*|SENT CONTROL \[client\]: .*|WARNING: '(tun|link)-mtu' is used inconsistently, local='(tun|link)-mtu \d+', remote='(tun|link)-mtu \d+')
+
+SYSLOG_IDENTIFIER = kernel
+xfs filesystem being remounted at /run/schroot/mount/schsh-[^ ]* supports timestamps until 2038 \(0x7fffffff\)
index 9a9db71d19ff6944c0099baf2f2d0ea58d73de3d..e89cf99074909c7a06374c67502b16ce2082cd7d 100644 (file)
@@ -2,3 +2,5 @@
   service: name=prosody state=restarted enabled=yes
 - name: apache
   service: name=apache2 state=restarted enabled=yes
+- name: coturn
+  service: name=coturn state=restarted enabled=yes
index 9b6f5dd57b927c514d71607ceaf47d32afdc4b67..662647f47b9ec33b097c5ce56a3deb3e05afc937 100644 (file)
@@ -1,6 +1,6 @@
 # install
 - name: install prosody
-  apt: name=prosody,lua-zlib,lua-dbi-sqlite3 state=latest default_release={{ansible_distribution_release}}-backports
+  apt: name=prosody,lua-zlib,lua-dbi-sqlite3,coturn state=latest default_release={{ansible_distribution_release}}-backports
 - name: fetch prosody modules
   hg:
     dest: "{{ prosody.paths.modules }}"
     dest: /etc/prosody/prosody.cfg.lua
     src: templates/prosody.cfg.lua
   notify: prosody
+- name: configure coturn
+  template:
+    dest: /etc/turnserver.conf
+    src: templates/turnserver.conf
+  notify: coturn
 - name: configure apache
   when: inventory_hostname in groups['apache']
   template:
index d92d82e2000caa491aee760ec77de7c254841b48..0fc27e23aaa784b2269ddf566faf318a738f82ef 100644 (file)
@@ -72,7 +72,7 @@ modules_enabled = {
         --"websocket"; -- XMPP over WebSockets
         --"http_files"; -- Serve static files from a directory over HTTP
 
-        -- Other specific functionality
+    -- Other specific functionality
         --"limits"; -- Enable bandwidth limiting for XMPP connections
         --"groups"; -- Shared roster support
         --"server_contact_info"; -- Publish contact information for this service
@@ -82,6 +82,7 @@ modules_enabled = {
         --"motd"; -- Send a message to users when they log in
         --"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
         --"proxy65"; -- Enables a file transfer proxy service which clients behind NAT can use
+        "external_services"; -- XEP-0215: External Service Discovery
 
     -- Community modules
         "smacks"; -- XEP-0198: Stream Management
@@ -117,7 +118,7 @@ pidfile = "/var/run/prosody/prosody.pid"
 -- to use SSL/TLS, you may comment or remove this
 ssl = {
        key = "/etc/ssl/private/letsencrypt/live.key";
-       certificate = "/etc/ssl/mycerts/letsencrypt/live.crt+chain";
+       certificate = "/etc/ssl/mycerts/letsencrypt/live.crt";
        ciphers = "ALL:!EXPORT:!LOW:!MEDIUM:!aNULL:!3DES";
        dhparam = "/etc/ssl/dh2048.pem";
 }
@@ -216,6 +217,22 @@ http_upload_file_size_limit = 5 * 1024 * 1024
 http_upload_expire_after = 60 * 60 * 24 * 7 -- a week in seconds
 http_upload_quota = 50 * 1024 * 1024
 
+-- Set up external services
+external_services = {
+    {
+        type = "stun",
+        transport = "udp",
+        host = "{{ prosody.host }}",
+        port = 3478
+    }, {
+        type = "turn",
+        transport = "udp",
+        host = "{{ prosody.host }}",
+        port = 3478,
+        secret = "{{ prosody.turn.secret }}"
+    }
+}
+
 ----------- Virtual hosts -----------
 -- You need to add a VirtualHost entry for each domain you wish Prosody to serve.
 -- Settings under each VirtualHost entry apply *only* to that host.
diff --git a/roles/prosody/templates/turnserver.conf b/roles/prosody/templates/turnserver.conf
new file mode 100644 (file)
index 0000000..dfd17da
--- /dev/null
@@ -0,0 +1,643 @@
+# Coturn TURN SERVER configuration file
+#
+# Boolean values note: where boolean value is supposed to be used,
+# you can use '0', 'off', 'no', 'false', 'f' as 'false,
+# and you can use '1', 'on', 'yes', 'true', 't' as 'true'
+# If the value is missed, then it means 'true'.
+#
+
+# Listener interface device (optional, Linux only).
+# NOT RECOMMENDED.
+#
+#listening-device=eth0
+
+# TURN listener port for UDP and TCP (Default: 3478).
+# Note: actually, TLS & DTLS sessions can connect to the
+# "plain" TCP & UDP port(s), too - if allowed by configuration.
+#
+#listening-port=3478
+
+# TURN listener port for TLS (Default: 5349).
+# Note: actually, "plain" TCP & UDP sessions can connect to the TLS & DTLS
+# port(s), too - if allowed by configuration. The TURN server
+# "automatically" recognizes the type of traffic. Actually, two listening
+# endpoints (the "plain" one and the "tls" one) are equivalent in terms of
+# functionality; but we keep both endpoints to satisfy the RFC 5766 specs.
+# For secure TCP connections, we currently support SSL version 3 and
+# TLS version 1.0, 1.1 and 1.2.
+# For secure UDP connections, we support DTLS version 1.
+#
+#tls-listening-port=5349
+
+# Alternative listening port for UDP and TCP listeners;
+# default (or zero) value means "listening port plus one".
+# This is needed for RFC 5780 support
+# (STUN extension specs, NAT behavior discovery). The TURN Server
+# supports RFC 5780 only if it is started with more than one
+# listening IP address of the same family (IPv4 or IPv6).
+# RFC 5780 is supported only by UDP protocol, other protocols
+# are listening to that endpoint only for "symmetry".
+#
+#alt-listening-port=0
+
+# Alternative listening port for TLS and DTLS protocols.
+# Default (or zero) value means "TLS listening port plus one".
+#
+#alt-tls-listening-port=0
+
+# Listener IP address of relay server. Multiple listeners can be specified.
+# If no IP(s) specified in the config file or in the command line options,
+# then all IPv4 and IPv6 system IPs will be used for listening.
+#
+#listening-ip=172.17.19.101
+#listening-ip=10.207.21.238
+#listening-ip=2607:f0d0:1002:51::4
+
+# Auxiliary STUN/TURN server listening endpoint.
+# Aux servers have almost full TURN and STUN functionality.
+# The (minor) limitations are:
+#
+# 1) Auxiliary servers do not have alternative ports and
+# they do not support STUN RFC 5780 functionality (CHANGE REQUEST).
+#
+# 2) Auxiliary servers also are never returning ALTERNATIVE-SERVER reply.
+#
+# Valid formats are 1.2.3.4:5555 for IPv4 and [1:2::3:4]:5555 for IPv6.
+#
+# There may be multiple aux-server options, each will be used for listening
+# to client requests.
+#
+#aux-server=172.17.19.110:33478
+#aux-server=[2607:f0d0:1002:51::4]:33478
+
+# (recommended for older Linuxes only)
+# Automatically balance UDP traffic over auxiliary servers (if configured).
+# The load balancing is using the ALTERNATE-SERVER mechanism.
+# The TURN client must support 300 ALTERNATE-SERVER response for this
+# functionality.
+#
+#udp-self-balance
+
+# Relay interface device for relay sockets (optional, Linux only).
+# NOT RECOMMENDED.
+#
+#relay-device=eth1
+
+# Relay address (the local IP address that will be used to relay the
+# packets to the peer).
+# Multiple relay addresses may be used.
+# The same IP(s) can be used as both listening IP(s) and relay IP(s).
+#
+# If no relay IP(s) specified, then the turnserver will apply the default
+# policy: it will decide itself which relay addresses to be used, and it
+# will always be using the client socket IP address as the relay IP address
+# of the TURN session (if the requested relay address family is the same
+# as the family of the client socket).
+#
+#relay-ip=172.17.19.105
+#relay-ip=2607:f0d0:1002:51::5
+
+# For Amazon EC2 users:
+#
+# TURN Server public/private address mapping, if the server is behind NAT.
+# In that situation, if a -X is used in form "-X <ip>" then that ip will be reported
+# as relay IP address of all allocations. This scenario works only in a simple case
+# when one single relay address is be used, and no RFC5780 functionality is required.
+# That single relay address must be mapped by NAT to the 'external' IP.
+# The "external-ip" value, if not empty, is returned in XOR-RELAYED-ADDRESS field.
+# For that 'external' IP, NAT must forward ports directly (relayed port 12345
+# must be always mapped to the same 'external' port 12345).
+#
+# In more complex case when more than one IP address is involved,
+# that option must be used several times, each entry must
+# have form "-X <public-ip/private-ip>", to map all involved addresses.
+# RFC5780 NAT discovery STUN functionality will work correctly,
+# if the addresses are mapped properly, even when the TURN server itself
+# is behind A NAT.
+#
+# By default, this value is empty, and no address mapping is used.
+#
+#external-ip=60.70.80.91
+#
+#OR:
+#
+#external-ip=60.70.80.91/172.17.19.101
+#external-ip=60.70.80.92/172.17.19.102
+
+
+# Number of the relay threads to handle the established connections
+# (in addition to authentication thread and the listener thread).
+# If explicitly set to 0 then application runs relay process in a
+# single thread, in the same thread with the listener process
+# (the authentication thread will still be a separate thread).
+#
+# If this parameter is not set, then the default OS-dependent
+# thread pattern algorithm will be employed. Usually the default
+# algorithm is the most optimal, so you have to change this option
+# only if you want to make some fine tweaks.
+#
+# In the older systems (Linux kernel before 3.9),
+# the number of UDP threads is always one thread per network listening
+# endpoint - including the auxiliary endpoints - unless 0 (zero) or
+# 1 (one) value is set.
+#
+#relay-threads=0
+
+# Lower and upper bounds of the UDP relay endpoints:
+# (default values are 49152 and 65535)
+#
+min-port=50000
+max-port=51000
+
+# Uncomment to run TURN server in 'normal' 'moderate' verbose mode.
+# By default the verbose mode is off.
+#verbose
+
+# Uncomment to run TURN server in 'extra' verbose mode.
+# This mode is very annoying and produces lots of output.
+# Not recommended under any normal circumstances.
+#
+#Verbose
+
+# Uncomment to use fingerprints in the TURN messages.
+# By default the fingerprints are off.
+#
+#fingerprint
+
+# Uncomment to use long-term credential mechanism.
+# By default no credentials mechanism is used (any user allowed).
+#
+#lt-cred-mech
+
+# This option is opposite to lt-cred-mech.
+# (TURN Server with no-auth option allows anonymous access).
+# If neither option is defined, and no users are defined,
+# then no-auth is default. If at least one user is defined,
+# in this file or in command line or in usersdb file, then
+# lt-cred-mech is default.
+#
+#no-auth
+
+# TURN REST API flag.
+# Flag that sets a special authorization option that is based upon authentication secret.
+# This feature can be used with the long-term authentication mechanism, only.
+# This feature purpose is to support "TURN Server REST API", see
+# "TURN REST API" link in the project's page
+# https://github.com/coturn/coturn/
+#
+# This option is used with timestamp:
+#
+# usercombo -> "timestamp:userid"
+# turn user -> usercombo
+# turn password -> base64(hmac(secret key, usercombo))
+#
+# This allows TURN credentials to be accounted for a specific user id.
+# If you don't have a suitable id, the timestamp alone can be used.
+# This option is just turning on secret-based authentication.
+# The actual value of the secret is defined either by option static-auth-secret,
+# or can be found in the turn_secret table in the database (see below).
+#
+use-auth-secret
+
+# 'Static' authentication secret value (a string) for TURN REST API only.
+# If not set, then the turn server
+# will try to use the 'dynamic' value in turn_secret table
+# in user database (if present). The database-stored  value can be changed on-the-fly
+# by a separate program, so this is why that other mode is 'dynamic'.
+#
+static-auth-secret={{ prosody.turn.secret }}
+
+# Server name used for
+# the oAuth authentication purposes.
+# The default value is the realm name.
+#
+#server-name=blackdow.carleon.gov
+
+# Flag that allows oAuth authentication.
+#
+#oauth
+
+# 'Static' user accounts for long term credentials mechanism, only.
+# This option cannot be used with TURN REST API.
+# 'Static' user accounts are NOT dynamically checked by the turnserver process,
+# so that they can NOT be changed while the turnserver is running.
+#
+#user=username1:key1
+#user=username2:key2
+# OR:
+#user=username1:password1
+#user=username2:password2
+#
+# Keys must be generated by turnadmin utility. The key value depends
+# on user name, realm, and password:
+#
+# Example:
+# $ turnadmin -k -u ninefingers -r north.gov -p youhavetoberealistic
+# Output: 0xbc807ee29df3c9ffa736523fb2c4e8ee
+# ('0x' in the beginning of the key is what differentiates the key from
+# password. If it has 0x then it is a key, otherwise it is a password).
+#
+# The corresponding user account entry in the config file will be:
+#
+#user=ninefingers:0xbc807ee29df3c9ffa736523fb2c4e8ee
+# Or, equivalently, with open clear password (less secure):
+#user=ninefingers:youhavetoberealistic
+#
+
+# SQLite database file name.
+#
+# Default file name is /var/db/turndb or /usr/local/var/db/turndb or
+# /var/lib/turn/turndb.
+#
+#userdb=/var/db/turndb
+
+# PostgreSQL database connection string in the case that we are using PostgreSQL
+# as the user database.
+# This database can be used for long-term credential mechanism
+# and it can store the secret value for secret-based timed authentication in TURN RESP API.
+# See http://www.postgresql.org/docs/8.4/static/libpq-connect.html for 8.x PostgreSQL
+# versions connection string format, see
+# http://www.postgresql.org/docs/9.2/static/libpq-connect.html#LIBPQ-CONNSTRING
+# for 9.x and newer connection string formats.
+#
+#psql-userdb="host=<host> dbname=<database-name> user=<database-user> password=<database-user-password> connect_timeout=30"
+
+# MySQL database connection string in the case that we are using MySQL
+# as the user database.
+# This database can be used for long-term credential mechanism
+# and it can store the secret value for secret-based timed authentication in TURN RESP API.
+#
+# Optional connection string parameters for the secure communications (SSL):
+# ca, capath, cert, key, cipher
+# (see http://dev.mysql.com/doc/refman/5.1/en/ssl-options.html for the
+# command options description).
+#
+# Use string format as below (space separated parameters, all optional):
+#
+#mysql-userdb="host=<host> dbname=<database-name> user=<database-user> password=<database-user-password> port=<port> connect_timeout=<seconds> read_timeout=<seconds>"
+
+# MongoDB database connection string in the case that we are using MongoDB
+# as the user database.
+# This database can be used for long-term credential mechanism
+# and it can store the secret value for secret-based timed authentication in TURN RESP API.
+# Use string format is described at http://hergert.me/docs/mongo-c-driver/mongoc_uri.html
+#
+#mongo-userdb="mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]"
+
+# Redis database connection string in the case that we are using Redis
+# as the user database.
+# This database can be used for long-term credential mechanism
+# and it can store the secret value for secret-based timed authentication in TURN RESP API.
+# Use string format as below (space separated parameters, all optional):
+#
+#redis-userdb="ip=<ip-address> dbname=<database-number> password=<database-user-password> port=<port> connect_timeout=<seconds>"
+
+# Redis status and statistics database connection string, if used (default - empty, no Redis stats DB used).
+# This database keeps allocations status information, and it can be also used for publishing
+# and delivering traffic and allocation event notifications.
+# The connection string has the same parameters as redis-userdb connection string.
+# Use string format as below (space separated parameters, all optional):
+#
+#redis-statsdb="ip=<ip-address> dbname=<database-number> password=<database-user-password> port=<port> connect_timeout=<seconds>"
+
+# The default realm to be used for the users when no explicit
+# origin/realm relationship was found in the database, or if the TURN
+# server is not using any database (just the commands-line settings
+# and the userdb file). Must be used with long-term credentials
+# mechanism or with TURN REST API.
+#
+realm= {{ prosody.host }}
+
+# The flag that sets the origin consistency
+# check: across the session, all requests must have the same
+# main ORIGIN attribute value (if the ORIGIN was
+# initially used by the session).
+#
+#check-origin-consistency
+
+# Per-user allocation quota.
+# default value is 0 (no quota, unlimited number of sessions per user).
+# This option can also be set through the database, for a particular realm.
+#
+#user-quota=0
+
+# Total allocation quota.
+# default value is 0 (no quota).
+# This option can also be set through the database, for a particular realm.
+#
+#total-quota=0
+
+# Max bytes-per-second bandwidth a TURN session is allowed to handle
+# (input and output network streams are treated separately). Anything above
+# that limit will be dropped or temporary suppressed (within
+# the available buffer limits).
+# This option can also be set through the database, for a particular realm.
+#
+#max-bps=0
+
+#
+# Maximum server capacity.
+# Total bytes-per-second bandwidth the TURN server is allowed to allocate
+# for the sessions, combined (input and output network streams are treated separately).
+#
+# bps-capacity=0
+
+# Uncomment if no UDP client listener is desired.
+# By default UDP client listener is always started.
+#
+#no-udp
+
+# Uncomment if no TCP client listener is desired.
+# By default TCP client listener is always started.
+#
+#no-tcp
+
+# Uncomment if no TLS client listener is desired.
+# By default TLS client listener is always started.
+#
+#no-tls
+
+# Uncomment if no DTLS client listener is desired.
+# By default DTLS client listener is always started.
+#
+#no-dtls
+
+# Uncomment if no UDP relay endpoints are allowed.
+# By default UDP relay endpoints are enabled (like in RFC 5766).
+#
+#no-udp-relay
+
+# Uncomment if no TCP relay endpoints are allowed.
+# By default TCP relay endpoints are enabled (like in RFC 6062).
+#
+#no-tcp-relay
+
+# Uncomment if extra security is desired,
+# with nonce value having limited lifetime.
+# By default, the nonce value is unique for a session,
+# and has unlimited lifetime.
+# Set this option to limit the nonce lifetime.
+# It defaults to 600 secs (10 min) if no value is provided. After that delay,
+# the client will get 438 error and will have to re-authenticate itself.
+#
+#stale-nonce=600
+
+# Uncomment if you want to set the maximum allocation
+# time before it has to be refreshed.
+# Default is 3600s.
+#
+#max-allocate-lifetime=3600
+
+
+# Uncomment to set the lifetime for the channel.
+# Default value is 600 secs (10 minutes).
+# This value MUST not be changed for production purposes.
+#
+#channel-lifetime=600
+
+# Uncomment to set the permission lifetime.
+# Default to 300 secs (5 minutes).
+# In production this value MUST not be changed,
+# however it can be useful for test purposes.
+#
+#permission-lifetime=300
+
+# Certificate file.
+# Use an absolute path or path relative to the
+# configuration file.
+#
+#cert=/usr/local/etc/turn_server_cert.pem
+
+# Private key file.
+# Use an absolute path or path relative to the
+# configuration file.
+# Use PEM file format.
+#
+#pkey=/usr/local/etc/turn_server_pkey.pem
+
+# Private key file password, if it is in encoded format.
+# This option has no default value.
+#
+#pkey-pwd=...
+
+# Allowed OpenSSL cipher list for TLS/DTLS connections.
+# Default value is "DEFAULT".
+#
+#cipher-list="DEFAULT"
+
+# CA file in OpenSSL format.
+# Forces TURN server to verify the client SSL certificates.
+# By default it is not set: there is no default value and the client
+# certificate is not checked.
+#
+# Example:
+#CA-file=/etc/ssh/id_rsa.cert
+
+# Curve name for EC ciphers, if supported by OpenSSL
+# library (TLS and DTLS). The default value is prime256v1,
+# if pre-OpenSSL 1.0.2 is used. With OpenSSL 1.0.2+,
+# an optimal curve will be automatically calculated, if not defined
+# by this option.
+#
+#ec-curve-name=prime256v1
+
+# Use 566 bits predefined DH TLS key. Default size of the key is 1066.
+#
+#dh566
+
+# Use 2066 bits predefined DH TLS key. Default size of the key is 1066.
+#
+#dh2066
+
+# Use custom DH TLS key, stored in PEM format in the file.
+# Flags --dh566 and --dh2066 are ignored when the DH key is taken from a file.
+#
+#dh-file=<DH-PEM-file-name>
+
+# Flag to prevent stdout log messages.
+# By default, all log messages are going to both stdout and to
+# the configured log file. With this option everything will be
+# going to the configured log only (unless the log file itself is stdout).
+#
+#no-stdout-log
+
+# Option to set the log file name.
+# By default, the turnserver tries to open a log file in
+# /var/log, /var/tmp, /tmp and current directories directories
+# (which open operation succeeds first that file will be used).
+# With this option you can set the definite log file name.
+# The special names are "stdout" and "-" - they will force everything
+# to the stdout. Also, the "syslog" name will force everything to
+# the system log (syslog).
+# In the runtime, the logfile can be reset with the SIGHUP signal
+# to the turnserver process.
+#
+#log-file=/var/tmp/turn.log
+
+# Option to redirect all log output into system log (syslog).
+#
+#syslog
+
+# This flag means that no log file rollover will be used, and the log file
+# name will be constructed as-is, without PID and date appendage.
+# This option can be used, for example, together with the logrotate tool.
+#
+#simple-log
+
+# Option to set the "redirection" mode. The value of this option
+# will be the address of the alternate server for UDP & TCP service in form of
+# <ip>[:<port>]. The server will send this value in the attribute
+# ALTERNATE-SERVER, with error 300, on ALLOCATE request, to the client.
+# Client will receive only values with the same address family
+# as the client network endpoint address family.
+# See RFC 5389 and RFC 5766 for ALTERNATE-SERVER functionality description.
+# The client must use the obtained value for subsequent TURN communications.
+# If more than one --alternate-server options are provided, then the functionality
+# can be more accurately described as "load-balancing" than a mere "redirection".
+# If the port number is omitted, then the default port
+# number 3478 for the UDP/TCP protocols will be used.
+# Colon (:) characters in IPv6 addresses may conflict with the syntax of
+# the option. To alleviate this conflict, literal IPv6 addresses are enclosed
+# in square brackets in such resource identifiers, for example:
+# [2001:db8:85a3:8d3:1319:8a2e:370:7348]:3478 .
+# Multiple alternate servers can be set. They will be used in the
+# round-robin manner. All servers in the pool are considered of equal weight and
+# the load will be distributed equally. For example, if we have 4 alternate servers,
+# then each server will receive 25% of ALLOCATE requests. A alternate TURN server
+# address can be used more than one time with the alternate-server option, so this
+# can emulate "weighting" of the servers.
+#
+# Examples:
+#alternate-server=1.2.3.4:5678
+#alternate-server=11.22.33.44:56789
+#alternate-server=5.6.7.8
+#alternate-server=[2001:db8:85a3:8d3:1319:8a2e:370:7348]:3478
+
+# Option to set alternative server for TLS & DTLS services in form of
+# <ip>:<port>. If the port number is omitted, then the default port
+# number 5349 for the TLS/DTLS protocols will be used. See the previous
+# option for the functionality description.
+#
+# Examples:
+#tls-alternate-server=1.2.3.4:5678
+#tls-alternate-server=11.22.33.44:56789
+#tls-alternate-server=[2001:db8:85a3:8d3:1319:8a2e:370:7348]:3478
+
+# Option to suppress TURN functionality, only STUN requests will be processed.
+# Run as STUN server only, all TURN requests will be ignored.
+# By default, this option is NOT set.
+#
+#stun-only
+
+# Option to suppress STUN functionality, only TURN requests will be processed.
+# Run as TURN server only, all STUN requests will be ignored.
+# By default, this option is NOT set.
+#
+#no-stun
+
+# This is the timestamp/username separator symbol (character) in TURN REST API.
+# The default value is ':'.
+# rest-api-separator=:
+
+# Flag that can be used to disallow peers on the loopback addresses (127.x.x.x and ::1).
+# This is an extra security measure.
+#
+#no-loopback-peers
+
+# Flag that can be used to disallow peers on well-known broadcast addresses (224.0.0.0 and above, and FFXX:*).
+# This is an extra security measure.
+#
+#no-multicast-peers
+
+# Option to set the max time, in seconds, allowed for full allocation establishment.
+# Default is 60 seconds.
+#
+#max-allocate-timeout=60
+
+# Option to allow or ban specific ip addresses or ranges of ip addresses.
+# If an ip address is specified as both allowed and denied, then the ip address is
+# considered to be allowed. This is useful when you wish to ban a range of ip
+# addresses, except for a few specific ips within that range.
+#
+# This can be used when you do not want users of the turn server to be able to access
+# machines reachable by the turn server, but would otherwise be unreachable from the
+# internet (e.g. when the turn server is sitting behind a NAT)
+#
+# Examples:
+# denied-peer-ip=83.166.64.0-83.166.95.255
+# allowed-peer-ip=83.166.68.45
+
+# File name to store the pid of the process.
+# Default is /var/run/turnserver.pid (if superuser account is used) or
+# /var/tmp/turnserver.pid .
+#
+#pidfile="/var/run/turnserver.pid"
+
+# Require authentication of the STUN Binding request.
+# By default, the clients are allowed anonymous access to the STUN Binding functionality.
+#
+#secure-stun
+
+# Mobility with ICE (MICE) specs support.
+#
+#mobility
+
+# User name to run the process. After the initialization, the turnserver process
+# will make an attempt to change the current user ID to that user.
+#
+#proc-user=<user-name>
+
+# Group name to run the process. After the initialization, the turnserver process
+# will make an attempt to change the current group ID to that group.
+#
+#proc-group=<group-name>
+
+# Turn OFF the CLI support.
+# By default it is always ON.
+# See also options cli-ip and cli-port.
+#
+#no-cli
+
+#Local system IP address to be used for CLI server endpoint. Default value
+# is 127.0.0.1.
+#
+#cli-ip=127.0.0.1
+
+# CLI server port. Default is 5766.
+#
+#cli-port=5766
+
+# CLI access password. Default is empty (no password).
+# For the security reasons, it is recommended to use the encrypted
+# for of the password (see the -P command in the turnadmin utility).
+#
+# Secure form for password 'qwerty':
+#
+#cli-password=$5$79a316b350311570$81df9cfb9af7f5e5a76eada31e7097b663a0670f99a3c07ded3f1c8e59c5658a
+#
+# Or unsecure form for the same paassword:
+#
+#cli-password=qwerty
+
+# Server relay. NON-STANDARD AND DANGEROUS OPTION.
+# Only for those applications when we want to run
+# server applications on the relay endpoints.
+# This option eliminates the IP permissions check on
+# the packets incoming to the relay endpoints.
+#
+#server-relay
+
+# Maximum number of output sessions in ps CLI command.
+# This value can be changed on-the-fly in CLI. The default value is 256.
+#
+#cli-max-output-sessions
+
+# Set network engine type for the process (for internal purposes).
+#
+#ne=[1|2|3]
+
+# Do not allow an TLS/DTLS version of protocol
+#
+#no-tlsv1
+#no-tlsv1_1
+#no-tlsv1_2
index d8d03e3dd0f84f98a1d6ebf17f2ca9fa93dc2406..f3aacac435c6509c6abb24054a4b6ce42e27f3b1 100644 (file)
     src: files/dhclient.conf
 - name: configure system DNS
   copy:
-    dest: /etc/resolv.conf
-    content: "nameserver 127.0.0.2\n"
+    dest: "{{ item }}"
+    content: "nameserver 127.0.0.2\noptions trust-ad\noptions edns0\n"
+  loop:
+  - /etc/resolv.conf.unbound
+  - /etc/resolv.conf
 # some providers need extra hacks to make our DNS persistent
 - name: install DNS-fix cronjob
   template:
index ca7f860df582eb6e5d4f4a67b679f1d01ebf7ce2..f7b4bf2ceeec299cb9c845a948a34ddd865ac159 100644 (file)
@@ -2,9 +2,9 @@
 set -e
 
 # Fix for some providers messing with DNS settings
-if ! diff /etc/resolv.conf <(echo "nameserver 127.0.0.2") > /dev/null; then
+if ! diff /etc/resolv.conf /etc/resolv.conf.unbound > /dev/null; then
        echo "Someone messed up our DNS! Fixing it..."
-       echo "nameserver 127.0.0.2" > /etc/resolv.conf
+       cp /etc/resolv.conf.unbound /etc/resolv.conf
 {% if 'email' in group_names %}
        # Just to make sure postfix uses the new settings
        systemctl restart postfix