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:
@@ -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.
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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.")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
12
tests/tls.py
12
tests/tls.py
@@ -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))
|
||||
|
||||
|
||||
19
tests/vagrant/Vagrantfile
vendored
19
tests/vagrant/Vagrantfile
vendored
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user