mirror of
https://github.com/chatmail/relay.git
synced 2026-05-11 16:34:39 +00:00
Compare commits
8 Commits
link2xt/do
...
chatmaild
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31e08832a6 | ||
|
|
9d175316ff | ||
|
|
fdd528841f | ||
|
|
00b4c484ff | ||
|
|
0950d7ea8f | ||
|
|
7dd2d0b9b4 | ||
|
|
dd232689a7 | ||
|
|
c613ca24af |
@@ -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"))
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import chatmaild.dictproxy
|
|
||||||
from .dictproxy import get_user_data, lookup_passdb
|
|
||||||
from .database import Database, DBError
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def db(tmpdir):
|
|
||||||
db_path = tmpdir / "passdb.sqlite"
|
|
||||||
print("database path:", db_path)
|
|
||||||
return Database(db_path)
|
|
||||||
|
|
||||||
|
|
||||||
def test_basic(db):
|
|
||||||
chatmaild.dictproxy.NOCREATE_FILE = "/tmp/nocreate"
|
|
||||||
if os.path.exists(chatmaild.dictproxy.NOCREATE_FILE):
|
|
||||||
os.remove(chatmaild.dictproxy.NOCREATE_FILE)
|
|
||||||
lookup_passdb(db, "link2xt@c1.testrun.org", "asdf")
|
|
||||||
data = get_user_data(db, "link2xt@c1.testrun.org")
|
|
||||||
assert data
|
|
||||||
|
|
||||||
|
|
||||||
def test_dont_overwrite_password_on_wrong_login(db):
|
|
||||||
"""Test that logging in with a different password doesn't create a new user"""
|
|
||||||
res = lookup_passdb(db, "newuser1@something.org", "kajdlkajsldk12l3kj1983")
|
|
||||||
assert res["password"]
|
|
||||||
res2 = lookup_passdb(db, "newuser1@something.org", "kajdlqweqwe")
|
|
||||||
# this function always returns a password hash, which is actually compared by dovecot.
|
|
||||||
assert res["password"] == res2["password"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_nocreate_file(db):
|
|
||||||
chatmaild.dictproxy.NOCREATE_FILE = "/tmp/nocreate"
|
|
||||||
with open(chatmaild.dictproxy.NOCREATE_FILE, "w+") as f:
|
|
||||||
f.write("")
|
|
||||||
assert os.path.exists(chatmaild.dictproxy.NOCREATE_FILE)
|
|
||||||
lookup_passdb(db, "newuser1@something.org", "kajdlqweqwe")
|
|
||||||
assert not get_user_data(db, "newuser1@something.org")
|
|
||||||
os.remove(chatmaild.dictproxy.NOCREATE_FILE)
|
|
||||||
|
|
||||||
|
|
||||||
def test_db_version(db):
|
|
||||||
assert db.get_schema_version() == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_too_high_db_version(db):
|
|
||||||
with db.write_transaction() as conn:
|
|
||||||
conn.execute("PRAGMA user_version=%s;" % (999,))
|
|
||||||
with pytest.raises(DBError):
|
|
||||||
db.ensure_tables()
|
|
||||||
@@ -1,338 +0,0 @@
|
|||||||
from .filtermail import check_encrypted, check_DATA, SendRateLimiter
|
|
||||||
from email.parser import BytesParser
|
|
||||||
from email import policy
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
|
|
||||||
def test_reject_forged_from():
|
|
||||||
def makemail(from_addr):
|
|
||||||
return BytesParser(policy=policy.default).parsebytes(
|
|
||||||
"\r\n".join(
|
|
||||||
[
|
|
||||||
f"From: <{from_addr}",
|
|
||||||
"To: <barbaz@c3.testrun.org>",
|
|
||||||
"Date: Sun, 15 Oct 2023 16:41:44 +0000",
|
|
||||||
"Message-ID: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>",
|
|
||||||
"References: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>",
|
|
||||||
"Chat-Version: 1.0",
|
|
||||||
"MIME-Version: 1.0",
|
|
||||||
"Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no",
|
|
||||||
"",
|
|
||||||
"Hi!",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
).encode()
|
|
||||||
)
|
|
||||||
|
|
||||||
class envelope:
|
|
||||||
mail_from = "bob@c3.testrun.org"
|
|
||||||
rcpt_tos = ["somebody@c3.testrun.org"]
|
|
||||||
|
|
||||||
# test that the filter lets good mail through
|
|
||||||
envelope.content = makemail(envelope.mail_from).as_bytes()
|
|
||||||
assert not check_DATA(envelope=envelope)
|
|
||||||
|
|
||||||
# test that the filter rejects forged mail
|
|
||||||
envelope.content = makemail("forged@c3.testrun.org").as_bytes()
|
|
||||||
error = check_DATA(envelope=envelope)
|
|
||||||
assert "500" in error
|
|
||||||
|
|
||||||
|
|
||||||
def test_filtermail():
|
|
||||||
def check_encrypted_bstr(content):
|
|
||||||
message = BytesParser(policy=policy.default).parsebytes(content)
|
|
||||||
return check_encrypted(message)
|
|
||||||
|
|
||||||
assert not check_encrypted_bstr(b"foo")
|
|
||||||
|
|
||||||
assert not check_encrypted_bstr(
|
|
||||||
"\r\n".join(
|
|
||||||
[
|
|
||||||
"Subject: =?utf-8?q?Message_from_foobar=40c2=2Etestrun=2Eorg?=",
|
|
||||||
"Chat-Disposition-Notification-To: foobar@c2.testrun.org",
|
|
||||||
"Chat-User-Avatar: 0",
|
|
||||||
"From: <foobar@c2.testrun.org>",
|
|
||||||
"To: <barbaz@c2.testrun.org>",
|
|
||||||
"Date: Sun, 15 Oct 2023 16:41:44 +0000",
|
|
||||||
"Message-ID: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>",
|
|
||||||
"References: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>",
|
|
||||||
"Chat-Version: 1.0",
|
|
||||||
"Autocrypt: addr=foobar@c2.testrun.org; prefer-encrypt=mutual;",
|
|
||||||
"\tkeydata=xjMEZSrw3hYJKwYBBAHaRw8BAQdAiEKNQFU28c6qsx4vo/JHdt73RXdjMOmByf/XsGiJ7m",
|
|
||||||
"\tnNFzxmb29iYXJAYzIudGVzdHJ1bi5vcmc+wosEEBYIADMCGQEFAmUq8N4CGwMECwkIBwYVCAkKCwID",
|
|
||||||
"\tFgIBFiEEGil0OvTIa6RngmCLUYNnEa9leJAACgkQUYNnEa9leJCX3gEAhm0MehE5byBBU1avPczr/I",
|
|
||||||
"\tHjNLht7Qf6++mAhlJmtDcA/0C8VYJhsUpmiDjuZaMDWNv4FO2BJG6LH7gSm6n7ClMJzjgEZSrw3hIK",
|
|
||||||
"\tKwYBBAGXVQEFAQEHQAxGG/QW0owCfMp1A+vXEMwgzWcBpNFr58kX2eXuPpM6AwEIB8J4BBgWCAAgBQ",
|
|
||||||
"\tJlKvDeAhsMFiEEGil0OvTIa6RngmCLUYNnEa9leJAACgkQUYNnEa9leJDg1gEAwLf8KDoAAKyYgjyI",
|
|
||||||
"\tvYvO9VEgBni1C4Xx1VjcaEmlDK8BALoFuUCK+enw76TtDcAUKhlhUiM6SDRExkS4Nskp/BcK",
|
|
||||||
"MIME-Version: 1.0",
|
|
||||||
"Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no",
|
|
||||||
"",
|
|
||||||
"Hi!",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
).encode()
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not check_encrypted_bstr(
|
|
||||||
"\r\n".join(
|
|
||||||
[
|
|
||||||
"Subject: =?utf-8?q?Message_from_foobar=40c2=2Etestrun=2Eorg?=",
|
|
||||||
"Chat-Disposition-Notification-To: foobar@c2.testrun.org",
|
|
||||||
"Chat-User-Avatar: 0",
|
|
||||||
"From: <foobar@c2.testrun.org>",
|
|
||||||
"To: <barbaz@c2.testrun.org>",
|
|
||||||
"Date: Sun, 15 Oct 2023 16:41:44 +0000",
|
|
||||||
"Message-ID: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>",
|
|
||||||
"References: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>",
|
|
||||||
"Chat-Version: 1.0",
|
|
||||||
"Autocrypt: addr=foobar@c2.testrun.org; prefer-encrypt=mutual;",
|
|
||||||
"\tkeydata=xjMEZSrw3hYJKwYBBAHaRw8BAQdAiEKNQFU28c6qsx4vo/JHdt73RXdjMOmByf/XsGiJ7m",
|
|
||||||
"\tnNFzxmb29iYXJAYzIudGVzdHJ1bi5vcmc+wosEEBYIADMCGQEFAmUq8N4CGwMECwkIBwYVCAkKCwID",
|
|
||||||
"\tFgIBFiEEGil0OvTIa6RngmCLUYNnEa9leJAACgkQUYNnEa9leJCX3gEAhm0MehE5byBBU1avPczr/I",
|
|
||||||
"\tHjNLht7Qf6++mAhlJmtDcA/0C8VYJhsUpmiDjuZaMDWNv4FO2BJG6LH7gSm6n7ClMJzjgEZSrw3hIK",
|
|
||||||
"\tKwYBBAGXVQEFAQEHQAxGG/QW0owCfMp1A+vXEMwgzWcBpNFr58kX2eXuPpM6AwEIB8J4BBgWCAAgBQ",
|
|
||||||
"\tJlKvDeAhsMFiEEGil0OvTIa6RngmCLUYNnEa9leJAACgkQUYNnEa9leJDg1gEAwLf8KDoAAKyYgjyI",
|
|
||||||
"\tvYvO9VEgBni1C4Xx1VjcaEmlDK8BALoFuUCK+enw76TtDcAUKhlhUiM6SDRExkS4Nskp/BcK",
|
|
||||||
"MIME-Version: 1.0",
|
|
||||||
"Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no",
|
|
||||||
"",
|
|
||||||
"Hi!",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
).encode()
|
|
||||||
)
|
|
||||||
|
|
||||||
# https://xkcd.com/1181/
|
|
||||||
assert not check_encrypted_bstr(
|
|
||||||
"\r\n".join(
|
|
||||||
[
|
|
||||||
"Subject: =?utf-8?q?Message_from_foobar=40c2=2Etestrun=2Eorg?=",
|
|
||||||
"Chat-Disposition-Notification-To: foobar@c2.testrun.org",
|
|
||||||
"Chat-User-Avatar: 0",
|
|
||||||
"From: <foobar@c2.testrun.org>",
|
|
||||||
"To: <barbaz@c2.testrun.org>",
|
|
||||||
"Date: Sun, 15 Oct 2023 16:41:44 +0000",
|
|
||||||
"Message-ID: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>",
|
|
||||||
"References: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>",
|
|
||||||
"Chat-Version: 1.0",
|
|
||||||
"Autocrypt: addr=foobar@c2.testrun.org; prefer-encrypt=mutual;",
|
|
||||||
"\tkeydata=xjMEZSrw3hYJKwYBBAHaRw8BAQdAiEKNQFU28c6qsx4vo/JHdt73RXdjMOmByf/XsGiJ7m",
|
|
||||||
"\tnNFzxmb29iYXJAYzIudGVzdHJ1bi5vcmc+wosEEBYIADMCGQEFAmUq8N4CGwMECwkIBwYVCAkKCwID",
|
|
||||||
"\tFgIBFiEEGil0OvTIa6RngmCLUYNnEa9leJAACgkQUYNnEa9leJCX3gEAhm0MehE5byBBU1avPczr/I",
|
|
||||||
"\tHjNLht7Qf6++mAhlJmtDcA/0C8VYJhsUpmiDjuZaMDWNv4FO2BJG6LH7gSm6n7ClMJzjgEZSrw3hIK",
|
|
||||||
"\tKwYBBAGXVQEFAQEHQAxGG/QW0owCfMp1A+vXEMwgzWcBpNFr58kX2eXuPpM6AwEIB8J4BBgWCAAgBQ",
|
|
||||||
"\tJlKvDeAhsMFiEEGil0OvTIa6RngmCLUYNnEa9leJAACgkQUYNnEa9leJDg1gEAwLf8KDoAAKyYgjyI",
|
|
||||||
"\tvYvO9VEgBni1C4Xx1VjcaEmlDK8BALoFuUCK+enw76TtDcAUKhlhUiM6SDRExkS4Nskp/BcK",
|
|
||||||
"MIME-Version: 1.0",
|
|
||||||
"Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no",
|
|
||||||
"",
|
|
||||||
"-----BEGIN PGP MESSAGE-----",
|
|
||||||
"Hi!",
|
|
||||||
"-----END PGP MESSAGE-----",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
).encode()
|
|
||||||
)
|
|
||||||
|
|
||||||
assert check_encrypted_bstr(
|
|
||||||
"\r\n".join(
|
|
||||||
[
|
|
||||||
"Subject: ...",
|
|
||||||
"From: <barbaz@c2.testrun.org>",
|
|
||||||
"To: <foobar@c2.testrun.org>",
|
|
||||||
"Date: Sun, 15 Oct 2023 16:43:21 +0000",
|
|
||||||
"Message-ID: <Mr.UVyJWZmkCKM.hGzNc6glBE_@c2.testrun.org>",
|
|
||||||
"In-Reply-To: <Mr.MvmCz-GQbi_.6FGRkhDf05c@c2.testrun.org>",
|
|
||||||
"References: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>",
|
|
||||||
"\t<Mr.MvmCz-GQbi_.6FGRkhDf05c@c2.testrun.org>",
|
|
||||||
"Chat-Version: 1.0",
|
|
||||||
"Autocrypt: addr=barbaz@c2.testrun.org; prefer-encrypt=mutual;",
|
|
||||||
"\tkeydata=xjMEZSwWjhYJKwYBBAHaRw8BAQdAQBEhqeJh0GueHB6kF/DUQqYCxARNBVokg/AzT+7LqH",
|
|
||||||
"\trNFzxiYXJiYXpAYzIudGVzdHJ1bi5vcmc+wosEEBYIADMCGQEFAmUsFo4CGwMECwkIBwYVCAkKCwID",
|
|
||||||
"\tFgIBFiEEFTfUNvVnY3b9F7yHnmme1PfUhX8ACgkQnmme1PfUhX9A4AEAnHWHp49eBCMHK5t66gYPiW",
|
|
||||||
"\tXQuB1mwUjzGfYWB+0RXUoA/0xcQ3FbUNlGKW7Blp6eMFfViv6Mv2d3kNSXACB6nmcMzjgEZSwWjhIK",
|
|
||||||
"\tKwYBBAGXVQEFAQEHQBpY5L2M1XHo0uxf8SX1wNLBp/OVvidoWHQF2Jz+kJsUAwEIB8J4BBgWCAAgBQ",
|
|
||||||
"\tJlLBaOAhsMFiEEFTfUNvVnY3b9F7yHnmme1PfUhX8ACgkQnmme1PfUhX/INgEA37AJaNvruYsJVanP",
|
|
||||||
"\tIXnYw4CKd55UAwl8Zcy+M2diAbkA/0fHHcGV4r78hpbbL1Os52DPOdqYQRauIeJUeG+G6bQO",
|
|
||||||
"MIME-Version: 1.0",
|
|
||||||
'Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";',
|
|
||||||
'\tboundary="YFrteb74qSXmggbOxZL9dRnhymywAi"',
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"--YFrteb74qSXmggbOxZL9dRnhymywAi",
|
|
||||||
"Content-Description: PGP/MIME version identification",
|
|
||||||
"Content-Type: application/pgp-encrypted",
|
|
||||||
"",
|
|
||||||
"Version: 1",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"--YFrteb74qSXmggbOxZL9dRnhymywAi",
|
|
||||||
"Content-Description: OpenPGP encrypted message",
|
|
||||||
'Content-Disposition: inline; filename="encrypted.asc";',
|
|
||||||
'Content-Type: application/octet-stream; name="encrypted.asc"',
|
|
||||||
"",
|
|
||||||
"-----BEGIN PGP MESSAGE-----",
|
|
||||||
"",
|
|
||||||
"wU4DhW3gBZ/VvCYSAQdA8bMs2spwbKdGjVsL1ByPkNrqD7frpB73maeL6I6SzDYg",
|
|
||||||
"O5G53tv339RdKq3WRcCtEEvxjHlUx2XNwXzC04BpmfvBTgNfPUyLDzjXnxIBB0Ae",
|
|
||||||
"8ymwGvXMCCimHXN0Dg8Ui62KOi03h0UgheoHWovJSCDF4CKre/xtFr3nL7lq/PKI",
|
|
||||||
"JsjVNz7/RK9FSXF6WwfONtLCyQGEuVAsB/KXfCBEyfKhaMwGHvhujRidGW5uV1no",
|
|
||||||
"lMGl3ODmo29Lgeu2uSE7EpJRZoe6hU6ddmBkqxax61ZtkaFlGFFpdo2K8balNNdz",
|
|
||||||
"ZsJ/9mmI9x3oOJ4/l1nhQbUO9ADbs7gJhFdV5Qkp30b5fCI7bU+aoe1ccBbLe/WM",
|
|
||||||
"YUty1PqcuQT7XjA+XmYuL261tvW8pBetT+i33/E2d8PzzYt2IuK9qeevyS+yxdwA",
|
|
||||||
"kfwejFWzzsUlJaDxs1x4XOxkMgSj+jo+g12dFOb7fyClsAnq23iDb8AuaT/BScAI",
|
|
||||||
"+lO+gher69+6LmM7VGHLG5k762J1jTaQCaKt1s8TAWV99Eo4491vL6fyvk3l/Cfg",
|
|
||||||
"RXSwiWFgj19Pn0Rq7CD9v22UE2vdUMBTcV4aw79mClk1YQ23jbF0y5DCjPdJ62Zo",
|
|
||||||
"tskBgFt3NoWV80jZ76zIBLrrjLwCCll8JjJtFwSkt2GX5RFBsVa4A8IDht9RtEk7",
|
|
||||||
"rrHgbSZQfkauEi/mH3/6CDZoLqSHudUZ7d4MaJwun1TkFYGe2ORwGJd4OBj3oGJp",
|
|
||||||
"H8YBwCpk///L/fKjX0Gg3M8nrpM4wrRFhPKidAgO/kcm25X4+ZHlVkWBTCt5RWKI",
|
|
||||||
"fHh6oLDZCqCfcgMkE1KKmwfIHaUkhq5BPRigwy6i5dh1DM4+1UCLh3dxzVbqE9b9",
|
|
||||||
"61NB19nXdRtDA2sOUnj9ve6m/wEPyCb6/zBQZqvCBYb1/AjdXpUrFT+DbpfyxaXN",
|
|
||||||
"XfhDVb5mNqNM/IVj0V5fvTc6vOfYbzQtPm10H+FdWWfb+rJRfyC3MA2w2IqstFe3",
|
|
||||||
"w3bu2iE6CQvSqRvge+ZqLKt/NqYwOURiUmpuklbl3kPJ97+mfKWoiqk8Iz1VY+bb",
|
|
||||||
"NMUC7aoGv+jcoj+WS6PYO8N6BeRVUUB3ZJSf8nzjgxm1/BcM+UD3BPrlhT11ODRs",
|
|
||||||
"baifGbprMWwt3dhb8cQgRT8GPdpO1OsDkzL6iikMjLHWWiA99GV6ruiHsIPw6boW",
|
|
||||||
"A6/uSOskbDHOROotKmddGTBd0iiHXAoQsJFt1ZjUkt6EHrgWs+GAvrvKpXs1mrz8",
|
|
||||||
"uj3GwEFrHS+Xuf2UDgpszYT3hI2cL/kUtGakVR7m7vVMZqXBUbZdGAEb1PZNPwsI",
|
|
||||||
"E4aMK02+EVB+tSN4Fzj99N2YD0inVYt+oPjr2tHhUS6aSGBNS/48Ki47DOg4Sxkn",
|
|
||||||
"lkOWnEbCD+XTnbDd",
|
|
||||||
"=agR5",
|
|
||||||
"-----END PGP MESSAGE-----",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"--YFrteb74qSXmggbOxZL9dRnhymywAi--",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
).encode()
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not check_encrypted_bstr(
|
|
||||||
"\r\n".join(
|
|
||||||
[
|
|
||||||
"Subject: Buy Penis Enlargement at www.malicious-domain.com",
|
|
||||||
"From: <barbaz@c2.testrun.org>",
|
|
||||||
"To: <foobar@c2.testrun.org>",
|
|
||||||
"Date: Sun, 15 Oct 2023 16:43:21 +0000",
|
|
||||||
"Message-ID: <Mr.UVyJWZmkCKM.hGzNc6glBE_@c2.testrun.org>",
|
|
||||||
"In-Reply-To: <Mr.MvmCz-GQbi_.6FGRkhDf05c@c2.testrun.org>",
|
|
||||||
"References: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>",
|
|
||||||
"\t<Mr.MvmCz-GQbi_.6FGRkhDf05c@c2.testrun.org>",
|
|
||||||
"Chat-Version: 1.0",
|
|
||||||
"Autocrypt: addr=barbaz@c2.testrun.org; prefer-encrypt=mutual;",
|
|
||||||
"\tkeydata=xjMEZSwWjhYJKwYBBAHaRw8BAQdAQBEhqeJh0GueHB6kF/DUQqYCxARNBVokg/AzT+7LqH",
|
|
||||||
"\trNFzxiYXJiYXpAYzIudGVzdHJ1bi5vcmc+wosEEBYIADMCGQEFAmUsFo4CGwMECwkIBwYVCAkKCwID",
|
|
||||||
"\tFgIBFiEEFTfUNvVnY3b9F7yHnmme1PfUhX8ACgkQnmme1PfUhX9A4AEAnHWHp49eBCMHK5t66gYPiW",
|
|
||||||
"\tXQuB1mwUjzGfYWB+0RXUoA/0xcQ3FbUNlGKW7Blp6eMFfViv6Mv2d3kNSXACB6nmcMzjgEZSwWjhIK",
|
|
||||||
"\tKwYBBAGXVQEFAQEHQBpY5L2M1XHo0uxf8SX1wNLBp/OVvidoWHQF2Jz+kJsUAwEIB8J4BBgWCAAgBQ",
|
|
||||||
"\tJlLBaOAhsMFiEEFTfUNvVnY3b9F7yHnmme1PfUhX8ACgkQnmme1PfUhX/INgEA37AJaNvruYsJVanP",
|
|
||||||
"\tIXnYw4CKd55UAwl8Zcy+M2diAbkA/0fHHcGV4r78hpbbL1Os52DPOdqYQRauIeJUeG+G6bQO",
|
|
||||||
"MIME-Version: 1.0",
|
|
||||||
'Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";',
|
|
||||||
'\tboundary="YFrteb74qSXmggbOxZL9dRnhymywAi"',
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"--YFrteb74qSXmggbOxZL9dRnhymywAi",
|
|
||||||
"Content-Description: PGP/MIME version identification",
|
|
||||||
"Content-Type: application/pgp-encrypted",
|
|
||||||
"",
|
|
||||||
"Version: 1",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"--YFrteb74qSXmggbOxZL9dRnhymywAi",
|
|
||||||
"Content-Description: OpenPGP encrypted message",
|
|
||||||
'Content-Disposition: inline; filename="encrypted.asc";',
|
|
||||||
'Content-Type: application/octet-stream; name="encrypted.asc"',
|
|
||||||
"",
|
|
||||||
"-----BEGIN PGP MESSAGE-----",
|
|
||||||
"",
|
|
||||||
"wU4DhW3gBZ/VvCYSAQdA8bMs2spwbKdGjVsL1ByPkNrqD7frpB73maeL6I6SzDYg",
|
|
||||||
"O5G53tv339RdKq3WRcCtEEvxjHlUx2XNwXzC04BpmfvBTgNfPUyLDzjXnxIBB0Ae",
|
|
||||||
"8ymwGvXMCCimHXN0Dg8Ui62KOi03h0UgheoHWovJSCDF4CKre/xtFr3nL7lq/PKI",
|
|
||||||
"JsjVNz7/RK9FSXF6WwfONtLCyQGEuVAsB/KXfCBEyfKhaMwGHvhujRidGW5uV1no",
|
|
||||||
"lMGl3ODmo29Lgeu2uSE7EpJRZoe6hU6ddmBkqxax61ZtkaFlGFFpdo2K8balNNdz",
|
|
||||||
"ZsJ/9mmI9x3oOJ4/l1nhQbUO9ADbs7gJhFdV5Qkp30b5fCI7bU+aoe1ccBbLe/WM",
|
|
||||||
"YUty1PqcuQT7XjA+XmYuL261tvW8pBetT+i33/E2d8PzzYt2IuK9qeevyS+yxdwA",
|
|
||||||
"kfwejFWzzsUlJaDxs1x4XOxkMgSj+jo+g12dFOb7fyClsAnq23iDb8AuaT/BScAI",
|
|
||||||
"+lO+gher69+6LmM7VGHLG5k762J1jTaQCaKt1s8TAWV99Eo4491vL6fyvk3l/Cfg",
|
|
||||||
"RXSwiWFgj19Pn0Rq7CD9v22UE2vdUMBTcV4aw79mClk1YQ23jbF0y5DCjPdJ62Zo",
|
|
||||||
"tskBgFt3NoWV80jZ76zIBLrrjLwCCll8JjJtFwSkt2GX5RFBsVa4A8IDht9RtEk7",
|
|
||||||
"rrHgbSZQfkauEi/mH3/6CDZoLqSHudUZ7d4MaJwun1TkFYGe2ORwGJd4OBj3oGJp",
|
|
||||||
"H8YBwCpk///L/fKjX0Gg3M8nrpM4wrRFhPKidAgO/kcm25X4+ZHlVkWBTCt5RWKI",
|
|
||||||
"fHh6oLDZCqCfcgMkE1KKmwfIHaUkhq5BPRigwy6i5dh1DM4+1UCLh3dxzVbqE9b9",
|
|
||||||
"61NB19nXdRtDA2sOUnj9ve6m/wEPyCb6/zBQZqvCBYb1/AjdXpUrFT+DbpfyxaXN",
|
|
||||||
"XfhDVb5mNqNM/IVj0V5fvTc6vOfYbzQtPm10H+FdWWfb+rJRfyC3MA2w2IqstFe3",
|
|
||||||
"w3bu2iE6CQvSqRvge+ZqLKt/NqYwOURiUmpuklbl3kPJ97+mfKWoiqk8Iz1VY+bb",
|
|
||||||
"NMUC7aoGv+jcoj+WS6PYO8N6BeRVUUB3ZJSf8nzjgxm1/BcM+UD3BPrlhT11ODRs",
|
|
||||||
"baifGbprMWwt3dhb8cQgRT8GPdpO1OsDkzL6iikMjLHWWiA99GV6ruiHsIPw6boW",
|
|
||||||
"A6/uSOskbDHOROotKmddGTBd0iiHXAoQsJFt1ZjUkt6EHrgWs+GAvrvKpXs1mrz8",
|
|
||||||
"uj3GwEFrHS+Xuf2UDgpszYT3hI2cL/kUtGakVR7m7vVMZqXBUbZdGAEb1PZNPwsI",
|
|
||||||
"E4aMK02+EVB+tSN4Fzj99N2YD0inVYt+oPjr2tHhUS6aSGBNS/48Ki47DOg4Sxkn",
|
|
||||||
"lkOWnEbCD+XTnbDd",
|
|
||||||
"=agR5",
|
|
||||||
"-----END PGP MESSAGE-----",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"--YFrteb74qSXmggbOxZL9dRnhymywAi--",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
).encode()
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not check_encrypted_bstr(
|
|
||||||
"\r\n".join(
|
|
||||||
[
|
|
||||||
"Subject: Message opened",
|
|
||||||
"From: <barbaz@c2.testrun.org>",
|
|
||||||
"To: <foobar@c2.testrun.org>",
|
|
||||||
"Date: Sun, 15 Oct 2023 16:43:25 +0000",
|
|
||||||
"Message-ID: <Mr.78MWtlV7RAi.goCFzBhCYfy@c2.testrun.org>",
|
|
||||||
"Auto-Submitted: auto-replied",
|
|
||||||
"Chat-Version: 1.0",
|
|
||||||
"MIME-Version: 1.0",
|
|
||||||
"Content-Type: multipart/report; report-type=disposition-notification;",
|
|
||||||
'\tboundary="Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi"',
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"--Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi",
|
|
||||||
"Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no",
|
|
||||||
"",
|
|
||||||
'The "Hi!" message you sent was displayed on the screen of the recipient.',
|
|
||||||
"",
|
|
||||||
"This is no guarantee the content was read.",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"--Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi",
|
|
||||||
"Content-Type: message/disposition-notification",
|
|
||||||
"",
|
|
||||||
"Reporting-UA: Delta Chat 1.124.1",
|
|
||||||
"Original-Recipient: rfc822;barbaz@c2.testrun.org",
|
|
||||||
"Final-Recipient: rfc822;barbaz@c2.testrun.org",
|
|
||||||
"Original-Message-ID: <Mr.MvmCz-GQbi_.6FGRkhDf05c@c2.testrun.org>",
|
|
||||||
"Disposition: manual-action/MDN-sent-automatically; displayed",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"--Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi--",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
).encode()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_send_rate_limiter():
|
|
||||||
limiter = SendRateLimiter()
|
|
||||||
for i in range(100):
|
|
||||||
if limiter.is_sending_allowed("some@example.org"):
|
|
||||||
if i <= SendRateLimiter.MAX_USER_SEND_PER_MINUTE:
|
|
||||||
continue
|
|
||||||
pytest.fail("limiter didn't work")
|
|
||||||
else:
|
|
||||||
assert i == SendRateLimiter.MAX_USER_SEND_PER_MINUTE + 1
|
|
||||||
break
|
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
9
tests/chatmaild/conftest.py
Normal file
9
tests/chatmaild/conftest.py
Normal 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)
|
||||||
48
tests/chatmaild/test_dictproxy.py
Normal file
48
tests/chatmaild/test_dictproxy.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import chatmaild.dictproxy
|
||||||
|
from chatmaild.dictproxy import DictProxy
|
||||||
|
from chatmaild.database import DBError
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dictproxy(db, maildomain):
|
||||||
|
return DictProxy(db, maildomain)
|
||||||
|
|
||||||
|
|
||||||
|
def test_basic(dictproxy, tmpdir, monkeypatch):
|
||||||
|
monkeypatch.setattr(
|
||||||
|
chatmaild.dictproxy, "NOCREATE_FILE", tmpdir.join("nocreate").strpath
|
||||||
|
)
|
||||||
|
dictproxy.lookup_passdb("link2xt@c1.testrun.org", "asdf")
|
||||||
|
assert dictproxy.get_user_data("link2xt@c1.testrun.org")
|
||||||
|
|
||||||
|
|
||||||
|
def test_dont_overwrite_password_on_wrong_login(dictproxy):
|
||||||
|
"""Test that logging in with a different password doesn't create a new user"""
|
||||||
|
res = dictproxy.lookup_passdb("newuser1@something.org", "kajdlkajsldk12l3kj1983")
|
||||||
|
assert res["password"]
|
||||||
|
res2 = dictproxy.lookup_passdb("newuser1@something.org", "kajdlqweqwe")
|
||||||
|
# this function always returns a password hash, which is actually compared by dovecot.
|
||||||
|
assert res["password"] == res2["password"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_nocreate_file(dictproxy, tmpdir, monkeypatch):
|
||||||
|
nocreate = tmpdir.join("nocreate")
|
||||||
|
monkeypatch.setattr(chatmaild.dictproxy, "NOCREATE_FILE", str(nocreate))
|
||||||
|
nocreate.write("")
|
||||||
|
dictproxy.lookup_passdb("newuser1@something.org", "kajdlqweqwe")
|
||||||
|
assert not dictproxy.get_user_data("newuser1@something.org")
|
||||||
|
|
||||||
|
|
||||||
|
def test_db_version(db):
|
||||||
|
assert db.get_schema_version() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_too_high_db_version(db):
|
||||||
|
with db.write_transaction() as conn:
|
||||||
|
conn.execute("PRAGMA user_version=%s;" % (999,))
|
||||||
|
with pytest.raises(DBError):
|
||||||
|
db.ensure_tables()
|
||||||
51
tests/chatmaild/test_filtermail.py
Normal file
51
tests/chatmaild/test_filtermail.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
from chatmaild.filtermail import check_encrypted, check_DATA, SendRateLimiter
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_reject_forged_from(maildata, gencreds):
|
||||||
|
class env:
|
||||||
|
mail_from = gencreds()[0]
|
||||||
|
rcpt_tos = [gencreds()[0]]
|
||||||
|
|
||||||
|
# test that the filter lets good mail through
|
||||||
|
env.content = maildata("plain.eml", from_addr=env.mail_from).as_bytes()
|
||||||
|
assert not check_DATA(envelope=env)
|
||||||
|
|
||||||
|
# test that the filter rejects forged mail
|
||||||
|
env.content = maildata("plain.eml", from_addr="forged@c3.testrun.org").as_bytes()
|
||||||
|
error = check_DATA(envelope=env)
|
||||||
|
assert "500" in error
|
||||||
|
|
||||||
|
|
||||||
|
def test_filtermail_no_encryption_detection(maildata):
|
||||||
|
msg = maildata("plain.eml")
|
||||||
|
assert not check_encrypted(msg)
|
||||||
|
|
||||||
|
# https://xkcd.com/1181/
|
||||||
|
msg = maildata("fake-encrypted.eml")
|
||||||
|
assert not check_encrypted(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filtermail_encryption_detection(maildata):
|
||||||
|
msg = maildata("encrypted.eml")
|
||||||
|
assert check_encrypted(msg)
|
||||||
|
|
||||||
|
# if the subject is not "..." it is not considered ac-encrypted
|
||||||
|
msg.replace_header("Subject", "Click this link")
|
||||||
|
assert not check_encrypted(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filtermail_mdn_is_not_encrypted(maildata):
|
||||||
|
assert not check_encrypted(maildata("mdn.eml"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_send_rate_limiter():
|
||||||
|
limiter = SendRateLimiter()
|
||||||
|
for i in range(100):
|
||||||
|
if limiter.is_sending_allowed("some@example.org"):
|
||||||
|
if i <= SendRateLimiter.MAX_USER_SEND_PER_MINUTE:
|
||||||
|
continue
|
||||||
|
pytest.fail("limiter didn't work")
|
||||||
|
else:
|
||||||
|
assert i == SendRateLimiter.MAX_USER_SEND_PER_MINUTE + 1
|
||||||
|
break
|
||||||
@@ -6,10 +6,16 @@ import subprocess
|
|||||||
import imaplib
|
import imaplib
|
||||||
import smtplib
|
import smtplib
|
||||||
import itertools
|
import itertools
|
||||||
|
from email.parser import BytesParser
|
||||||
|
from email import policy
|
||||||
|
from pathlib import Path
|
||||||
from math import ceil
|
from math import ceil
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
conftestdir = Path(__file__).parent
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
parser.addoption(
|
parser.addoption(
|
||||||
"--slow", action="store_true", default=False, help="also run slow tests"
|
"--slow", action="store_true", default=False, help="also run slow tests"
|
||||||
@@ -86,7 +92,7 @@ def pytest_terminal_summary(terminalreporter):
|
|||||||
return
|
return
|
||||||
|
|
||||||
tr.section("benchmark results")
|
tr.section("benchmark results")
|
||||||
float_names = 'median min max'.split()
|
float_names = "median min max".split()
|
||||||
width = max(map(len, float_names))
|
width = max(map(len, float_names))
|
||||||
|
|
||||||
def fcol(parts):
|
def fcol(parts):
|
||||||
@@ -281,13 +287,19 @@ class Remote:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mailgen(request):
|
def maildata(request, gencreds):
|
||||||
class Mailgen:
|
datadir = conftestdir.joinpath("mail-data")
|
||||||
def get_encrypted(self, from_addr, to_addr):
|
|
||||||
data = request.fspath.dirpath("mailgen/encrypted.eml").read()
|
|
||||||
return data.format(from_addr=from_addr, to_addr=to_addr)
|
|
||||||
|
|
||||||
return Mailgen()
|
def maildata(name, parsed=True, from_addr=None, to_addr=None):
|
||||||
|
if from_addr is None:
|
||||||
|
from_addr = gencreds()[0]
|
||||||
|
if to_addr is None:
|
||||||
|
to_addr = gencreds()[0]
|
||||||
|
data = datadir.joinpath(name).read_text()
|
||||||
|
text = data.format(from_addr=from_addr, to_addr=to_addr)
|
||||||
|
return BytesParser(policy=policy.default).parsebytes(text.encode())
|
||||||
|
|
||||||
|
return maildata
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
25
tests/mail-data/fake-encrypted.eml
Normal file
25
tests/mail-data/fake-encrypted.eml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
Subject: =?utf-8?q?Message_from_foobar=40c2=2Etestrun=2Eorg?=
|
||||||
|
Chat-Disposition-Notification-To: foobar@c2.testrun.org
|
||||||
|
Chat-User-Avatar: 0
|
||||||
|
From: <{from_addr}>
|
||||||
|
To: <{to_addr}>
|
||||||
|
Date: Sun, 15 Oct 2023 16:41:44 +0000
|
||||||
|
Message-ID: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>
|
||||||
|
References: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>
|
||||||
|
Chat-Version: 1.0
|
||||||
|
Autocrypt: addr={from_addr}; prefer-encrypt=mutual;
|
||||||
|
keydata=xjMEZSrw3hYJKwYBBAHaRw8BAQdAiEKNQFU28c6qsx4vo/JHdt73RXdjMOmByf/XsGiJ7m
|
||||||
|
nNFzxmb29iYXJAYzIudGVzdHJ1bi5vcmc+wosEEBYIADMCGQEFAmUq8N4CGwMECwkIBwYVCAkKCwID
|
||||||
|
FgIBFiEEGil0OvTIa6RngmCLUYNnEa9leJAACgkQUYNnEa9leJCX3gEAhm0MehE5byBBU1avPczr/I
|
||||||
|
HjNLht7Qf6++mAhlJmtDcA/0C8VYJhsUpmiDjuZaMDWNv4FO2BJG6LH7gSm6n7ClMJzjgEZSrw3hIK
|
||||||
|
KwYBBAGXVQEFAQEHQAxGG/QW0owCfMp1A+vXEMwgzWcBpNFr58kX2eXuPpM6AwEIB8J4BBgWCAAgBQ
|
||||||
|
JlKvDeAhsMFiEEGil0OvTIa6RngmCLUYNnEa9leJAACgkQUYNnEa9leJDg1gEAwLf8KDoAAKyYgjyI
|
||||||
|
vYvO9VEgBni1C4Xx1VjcaEmlDK8BALoFuUCK+enw76TtDcAUKhlhUiM6SDRExkS4Nskp/BcK
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||||
|
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
Hi!
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
|
||||||
|
|
||||||
33
tests/mail-data/mdn.eml
Normal file
33
tests/mail-data/mdn.eml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
Subject: Message opened
|
||||||
|
From: <barbaz@c2.testrun.org>
|
||||||
|
To: <foobar@c2.testrun.org>
|
||||||
|
Date: Sun, 15 Oct 2023 16:43:25 +0000
|
||||||
|
Message-ID: <Mr.78MWtlV7RAi.goCFzBhCYfy@c2.testrun.org>
|
||||||
|
Auto-Submitted: auto-replied
|
||||||
|
Chat-Version: 1.0
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/report; report-type=disposition-notification;
|
||||||
|
boundary="Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi"
|
||||||
|
|
||||||
|
|
||||||
|
--Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi
|
||||||
|
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||||
|
|
||||||
|
The "Hi!" message you sent was displayed on the screen of the recipient.
|
||||||
|
|
||||||
|
This is no guarantee the content was read.
|
||||||
|
|
||||||
|
|
||||||
|
--Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi
|
||||||
|
Content-Type: message/disposition-notification
|
||||||
|
|
||||||
|
Reporting-UA: Delta Chat 1.124.1
|
||||||
|
Original-Recipient: rfc822;barbaz@c2.testrun.org
|
||||||
|
Final-Recipient: rfc822;barbaz@c2.testrun.org
|
||||||
|
Original-Message-ID: <Mr.MvmCz-GQbi_.6FGRkhDf05c@c2.testrun.org>
|
||||||
|
Disposition: manual-action/MDN-sent-automatically; displayed
|
||||||
|
|
||||||
|
|
||||||
|
--Gl92xgZjOShJ5PGHntqYkoo2OK2Dvi--
|
||||||
|
|
||||||
|
|
||||||
23
tests/mail-data/plain.eml
Normal file
23
tests/mail-data/plain.eml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
Subject: =?utf-8?q?Message_from_foobar=40c2=2Etestrun=2Eorg?=
|
||||||
|
Chat-Disposition-Notification-To: foobar@c2.testrun.org
|
||||||
|
Chat-User-Avatar: 0
|
||||||
|
From: <{from_addr}>
|
||||||
|
To: <{to_addr}>
|
||||||
|
Date: Sun, 15 Oct 2023 16:41:44 +0000
|
||||||
|
Message-ID: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>
|
||||||
|
References: <Mr.3gckbNy5bch.uK3Hd2Ws6-w@c2.testrun.org>
|
||||||
|
Chat-Version: 1.0
|
||||||
|
Autocrypt: addr=foobar@c2.testrun.org; prefer-encrypt=mutual;
|
||||||
|
keydata=xjMEZSrw3hYJKwYBBAHaRw8BAQdAiEKNQFU28c6qsx4vo/JHdt73RXdjMOmByf/XsGiJ7m
|
||||||
|
nNFzxmb29iYXJAYzIudGVzdHJ1bi5vcmc+wosEEBYIADMCGQEFAmUq8N4CGwMECwkIBwYVCAkKCwID
|
||||||
|
FgIBFiEEGil0OvTIa6RngmCLUYNnEa9leJAACgkQUYNnEa9leJCX3gEAhm0MehE5byBBU1avPczr/I
|
||||||
|
HjNLht7Qf6++mAhlJmtDcA/0C8VYJhsUpmiDjuZaMDWNv4FO2BJG6LH7gSm6n7ClMJzjgEZSrw3hIK
|
||||||
|
KwYBBAGXVQEFAQEHQAxGG/QW0owCfMp1A+vXEMwgzWcBpNFr58kX2eXuPpM6AwEIB8J4BBgWCAAgBQ
|
||||||
|
JlKvDeAhsMFiEEGil0OvTIa6RngmCLUYNnEa9leJAACgkQUYNnEa9leJDg1gEAwLf8KDoAAKyYgjyI
|
||||||
|
vYvO9VEgBni1C4Xx1VjcaEmlDK8BALoFuUCK+enw76TtDcAUKhlhUiM6SDRExkS4Nskp/BcK
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||||
|
|
||||||
|
Hi!
|
||||||
|
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ def test_use_two_chatmailservers(cmfactory, maildomain2):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("forgeaddr", ["internal", "someone@example.org"])
|
@pytest.mark.parametrize("forgeaddr", ["internal", "someone@example.org"])
|
||||||
def test_reject_forged_from(cmsetup, mailgen, lp, forgeaddr):
|
def test_reject_forged_from(cmsetup, maildata, lp, forgeaddr):
|
||||||
user1, user3 = cmsetup.gen_users(2)
|
user1, user3 = cmsetup.gen_users(2)
|
||||||
|
|
||||||
lp.sec("send encrypted message with forged from")
|
lp.sec("send encrypted message with forged from")
|
||||||
@@ -31,7 +31,8 @@ def test_reject_forged_from(cmsetup, mailgen, lp, forgeaddr):
|
|||||||
addr_to_forge = "someone@example.org"
|
addr_to_forge = "someone@example.org"
|
||||||
|
|
||||||
print("message to inject:")
|
print("message to inject:")
|
||||||
msg = mailgen.get_encrypted(from_addr=addr_to_forge, to_addr=user3.addr)
|
msg = maildata("encrypted.eml", from_addr=addr_to_forge, to_addr=user3.addr)
|
||||||
|
msg = msg.as_string()
|
||||||
for line in msg.split("\n")[:4]:
|
for line in msg.split("\n")[:4]:
|
||||||
print(f" {line}")
|
print(f" {line}")
|
||||||
|
|
||||||
@@ -42,10 +43,12 @@ def test_reject_forged_from(cmsetup, mailgen, lp, forgeaddr):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow
|
@pytest.mark.slow
|
||||||
def test_exceed_rate_limit(cmsetup, gencreds, mailgen):
|
def test_exceed_rate_limit(cmsetup, gencreds, maildata):
|
||||||
"""Test that the per-account send-mail limit is exceeded."""
|
"""Test that the per-account send-mail limit is exceeded."""
|
||||||
user1, user2 = cmsetup.gen_users(2)
|
user1, user2 = cmsetup.gen_users(2)
|
||||||
mail = mailgen.get_encrypted(user1.addr, user2.addr)
|
mail = maildata(
|
||||||
|
"encrypted.eml", from_addr=user1.addr, to_addr=user2.addr
|
||||||
|
).as_string()
|
||||||
for i in range(100):
|
for i in range(100):
|
||||||
print("Sending mail", str(i))
|
print("Sending mail", str(i))
|
||||||
try:
|
try:
|
||||||
@@ -55,6 +58,6 @@ def test_exceed_rate_limit(cmsetup, gencreds, mailgen):
|
|||||||
pytest.fail(f"rate limit was exceeded too early with msg {i}")
|
pytest.fail(f"rate limit was exceeded too early with msg {i}")
|
||||||
outcome = e.recipients[user2.addr]
|
outcome = e.recipients[user2.addr]
|
||||||
assert outcome[0] == 450
|
assert outcome[0] == 450
|
||||||
assert b'4.7.1: Too much mail from' in outcome[1]
|
assert b"4.7.1: Too much mail from" in outcome[1]
|
||||||
return
|
return
|
||||||
pytest.fail("Rate limit was not exceeded")
|
pytest.fail("Rate limit was not exceeded")
|
||||||
Reference in New Issue
Block a user