mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2026-03-12 17:07:23 +01:00
Compare commits
3 Commits
v68
...
d0e4671000
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0e4671000 | ||
|
|
d1c63b6517 | ||
|
|
09d1feab80 |
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,31 +1,6 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Version 68 (April 1, 2024)
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
Package updates:
|
|
||||||
|
|
||||||
* Roundcube updated to version 1.6.6.
|
|
||||||
* Nextcloud is updated to version 26.0.12.
|
|
||||||
|
|
||||||
Mail:
|
|
||||||
|
|
||||||
* Updated postfix's configuration to guard against SMTP smuggling to the long-term fix (https://www.postfix.org/smtp-smuggling.html).
|
|
||||||
|
|
||||||
Control Panel:
|
|
||||||
|
|
||||||
* Improved reporting of Spamhaus response codes.
|
|
||||||
* Improved detection of SSH port.
|
|
||||||
* Fixed an error if last saved status check results were corrupted.
|
|
||||||
* Other minor fixes.
|
|
||||||
|
|
||||||
Other:
|
|
||||||
|
|
||||||
* fail2ban is updated to see "HTTP/2.0" requests to munin also.
|
|
||||||
* Internal improvements to the code to make it more reliable and readable.
|
|
||||||
|
|
||||||
|
|
||||||
Version 67 (December 22, 2023)
|
Version 67 (December 22, 2023)
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ Clone this repository and checkout the tag corresponding to the most recent rele
|
|||||||
|
|
||||||
$ git clone https://github.com/mail-in-a-box/mailinabox
|
$ git clone https://github.com/mail-in-a-box/mailinabox
|
||||||
$ cd mailinabox
|
$ cd mailinabox
|
||||||
$ git checkout v68
|
$ git checkout v67
|
||||||
|
|
||||||
Begin the installation.
|
Begin the installation.
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import ipaddress
|
|||||||
import rtyaml
|
import rtyaml
|
||||||
import dns.resolver
|
import dns.resolver
|
||||||
|
|
||||||
from utils import shell, load_env_vars_from_file, safe_domain_name, sort_domains, get_ssh_port
|
from utils import shell, load_env_vars_from_file, safe_domain_name, sort_domains
|
||||||
from ssl_certificates import get_ssl_certificates, check_certificate
|
from ssl_certificates import get_ssl_certificates, check_certificate
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
@@ -448,11 +448,14 @@ def build_sshfp_records():
|
|||||||
# if SSH has been configured to listen on a nonstandard port, we must
|
# if SSH has been configured to listen on a nonstandard port, we must
|
||||||
# specify that port to sshkeyscan.
|
# specify that port to sshkeyscan.
|
||||||
|
|
||||||
port = get_ssh_port()
|
port = 22
|
||||||
|
with open('/etc/ssh/sshd_config', encoding="utf-8") as f:
|
||||||
# If nothing returned, SSH is probably not installed.
|
for line in f:
|
||||||
if not port:
|
s = line.rstrip().split()
|
||||||
return
|
if len(s) == 2 and s[0] == 'Port':
|
||||||
|
with contextlib.suppress(ValueError):
|
||||||
|
port = int(s[1])
|
||||||
|
break
|
||||||
|
|
||||||
keys = shell("check_output", ["ssh-keyscan", "-4", "-t", "rsa,dsa,ecdsa,ed25519", "-p", str(port), "localhost"])
|
keys = shell("check_output", ["ssh-keyscan", "-4", "-t", "rsa,dsa,ecdsa,ed25519", "-p", str(port), "localhost"])
|
||||||
keys = sorted(keys.split("\n"))
|
keys = sorted(keys.split("\n"))
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from web_update import get_web_domains, get_domains_with_a_records
|
|||||||
from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate
|
from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate
|
||||||
from mailconfig import get_mail_domains, get_mail_aliases
|
from mailconfig import get_mail_domains, get_mail_aliases
|
||||||
|
|
||||||
from utils import shell, sort_domains, load_env_vars_from_file, load_settings, get_ssh_port, get_ssh_config_value
|
from utils import shell, sort_domains, load_env_vars_from_file, load_settings
|
||||||
|
|
||||||
def get_services():
|
def get_services():
|
||||||
return [
|
return [
|
||||||
@@ -65,6 +65,24 @@ def run_checks(rounded_values, env, output, pool, domains_to_check=None):
|
|||||||
run_network_checks(env, output)
|
run_network_checks(env, output)
|
||||||
run_domain_checks(rounded_values, env, output, pool, domains_to_check=domains_to_check)
|
run_domain_checks(rounded_values, env, output, pool, domains_to_check=domains_to_check)
|
||||||
|
|
||||||
|
def get_ssh_port():
|
||||||
|
# Returns ssh port
|
||||||
|
try:
|
||||||
|
output = shell('check_output', ['sshd', '-T'])
|
||||||
|
except FileNotFoundError:
|
||||||
|
# sshd is not installed. That's ok.
|
||||||
|
return None
|
||||||
|
|
||||||
|
returnNext = False
|
||||||
|
for e in output.split():
|
||||||
|
if returnNext:
|
||||||
|
return int(e)
|
||||||
|
if e == "port":
|
||||||
|
returnNext = True
|
||||||
|
|
||||||
|
# Did not find port!
|
||||||
|
return None
|
||||||
|
|
||||||
def run_services_checks(env, output, pool):
|
def run_services_checks(env, output, pool):
|
||||||
# Check that system services are running.
|
# Check that system services are running.
|
||||||
all_running = True
|
all_running = True
|
||||||
@@ -188,15 +206,21 @@ def is_port_allowed(ufw, port):
|
|||||||
return any(re.match(str(port) +"[/ \t].*", item) for item in ufw)
|
return any(re.match(str(port) +"[/ \t].*", item) for item in ufw)
|
||||||
|
|
||||||
def check_ssh_password(env, output):
|
def check_ssh_password(env, output):
|
||||||
config_value = get_ssh_config_value("passwordauthentication")
|
# Check that SSH login with password is disabled. The openssh-server
|
||||||
if config_value:
|
# package may not be installed so check that before trying to access
|
||||||
if config_value == "no":
|
# the configuration file.
|
||||||
output.print_ok("SSH disallows password-based login.")
|
if not os.path.exists("/etc/ssh/sshd_config"):
|
||||||
else:
|
return
|
||||||
|
with open("/etc/ssh/sshd_config", encoding="utf-8") as f:
|
||||||
|
sshd = f.read()
|
||||||
|
if re.search("\nPasswordAuthentication\\s+yes", sshd) \
|
||||||
|
or not re.search("\nPasswordAuthentication\\s+no", sshd):
|
||||||
output.print_error("""The SSH server on this machine permits password-based login. A more secure
|
output.print_error("""The SSH server on this machine permits password-based login. A more secure
|
||||||
way to log in is using a public key. Add your SSH public key to $HOME/.ssh/authorized_keys, check
|
way to log in is using a public key. Add your SSH public key to $HOME/.ssh/authorized_keys, check
|
||||||
that you can log in without a password, set the option 'PasswordAuthentication no' in
|
that you can log in without a password, set the option 'PasswordAuthentication no' in
|
||||||
/etc/ssh/sshd_config, and then restart the openssh via 'sudo service ssh restart'.""")
|
/etc/ssh/sshd_config, and then restart the openssh via 'sudo service ssh restart'.""")
|
||||||
|
else:
|
||||||
|
output.print_ok("SSH disallows password-based login.")
|
||||||
|
|
||||||
def is_reboot_needed_due_to_package_installation():
|
def is_reboot_needed_due_to_package_installation():
|
||||||
return os.path.exists("/var/run/reboot-required")
|
return os.path.exists("/var/run/reboot-required")
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<h4>Automatic configuration</h4>
|
<h4>Automatic configuration</h4>
|
||||||
|
|
||||||
<p>iOS and macOS only: Open <a style="font-weight: bold" href="https://{{hostname}}/mailinabox.mobileconfig">this configuration link</a> on your iOS device or on your Mac desktop to easily set up mail (IMAP/SMTP), Contacts, and Calendar. Your username is your whole email address.</p>
|
<p>iOS and OS X only: Open <a style="font-weight: bold" href="https://{{hostname}}/mailinabox.mobileconfig">this configuration link</a> on your iOS device or on your Mac desktop to easily set up mail (IMAP/SMTP), Contacts, and Calendar. Your username is your whole email address.</p>
|
||||||
|
|
||||||
<h4>Manual configuration</h4>
|
<h4>Manual configuration</h4>
|
||||||
|
|
||||||
|
|||||||
@@ -179,34 +179,6 @@ def wait_for_service(port, public, env, timeout):
|
|||||||
return False
|
return False
|
||||||
time.sleep(min(timeout/4, 1))
|
time.sleep(min(timeout/4, 1))
|
||||||
|
|
||||||
def get_ssh_port():
|
|
||||||
port_value = get_ssh_config_value("port")
|
|
||||||
|
|
||||||
if port_value:
|
|
||||||
return int(port_value)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_ssh_config_value(parameter_name):
|
|
||||||
# Returns ssh configuration value for the provided parameter
|
|
||||||
try:
|
|
||||||
output = shell('check_output', ['sshd', '-T'])
|
|
||||||
except FileNotFoundError:
|
|
||||||
# sshd is not installed. That's ok.
|
|
||||||
return None
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
# error while calling shell command
|
|
||||||
return None
|
|
||||||
|
|
||||||
for line in output.split("\n"):
|
|
||||||
if " " not in line: continue # there's a blank line at the end
|
|
||||||
key, values = line.split(" ", 1)
|
|
||||||
if key == parameter_name:
|
|
||||||
return values # space-delimited if there are multiple values
|
|
||||||
|
|
||||||
# Did not find the parameter!
|
|
||||||
return None
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from web_update import get_web_domains
|
from web_update import get_web_domains
|
||||||
env = load_environment()
|
env = load_environment()
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ if [ -z "$TAG" ]; then
|
|||||||
if [ "$UBUNTU_VERSION" == "Ubuntu 22.04 LTS" ]; then
|
if [ "$UBUNTU_VERSION" == "Ubuntu 22.04 LTS" ]; then
|
||||||
# This machine is running Ubuntu 22.04, which is supported by
|
# This machine is running Ubuntu 22.04, which is supported by
|
||||||
# Mail-in-a-Box versions 60 and later.
|
# Mail-in-a-Box versions 60 and later.
|
||||||
TAG=v68
|
TAG=v67
|
||||||
elif [ "$UBUNTU_VERSION" == "Ubuntu 18.04 LTS" ]; then
|
elif [ "$UBUNTU_VERSION" == "Ubuntu 18.04 LTS" ]; then
|
||||||
# This machine is running Ubuntu 18.04, which is supported by
|
# This machine is running Ubuntu 18.04, which is supported by
|
||||||
# Mail-in-a-Box versions 0.40 through 5x.
|
# Mail-in-a-Box versions 0.40 through 5x.
|
||||||
|
|||||||
@@ -70,16 +70,10 @@ tools/editconf.py /etc/postfix/main.cf \
|
|||||||
bounce_queue_lifetime=1d
|
bounce_queue_lifetime=1d
|
||||||
|
|
||||||
# Guard against SMTP smuggling
|
# Guard against SMTP smuggling
|
||||||
# This "long-term" fix is recommended at https://www.postfix.org/smtp-smuggling.html.
|
# This short-term workaround is recommended at https://www.postfix.org/smtp-smuggling.html
|
||||||
# This beecame supported in a backported fix in package version 3.6.4-1ubuntu1.3. It is
|
|
||||||
# unnecessary in Postfix 3.9+ where this is the default. The "short-term" workarounds
|
|
||||||
# that we previously had are reverted to postfix defaults (though smtpd_discard_ehlo_keywords
|
|
||||||
# was never included in a released version of Mail-in-a-Box).
|
|
||||||
tools/editconf.py /etc/postfix/main.cf -e \
|
|
||||||
smtpd_data_restrictions= \
|
|
||||||
smtpd_discard_ehlo_keywords=
|
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
tools/editconf.py /etc/postfix/main.cf \
|
||||||
smtpd_forbid_bare_newline=normalize
|
smtpd_data_restrictions=reject_unauth_pipelining \
|
||||||
|
smtpd_discard_ehlo_keywords="chunking, silent-discard"
|
||||||
|
|
||||||
# ### Outgoing Mail
|
# ### Outgoing Mail
|
||||||
|
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ sed -i.miabold 's/^[^#]\+.\+PRAGMA journal_mode = WAL.\+$/#&/' \
|
|||||||
# Because Roundcube wants to set the PRAGMA we just deleted from the source, we apply it here
|
# Because Roundcube wants to set the PRAGMA we just deleted from the source, we apply it here
|
||||||
# to the roundcube database (see https://github.com/roundcube/roundcubemail/issues/8035)
|
# to the roundcube database (see https://github.com/roundcube/roundcubemail/issues/8035)
|
||||||
# Database should exist, created by migration script
|
# Database should exist, created by migration script
|
||||||
hide_output sqlite3 $STORAGE_ROOT/mail/roundcube/roundcube.sqlite 'PRAGMA journal_mode=WAL;'
|
sqlite3 $STORAGE_ROOT/mail/roundcube/roundcube.sqlite 'PRAGMA journal_mode=WAL;' | 2>&1
|
||||||
|
|
||||||
# Enable PHP modules.
|
# Enable PHP modules.
|
||||||
phpenmod -v $PHP_VER imap
|
phpenmod -v $PHP_VER imap
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import sys, re
|
|||||||
|
|
||||||
# sanity check
|
# sanity check
|
||||||
if len(sys.argv) < 3:
|
if len(sys.argv) < 3:
|
||||||
print("usage: python3 editconf.py /etc/file.conf [-e] [-s] [-w] [-c <CHARACTER>] [-t] NAME=VAL [NAME=VAL ...]")
|
print("usage: python3 editconf.py /etc/file.conf [-s] [-w] [-c <CHARACTER>] [-t] NAME=VAL [NAME=VAL ...]")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# parse command line arguments
|
# parse command line arguments
|
||||||
|
|||||||
@@ -227,10 +227,12 @@ class EditConf(Grammar):
|
|||||||
options = []
|
options = []
|
||||||
eq = "="
|
eq = "="
|
||||||
if self[3] and "-s" in self[3].string: eq = " "
|
if self[3] and "-s" in self[3].string: eq = " "
|
||||||
|
try:
|
||||||
for opt in re.split("\s+", self[4].string):
|
for opt in re.split("\s+", self[4].string):
|
||||||
k, v = opt.split("=", 1)
|
k, v = opt.split("=", 1) # try except , else err: 1 arg missing
|
||||||
v = re.sub(r"\n+", "", fixup_tokens(v)) # not sure why newlines are getting doubled
|
v = re.sub(r"\n+", "", fixup_tokens(v)) # not sure why newlines are getting doubled
|
||||||
options.append("%s%s%s" % (k, eq, v))
|
options.append("%s%s%s" % (k, eq, v))
|
||||||
|
except: pass
|
||||||
return "<div class='write-to'><div class='filename'>" + self[1].string + " <span>(change settings)</span></div><pre>" + "\n".join(cgi.escape(s) for s in options) + "</pre></div>\n"
|
return "<div class='write-to'><div class='filename'>" + self[1].string + " <span>(change settings)</span></div><pre>" + "\n".join(cgi.escape(s) for s in options) + "</pre></div>\n"
|
||||||
|
|
||||||
class CaptureOutput(Grammar):
|
class CaptureOutput(Grammar):
|
||||||
|
|||||||
Reference in New Issue
Block a user