diff --git a/CHANGELOG.md b/CHANGELOG.md index 01f860fe..36656e53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,17 @@ Mail: * An MTA-STS policy for incoming mail is now published (in DNS and over HTTPS) when the primary hostname and email address domain both have a signed TLS certificate installed. * MTA-STS reporting is enabled with reports sent to administrator@ the primary hostname. +DNS: + +* autoconfig and autodiscover subdomains and CalDAV/CardDAV SRV records are no longer generated for domains that don't have user accounts since they are unnecessary. + +v0.46 (June 11, 2020) +--------------------- + +Security fixes: + +* Roundcube is updated to version 1.4.6 (https://roundcube.net/news/2020/06/02/security-updates-1.4.5-and-1.3.12). + v0.45 (May 16, 2020) -------------------- diff --git a/README.md b/README.md index 630ce259..fe0e5317 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ by him: $ curl -s https://keybase.io/joshdata/key.asc | gpg --import gpg: key C10BDD81: public key "Joshua Tauberer " imported - $ git verify-tag v0.45 + $ git verify-tag v0.46 gpg: Signature made ..... using RSA key ID C10BDD81 gpg: Good signature from "Joshua Tauberer " gpg: WARNING: This key is not certified with a trusted signature! @@ -76,7 +76,7 @@ and on his [personal homepage](https://razor.occams.info/). (Of course, if this Checkout the tag corresponding to the most recent release: - $ git checkout v0.45 + $ git checkout v0.46 Begin the installation. diff --git a/management/daily_tasks.sh b/management/daily_tasks.sh index 2f723352..db496399 100755 --- a/management/daily_tasks.sh +++ b/management/daily_tasks.sh @@ -16,10 +16,10 @@ if [ `date "+%u"` -eq 1 ]; then fi # Take a backup. -management/backup.py | management/email_administrator.py "Backup Status" +management/backup.py 2>&1 | management/email_administrator.py "Backup Status" # Provision any new certificates for new domains or domains with expiring certificates. -management/ssl_certificates.py -q | management/email_administrator.py "TLS Certificate Provisioning Result" +management/ssl_certificates.py -q 2>&1 | management/email_administrator.py "TLS Certificate Provisioning Result" # Run status checks and email the administrator if anything changed. -management/status_checks.py --show-changes | management/email_administrator.py "Status Checks Change Notice" +management/status_checks.py --show-changes 2>&1 | management/email_administrator.py "Status Checks Change Notice" diff --git a/management/dns_update.py b/management/dns_update.py index 5fdb3e0f..80273a12 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -281,28 +281,30 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en if not has_rec(dmarc_qname, "TXT", prefix="v=DMARC1; "): records.append((dmarc_qname, "TXT", 'v=DMARC1; p=reject', "Recommended. Prevents use of this domain name for outbound mail by specifying that the SPF rule should be honoured for mail from @%s." % (qname + "." + domain))) - # Add CardDAV/CalDAV SRV records on the non-primary hostname that points to the primary hostname. + # Add CardDAV/CalDAV SRV records on the non-primary hostname that points to the primary hostname + # for autoconfiguration of mail clients (so only domains hosting user accounts need it). # The SRV record format is priority (0, whatever), weight (0, whatever), port, service provider hostname (w/ trailing dot). - if domain != env["PRIMARY_HOSTNAME"]: + if domain != env["PRIMARY_HOSTNAME"] and domain in get_mail_domains(env, users_only=True): for dav in ("card", "cal"): qname = "_" + dav + "davs._tcp" if not has_rec(qname, "SRV"): records.append((qname, "SRV", "0 0 443 " + env["PRIMARY_HOSTNAME"] + ".", "Recommended. Specifies the hostname of the server that handles CardDAV/CalDAV services for email addresses on this domain.")) - # Adds autoconfiguration A records for all domains. + # Adds autoconfiguration A records for all domains that there are user accounts at. # This allows the following clients to automatically configure email addresses in the respective applications. # autodiscover.* - Z-Push ActiveSync Autodiscover # autoconfig.* - Thunderbird Autoconfig - autodiscover_records = [ - ("autodiscover", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Z-Push ActiveSync Autodiscover."), - ("autodiscover", "AAAA", env["PUBLIC_IPV6"], "Provides email configuration autodiscovery support for Z-Push ActiveSync Autodiscover."), - ("autoconfig", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Thunderbird Autoconfig."), - ("autoconfig", "AAAA", env["PUBLIC_IPV6"], "Provides email configuration autodiscovery support for Thunderbird Autoconfig.") - ] - for qname, rtype, value, explanation in autodiscover_records: - if value is None or value.strip() == "": continue # skip IPV6 if not set - if not has_rec(qname, rtype): - records.append((qname, rtype, value, explanation)) + if domain in get_mail_domains(env, users_only=True): + autodiscover_records = [ + ("autodiscover", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Z-Push ActiveSync Autodiscover."), + ("autodiscover", "AAAA", env["PUBLIC_IPV6"], "Provides email configuration autodiscovery support for Z-Push ActiveSync Autodiscover."), + ("autoconfig", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Thunderbird Autoconfig."), + ("autoconfig", "AAAA", env["PUBLIC_IPV6"], "Provides email configuration autodiscovery support for Thunderbird Autoconfig.") + ] + for qname, rtype, value, explanation in autodiscover_records: + if value is None or value.strip() == "": continue # skip IPV6 if not set + if not has_rec(qname, rtype): + records.append((qname, rtype, value, explanation)) # If this is a domain name that there are email addresses configured for, i.e. "something@" # this domain name, then the domain name is a MTA-STS (https://tools.ietf.org/html/rfc8461) diff --git a/management/mailconfig.py b/management/mailconfig.py index 5f253c14..dd597cd6 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -258,13 +258,15 @@ def get_domain(emailaddr, as_unicode=True): pass return ret -def get_mail_domains(env, filter_aliases=lambda alias : True): +def get_mail_domains(env, filter_aliases=lambda alias : True, users_only=False): # Returns the domain names (IDNA-encoded) of all of the email addresses - # configured on the system. - return set( - [get_domain(login, as_unicode=False) for login in get_mail_users(env)] - + [get_domain(address, as_unicode=False) for address, *_ in get_mail_aliases(env) if filter_aliases(address) ] - ) + # configured on the system. If users_only is True, only return domains + # with email addresses that correspond to user accounts. + domains = [] + domains.extend([get_domain(login, as_unicode=False) for login in get_mail_users(env)]) + if not users_only: + domains.extend([get_domain(address, as_unicode=False) for address, *_ in get_mail_aliases(env) if filter_aliases(address) ]) + return set(domains) def add_mail_user(email, pw, privs, env): # validate email diff --git a/management/web_update.py b/management/web_update.py index 4a07dc9e..66340619 100644 --- a/management/web_update.py +++ b/management/web_update.py @@ -24,13 +24,13 @@ def get_web_domains(env, include_www_redirects=True, exclude_dns_elsewhere=True) # the topmost of each domain we serve. domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env)) - # Add Autoconfiguration domains, allowing us to serve correct SSL certs. + # Add Autoconfiguration domains for domains that there are user accounts at: # 'autoconfig.' for Mozilla Thunderbird auto setup. # 'autodiscover.' for Activesync autodiscovery. - domains |= set('autoconfig.' + maildomain for maildomain in get_mail_domains(env)) - domains |= set('autodiscover.' + maildomain for maildomain in get_mail_domains(env)) + domains |= set('autoconfig.' + maildomain for maildomain in get_mail_domains(env, users_only=True)) + domains |= set('autodiscover.' + maildomain for maildomain in get_mail_domains(env, users_only=True)) - # 'mta-sts.' for MTA-STS support. + # 'mta-sts.' for MTA-STS support for all domains that have email addresses. domains |= set('mta-sts.' + maildomain for maildomain in get_mail_domains(env)) if exclude_dns_elsewhere: @@ -158,9 +158,23 @@ def make_domain_config(domain, templates, ssl_certificates, env): # any proxy or redirect here? for path, url in yaml.get("proxies", {}).items(): + # Parse some flags in the fragment of the URL. + pass_http_host_header = False + m = re.search("#(.*)$", url) + if m: + for flag in m.group(1).split(","): + if flag == "pass-http-host": + pass_http_host_header = True + url = re.sub("#(.*)$", "", url) + nginx_conf_extra += "\tlocation %s {" % path nginx_conf_extra += "\n\t\tproxy_pass %s;" % url + if pass_http_host_header: + nginx_conf_extra += "\n\t\tproxy_set_header Host $http_host;" nginx_conf_extra += "\n\t\tproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;" + nginx_conf_extra += "\n\t\tproxy_set_header X-Forwarded-Host $http_host;" + nginx_conf_extra += "\n\t\tproxy_set_header X-Forwarded-Proto $scheme;" + nginx_conf_extra += "\n\t\tproxy_set_header X-Real-IP $remote_addr;" nginx_conf_extra += "\n\t}\n" for path, alias in yaml.get("aliases", {}).items(): nginx_conf_extra += "\tlocation %s {" % path diff --git a/setup/bootstrap.sh b/setup/bootstrap.sh index 4fcb85cc..6aae9500 100644 --- a/setup/bootstrap.sh +++ b/setup/bootstrap.sh @@ -20,7 +20,7 @@ if [ -z "$TAG" ]; then # want to display in status checks. if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then # This machine is running Ubuntu 18.04. - TAG=v0.45 + TAG=v0.46 elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then # This machine is running Ubuntu 14.04. diff --git a/setup/functions.sh b/setup/functions.sh index b36d14bc..90c4c55d 100644 --- a/setup/functions.sh +++ b/setup/functions.sh @@ -136,7 +136,14 @@ function get_default_privateip { function ufw_allow { if [ -z "${DISABLE_FIREWALL:-}" ]; then # ufw has completely unhelpful output - ufw allow $1 > /dev/null; + ufw allow "$1" > /dev/null; + fi +} + +function ufw_limit { + if [ -z "${DISABLE_FIREWALL:-}" ]; then + # ufw has completely unhelpful output + ufw limit "$1" > /dev/null; fi } diff --git a/setup/system.sh b/setup/system.sh index 28043b16..4d33deb6 100755 --- a/setup/system.sh +++ b/setup/system.sh @@ -256,7 +256,7 @@ if [ -z "${DISABLE_FIREWALL:-}" ]; then apt_install ufw # Allow incoming connections to SSH. - ufw_allow ssh; + ufw_limit ssh; # ssh might be running on an alternate port. Use sshd -T to dump sshd's #NODOC # settings, find the port it is supposedly running on, and open that port #NODOC @@ -266,7 +266,7 @@ if [ -z "${DISABLE_FIREWALL:-}" ]; then if [ "$SSH_PORT" != "22" ]; then echo Opening alternate SSH port $SSH_PORT. #NODOC - ufw_allow $SSH_PORT #NODOC + ufw_limit $SSH_PORT #NODOC fi fi diff --git a/setup/webmail.sh b/setup/webmail.sh index 30014122..fab61445 100755 --- a/setup/webmail.sh +++ b/setup/webmail.sh @@ -28,8 +28,8 @@ apt_install \ # Install Roundcube from source if it is not already present or if it is out of date. # Combine the Roundcube version number with the commit hash of plugins to track # whether we have the latest version of everything. -VERSION=1.4.4 -HASH=4e425263f5bec27d39c07bde524f421bda205c07 +VERSION=1.4.6 +HASH=44961ef62bb9c9875141ca34704bbc7d6f36373d PERSISTENT_LOGIN_VERSION=6b3fc450cae23ccb2f393d0ef67aa319e877e435 HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5 CARDDAV_VERSION=3.0.3 @@ -160,7 +160,7 @@ mkdir -p /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundc chown -R www-data.www-data /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundcube # Ensure the log file monitored by fail2ban exists, or else fail2ban can't start. -sudo -u www-data touch /var/log/roundcubemail/errors +sudo -u www-data touch /var/log/roundcubemail/errors.log # Password changing plugin settings # The config comes empty by default, so we need the settings diff --git a/tools/readable_bash.py b/tools/readable_bash.py index 5207a78a..1fcdd5cd 100644 --- a/tools/readable_bash.py +++ b/tools/readable_bash.py @@ -58,7 +58,7 @@ def generate_documentation(): } .prose { - padding-top: 1em; + padding-top: 1em; padding-bottom: 1em; } .terminal { @@ -261,6 +261,10 @@ class UfwAllow(Grammar): grammar = (ZERO_OR_MORE(SPACE), L("ufw_allow "), REST_OF_LINE, EOL) def value(self): return shell_line("ufw allow " + self[2].string) +class UfwLimit(Grammar): + grammar = (ZERO_OR_MORE(SPACE), L("ufw_limit "), REST_OF_LINE, EOL) + def value(self): + return shell_line("ufw limit " + self[2].string) class RestartService(Grammar): grammar = (ZERO_OR_MORE(SPACE), L("restart_service "), REST_OF_LINE, EOL) def value(self): @@ -275,7 +279,7 @@ class OtherLine(Grammar): return "
" + recode_bash(self.string.strip()) + "
\n" class BashElement(Grammar): - grammar = Comment | CatEOF | EchoPipe | EchoLine | HideOutput | EditConf | SedReplace | AptGet | UfwAllow | RestartService | OtherLine + grammar = Comment | CatEOF | EchoPipe | EchoLine | HideOutput | EditConf | SedReplace | AptGet | UfwAllow | UfwLimit | RestartService | OtherLine def value(self): return self[0].value()