Compare commits

..

8 Commits

Author SHA1 Message Date
holger krekel
31e08832a6 shift functions to a DictProxy class 2023-10-21 01:40:58 +02:00
holger krekel
9d175316ff formatting and fixture move 2023-10-21 01:32:36 +02:00
holger krekel
fdd528841f fix nocreate tests 2023-10-21 01:17:09 +02:00
holger krekel
00b4c484ff add missing file 2023-10-21 01:16:35 +02:00
holger krekel
0950d7ea8f rename fixture to maildata and rename doveauth 2023-10-21 00:53:47 +02:00
holger krekel
7dd2d0b9b4 more maildata shifting 2023-10-21 00:47:19 +02:00
holger krekel
dd232689a7 move all inlined mails to a data directory 2023-10-21 00:06:30 +02:00
holger krekel
c613ca24af move all tests into a root "tests" folder so they can share setup and config 2023-10-20 23:07:48 +02:00
23 changed files with 140 additions and 286 deletions

View File

@@ -5,27 +5,14 @@ on:
push: push:
jobs: jobs:
tox: lint:
name: chatmail tests name: Lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: run chatmaild tests - name: Lint chatmaild
working-directory: chatmaild working-directory: chatmaild
run: pipx run tox run: pipx run tox
- name: run deploy-chatmail offline tests - name: Lint deploy-chatmail
working-directory: deploy-chatmail working-directory: deploy-chatmail
run: pipx run tox run: pipx run tox
- name: run deploy-chatmail offline tests
working-directory: deploy-chatmail
run: pipx run tox
scripts:
name: chatmail script invocations
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: run init.sh
run: ./scripts/init.sh
- name: run test.sh
run: ./scripts/test.sh

2
.gitignore vendored
View File

@@ -159,5 +159,3 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
chatmail.zone

View File

@@ -10,17 +10,14 @@ comprised of a minimal setup of the battle-tested
scripts/init.sh scripts/init.sh
2. setup a domain with `A` and `AAAA` records for your chatmail server 2. set environment variable to the chatmail domain you want to setup:
3. set environment variable to the chatmail domain you want to setup:
export CHATMAIL_DOMAIN=c1.testrun.org # replace with your host export CHATMAIL_DOMAIN=c1.testrun.org # replace with your host
4. run the deploy of the chat mail instance: 3. run the deploy of the chat mail instance:
scripts/deploy.sh scripts/deploy.sh
5. run `scripts/generate-dns-zone.sh` and create the generated DNS records at your DNS provider
## Running tests and benchmarks (offline and online) ## Running tests and benchmarks (offline and online)

View File

@@ -20,7 +20,7 @@ addopts = "-v -ra --strict-markers"
legacy_tox_ini = """ legacy_tox_ini = """
[tox] [tox]
isolated_build = true isolated_build = true
envlist = lint,py envlist = lint
[testenv:lint] [testenv:lint]
skipdist = True skipdist = True
@@ -31,10 +31,4 @@ deps =
commands = commands =
black --quiet --check --diff src/ black --quiet --check --diff src/
ruff src/ ruff src/
[testenv]
passenv = CHATMAIL_DOMAIN
deps = pytest
pdbpp
commands = pytest -v -rsXx {posargs: ../tests/chatmaild}
""" """

View File

@@ -21,66 +21,68 @@ def encrypt_password(password: str):
return "{SHA512-CRYPT}" + passhash return "{SHA512-CRYPT}" + passhash
def create_user(db, user, password): class DictProxy:
if os.path.exists(NOCREATE_FILE): def __init__(self, db, mail_domain):
logging.warning( self.db = db
f"Didn't create account: {NOCREATE_FILE} exists. Delete the file to enable account creation." self.mail_domain = mail_domain
)
return def create_user(self, user, password):
with db.write_transaction() as conn: if os.path.exists(NOCREATE_FILE):
conn.create_user(user, password) logging.warning(f"Didn't create account: {NOCREATE_FILE} exists.")
return dict(home=f"/home/vmail/{user}", uid="vmail", gid="vmail", password=password) return
with self.db.write_transaction() as conn:
conn.create_user(user, password)
return dict(home=f"/home/vmail/{user}", uid="vmail", gid="vmail", password=password)
def get_user_data(self, user):
with self.db.read_connection() as conn:
result = conn.get_user(user)
if result:
result["uid"] = "vmail"
result["gid"] = "vmail"
return result
def get_user_data(db, user): def lookup_userdb(self, user):
with db.read_connection() as conn: return self.get_user_data(user)
result = conn.get_user(user)
if result:
result["uid"] = "vmail"
result["gid"] = "vmail"
return result
def lookup_userdb(db, user): def lookup_passdb(self, user, password):
return get_user_data(db, user) userdata = self.get_user_data(user)
if not userdata:
return self.create_user(user, encrypt_password(password))
userdata["password"] = userdata["password"].strip()
return userdata
def lookup_passdb(db, user, password): def handle_dovecot_request(self, msg):
userdata = get_user_data(db, user) print(f"received msg: {msg!r}", file=sys.stderr)
if not userdata: short_command = msg[0]
return create_user(db, user, encrypt_password(password)) if short_command == "L": # LOOKUP
userdata["password"] = userdata["password"].strip() parts = msg[1:].split("\t")
return userdata keyname, user = parts[:2]
namespace, type, *args = keyname.split("/")
reply_command = "F"
def handle_dovecot_request(msg, db, mail_domain): res = ""
print(f"received msg: {msg!r}", file=sys.stderr) if namespace == "shared":
short_command = msg[0] if type == "userdb":
if short_command == "L": # LOOKUP if user.endswith(f"@{self.mail_domain}"):
parts = msg[1:].split("\t") res = lookup_userdb(db, user)
keyname, user = parts[:2] if res:
namespace, type, *args = keyname.split("/") reply_command = "O"
reply_command = "F" else:
res = "" reply_command = "N"
if namespace == "shared": elif type == "passdb":
if type == "userdb": if user.endswith(f"@{self.mail_domain}"):
if user.endswith(f"@{mail_domain}"): res = lookup_passdb(db, user, password=args[0])
res = lookup_userdb(db, user) if res:
if res: reply_command = "O"
reply_command = "O" else:
else: reply_command = "N"
reply_command = "N" print(f"res: {res!r}", file=sys.stderr)
elif type == "passdb": json_res = json.dumps(res) if res else ""
if user.endswith(f"@{mail_domain}"): return f"{reply_command}{json_res}\n"
res = lookup_passdb(db, user, password=args[0]) return None
if res:
reply_command = "O"
else:
reply_command = "N"
print(f"res: {res!r}", file=sys.stderr)
json_res = json.dumps(res) if res else ""
return f"{reply_command}{json_res}\n"
return None
class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer): class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer):
@@ -90,17 +92,18 @@ class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer):
def main(): def main():
socket = sys.argv[1] socket = sys.argv[1]
passwd_entry = pwd.getpwnam(sys.argv[2]) passwd_entry = pwd.getpwnam(sys.argv[2])
db = Database(sys.argv[3])
with open("/etc/mailname", "r") as fp: with open("/etc/mailname", "r") as fp:
mail_domain = fp.read().strip() mail_domain = fp.read().strip()
db = Database(sys.argv[3])
dictproxy = DictProxy(db, mail_domain)
class Handler(StreamRequestHandler): class Handler(StreamRequestHandler):
def handle(self): def handle(self):
while True: while True:
msg = self.rfile.readline().strip().decode() msg = self.rfile.readline().strip().decode()
if not msg: if not msg:
break break
res = handle_dovecot_request(msg, db, mail_domain) res = dictproxy.handle_dovecot_request(msg)
if res: if res:
print(f"sending result: {res!r}", file=sys.stderr) print(f"sending result: {res!r}", file=sys.stderr)
self.wfile.write(res.encode("ascii")) self.wfile.write(res.encode("ascii"))

View File

@@ -34,34 +34,6 @@ def check_encrypted(message):
return True return True
def check_mdn(message, envelope):
if len(envelope.rcpt_tos) != 1:
return False
for name in ["auto-submitted", "chat-version"]:
if not message.get(name):
return False
if message.get_content_type() != "multipart/report":
return False
body = message.get_body()
if body.get_content_type() != "text/plain":
return False
if list(body.iter_attachments()) or list(body.iter_parts()):
return False
# even with all mime-structural checks an attacker
# could try to abuse the subject or body to contain links or other
# annoyance -- we skip on checking subject/body for now as Delta Chat
# should evolve to create E2E-encrypted read receipts anyway.
# and then MDNs are just encrypted mail and can pass the border
# to other instances.
return True
class SMTPController(Controller): class SMTPController(Controller):
def factory(self): def factory(self):
return SMTP(self.handler, **self.SMTP_kwargs) return SMTP(self.handler, **self.SMTP_kwargs)
@@ -110,9 +82,6 @@ def check_DATA(envelope):
if envelope.mail_from.lower() != from_addr.lower(): if envelope.mail_from.lower() != from_addr.lower():
return f"500 Invalid FROM <{from_addr!r}> for <{envelope.mail_from!r}>" return f"500 Invalid FROM <{from_addr!r}> for <{envelope.mail_from!r}>"
if not mail_encrypted and check_mdn(message, envelope):
return
envelope_from_domain = from_addr.split("@").pop() envelope_from_domain = from_addr.split("@").pop()
for recipient in envelope.rcpt_tos: for recipient in envelope.rcpt_tos:
if envelope.mail_from == recipient: if envelope.mail_from == recipient:

View File

@@ -4,8 +4,8 @@ Chat Mail pyinfra deploy.
import importlib.resources import importlib.resources
from pathlib import Path from pathlib import Path
from pyinfra import host from pyinfra import host, logger
from pyinfra.operations import apt, files, server, systemd from pyinfra.operations import apt, files, server, systemd, python
from pyinfra.facts.files import File from pyinfra.facts.files import File
from .acmetool import deploy_acmetool from .acmetool import deploy_acmetool
@@ -70,36 +70,6 @@ def _configure_opendkim(domain: str, dkim_selector: str) -> bool:
mode="644", mode="644",
config={"domain_name": domain, "opendkim_selector": dkim_selector}, config={"domain_name": domain, "opendkim_selector": dkim_selector},
) )
need_restart |= main_config.changed
files.directory(
name="Add opendkim directory to /etc",
path="/etc/opendkim",
user="opendkim",
group="opendkim",
mode="750",
present=True,
)
keytable = files.template(
src=importlib.resources.files(__package__).joinpath("opendkim/KeyTable"),
dest="/etc/dkimkeys/KeyTable",
user="opendkim",
group="opendkim",
mode="644",
config={"domain_name": domain, "opendkim_selector": dkim_selector},
)
need_restart |= keytable.changed
signing_table = files.template(
src=importlib.resources.files(__package__).joinpath("opendkim/SigningTable"),
dest="/etc/dkimkeys/SigningTable",
user="opendkim",
group="opendkim",
mode="644",
config={"domain_name": domain, "opendkim_selector": dkim_selector},
)
need_restart |= signing_table.changed
files.directory( files.directory(
name="Add opendkim socket directory to /var/spool/postfix", name="Add opendkim socket directory to /var/spool/postfix",
@@ -120,6 +90,8 @@ def _configure_opendkim(domain: str, dkim_selector: str) -> bool:
_sudo_user="opendkim", _sudo_user="opendkim",
) )
need_restart |= main_config.changed
return need_restart return need_restart
@@ -320,3 +292,14 @@ def deploy_chatmail(mail_domain: str, mail_server: str, dkim_selector: str) -> N
enabled=True, enabled=True,
restarted=journald_conf, restarted=journald_conf,
) )
def callback():
result = server.shell(
commands=[
f"""sed 's/\tIN/ 600 IN/;s/\t(//;s/\"$//;s/^\t \"//g; s/ ).*//' """
f"""/etc/dkimkeys/{dkim_selector}.txt | tr --delete '\n'"""
]
)
logger.info(f"Add this TXT entry into DNS zone: {result.stdout}")
python.call(name="Print TXT entry for DKIM", function=callback)

View File

@@ -6,7 +6,7 @@ from deploy_chatmail import deploy_chatmail
def main(): def main():
mail_domain = os.getenv("CHATMAIL_DOMAIN") mail_domain = os.getenv("CHATMAIL_DOMAIN")
mail_server = os.getenv("CHATMAIL_SERVER", mail_domain) mail_server = os.getenv("CHATMAIL_SERVER", mail_domain)
dkim_selector = os.getenv("CHATMAIL_DKIM_SELECTOR", "dkim") dkim_selector = os.getenv("CHATMAIL_DKIM_SELECTOR", "2023")
assert mail_domain assert mail_domain
assert mail_server assert mail_server

View File

@@ -1 +0,0 @@
dkim._domainkey.{{ config.domain_name }} {{ config.domain_name }}:{{ config.opendkim_selector }}:/etc/dkimkeys/dkim.private

View File

@@ -1 +0,0 @@
*@{{ config.domain_name }} {{ config.opendkim_selector }}._domainkey.{{ config.domain_name }}

View File

@@ -1,4 +1,7 @@
# OpenDKIM configuration. # This is a basic configuration for signing and verifying. It can easily be
# adapted to suit a basic installation. See opendkim.conf(5) and
# /usr/share/doc/opendkim/examples/opendkim.conf.sample for complete
# documentation of available configuration parameters.
Syslog yes Syslog yes
SyslogSuccess yes SyslogSuccess yes
@@ -18,9 +21,7 @@ OversignHeaders From
# setup options can be found in /usr/share/doc/opendkim/README.opendkim. # setup options can be found in /usr/share/doc/opendkim/README.opendkim.
Domain {{ config.domain_name }} Domain {{ config.domain_name }}
Selector {{ config.opendkim_selector }} Selector {{ config.opendkim_selector }}
KeyFile /etc/dkimkeys/{{ config.opendkim_selector }}.private KeyFile /etc/dkimkeys/{{ config.opendkim_selector }}.private
KeyTable /etc/dkimkeys/KeyTable
SigningTable /etc/dkimkeys/SigningTable
# In Debian, opendkim runs as user "opendkim". A umask of 007 is required when # In Debian, opendkim runs as user "opendkim". A umask of 007 is required when
# using a local socket with MTAs that access the socket as a non-privileged # using a local socket with MTAs that access the socket as a non-privileged

View File

@@ -1,15 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
: ${CHATMAIL_DOMAIN:=c1.testrun.org}
export CHATMAIL_DOMAIN
echo ----------------------------------------- chatmaild/venv/bin/python3 -m build -n --sdist chatmaild --outdir dist
echo deploying to $CHATMAIL_DOMAIN
echo -----------------------------------------
echo WARNING: in five seconds deploy to $CHATMAIL_DOMAIN starts deploy-chatmail/venv/bin/pyinfra --ssh-user root "$CHATMAIL_DOMAIN" \
sleep 5
venv/bin/python3 -m build -n --sdist chatmaild --outdir dist
venv/bin/pyinfra --ssh-user root "$CHATMAIL_DOMAIN" \
deploy-chatmail/src/deploy_chatmail/deploy.py deploy-chatmail/src/deploy_chatmail/deploy.py
rm -r dist/ rm -r dist/

View File

@@ -1,20 +0,0 @@
#!/bin/sh
: ${CHATMAIL_DOMAIN:=c1.testrun.org}
: ${CHATMAIL_SSH:=$CHATMAIL_DOMAIN}
set -e
SSH="ssh root@$CHATMAIL_SSH"
EMAIL="root@$CHATMAIL_DOMAIN"
ACME_ACCOUNT_URL="$($SSH -- acmetool account-url)"
cat <<EOF
$CHATMAIL_DOMAIN. MX 10 $CHATMAIL_DOMAIN.
$CHATMAIL_DOMAIN. TXT "v=spf1 a:$CHATMAIL_DOMAIN -all"
_dmarc.$CHATMAIL_DOMAIN. TXT "v=DMARC1;p=reject;rua=mailto:$EMAIL;ruf=mailto:$EMAIL;fo=1;adkim=r;aspf=r"
_submission._tcp.$CHATMAIL_DOMAIN. SRV 0 1 587 $CHATMAIL_DOMAIN.
_submissions._tcp.$CHATMAIL_DOMAIN. SRV 0 1 465 $CHATMAIL_DOMAIN.
_imap._tcp.$CHATMAIL_DOMAIN. SRV 0 1 143 $CHATMAIL_DOMAIN.
_imaps._tcp.$CHATMAIL_DOMAIN. SRV 0 1 993 $CHATMAIL_DOMAIN.
$CHATMAIL_DOMAIN. IN CAA 0 issue "letsencrypt.org; accounturi=$ACME_ACCOUNT_URL"
EOF
$SSH opendkim-genzone -F | sed 's/^;.*$//;/^$/d'

View File

@@ -11,4 +11,3 @@ conn.login(f"imapcapa", "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

@@ -1,8 +1,13 @@
#!/bin/sh #!/bin/sh
set -e set -e
python3 -m venv venv python3 -m venv deploy-chatmail/venv
pip=venv/bin/pip deploy-chatmail/venv/bin/pip install pyinfra pytest
deploy-chatmail/venv/bin/pip install -e deploy-chatmail
deploy-chatmail/venv/bin/pip install -e chatmaild
$pip install pyinfra pytest build 'setuptools>=68' tox deltachat python3 -m venv chatmaild/venv
$pip install -e deploy-chatmail chatmaild/venv/bin/pip install --upgrade pytest build 'setuptools>=68'
$pip install -e chatmaild chatmaild/venv/bin/pip install -e chatmaild
python3 -m venv online-tests/venv
online-tests/venv/bin/pip install pytest pytest-timeout pdbpp deltachat

View File

@@ -5,7 +5,7 @@ import imaplib
domain = os.environ.get("CHATMAIL_DOMAIN", "c3.testrun.org") domain = os.environ.get("CHATMAIL_DOMAIN", "c3.testrun.org")
NUM_CONNECTIONS=10 NUM_CONNECTIONS = 10
conns = [] conns = []
@@ -16,7 +16,7 @@ for i in range(NUM_CONNECTIONS):
conns.append(conn) conns.append(conn)
tlsdone = time.time() tlsdone = time.time()
duration = tlsdone-start duration = tlsdone - start
print(f"{duration}: TLS connections opening TLS connections") print(f"{duration}: TLS connections opening TLS connections")
for i, conn in enumerate(conns): for i, conn in enumerate(conns):

View File

@@ -1,4 +1,3 @@
#!/bin/bash #!/bin/bash
venv/bin/tox -c chatmaild chatmaild/venv/bin/pytest chatmaild/ $@
venv/bin/tox -c deploy-chatmail online-tests/venv/bin/pytest online-tests/ -vrx --durations=5 $@
venv/bin/pytest tests/online -vrx --durations=5 $@

View File

@@ -0,0 +1,9 @@
import pytest
from chatmaild.database import Database
@pytest.fixture()
def db(tmpdir):
db_path = tmpdir / "passdb.sqlite"
print("database path:", db_path)
return Database(db_path)

View File

@@ -3,43 +3,38 @@ import os
import pytest import pytest
import chatmaild.dictproxy import chatmaild.dictproxy
from chatmaild.dictproxy import get_user_data, lookup_passdb from chatmaild.dictproxy import DictProxy
from chatmaild.database import Database, DBError from chatmaild.database import DBError
@pytest.fixture() @pytest.fixture
def db(tmpdir): def dictproxy(db, maildomain):
db_path = tmpdir / "passdb.sqlite" return DictProxy(db, maildomain)
print("database path:", db_path)
return Database(db_path)
def test_basic(db): def test_basic(dictproxy, tmpdir, monkeypatch):
chatmaild.dictproxy.NOCREATE_FILE = "/tmp/nocreate" monkeypatch.setattr(
if os.path.exists(chatmaild.dictproxy.NOCREATE_FILE): chatmaild.dictproxy, "NOCREATE_FILE", tmpdir.join("nocreate").strpath
os.remove(chatmaild.dictproxy.NOCREATE_FILE) )
lookup_passdb(db, "link2xt@c1.testrun.org", "asdf") dictproxy.lookup_passdb("link2xt@c1.testrun.org", "asdf")
data = get_user_data(db, "link2xt@c1.testrun.org") assert dictproxy.get_user_data("link2xt@c1.testrun.org")
assert data
def test_dont_overwrite_password_on_wrong_login(db): def test_dont_overwrite_password_on_wrong_login(dictproxy):
"""Test that logging in with a different password doesn't create a new user""" """Test that logging in with a different password doesn't create a new user"""
res = lookup_passdb(db, "newuser1@something.org", "kajdlkajsldk12l3kj1983") res = dictproxy.lookup_passdb("newuser1@something.org", "kajdlkajsldk12l3kj1983")
assert res["password"] assert res["password"]
res2 = lookup_passdb(db, "newuser1@something.org", "kajdlqweqwe") res2 = dictproxy.lookup_passdb("newuser1@something.org", "kajdlqweqwe")
# this function always returns a password hash, which is actually compared by dovecot. # this function always returns a password hash, which is actually compared by dovecot.
assert res["password"] == res2["password"] assert res["password"] == res2["password"]
def test_nocreate_file(db): def test_nocreate_file(dictproxy, tmpdir, monkeypatch):
chatmaild.dictproxy.NOCREATE_FILE = "/tmp/nocreate" nocreate = tmpdir.join("nocreate")
with open(chatmaild.dictproxy.NOCREATE_FILE, "w+") as f: monkeypatch.setattr(chatmaild.dictproxy, "NOCREATE_FILE", str(nocreate))
f.write("") nocreate.write("")
assert os.path.exists(chatmaild.dictproxy.NOCREATE_FILE) dictproxy.lookup_passdb("newuser1@something.org", "kajdlqweqwe")
lookup_passdb(db, "newuser1@something.org", "kajdlqweqwe") assert not dictproxy.get_user_data("newuser1@something.org")
assert not get_user_data(db, "newuser1@something.org")
os.remove(chatmaild.dictproxy.NOCREATE_FILE)
def test_db_version(db): def test_db_version(db):

View File

@@ -1,13 +1,7 @@
from chatmaild.filtermail import check_encrypted, check_DATA, SendRateLimiter, check_mdn from chatmaild.filtermail import check_encrypted, check_DATA, SendRateLimiter
import pytest import pytest
@pytest.fixture
def maildomain():
# let's not depend on a real chatmail instance for the offline tests below
return "chatmail.example.org"
def test_reject_forged_from(maildata, gencreds): def test_reject_forged_from(maildata, gencreds):
class env: class env:
mail_from = gencreds()[0] mail_from = gencreds()[0]
@@ -41,33 +35,8 @@ def test_filtermail_encryption_detection(maildata):
assert not check_encrypted(msg) assert not check_encrypted(msg)
def test_filtermail_is_mdn(maildata, gencreds): def test_filtermail_mdn_is_not_encrypted(maildata):
from_addr = gencreds()[0] assert not check_encrypted(maildata("mdn.eml"))
to_addr = gencreds()[0] + ".other"
msg = maildata("mdn.eml", from_addr, to_addr)
class env:
mail_from = from_addr
rcpt_tos = [to_addr]
content = msg.as_bytes()
assert check_mdn(msg, env)
print(msg.as_string())
assert not check_DATA(env)
def test_filtermail_to_multiple_recipients_no_mdn(maildata, gencreds):
from_addr = gencreds()[0]
to_addr = gencreds()[0] + ".other"
thirdaddr = gencreds()[0]
msg = maildata("mdn.eml", from_addr, to_addr)
class env:
mail_from = from_addr
rcpt_tos = [to_addr, thirdaddr]
content = msg.as_bytes()
assert not check_mdn(msg, env)
def test_send_rate_limiter(): def test_send_rate_limiter():

View File

@@ -290,7 +290,7 @@ class Remote:
def maildata(request, gencreds): def maildata(request, gencreds):
datadir = conftestdir.joinpath("mail-data") datadir = conftestdir.joinpath("mail-data")
def maildata(name, from_addr=None, to_addr=None): def maildata(name, parsed=True, from_addr=None, to_addr=None):
if from_addr is None: if from_addr is None:
from_addr = gencreds()[0] from_addr = gencreds()[0]
if to_addr is None: if to_addr is None:

View File

@@ -1,6 +1,6 @@
Subject: Message opened Subject: Message opened
From: <{from_addr}> From: <barbaz@c2.testrun.org>
To: <{to_addr}> To: <foobar@c2.testrun.org>
Date: Sun, 15 Oct 2023 16:43:25 +0000 Date: Sun, 15 Oct 2023 16:43:25 +0000
Message-ID: <Mr.78MWtlV7RAi.goCFzBhCYfy@c2.testrun.org> Message-ID: <Mr.78MWtlV7RAi.goCFzBhCYfy@c2.testrun.org>
Auto-Submitted: auto-replied Auto-Submitted: auto-replied

View File

@@ -1,4 +1,3 @@
import time
import random import random
import pytest import pytest
@@ -82,29 +81,3 @@ class TestEndToEndDeltaChat:
ch = ac2.qr_setup_contact(qr) ch = ac2.qr_setup_contact(qr)
assert ch.id >= 10 assert ch.id >= 10
ac1._evtracker.wait_securejoin_inviter_progress(1000) ac1._evtracker.wait_securejoin_inviter_progress(1000)
def test_read_receipts_between_instances(self, cmfactory, lp, maildomain2):
ac1 = cmfactory.new_online_configuring_account(cache=False)
cmfactory.switch_maildomain(maildomain2)
ac2 = cmfactory.new_online_configuring_account(cache=False)
cmfactory.bring_accounts_online()
lp.sec("setup encrypted comms between ac1 and ac2 on different instances")
qr = ac1.get_setup_contact_qr()
ch = ac2.qr_setup_contact(qr)
msg = ac2.wait_next_incoming_message()
assert "verified" in msg.text
lp.sec("ac1 sends a message and ac2 marks it as seen")
chat = ac1.create_chat(ac2)
msg = chat.send_text("hi")
m = ac2.wait_next_incoming_message()
m.mark_seen()
# we can only indirectly wait for mark-seen to cause an smtp-error
lp.sec("try to wait for markseen to complete and check error states")
deadline = time.time() + 3.1
while time.time() < deadline:
msgs = m.chat.get_messages()
for msg in msgs:
assert "error" not in m.get_message_info()
time.sleep(1)