From cfc8fb484cfdb3ee581630a869fd93d4e1b3cb03 Mon Sep 17 00:00:00 2001
From: Marcus Bointon <marcus@synchromedia.co.uk>
Date: Sun, 7 Jun 2020 15:47:51 +0200
Subject: [PATCH 1/8] Add rate limiting of SSH in the firewall (#1770)

See #1767.
---
 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 "<pre class='shell'><div>" + recode_bash(self.string.strip()) + "</div></pre>\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 339c330b4ff61e6bf116d98947e4a8e93e1b72f8 Mon Sep 17 00:00:00 2001
From: Faye Duxovni <duxovni@duxovni.org>
Date: Sun, 7 Jun 2020 09:50:04 -0400
Subject: [PATCH 2/8] Fix roundcube error log file path in setup script (#1775)

---
 setup/webmail.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup/webmail.sh b/setup/webmail.sh
index 20d43c57..bd31e221 100755
--- a/setup/webmail.sh
+++ b/setup/webmail.sh
@@ -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

From df9bb263dc7983b71b5a1ecd400f5ae10ab16fbe Mon Sep 17 00:00:00 2001
From: Vasek Sraier <vakabus@users.noreply.github.com>
Date: Sun, 7 Jun 2020 15:56:45 +0200
Subject: [PATCH 3/8] daily_tasks.sh: redirect stderr to stdout (#1768)

When the management commands fail, they can print something to the standard error output.
The administrator would never notice, because it wouldn't be send to him with the usual emails.
Fixes #1763
---
 management/daily_tasks.sh | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

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"

From 41642f2f5947f64a267130590afd8d39aee17cb3 Mon Sep 17 00:00:00 2001
From: Faye Duxovni <duxovni@duxovni.org>
Date: Sun, 7 Jun 2020 09:50:04 -0400
Subject: [PATCH 4/8] [backport] Fix roundcube error log file path in setup
 script (#1775)

---
 setup/webmail.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup/webmail.sh b/setup/webmail.sh
index 20d43c57..bd31e221 100755
--- a/setup/webmail.sh
+++ b/setup/webmail.sh
@@ -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

From e03a6541ced593b6c19a875f3fe59139d193a41c Mon Sep 17 00:00:00 2001
From: Joshua Tauberer <jt@occams.info>
Date: Fri, 5 Jun 2020 13:45:50 -0400
Subject: [PATCH 5/8] Don't make autoconfig/autodiscover subdomains and SRV
 records when the parent domain has no user accounts

These subdomains/records are for automatic configuration of mail clients, but if there are no user accounts on a domain, there is no need to publish a DNS record, provision a TLS certificate, or create an nginx server config block.
---
 CHANGELOG.md             |  4 ++++
 management/dns_update.py | 28 +++++++++++++++-------------
 management/mailconfig.py | 14 ++++++++------
 management/web_update.py |  8 ++++----
 4 files changed, 31 insertions(+), 23 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 01f860fe..04cfb753 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,10 @@ 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.45 (May 16, 2020)
 --------------------
 
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..78f86f4c 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:

From 9db2fc7f0551b6ea9b7c73f447495fda722473fb Mon Sep 17 00:00:00 2001
From: Joshua Tauberer <jt@occams.info>
Date: Sun, 7 Jun 2020 09:45:04 -0400
Subject: [PATCH 6/8] In web proxies, add X-{Forwarded-{Host,Proto},Real-IP}
 and 'proxy_set_header Host' when there is a flag

Merges #1432, more or less.
---
 management/web_update.py | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/management/web_update.py b/management/web_update.py
index 78f86f4c..66340619 100644
--- a/management/web_update.py
+++ b/management/web_update.py
@@ -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

From 12d60d102b0cddf6a09d8b68ba2d0a2531efd0e3 Mon Sep 17 00:00:00 2001
From: Joshua Tauberer <jt@occams.info>
Date: Thu, 11 Jun 2020 12:19:00 -0400
Subject: [PATCH 7/8] Update Roundcube to 1.4.6

Fixes #1776
---
 CHANGELOG.md     | 7 +++++++
 setup/webmail.sh | 4 ++--
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3cd9e724..23ddd136 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,13 @@
 CHANGELOG
 =========
 
+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/setup/webmail.sh b/setup/webmail.sh
index bd31e221..7054e38e 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

From 049bfb6f7f0ce918e5437bcf3a18f66ceef2ea3d Mon Sep 17 00:00:00 2001
From: Joshua Tauberer <jt@occams.info>
Date: Thu, 11 Jun 2020 12:23:18 -0400
Subject: [PATCH 8/8] v0.46

---
 README.md          | 4 ++--
 setup/bootstrap.sh | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index e787c8d8..1d4452b8 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 <jt@occams.info>" 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 <jt@occams.info>"
 	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.45
+	$ git checkout v0.46
 
 Begin the installation.
 
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.