mirror of
https://github.com/chatmail/relay.git
synced 2026-05-13 17:34:38 +00:00
Compare commits
12 Commits
link2xt/no
...
hpk/fix_ac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9061cbbb4a | ||
|
|
e27ec22465 | ||
|
|
073f567292 | ||
|
|
d74f3dfeda | ||
|
|
43c02377ef | ||
|
|
70f330b0e4 | ||
|
|
02eaa55441 | ||
|
|
6c3ec903c2 | ||
|
|
7d9b81863f | ||
|
|
af90d0a7de | ||
|
|
322bc9a3aa | ||
|
|
e4009854dc |
@@ -81,10 +81,11 @@ comprised of minimal setups of
|
|||||||
|
|
||||||
as well as two custom services that are integrated with these two:
|
as well as two custom services that are integrated with these two:
|
||||||
|
|
||||||
- `chatmaild/src/chatmaild/dictproxy.py` implements
|
- `chatmaild/src/chatmaild/doveauth.py` implements
|
||||||
create-on-login account creation semantics and is used
|
create-on-login account creation semantics and is used
|
||||||
by Dovecot during login authentication and by Postfix
|
by Dovecot during login authentication and by Postfix
|
||||||
which in turn uses Dovecot SASL to authenticate users
|
which in turn uses [Dovecot SASL](https://doc.dovecot.org/configuration_manual/authentication/dict/#complete-example-for-authenticating-via-a-unix-socket)
|
||||||
|
to authenticate users
|
||||||
to send mails for them.
|
to send mails for them.
|
||||||
|
|
||||||
- `chatmaild/src/chatmaild/filtermail.py` prevents
|
- `chatmaild/src/chatmaild/filtermail.py` prevents
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
doveauth-dictproxy = "chatmaild.dictproxy:main"
|
doveauth = "chatmaild.doveauth:main"
|
||||||
filtermail = "chatmaild.filtermail:main"
|
filtermail = "chatmaild.filtermail:main"
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = "-v -ra --strict-markers"
|
addopts = "-v -ra --strict-markers"
|
||||||
|
log_format = "%(asctime)s %(levelname)s %(message)s"
|
||||||
|
log_date_format = "%Y-%m-%d %H:%M:%S"
|
||||||
|
log_level = "INFO"
|
||||||
|
|
||||||
[tool.tox]
|
[tool.tox]
|
||||||
legacy_tox_ini = """
|
legacy_tox_ini = """
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ def handle_dovecot_request(msg, db, mail_domain):
|
|||||||
|
|
||||||
|
|
||||||
class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer):
|
class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer):
|
||||||
pass
|
request_queue_size = 100
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -128,14 +128,20 @@ def main():
|
|||||||
|
|
||||||
class Handler(StreamRequestHandler):
|
class Handler(StreamRequestHandler):
|
||||||
def handle(self):
|
def handle(self):
|
||||||
while True:
|
try:
|
||||||
msg = self.rfile.readline().strip().decode()
|
while True:
|
||||||
if not msg:
|
msg = self.rfile.readline().strip().decode()
|
||||||
break
|
if not msg:
|
||||||
res = handle_dovecot_request(msg, db, mail_domain)
|
break
|
||||||
if res:
|
res = handle_dovecot_request(msg, db, mail_domain)
|
||||||
self.wfile.write(res.encode("ascii"))
|
if res:
|
||||||
self.wfile.flush()
|
self.wfile.write(res.encode("ascii"))
|
||||||
|
self.wfile.flush()
|
||||||
|
else:
|
||||||
|
logging.warn("request had no answer: %r", msg)
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Exception in the handler")
|
||||||
|
raise
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.unlink(socket)
|
os.unlink(socket)
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
Description=Dict authentication proxy for dovecot
|
Description=Dict authentication proxy for dovecot
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/usr/local/bin/doveauth-dictproxy /run/dovecot/doveauth.socket vmail /home/vmail/passdb.sqlite
|
ExecStart=/usr/local/bin/doveauth /run/dovecot/doveauth.socket vmail /home/vmail/passdb.sqlite
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=30
|
RestartSec=30
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ class SendRateLimiter:
|
|||||||
def main():
|
def main():
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
assert len(args) == 1
|
assert len(args) == 1
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.WARN)
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
task = asyncmain_beforequeue(port=int(args[0]))
|
task = asyncmain_beforequeue(port=int(args[0]))
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from pathlib import Path
|
|||||||
from pyinfra import host
|
from pyinfra import host
|
||||||
from pyinfra.operations import apt, files, server, systemd
|
from pyinfra.operations import apt, files, server, systemd
|
||||||
from pyinfra.facts.files import File
|
from pyinfra.facts.files import File
|
||||||
|
from pyinfra.facts.systemd import SystemdEnabled
|
||||||
from .acmetool import deploy_acmetool
|
from .acmetool import deploy_acmetool
|
||||||
|
|
||||||
|
|
||||||
@@ -34,8 +35,17 @@ def _install_chatmaild() -> None:
|
|||||||
commands=[f"pip install --break-system-packages {remote_path}"],
|
commands=[f"pip install --break-system-packages {remote_path}"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# disable legacy doveauth-dictproxy.service
|
||||||
|
if host.get_fact(SystemdEnabled).get("doveauth-dictproxy.service"):
|
||||||
|
systemd.service(
|
||||||
|
name="Disable legacy doveauth-dictproxy.service",
|
||||||
|
service="doveauth-dictproxy.service",
|
||||||
|
running=False,
|
||||||
|
enabled=False,
|
||||||
|
)
|
||||||
|
|
||||||
for fn in (
|
for fn in (
|
||||||
"doveauth-dictproxy",
|
"doveauth",
|
||||||
"filtermail",
|
"filtermail",
|
||||||
):
|
):
|
||||||
files.put(
|
files.put(
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ mail_plugins = quota
|
|||||||
# these are the capabilities Delta Chat cares about actually
|
# these are the capabilities Delta Chat cares about actually
|
||||||
# so let's keep the network overhead per login small
|
# so let's keep the network overhead per login small
|
||||||
# https://github.com/deltachat/deltachat-core-rust/blob/master/src/imap/capabilities.rs
|
# https://github.com/deltachat/deltachat-core-rust/blob/master/src/imap/capabilities.rs
|
||||||
imap_capability = IMAP4rev1 IDLE MOVE QUOTA CONDSTORE
|
imap_capability = IMAP4rev1 IDLE MOVE QUOTA CONDSTORE NOTIFY
|
||||||
|
|
||||||
|
|
||||||
# Authentication for system users.
|
# Authentication for system users.
|
||||||
|
|||||||
@@ -32,6 +32,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 smtpd_client_connection_count_limit=1000
|
||||||
-o smtpd_proxy_filter=127.0.0.1:10080
|
-o smtpd_proxy_filter=127.0.0.1:10080
|
||||||
smtps inet n - y - - smtpd
|
smtps inet n - y - - smtpd
|
||||||
-o syslog_name=postfix/smtps
|
-o syslog_name=postfix/smtps
|
||||||
@@ -46,6 +47,7 @@ smtps inet n - y - - smtpd
|
|||||||
-o smtpd_sender_restrictions=$mua_sender_restrictions
|
-o smtpd_sender_restrictions=$mua_sender_restrictions
|
||||||
-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 smtpd_client_connection_count_limit=1000
|
||||||
-o milter_macro_daemon_name=ORIGINATING
|
-o milter_macro_daemon_name=ORIGINATING
|
||||||
-o smtpd_proxy_filter=127.0.0.1:10080
|
-o smtpd_proxy_filter=127.0.0.1:10080
|
||||||
#628 inet n - y - - qmqpd
|
#628 inet n - y - - qmqpd
|
||||||
|
|||||||
@@ -15,6 +15,6 @@ _submission._tcp.$CHATMAIL_DOMAIN. SRV 0 1 587 $CHATMAIL_DOMAIN.
|
|||||||
_submissions._tcp.$CHATMAIL_DOMAIN. SRV 0 1 465 $CHATMAIL_DOMAIN.
|
_submissions._tcp.$CHATMAIL_DOMAIN. SRV 0 1 465 $CHATMAIL_DOMAIN.
|
||||||
_imap._tcp.$CHATMAIL_DOMAIN. SRV 0 1 143 $CHATMAIL_DOMAIN.
|
_imap._tcp.$CHATMAIL_DOMAIN. SRV 0 1 143 $CHATMAIL_DOMAIN.
|
||||||
_imaps._tcp.$CHATMAIL_DOMAIN. SRV 0 1 993 $CHATMAIL_DOMAIN.
|
_imaps._tcp.$CHATMAIL_DOMAIN. SRV 0 1 993 $CHATMAIL_DOMAIN.
|
||||||
$CHATMAIL_DOMAIN. IN CAA 0 issue "letsencrypt.org; accounturi=$ACME_ACCOUNT_URL"
|
$CHATMAIL_DOMAIN. IN CAA 128 issue "letsencrypt.org; accounturi=$ACME_ACCOUNT_URL"
|
||||||
EOF
|
EOF
|
||||||
$SSH opendkim-genzone -F | sed 's/^;.*$//;/^$/d'
|
$SSH opendkim-genzone -F | sed 's/^;.*$//;/^$/d'
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
import pytest
|
import pytest
|
||||||
import threading
|
import threading
|
||||||
import queue
|
import queue
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
import chatmaild.dictproxy
|
import chatmaild.doveauth
|
||||||
from chatmaild.dictproxy import get_user_data, lookup_passdb, handle_dovecot_request
|
from chatmaild.doveauth import get_user_data, lookup_passdb, handle_dovecot_request
|
||||||
from chatmaild.database import Database, DBError
|
from chatmaild.database import Database, DBError
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ def test_dont_overwrite_password_on_wrong_login(db):
|
|||||||
def test_nocreate_file(db, monkeypatch, tmpdir):
|
def test_nocreate_file(db, monkeypatch, tmpdir):
|
||||||
p = tmpdir.join("nocreate")
|
p = tmpdir.join("nocreate")
|
||||||
p.write("")
|
p.write("")
|
||||||
monkeypatch.setattr(chatmaild.dictproxy, "NOCREATE_FILE", str(p))
|
monkeypatch.setattr(chatmaild.doveauth, "NOCREATE_FILE", str(p))
|
||||||
lookup_passdb(db, "newuser1@something.org", "zequ0Aimuchoodaechik")
|
lookup_passdb(db, "newuser1@something.org", "zequ0Aimuchoodaechik")
|
||||||
assert not get_user_data(db, "newuser1@something.org")
|
assert not get_user_data(db, "newuser1@something.org")
|
||||||
|
|
||||||
@@ -60,27 +60,31 @@ def test_handle_dovecot_request(db):
|
|||||||
assert userdata["password"].startswith("{SHA512-CRYPT}")
|
assert userdata["password"].startswith("{SHA512-CRYPT}")
|
||||||
|
|
||||||
|
|
||||||
def test_100_concurrent_lookups(db):
|
def test_100_concurrent_lookups_different_accounts(db, gencreds):
|
||||||
num = 100
|
num_threads = 100
|
||||||
dbs = [Database(db.path) for i in range(num)]
|
req_per_thread = 5
|
||||||
print(f"created {num} databases")
|
|
||||||
results = queue.Queue()
|
results = queue.Queue()
|
||||||
|
|
||||||
def lookup(db):
|
def lookup(db):
|
||||||
try:
|
for i in range(req_per_thread):
|
||||||
lookup_passdb(db, "something@c1.testrun.org", "Pieg9aeToe3eghuthe5u")
|
addr, password = gencreds()
|
||||||
except Exception:
|
try:
|
||||||
results.put(traceback.format_exc())
|
lookup_passdb(db, addr, password)
|
||||||
else:
|
except Exception:
|
||||||
results.put(None)
|
results.put(traceback.format_exc())
|
||||||
|
else:
|
||||||
|
results.put(None)
|
||||||
|
|
||||||
threads = [threading.Thread(target=lookup, args=(db,), daemon=True) for db in dbs]
|
threads = []
|
||||||
|
for i in range(num_threads):
|
||||||
|
thread = threading.Thread(target=lookup, args=(db,), daemon=True)
|
||||||
|
threads.append(thread)
|
||||||
|
|
||||||
print(f"created {num} threads, starting them and waiting for results")
|
print(f"created {num_threads} threads, starting them and waiting for results")
|
||||||
for thread in threads:
|
for thread in threads:
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
for _ in dbs:
|
for i in range(num_threads * req_per_thread):
|
||||||
res = results.get()
|
res = results.get()
|
||||||
if res is not None:
|
if res is not None:
|
||||||
pytest.fail(f"concurrent lookup failed\n{res}")
|
pytest.fail(f"concurrent lookup failed\n{res}")
|
||||||
@@ -20,15 +20,14 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 9px;
|
padding: 9px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-family: "Courier New", monospace;
|
font-family: "Swansea", "Helvetica", sans-serif;
|
||||||
color: white;
|
color: black;
|
||||||
background-position: left top;
|
}
|
||||||
background-image: url(collage-bg.png);
|
a {
|
||||||
background-repeat: no-repeat;
|
color: black;
|
||||||
background-size: 100% 100%;
|
|
||||||
}
|
}
|
||||||
h1, h2, h3 {
|
h1, h2, h3 {
|
||||||
font-size: 16px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -37,24 +36,39 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<img class="section" src="collage-top.png" />
|
<img class="section" src="collage-top.png" />
|
||||||
<div class="section text">
|
<div class="section text">
|
||||||
<h1>welcome to nine.testrun.org</h1>
|
<h1>Dear Delta Chat users and newcomers,</h1>
|
||||||
<p>
|
<p>
|
||||||
to get an account,
|
welcome to the first public "chat-mail instance",
|
||||||
invent a word with <i>exactly</i> nine characters
|
a small and lean e-mail provider for smooth chatting.
|
||||||
and append @nine.testrun.org to it.
|
Install Delta Chat or add an account:
|
||||||
eg. <b>hellofits@nine.testrun.org</b>
|
<ul>
|
||||||
|
<li>Tap "LOG INTO YOUR E-MAIL ACCOUNT".</li>
|
||||||
|
<li>Address: invent a word with <i>exactly</i> nine characters
|
||||||
|
and append @nine.testrun.org to it.</li>
|
||||||
|
<li>Password: invent at least 10 characters. The first login sets your password.</li>
|
||||||
|
</ul>
|
||||||
|
If the e-mail address is not yet taken, you'll get that account.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
if the email address is not yet taken, you'll get that account.
|
<img class="section" src="collage-down.png" />
|
||||||
the first login sets your password.
|
|
||||||
that's it.
|
<h2>What's behind it, how does it operate?</h2>
|
||||||
</p>
|
<p>nine.testrun.org is run
|
||||||
</div>
|
by a small group of devs and sysadmins, reachable via root@.
|
||||||
<img class="section" src="collage-down.png" />
|
They want to keep this instance running at least until end 2024.
|
||||||
<div class="section text">
|
Current limits:
|
||||||
<h1>faq</h1>
|
<ul>
|
||||||
<p><i>why are other email providers 1000 times more complicated?</i></p>
|
<li>Un-encrypted mails can not leave the chat-mail instance.</li>
|
||||||
<p>because they want to for $reasons</p>
|
<li>Use <a href="https://delta.chat/en/help#howtoe2ee">
|
||||||
|
guaranteed end-to-end encryption via QR code scans</a>
|
||||||
|
to setup contact with users outside of the chat-mail instance.
|
||||||
|
</li>
|
||||||
|
<li>You may send up to 60 messages per minute.</li>
|
||||||
|
<li>Messages are unconditionally removed 40 days after arrival.</li>
|
||||||
|
<li>Max storage per user is 100MB.</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Why are other email providers 1000 times more complicated?</h2>
|
||||||
|
<p>¯\_(ツ)_/¯</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user