Compare commits

..

3 Commits

Author SHA1 Message Date
missytake
6b6f6f1c50 tried to write a test to test exceeded quota, but not sure delta even gets a proper error on recipient's full mailbox 2023-10-16 01:51:17 +02:00
missytake
f4cf4ab955 sieve is not installed and we don't need it 2023-10-16 01:41:38 +02:00
holger krekel
48d890ee82 make quota work 2023-10-16 01:41:36 +02:00
8 changed files with 88 additions and 160 deletions

View File

@@ -4,14 +4,15 @@ protocols = imap lmtp
auth_mechanisms = plain auth_mechanisms = plain
auth_verbose = yes
auth_debug = yes
auth_debug_passwords = yes
auth_verbose_passwords = plain
auth_cache_size = 100M
mail_plugins = quota mail_plugins = quota
mail_debug = yes mail_debug = yes
# uncomment this if you want to debug authentication and user creation
#auth_verbose = yes
#auth_debug = yes
#auth_debug_passwords = yes
#auth_verbose_passwords = plain
# Authentication for system users. # Authentication for system users.
passdb { passdb {
driver = dict driver = dict
@@ -79,7 +80,7 @@ plugin {
quota_rule = *:storage=100M quota_rule = *:storage=100M
quota_max_mail_size=30M quota_max_mail_size=30M
quota_grace = 0 quota_grace = 0
# quota_over_flag_value = TRUE quota_over_flag_value = TRUE
} }

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

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,45 @@
import os.path
import random import random
import pytest import time
class TestEndToEndDeltaChat: class TestMailSending:
"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):
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) ac1, ac2 = cmfactory.get_online_accounts(2)
chat = cmfactory.get_accepted_chat(ac1, ac2) chat = cmfactory.get_accepted_chat(ac1, ac2)
quota = 1024 * 1024 * 100 ac2.set_config("download_limit", 1024 * 2) # set download_limit to 2 KB avoid downloading all those 5MB files
attachsize = 1 * 1024 * 1024
num_to_send = quota // attachsize + 2 lp.sec("ac1: send 25 5 MB files to ac2")
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" alphanumeric = "abcdefghijklmnopqrstuvwxyz1234567890"
msgs = [] for i in range(25):
for i in range(num_to_send):
attachment = tmpdir / f"attachment{i}" attachment = tmpdir / f"attachment{i}"
data = "".join(random.choice(alphanumeric) for i in range(1024))
with open(attachment, "w+") as f: with open(attachment, "w+") as f:
for j in range(attachsize // len(data)): for j in range(1024 * 1024 * 5):
f.write(data) f.write(random.choice(alphanumeric))
msg = chat.send_file(str(attachment)) print("Sent out msg", str(i))
msgs.append(msg) chat.send_file(str(attachment))
lp.indent(f"Sent out msg {i}, size {attachsize/(1024*1024)}MB")
lp.sec("ac2: check messages are arriving until quota is reached") ac2.wait_next_incoming_message()
lp.sec("ac2: check that at least one message failed")
addr = ac2.get_config("addr").lower() failed = False
saved_ok = 0 for i in range(25):
for line in dovelogreader(): if chat.get_messages()[i].is_out_failed():
line = line.decode().lower().strip() failed = True
if addr not in line: print(chat.get_messages()[i].get_message_info())
# print(line) try:
continue assert failed
if "quota" in line: except:
if "quota exceeded" in line: import pdb; pdb.set_trace()
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

@@ -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