mirror of
				https://github.com/mail-in-a-box/mailinabox.git
				synced 2025-10-30 18:50:53 +00:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/master'
This commit is contained in:
		
						commit
						2bb7753c10
					
				
							
								
								
									
										27
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @ -6,12 +6,37 @@ 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.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) | ||||
| --------------------- | ||||
|  | ||||
| @ -63,7 +63,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.46 | ||||
| 	$ git verify-tag v0.47 | ||||
| 	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! | ||||
| @ -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.46 | ||||
| 	$ git checkout v0.47 | ||||
| 
 | ||||
| Begin the installation. | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| <html> | ||||
| 	<head> | ||||
| 		<title>this is a mail-in-a-box</title> | ||||
| 		<meta name="robots" content="noindex"> | ||||
| 	</head> | ||||
| 	<body> | ||||
| 		<h1>this is a mail-in-a-box</h1> | ||||
|  | ||||
| @ -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') | ||||
|  | ||||
| @ -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.") | ||||
| 		]) | ||||
| @ -965,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) | ||||
|  | ||||
| @ -605,8 +605,6 @@ def validate_password(pw): | ||||
| 	# validate password | ||||
| 	if pw.strip() == "": | ||||
| 		raise ValueError("No password provided.") | ||||
| 	if re.search(r"[\s]", pw): | ||||
| 		raise ValueError("Passwords cannot contain spaces.") | ||||
| 	if len(pw) < 8: | ||||
| 		raise ValueError("Passwords must be at least eight characters.") | ||||
| 
 | ||||
|  | ||||
| @ -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. | ||||
|  | ||||
| @ -288,7 +288,7 @@ function aliases_remove(elem) { | ||||
|         }, | ||||
|         function(r) { | ||||
|           // Responses are multiple lines of pre-formatted text. | ||||
|           show_modal_error("Remove User", $("<pre/>").text(r)); | ||||
|           show_modal_error("Remove Alias", $("<pre/>").text(r)); | ||||
|           show_aliases(); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
| @ -90,7 +90,7 @@ | ||||
|     <div class="col-sm-offset-1 col-sm-11"> | ||||
|       <p class="small"> | ||||
|         Multiple secondary servers can be separated with commas or spaces (i.e., <code>ns2.hostingcompany.com ns3.hostingcompany.com</code>).  | ||||
|         To enable zone transfers to additional servers without listing them as secondary nameservers, add an IP address or subnet using <code>xfr:10.20.30.40</code> or <code>xfr:10.20.30.40/24</code>. | ||||
|         To enable zone transfers to additional servers without listing them as secondary nameservers, add an IP address or subnet using <code>xfr:10.20.30.40</code> or <code>xfr:10.0.0.0/8</code>. | ||||
|       </p> | ||||
|       <p id="secondarydns-clear-instructions" style="display: none" class="small"> | ||||
|         Clear the input field above and click Update to use this machine itself as secondary DNS, which is the default/normal setup. | ||||
|  | ||||
| @ -188,9 +188,9 @@ def make_domain_config(domain, templates, ssl_certificates, env): | ||||
| 
 | ||||
| 	# Add the HSTS header. | ||||
| 	if hsts == "yes": | ||||
| 		nginx_conf_extra += "add_header Strict-Transport-Security max-age=15768000;\n" | ||||
| 		nginx_conf_extra += "add_header Strict-Transport-Security \"max-age=15768000\" always;\n" | ||||
| 	elif hsts == "preload": | ||||
| 		nginx_conf_extra += "add_header Strict-Transport-Security \"max-age=15768000; includeSubDomains; preload\";\n" | ||||
| 		nginx_conf_extra += "add_header Strict-Transport-Security \"max-age=15768000; includeSubDomains; preload\" always;\n" | ||||
| 
 | ||||
| 	# Add in any user customizations in the includes/ folder. | ||||
| 	nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf") | ||||
|  | ||||
| @ -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. | ||||
|  | ||||
| @ -144,7 +144,7 @@ service imap-login { | ||||
|   } | ||||
| } | ||||
| protocol imap { | ||||
|   mail_max_userip_connections = 20 | ||||
|   mail_max_userip_connections = 40 | ||||
| } | ||||
| EOF | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -33,9 +33,6 @@ def read_password(): | ||||
|         if len(first) < 8: | ||||
|             print("Passwords must be at least eight characters.") | ||||
|             continue | ||||
|         if re.search(r'[\s]', first): | ||||
|             print("Passwords cannot contain spaces.") | ||||
|             continue | ||||
|         second = getpass.getpass(' (again): ') | ||||
|         if first != second: | ||||
|             print("Passwords not the same. Try again.") | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user