1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2026-03-12 17:07:23 +01:00

Merge remote-tracking branch 'upstream/main' into merge-upstream

# Conflicts:
#	.gitignore
#	management/auth.py
#	management/daemon.py
#	management/mail_log.py
#	management/mailconfig.py
#	management/mfa.py
#	management/ssl_certificates.py
#	management/status_checks.py
#	management/utils.py
#	management/web_update.py
#	setup/mail-postfix.sh
#	setup/migrate.py
#	setup/preflight.sh
#	setup/webmail.sh
#	tests/test_mail.py
#	tools/editconf.py
This commit is contained in:
downtownallday
2024-03-12 07:41:14 -04:00
33 changed files with 582 additions and 571 deletions

View File

@@ -15,12 +15,12 @@
# try to log in to.
######################################################################
import sys, os, time, functools
import sys, os, time
# parse command line
if len(sys.argv) != 4:
print("Usage: tests/fail2ban.py \"ssh user@hostname\" hostname owncloud_user")
print('Usage: tests/fail2ban.py "ssh user@hostname" hostname owncloud_user')
sys.exit(1)
ssh_command, hostname, owncloud_user = sys.argv[1:4]
@@ -33,7 +33,6 @@ socket.setdefaulttimeout(10)
class IsBlocked(Exception):
"""Tests raise this exception when it appears that a fail2ban
jail is in effect, i.e. on a connection refused error."""
pass
def smtp_test():
import smtplib
@@ -42,13 +41,14 @@ def smtp_test():
server = smtplib.SMTP(hostname, 587)
except ConnectionRefusedError:
# looks like fail2ban worked
raise IsBlocked()
raise IsBlocked
server.starttls()
server.ehlo_or_helo_if_needed()
try:
server.login("fakeuser", "fakepassword")
raise Exception("authentication didn't fail")
msg = "authentication didn't fail"
raise Exception(msg)
except smtplib.SMTPAuthenticationError:
# athentication should fail
pass
@@ -66,11 +66,12 @@ def imap_test():
M = imaplib.IMAP4_SSL(hostname)
except ConnectionRefusedError:
# looks like fail2ban worked
raise IsBlocked()
raise IsBlocked
try:
M.login("fakeuser", "fakepassword")
raise Exception("authentication didn't fail")
msg = "authentication didn't fail"
raise Exception(msg)
except imaplib.IMAP4.error:
# authentication should fail
pass
@@ -84,17 +85,18 @@ def pop_test():
M = poplib.POP3_SSL(hostname)
except ConnectionRefusedError:
# looks like fail2ban worked
raise IsBlocked()
raise IsBlocked
try:
M.user('fakeuser')
try:
M.pass_('fakepassword')
except poplib.error_proto as e:
except poplib.error_proto:
# Authentication should fail.
M = None # don't .quit()
return
M.list()
raise Exception("authentication didn't fail")
msg = "authentication didn't fail"
raise Exception(msg)
finally:
if M:
M.quit()
@@ -108,11 +110,12 @@ def managesieve_test():
M = imaplib.IMAP4(hostname, 4190)
except ConnectionRefusedError:
# looks like fail2ban worked
raise IsBlocked()
raise IsBlocked
try:
M.login("fakeuser", "fakepassword")
raise Exception("authentication didn't fail")
msg = "authentication didn't fail"
raise Exception(msg)
except imaplib.IMAP4.error:
# authentication should fail
pass
@@ -138,17 +141,17 @@ def http_test(url, expected_status, postdata=None, qsargs=None, auth=None):
headers={'User-Agent': 'Mail-in-a-Box fail2ban tester'},
timeout=8,
verify=False) # don't bother with HTTPS validation, it may not be configured yet
except requests.exceptions.ConnectTimeout as e:
raise IsBlocked()
except requests.exceptions.ConnectTimeout:
raise IsBlocked
except requests.exceptions.ConnectionError as e:
if "Connection refused" in str(e):
raise IsBlocked()
raise IsBlocked
raise # some other unexpected condition
# return response status code
if r.status_code != expected_status:
r.raise_for_status() # anything but 200
raise IOError("Got unexpected status code %s." % r.status_code)
raise OSError("Got unexpected status code %s." % r.status_code)
# define how to run a test
@@ -158,7 +161,7 @@ def restart_fail2ban_service(final=False):
if not final:
# Stop recidive jails during testing.
command += " && sudo fail2ban-client stop recidive"
os.system("%s \"%s\"" % (ssh_command, command))
os.system(f'{ssh_command} "{command}"')
def testfunc_runner(i, testfunc, *args):
print(i+1, end=" ", flush=True)
@@ -172,7 +175,6 @@ def run_test(testfunc, args, count, within_seconds, parallel):
# run testfunc sequentially and still get to count requests within
# the required time. So we split the requests across threads.
import requests.exceptions
from multiprocessing import Pool
restart_fail2ban_service()
@@ -188,7 +190,7 @@ def run_test(testfunc, args, count, within_seconds, parallel):
# Distribute the requests across the pool.
asyncresults = []
for i in range(count):
ar = p.apply_async(testfunc_runner, [i, testfunc] + list(args))
ar = p.apply_async(testfunc_runner, [i, testfunc, *list(args)])
asyncresults.append(ar)
# Wait for all runs to finish.

View File

@@ -16,7 +16,7 @@
# where ipaddr is the IP address of your Mail-in-a-Box
# and hostname is the domain name to check the DNS for.
import sys, re, difflib
import sys, re
import dns.reversename, dns.resolver
if len(sys.argv) < 3:
@@ -36,10 +36,10 @@ def test(server, description):
("ns2." + primary_hostname, "A", ipaddr),
("www." + hostname, "A", ipaddr),
(hostname, "MX", "10 " + primary_hostname + "."),
(hostname, "TXT", "\"v=spf1 mx -all\""),
("mail._domainkey." + hostname, "TXT", "\"v=DKIM1; k=rsa; s=email; \" \"p=__KEY__\""),
(hostname, "TXT", '"v=spf1 mx -all"'),
("mail._domainkey." + hostname, "TXT", '"v=DKIM1; k=rsa; s=email; " "p=__KEY__"'),
#("_adsp._domainkey." + hostname, "TXT", "\"dkim=all\""),
("_dmarc." + hostname, "TXT", "\"v=DMARC1; p=quarantine;\""),
("_dmarc." + hostname, "TXT", '"v=DMARC1; p=quarantine;"'),
]
return test2(tests, server, description)
@@ -68,7 +68,7 @@ def test2(tests, server, description):
response = ["[no value]"]
response = ";".join(str(r) for r in response)
response = re.sub(r"(\"p=).*(\")", r"\1__KEY__\2", response) # normalize DKIM key
response = response.replace("\"\" ", "") # normalize TXT records (DNSSEC signing inserts empty text string components)
response = response.replace('"" ', "") # normalize TXT records (DNSSEC signing inserts empty text string components)
# is it right?
if response == expected_answer:
@@ -107,7 +107,7 @@ else:
# And if that's OK, also check reverse DNS (the PTR record).
if not test_ptr("8.8.8.8", "Google Public DNS (Reverse DNS)"):
print ()
print ("The reverse DNS for %s is not correct. Consult your ISP for how to set the reverse DNS (also called the PTR record) for %s to %s." % (hostname, hostname, ipaddr))
print (f"The reverse DNS for {hostname} is not correct. Consult your ISP for how to set the reverse DNS (also called the PTR record) for {hostname} to {ipaddr}.")
sys.exit(1)
else:
print ("And the reverse DNS for the domain is correct.")

View File

@@ -83,7 +83,7 @@ while argi<len(sys.argv):
argi+=2
else:
usage()
if not smtpd:
if len(sys.argv) - argi != 3: usage()
host, login, pw = sys.argv[argi:argi+3]
@@ -222,13 +222,12 @@ if delete_msg:
if not found:
print("Test message not present in the inbox yet...")
time.sleep(wait_cycle_sleep)
M.close()
M.logout()
if not found:
raise TimeoutError("Timeout waiting for message")
if send_msg and delete_msg:
print("Test message sent & received successfully.")

View File

@@ -15,11 +15,11 @@ if len(sys.argv) < 3:
sys.exit(1)
host, toaddr, fromaddr = sys.argv[1:4]
msg = """From: %s
To: %s
msg = f"""From: {fromaddr}
To: {toaddr}
Subject: SMTP server test
This is a test message.""" % (fromaddr, toaddr)
This is a test message."""
server = smtplib.SMTP(host, 25)
server.set_debuglevel(1)

View File

@@ -97,14 +97,14 @@ def sslyze(opts, port, ok_ciphers):
try:
# Execute SSLyze.
out = subprocess.check_output([SSLYZE] + common_opts + opts + [connection_string])
out = subprocess.check_output([SSLYZE, *common_opts, *opts, connection_string])
out = out.decode("utf8")
# Trim output to make better for storing in git.
if "SCAN RESULTS FOR" not in out:
# Failed. Just output the error.
out = re.sub("[\w\W]*CHECKING HOST\(S\) AVAILABILITY\n\s*-+\n", "", out) # chop off header that shows the host we queried
out = re.sub("[\w\W]*SCAN RESULTS FOR.*\n\s*-+\n", "", out) # chop off header that shows the host we queried
out = re.sub("[\\w\\W]*CHECKING HOST\\(S\\) AVAILABILITY\n\\s*-+\n", "", out) # chop off header that shows the host we queried
out = re.sub("[\\w\\W]*SCAN RESULTS FOR.*\n\\s*-+\n", "", out) # chop off header that shows the host we queried
out = re.sub("SCAN COMPLETED IN .*", "", out)
out = out.rstrip(" \n-") + "\n"
@@ -114,8 +114,8 @@ def sslyze(opts, port, ok_ciphers):
# Pull out the accepted ciphers list for each SSL/TLS protocol
# version outputted.
accepted_ciphers = set()
for ciphers in re.findall(" Accepted:([\w\W]*?)\n *\n", out):
accepted_ciphers |= set(re.findall("\n\s*(\S*)", ciphers))
for ciphers in re.findall(" Accepted:([\\w\\W]*?)\n *\n", out):
accepted_ciphers |= set(re.findall("\n\\s*(\\S*)", ciphers))
# Compare to what Mozilla recommends, for a given modernness-level.
print(" Should Not Offer: " + (", ".join(sorted(accepted_ciphers-set(ok_ciphers))) or "(none -- good)"))
@@ -151,7 +151,7 @@ for cipher in csv.DictReader(io.StringIO(urllib.request.urlopen("https://raw.git
client_compatibility = json.loads(urllib.request.urlopen("https://raw.githubusercontent.com/mail-in-a-box/user-agent-tls-capabilities/master/clients.json").read().decode("utf8"))
cipher_clients = { }
for client in client_compatibility:
if len(set(client['protocols']) & set(["TLS 1.0", "TLS 1.1", "TLS 1.2"])) == 0: continue # does not support TLS
if len(set(client['protocols']) & {"TLS 1.0", "TLS 1.1", "TLS 1.2"}) == 0: continue # does not support TLS
for cipher in client['ciphers']:
cipher_clients.setdefault(cipher_names.get(cipher), set()).add("/".join(x for x in [client['client']['name'], client['client']['version'], client['client']['platform']] if x))

View File

@@ -25,7 +25,7 @@ export FEATURE_MUNIN=false
export EHDD_KEYFILE=$HOME/keyfile
echo -n "boo" >$EHDD_KEYFILE
tests/system-setup/remote-nextcloud-docker.sh || exit 1
tests/runner.sh remote-nextcloud ehdd default || exit 2
tests/runner.sh -no-smtp-remote remote-nextcloud ehdd default || exit 2
SH
end
end
@@ -38,25 +38,26 @@ cd /mailinabox
export PRIMARY_HOSTNAME=qa2.abc.com
export FEATURE_MUNIN=false
tests/system-setup/remote-nextcloud-docker.sh upgrade --populate=basic || exit 1
tests/runner.sh remote-nextcloud upgrade-basic default || exit 2
tests/runner.sh -no-smtp-remote remote-nextcloud upgrade-basic default || exit 2
SH
end
# upgrade-from-upstream
config.vm.define "upgrade-from-upstream" do |m1|
m1.vm.provision :shell, :inline => <<-SH
cd /mailinabox
export PRIMARY_HOSTNAME=qa3.abc.com
export UPSTREAM_TAG=main
# TODO: change UPSTREAM_TAG to 'main' once upstream is installable
export UPSTREAM_TAG=v67
tests/system-setup/upgrade-from-upstream.sh --populate=basic --populate=totpuser || exit 1
tests/runner.sh upgrade-basic upgrade-totpuser default || exit 2
tests/runner.sh -no-smtp-remote upgrade-basic upgrade-totpuser default || exit 2
SH
end
# upgrade
# this test is only needed when testing migrations from miabldap
# to a newer miabldap with a migration step
#
@@ -65,9 +66,11 @@ SH
config.vm.define "upgrade" do |m1|
m1.vm.provision :shell, :inline => <<-SH
cd /mailinabox
# TODO: remove DEB_PYTHON_INSTALL_LAYOUT once MIABLDAP_RELEASE_TAG >= v66 (see https://github.com/downtownallday/mailinabox-ldap/commit/371f5bc1b236de40a1ed5d9118140ee13fddf5dc)
export DEB_PYTHON_INSTALL_LAYOUT='deb'
export PRIMARY_HOSTNAME=upgrade.abc.com
tests/system-setup/upgrade.sh --populate=basic --populate=totpuser || exit 1
tests/runner.sh upgrade-basic upgrade-totpuser default || exit 2
tests/runner.sh -no-smtp-remote upgrade-basic upgrade-totpuser default || exit 2
SH
end
@@ -96,6 +99,6 @@ setup/start.sh
SH
end
end
end