From 6fd3195275fdfef3edd748a44b70dc830f320802 Mon Sep 17 00:00:00 2001 From: Joshua Tauberer Date: Fri, 12 Jun 2020 13:09:11 -0400 Subject: [PATCH 01/12] Fix MTA-STS policy id so it does not have invalid characters, fixes #1779 --- management/dns_update.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/management/dns_update.py b/management/dns_update.py index 80273a12..2fb7b1b8 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -340,11 +340,13 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en # 'break' was not encountered above, so both domains are good mta_sts_enabled = True if mta_sts_enabled: - # Compute a up-to-32-character hash of the policy file. We'll take a SHA-1 hash of the policy - # file (20 bytes) and encode it as base-64 (60 bytes) but then just take its first 20 bytes - # which should be sufficient to change whenever the policy file changes. + # Compute an up-to-32-character hash of the policy file. We'll take a SHA-1 hash of the policy + # file (20 bytes) and encode it as base-64 (28 bytes, using alphanumeric alternate characters + # instead of '+' and '/' which are not allowed in an MTA-STS policy id) but then just take its + # first 20 characters, which is more than sufficient to change whenever the policy file changes + # (and ensures any '=' padding at the end of the base64 encoding is dropped). with open("/var/lib/mailinabox/mta-sts.txt", "rb") as f: - mta_sts_policy_id = base64.b64encode(hashlib.sha1(f.read()).digest()).decode("ascii")[0:20] + mta_sts_policy_id = base64.b64encode(hashlib.sha1(f.read()).digest(), altchars=b"AA").decode("ascii")[0:20] mta_sts_records.extend([ ("_mta-sts", "TXT", "v=STSv1; id=" + mta_sts_policy_id, "Optional. Part of the MTA-STS policy for incoming mail. If set, a MTA-STS policy must also be published.") ]) From e6102eacfb1637d49eed62c6a2b9d499803b9cf8 Mon Sep 17 00:00:00 2001 From: David Duque Date: Wed, 8 Jul 2020 23:26:47 +0100 Subject: [PATCH 02/12] AXFR Transfers (for secondary DNS servers): Allow IPv6 addresses (#1787) --- management/dns_update.py | 9 +++++---- management/templates/custom-dns.html | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/management/dns_update.py b/management/dns_update.py index 2fb7b1b8..19830749 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -967,18 +967,19 @@ def set_secondary_dns(hostnames, env): try: response = resolver.query(item, "A") except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): - raise ValueError("Could not resolve the IP address of %s." % item) + try: + response = resolver.query(item, "AAAA") + except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + raise ValueError("Could not resolve the IP address of %s." % item) else: # Validate IP address. try: if "/" in item[4:]: v = ipaddress.ip_network(item[4:]) # raises a ValueError if there's a problem - if not isinstance(v, ipaddress.IPv4Network): raise ValueError("That's an IPv6 subnet.") else: v = ipaddress.ip_address(item[4:]) # raises a ValueError if there's a problem - if not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.") except ValueError: - raise ValueError("'%s' is not an IPv4 address or subnet." % item[4:]) + raise ValueError("'%s' is not an IPv4 or IPv6 address or subnet." % item[4:]) # Set. set_custom_dns_record("_secondary_nameserver", "A", " ".join(hostnames), "set", env) diff --git a/management/templates/custom-dns.html b/management/templates/custom-dns.html index a2d5042d..6984b081 100644 --- a/management/templates/custom-dns.html +++ b/management/templates/custom-dns.html @@ -90,7 +90,7 @@

Multiple secondary servers can be separated with commas or spaces (i.e., ns2.hostingcompany.com ns3.hostingcompany.com). - To enable zone transfers to additional servers without listing them as secondary nameservers, add an IP address or subnet using xfr:10.20.30.40 or xfr:10.20.30.40/24. + To enable zone transfers to additional servers without listing them as secondary nameservers, add an IP address or subnet using xfr:10.20.30.40 or xfr:10.0.0.0/8.

").text(r));
+          show_modal_error("Remove Alias", $("
").text(r));
           show_aliases();
         });
     });

From 1098e2b48e3cf542f114caed591337d3cddae762 Mon Sep 17 00:00:00 2001
From: Hilko 
Date: Wed, 29 Jul 2020 16:03:33 +0200
Subject: [PATCH 07/12] Add noindex to www_default meta tags (#1791)

---
 conf/www_default.html | 1 +
 1 file changed, 1 insertion(+)

diff --git a/conf/www_default.html b/conf/www_default.html
index edefc428..68d0366b 100644
--- a/conf/www_default.html
+++ b/conf/www_default.html
@@ -1,6 +1,7 @@
 
 	
 		this is a mail-in-a-box
+		
 	
 	
 		

this is a mail-in-a-box

From 2c34a6df2bf0a319502c251eb0f310002f996ca3 Mon Sep 17 00:00:00 2001 From: Hilko Date: Sun, 26 Jul 2020 17:50:59 +0200 Subject: [PATCH 08/12] Update roundcube to 1.4.7 --- setup/webmail.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup/webmail.sh b/setup/webmail.sh index 7054e38e..f2202244 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.6 -HASH=44961ef62bb9c9875141ca34704bbc7d6f36373d +VERSION=1.4.7 +HASH=49F194D25AC7B9BF175BD52285BB61CDE7BAED44 PERSISTENT_LOGIN_VERSION=6b3fc450cae23ccb2f393d0ef67aa319e877e435 HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5 CARDDAV_VERSION=3.0.3 From 4bbe4af37741b9dba3c766657ca36e198532b506 Mon Sep 17 00:00:00 2001 From: Joshua Tauberer Date: Wed, 29 Jul 2020 10:11:47 -0400 Subject: [PATCH 09/12] Update CHANGELOG --- CHANGELOG.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36656e53..be38130e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,29 @@ In Development 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. +* 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, allowing senders to know that an encrypted connection should be enforced. * MTA-STS reporting is enabled with reports sent to administrator@ the primary hostname. +* The per-IP connection limit to the IMAP server has been doubled to allow more devices to connect at once, especially with multiple users behind a NAT. 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. +* IPv6 addresses can now be specified for secondary DNS nameservers in the control panel. + +TLS: + +* TLS certificates are now provisioned in groups by parent domain to limit easy domain enumeration and make provisioning more resilient to errors for particular domains. + +Control Panel: + +* User passwords can now have spaces. +* Status checks for automatic subdomains have been moved into the section for the parent domain. +* Typo fixed. + +Web: + +* The default web page served on fresh installations now adds the `noindex` meta tag. +* The HSTS header is revised to also be sent on non-success responses. v0.46 (June 11, 2020) --------------------- From f253c400120da768fd6268f3df5220b45d4dc24d Mon Sep 17 00:00:00 2001 From: Marcus Bointon Date: Sun, 7 Jun 2020 15:47:51 +0200 Subject: [PATCH 10/12] [backport] Add rate limiting of SSH in the firewall (#1770) See #1767. Backport of cfc8fb484cfdb3ee581630a869fd93d4e1b3cb03. --- setup/functions.sh | 9 ++++++++- setup/system.sh | 4 ++-- tools/readable_bash.py | 8 ++++++-- 3 files changed, 16 insertions(+), 5 deletions(-) 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/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() From 56d0289ed98e781ff759e62c40ff71327103fd48 Mon Sep 17 00:00:00 2001 From: hija Date: Sun, 26 Jul 2020 18:57:04 +0200 Subject: [PATCH 11/12] v0.47 --- CHANGELOG.md | 8 ++++++++ README.md | 4 ++-- setup/bootstrap.sh | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23ddd136..e9b8b759 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ CHANGELOG ========= +v0.47 (July 29, 2020) +--------------------- + +Security fixes: + +* Roundcube is updated to version 1.4.7 fixing a cross-site scripting (XSS) vulnerability with HTML messages with malicious svg/namespace (CVE-2020-15562) (https://roundcube.net/news/2020/07/05/security-updates-1.4.7-1.3.14-and-1.2.11). +* SSH connections are now rate-limited at the firewall level (in addition to fail2ban). + v0.46 (June 11, 2020) --------------------- diff --git a/README.md b/README.md index 1d4452b8..5ef58a29 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,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.46 + $ git verify-tag v0.47 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! @@ -71,7 +71,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.46 + $ git checkout v0.47 Begin the installation. diff --git a/setup/bootstrap.sh b/setup/bootstrap.sh index 6aae9500..098de977 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.46 + TAG=v0.47 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. From 94da7bb088d48ff5d4b87b9bc4a43c5585a51166 Mon Sep 17 00:00:00 2001 From: David Duque Date: Sun, 9 Aug 2020 16:42:39 +0100 Subject: [PATCH 12/12] status_checks.py: Properly terminate the process pools (#1795) * Only spawn a thread pool when strictly needed For --check-primary-hostname, the pool is not used. When exiting, the other processes are left alive and will hang. * Acquire pools with the 'with' statement --- management/daemon.py | 5 ++--- management/status_checks.py | 7 ++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/management/daemon.py b/management/daemon.py index 572b6b4a..b7bf2a66 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -437,9 +437,8 @@ def system_status(): self.items[-1]["extra"].append({ "text": message, "monospace": monospace }) output = WebOutput() # Create a temporary pool of processes for the status checks - pool = multiprocessing.pool.Pool(processes=5) - run_checks(False, env, output, pool) - pool.terminate() + with multiprocessing.pool.Pool(processes=5) as pool: + run_checks(False, env, output, pool) return json_response(output.items) @app.route('/system/updates') diff --git a/management/status_checks.py b/management/status_checks.py index 101a3537..36da034a 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -1021,13 +1021,14 @@ if __name__ == "__main__": from utils import load_environment env = load_environment() - pool = multiprocessing.pool.Pool(processes=10) if len(sys.argv) == 1: - run_checks(False, env, ConsoleOutput(), pool) + with multiprocessing.pool.Pool(processes=10) as pool: + run_checks(False, env, ConsoleOutput(), pool) elif sys.argv[1] == "--show-changes": - run_and_output_changes(env, pool) + with multiprocessing.pool.Pool(processes=10) as pool: + run_and_output_changes(env, pool) elif sys.argv[1] == "--check-primary-hostname": # See if the primary hostname appears resolvable and has a signed certificate.