Compare commits

..

5 Commits

Author SHA1 Message Date
link2xt
7e15094dd1 Switch from BLF-CRYPT to SHA512-CRYPT 2023-10-15 20:50:06 +00:00
link2xt
e19cce7c69 Make scripts/measure_tls_and_logins.py executable 2023-10-15 20:42:09 +00:00
link2xt
1d312f7cfe dovecot: enable authentication cache 2023-10-15 20:42:09 +00:00
link2xt
8bed8578ad Test different users logging in with the same password 2023-10-15 20:42:09 +00:00
link2xt
0bfeb2ae5e Avoid reusing accounts between tests
Add time as a prefix.
2023-10-15 20:42:09 +00:00
10 changed files with 67 additions and 188 deletions

View File

@@ -9,8 +9,6 @@ auth_debug = yes
auth_debug_passwords = yes auth_debug_passwords = yes
auth_verbose_passwords = plain auth_verbose_passwords = plain
auth_cache_size = 100M auth_cache_size = 100M
mail_plugins = quota
mail_debug = yes
# Authentication for system users. # Authentication for system users.
passdb { passdb {
@@ -62,28 +60,13 @@ mail_privileged_group = vmail
# Enable IMAP COMPRESS (RFC 4978). # Enable IMAP COMPRESS (RFC 4978).
# <https://datatracker.ietf.org/doc/html/rfc4978.html> # <https://datatracker.ietf.org/doc/html/rfc4978.html>
protocol imap { protocol imap {
mail_plugins = $mail_plugins imap_zlib imap_quota mail_plugins = $mail_plugins imap_zlib
}
protocol lmtp {
mail_plugins = $mail_plugins quota
} }
plugin { plugin {
imap_compress_deflate_level = 6 imap_compress_deflate_level = 6
} }
plugin {
# for now we define static quota-rules for all users
quota = maildir:User quota
quota_rule = *:storage=100M
quota_max_mail_size=30M
quota_grace = 0
# quota_over_flag_value = TRUE
}
service lmtp { service lmtp {
user=vmail user=vmail

View File

@@ -37,8 +37,6 @@ mydestination =
relayhost = relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0 mailbox_size_limit = 0
# maximum 30MB sized messages
message_size_limit = 31457280
recipient_delimiter = + recipient_delimiter = +
inet_interfaces = all inet_interfaces = all
inet_protocols = all inet_protocols = all

View File

@@ -9,7 +9,7 @@
# service type private unpriv chroot wakeup maxproc command + args # service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (no) (never) (100) # (yes) (yes) (no) (never) (100)
# ========================================================================== # ==========================================================================
smtp inet n - y - - smtpd -v smtp inet n - y - - smtpd
#smtp inet n - y - 1 postscreen #smtp inet n - y - 1 postscreen
#smtpd pass - - y - - smtpd #smtpd pass - - y - - smtpd
#dnsblog unix - - y - 0 dnsblog #dnsblog unix - - y - 0 dnsblog
@@ -28,7 +28,7 @@ submission inet n - y - - smtpd
-o smtpd_recipient_restrictions= -o smtpd_recipient_restrictions=
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING -o milter_macro_daemon_name=ORIGINATING
-o content_filter=filter:unix:private/filtermail -o content_filter=filter:unix:private/filtemail
smtps inet n - y - - smtpd smtps inet n - y - - smtpd
-o syslog_name=postfix/smtps -o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes -o smtpd_tls_wrappermode=yes

View File

@@ -1,47 +1,15 @@
import os import os
import io import io
import random
import subprocess
import imaplib import imaplib
import smtplib import smtplib
import itertools import itertools
import pytest import pytest
import time
def pytest_addoption(parser):
parser.addoption(
"--slow", action="store_true", default=False, help="also run slow tests"
)
def pytest_runtest_setup(item):
markers = list(item.iter_markers(name="slow"))
if markers:
if not item.config.getoption("--slow"):
pytest.skip("skipping slow test, use --slow to run")
@pytest.fixture @pytest.fixture
def maildomain(): def maildomain():
domain = os.environ.get("CHATMAIL_DOMAIN") return os.environ.get("CHATMAIL_DOMAIN", "c1.testrun.org")
if not domain:
pytest.skip("set CHATMAIL_DOMAIN to a ssh-reachable chatmail instance")
return domain
@pytest.fixture
def chatmail_ssh(maildomain):
domain = os.environ.get("CHATMAIL_SSH")
if not domain:
domain = maildomain
return domain
def pytest_report_header():
domain = os.environ.get("CHATMAIL_DOMAIN")
if domain:
text = f"chatmail test instance: {domain}"
return ["-" * len(text), text, "-" * len(text)]
@pytest.fixture @pytest.fixture
@@ -50,8 +18,6 @@ def imap(maildomain):
class ImapConn: class ImapConn:
AuthError = imaplib.IMAP4.error
def __init__(self, host): def __init__(self, host):
self.host = host self.host = host
@@ -70,8 +36,6 @@ def smtp(maildomain):
class SmtpConn: class SmtpConn:
AuthError = smtplib.SMTPAuthenticationError
def __init__(self, host): def __init__(self, host):
self.host = host self.host = host
@@ -84,24 +48,15 @@ class SmtpConn:
self.conn.login(user, password) self.conn.login(user, password)
@pytest.fixture(params=["imap", "smtp"])
def imap_or_smtp(request):
return request.getfixturevalue(request.param)
@pytest.fixture @pytest.fixture
def gencreds(maildomain): def gencreds(maildomain):
prefix = str(time.time())
count = itertools.count() count = itertools.count()
next(count)
def gen(): def gen():
while 1: while 1:
num = next(count) num = next(count)
alphanumeric = "abcdefghijklmnopqrstuvwxyz1234567890" yield f"user{prefix}_{num}@{maildomain}", f"password{prefix}_{num}"
user = "".join(random.choices(alphanumeric, k=10))
user = f"ac{num}_{user}"
password = "".join(random.choices(alphanumeric, k=10))
yield f"{user}@{maildomain}", f"{password}"
return lambda: next(gen()) return lambda: next(gen())
@@ -124,13 +79,7 @@ class ChatmailTestProcess:
def get_liveconfig_producer(self): def get_liveconfig_producer(self):
while 1: while 1:
user, password = self.gencreds() user, password = self.gencreds()
config = { config = {"addr": user, "mail_pw": password}
"addr": user,
"mail_pw": password,
}
# speed up account configuration
config["mail_server"] = self.maildomain
config["send_server"] = self.maildomain
yield config yield config
def cache_maybe_retrieve_configured_db_files(self, cache_addr, db_target_path): def cache_maybe_retrieve_configured_db_files(self, cache_addr, db_target_path):
@@ -150,21 +99,8 @@ def cmfactory(request, maildomain, gencreds, tmpdir, data):
am = ACFactory(request=request, tmpdir=tmpdir, testprocess=testproc, data=data) am = ACFactory(request=request, tmpdir=tmpdir, testprocess=testproc, data=data)
yield am yield am
if hasattr(request.node, "rep_call") and request.node.rep_call.failed: if hasattr(request.node, "rep_call") and request.node.rep_call.failed:
if testproc.pytestconfig.getoption("--extra-info"): if testprocess.pytestconfig.getoption("--extra-info"):
logfile = io.StringIO() logfile = io.StringIO()
am.dump_imap_summary(logfile=logfile) am.dump_imap_summary(logfile=logfile)
print(logfile.getvalue()) print(logfile.getvalue())
# request.node.add_report_section("call", "imap-server-state", s) # request.node.add_report_section("call", "imap-server-state", s)
@pytest.fixture
def dovelogreader(chatmail_ssh):
def remote_reader():
popen = subprocess.Popen(
["ssh", f"root@{chatmail_ssh}", "journalctl -f -u dovecot"],
stdout=subprocess.PIPE,
)
while 1:
yield popen.stdout.readline()
return remote_reader

View File

@@ -1,3 +1,2 @@
[pytest] [pytest]
addopts = -vrsx --strict-markers addopts = -vrsx
markers = slow: mark test as slow (requires --slow option to run)

View File

@@ -1,36 +1,55 @@
import pytest import pytest
import imaplib
import smtplib
def test_login_basic_functioning(imap_or_smtp, gencreds, lp): class TestDovecot:
"""Test a) that an initial login creates a user automatically def test_login_ok(self, imap, gencreds):
and b) verify we can also login a second time with the same password user, password = gencreds()
and c) that using a different password fails the login.""" imap.connect()
user, password = gencreds() imap.login(user, password)
lp.sec(f"login first time with {user} {password}") # verify it works on another connection
imap_or_smtp.connect() imap.connect()
imap_or_smtp.login(user, password) imap.login(user, password)
lp.indent("success")
lp.sec(f"reconnect and login second time {user} {password}") def test_login_same_password(self, imap, gencreds):
imap_or_smtp.connect() """Test two different users logging in with the same password.
imap_or_smtp.login(user, password)
imap_or_smtp.connect()
lp.sec("success")
lp.sec("reconnect and verify wrong password fails {user} ") This ensures that authentication process does not confuse the users
imap_or_smtp.connect() by using only the password hash as a key.
with pytest.raises(imap_or_smtp.AuthError): """
imap_or_smtp.login(user, password + "wrong") user1, password1 = gencreds()
user2, _password2 = gencreds()
imap.connect()
imap.login(user1, password1)
imap.connect()
imap.login(user2, password1)
def test_login_fail(self, imap, gencreds):
user, password = gencreds()
imap.connect()
imap.login(user, password)
imap.connect()
with pytest.raises(imaplib.IMAP4.error) as excinfo:
imap.login(user, password + "wrong")
assert "AUTHENTICATIONFAILED" in str(excinfo)
def test_login_same_password(imap_or_smtp, gencreds): class TestPostfix:
"""Test two different users logging in with the same password def test_login_ok(self, smtp, gencreds):
to ensure that authentication process does not confuse the users user, password = gencreds()
by using only the password hash as a key. smtp.connect()
""" smtp.login(user, password)
user1, password1 = gencreds() # verify it works on another connection
user2, _ = gencreds() smtp.connect()
imap_or_smtp.connect() smtp.login(user, password)
imap_or_smtp.login(user1, password1)
imap_or_smtp.connect() def test_login_fail(self, smtp, gencreds):
imap_or_smtp.login(user2, password1) user, password = gencreds()
smtp.connect()
smtp.login(user, password)
smtp.connect()
with pytest.raises(smtplib.SMTPAuthenticationError) as excinfo:
smtp.login(user, password + "wrong")
assert excinfo.value.smtp_code == 535
assert "authentication failed" in str(excinfo)

View File

@@ -1,70 +1,11 @@
import random class TestMailSending:
import pytest
class TestEndToEndDeltaChat:
"Tests that use Delta Chat accounts on the chat mail instance."
def test_one_on_one(self, cmfactory, lp): def test_one_on_one(self, cmfactory, lp):
"""Test that a DC account can send a message to a second DC account
on the same chat-mail instance."""
ac1, ac2 = cmfactory.get_online_accounts(2) ac1, ac2 = cmfactory.get_online_accounts(2)
chat = cmfactory.get_accepted_chat(ac1, ac2) chat = cmfactory.get_accepted_chat(ac1, ac2)
lp.sec("ac1: prepare and send text message to ac2") lp.sec("ac1: prepare and send text message to ac2")
chat.send_text("message0") msg1 = chat.send_text("message0")
lp.sec("wait for ac2 to receive message") lp.sec("wait for ac2 to receive message")
msg2 = ac2._evtracker.wait_next_incoming_message() msg2 = ac2._evtracker.wait_next_incoming_message()
assert msg2.text == "message0" assert msg2.text == "message0"
@pytest.mark.slow
def test_exceed_quota(self, cmfactory, lp, tmpdir, dovelogreader):
"""This is a very slow test as it needs to upload >100MB of mail data
before quota is exceeded, and thus depends on the speed of the upload.
"""
ac1, ac2 = cmfactory.get_online_accounts(2)
chat = cmfactory.get_accepted_chat(ac1, ac2)
quota = 1024 * 1024 * 100
attachsize = 1 * 1024 * 1024
num_to_send = quota // attachsize + 2
lp.sec(f"ac1: send {num_to_send} large files to ac2")
lp.indent(f"per-user quota is assumed to be: {quota/(1024*1024)}MB")
alphanumeric = "abcdefghijklmnopqrstuvwxyz1234567890"
msgs = []
for i in range(num_to_send):
attachment = tmpdir / f"attachment{i}"
data = "".join(random.choice(alphanumeric) for i in range(1024))
with open(attachment, "w+") as f:
for j in range(attachsize // len(data)):
f.write(data)
msg = chat.send_file(str(attachment))
msgs.append(msg)
lp.indent(f"Sent out msg {i}, size {attachsize/(1024*1024)}MB")
lp.sec("ac2: check messages are arriving until quota is reached")
addr = ac2.get_config("addr").lower()
saved_ok = 0
for line in dovelogreader():
line = line.decode().lower().strip()
if addr not in line:
# print(line)
continue
if "quota" in line:
if "quota exceeded" in line:
if saved_ok < num_to_send // 2:
pytest.fail(
f"quota exceeded too early: after {saved_ok} messages already"
)
lp.indent("good, message sending failed because quota was exceeded")
return
if "saved mail to inbox" in line:
saved_ok += 1
print(f"{saved_ok}: {line}")
if saved_ok >= num_to_send:
break
pytest.fail("sending succeeded although messages should exceed quota")

View File

@@ -2,10 +2,13 @@
## Dovecot goals/steps ## Dovecot goals/steps
- automatic expiry of messages older than M days 2. (holger) per-user storage quota (adaptive)
a) define a static 100MB per-user quota
3. automatic expiry of messages older than M days
- delete unconditionally messages older than 40 days - delete unconditionally messages older than 40 days
- limit: configure max-connections per account 4. limit: max-connections per account
## Filtermail ## Filtermail

View File

@@ -7,7 +7,7 @@ domain = os.environ.get("CHATMAIL_DOMAIN", "c3.testrun.org")
print("connecting") print("connecting")
conn = imaplib.IMAP4_SSL(domain) conn = imaplib.IMAP4_SSL(domain)
print("logging in") print("logging in")
conn.login(f"imapcapa", "pass") conn.login(f"measure{time.time()}", "pass")
status, res = conn.capability() status, res = conn.capability()
for capa in sorted(res[0].decode().split()): for capa in sorted(res[0].decode().split()):
print(capa) print(capa)

View File

@@ -4,4 +4,4 @@ pushd chatmaild/src/chatmaild
../../venv/bin/pytest ../../venv/bin/pytest
popd popd
online-tests/venv/bin/pytest online-tests/ -vrx --durations=5 --slow online-tests/venv/bin/pytest online-tests/ -vrx --durations=5