Compare commits

...

28 Commits

Author SHA1 Message Date
missytake
fbcf071e89 doveauth-http: deploying HTTP route for account creation 2023-12-01 18:32:45 +01:00
missytake
ccfbb59e17 doveauth: flask app to create accounts via HTTP 2023-12-01 18:05:40 +01:00
missytake
a86e135967 opendkim: correctly specify SigningTable in opendkim.conf 2023-11-26 07:40:25 +01:00
missytake
776bd87888 moved mta-sts-resolver to /usr/local/lib 2023-11-25 00:39:27 +01:00
link2xt
d7683ed3f7 Move ssl_certificate back to http and fix indentation 2023-11-25 00:39:27 +01:00
missytake
0cc9f18468 acmetool: request one TLS cert for all domains 2023-11-25 00:39:27 +01:00
missytake
889e18f803 generate-dns-zone.sh doesn't need to support CHATMAIL_SERVER env var for now, let's assume A/AAAA point to the chatmail server, too 2023-11-25 00:39:27 +01:00
missytake
773b8d1e00 MTA-STS: fixing lint issues 2023-11-25 00:39:27 +01:00
missytake
dca6d35a6f MTA-STS: adding correct line breaks to config 2023-11-25 00:39:27 +01:00
missytake
d29d2d147b MTA-STS: the HTTPS route needs to be mta-sts.@ not _mta-sts 2023-11-25 00:39:27 +01:00
missytake
347dae1f84 MTA-STS: CNAME doesn't work, it needs to be A and AAAA 2023-11-25 00:39:27 +01:00
missytake
63cbb83344 fix: hetzner doesn't accept whitespace in TXT and CAA records apparently 2023-11-25 00:39:27 +01:00
missytake
27d135fee7 python3-venv was missing 2023-11-25 00:39:27 +01:00
missytake
ccd7c789f0 postfix: install MTA-STS resolver daemon 2023-11-25 00:39:27 +01:00
missytake
c7625fad81 DNS: distinguish between mail_server and mail_domain 2023-11-25 00:39:27 +01:00
missytake
5305dfab12 Added MTA-STS records and .well-known file 2023-11-25 00:39:27 +01:00
holger krekel
4478270fc9 properly call logging.exception 2023-11-20 22:54:15 +01:00
holger krekel
e7c9992fdc it's unclear what this limit really means -- with ipv6 one can easily create lots of IP addresses anyway 2023-11-20 22:54:15 +01:00
holger krekel
a9d43c42f4 - tune down logging for filtermail
- allow higher smtp connection limit
2023-11-20 22:54:15 +01:00
holger krekel
bbf2f0dd36 with help/side-comments from alex i fixed the concurrent account creation problem 2023-11-20 22:54:15 +01:00
holger krekel
43c02377ef make headlines as big as normal text 2023-11-16 11:46:47 +01:00
missytake
70f330b0e4 Changed typo to sans-serif, feel free to revert 2023-11-16 11:46:47 +01:00
holger krekel
02eaa55441 reduce retro-ness of design after @hocuri's comment :) 2023-11-16 11:46:47 +01:00
holger krekel
6c3ec903c2 Update www/nine.testrun.org/index.html
Co-authored-by: Hocuri <hocuri@gmx.de>
2023-11-15 20:48:30 +01:00
holger krekel
7d9b81863f refining the entry point, more info, more directly speaking to DC users
(we don't want to get arbitrary users to report issues)
2023-11-15 20:48:30 +01:00
missytake
af90d0a7de rename doveauth-dictproxy to doveauth 2023-11-15 15:00:27 +01:00
link2xt
322bc9a3aa Set critical flag on generated CAA record
This does not really matter as Let's Encrypt
supports current CAA `issue` syntax,
but may be useful if more records are added and this flag is copy-pasted.

For reference: <https://www.rfc-editor.org/rfc/rfc8659#name-critical-flag>
2023-11-13 15:12:32 +00:00
link2xt
e4009854dc Add NOTIFY capability
Delta Chat does not use it now,
but should: <https://github.com/deltachat/deltachat-core-rust/issues/4983>
Having no capability will confuse whoever develops it.
2023-11-12 20:41:29 +01:00
21 changed files with 318 additions and 102 deletions

View File

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

View File

@@ -6,15 +6,21 @@ build-backend = "setuptools.build_meta"
name = "chatmaild" name = "chatmaild"
version = "0.1" version = "0.1"
dependencies = [ dependencies = [
"aiosmtpd" "aiosmtpd",
"flask",
"gunicorn",
] ]
[project.scripts] [project.scripts]
doveauth-dictproxy = "chatmaild.dictproxy:main" doveauth = "chatmaild.doveauth:main"
doveauth-http = "chatmaild.web: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 = """

View File

@@ -0,0 +1,10 @@
[Unit]
Description=HTTP endpoint for creating chatmail accounts
[Service]
ExecStart=/usr/local/bin/gunicorn --timeout 60 -b :3691 -w 1 chatmaild.web:main
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target

View File

@@ -3,7 +3,6 @@ import os
import time import time
import sys import sys
import json import json
import crypt
from socketserver import ( from socketserver import (
UnixStreamServer, UnixStreamServer,
StreamRequestHandler, StreamRequestHandler,
@@ -12,39 +11,7 @@ from socketserver import (
import pwd import pwd
from .database import Database from .database import Database
from .util import is_allowed_to_create, encrypt_password, get_mail_domain
NOCREATE_FILE = "/etc/chatmail-nocreate"
def encrypt_password(password: str):
# https://doc.dovecot.org/configuration_manual/authentication/password_schemes/
passhash = crypt.crypt(password, crypt.METHOD_SHA512)
return "{SHA512-CRYPT}" + passhash
def is_allowed_to_create(user, cleartext_password) -> bool:
"""Return True if user and password are admissable."""
if os.path.exists(NOCREATE_FILE):
logging.warning(f"blocked account creation because {NOCREATE_FILE!r} exists.")
return False
if len(cleartext_password) < 10:
logging.warning("Password needs to be at least 10 characters long")
return False
parts = user.split("@")
if len(parts) != 2:
logging.warning(f"user {user!r} is not a proper e-mail address")
return False
localpart, domain = parts
if domain == "nine.testrun.org":
# nine.testrun.org policy, username has to be exactly nine chars
if len(localpart) != 9:
logging.warning(f"localpart {localpart!r} has not exactly nine chars")
return False
return True
def get_user_data(db, user): def get_user_data(db, user):
@@ -116,26 +83,31 @@ 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():
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]) db = Database(sys.argv[3])
with open("/etc/mailname", "r") as fp: mail_domain = get_mail_domain()
mail_domain = fp.read().strip()
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)

View File

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

View File

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

View File

@@ -0,0 +1,56 @@
import base64
import random
import logging
import os
import crypt
NOCREATE_FILE = "/etc/chatmail-nocreate"
def is_allowed_to_create(user, cleartext_password) -> bool:
"""Return True if user and password are admissable."""
if os.path.exists(NOCREATE_FILE):
logging.warning(f"blocked account creation because {NOCREATE_FILE!r} exists.")
return False
if len(cleartext_password) < 10:
logging.warning("Password needs to be at least 10 characters long")
return False
parts = user.split("@")
if len(parts) != 2:
logging.warning(f"user {user!r} is not a proper e-mail address")
return False
localpart, domain = parts
if domain == "nine.testrun.org":
# nine.testrun.org policy, username has to be exactly nine chars
if len(localpart) != 9:
logging.warning(f"localpart {localpart!r} has not exactly nine chars")
return False
return True
def gen_password():
with open("/dev/urandom", "rb") as f:
s = f.read(21)
return base64.b64encode(s).decode("ascii")[:12]
def encrypt_password(password: str):
# https://doc.dovecot.org/configuration_manual/authentication/password_schemes/
passhash = crypt.crypt(password, crypt.METHOD_SHA512)
return "{SHA512-CRYPT}" + passhash
def get_mail_domain():
with open("/etc/mailname", "r") as fp:
return fp.read().strip()
def get_valid_email_addr(length=9, chars="2345789acdefghjkmnpqrstuvwxyz"):
localpart = "".join(random.choice(chars) for i in range(length))
mail_domain = get_mail_domain()
return f"{localpart}@{mail_domain}"

View File

@@ -0,0 +1,48 @@
from flask import Flask, jsonify, request
import time
import os
from database import Database
from util import gen_password, get_valid_email_addr, encrypt_password
from doveauth import get_user_data
def create_app_from_db_path(db_path=None):
db = Database(db_path)
return create_app_from_db(db)
def create_app_from_db(db):
app = Flask("chatmaild-http")
app.db = db
@app.route("/", methods=["POST"])
def new_email():
for i in range(10):
addr = get_valid_email_addr()
if not get_user_data(db, addr):
cleartext_password = gen_password()
encrypted_password = encrypt_password(cleartext_password)
q = """INSERT INTO users (addr, password, last_login)
VALUES (?, ?, ?)"""
with db.write_transaction() as conn:
conn.execute(q, (addr, encrypted_password, int(time.time())))
return jsonify(
email=addr,
password=cleartext_password,
)
return jsonify(
type="error",
status_code=409,
reason="all 10 email addresses we tried are taken"
)
return app
def main():
"""(debugging-only!) serve http account creation Web API on localhost"""
db_path = os.getenv("CHATMAIL_DATABASE", "/home/vmail/passdb.sqlite")
app = create_app_from_db_path(db_path)
if __name__ == "__main__":
app.run(debug=True, host="localhost", port=3691)

View File

@@ -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
@@ -24,8 +25,8 @@ def _install_chatmaild() -> None:
) )
apt.packages( apt.packages(
name="apt install python3-aiosmtpd", name="apt install python3-aiosmtpd python3-pip python3-venv",
packages=["python3-aiosmtpd", "python3-pip"], packages=["python3-aiosmtpd", "python3-pip", "python3-venv"],
) )
# --no-deps because aiosmtplib is installed with `apt`. # --no-deps because aiosmtplib is installed with `apt`.
@@ -34,8 +35,18 @@ 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",
"doveauth-http",
"filtermail", "filtermail",
): ):
files.put( files.put(
@@ -123,6 +134,44 @@ def _configure_opendkim(domain: str, dkim_selector: str) -> bool:
return need_restart return need_restart
def _install_mta_sts_daemon() -> bool:
need_restart = False
config = files.put(
name="upload postfix-mta-sts-resolver config",
src=importlib.resources.files(__package__).joinpath(
"postfix/mta-sts-daemon.yml"
),
dest="/etc/mta-sts-daemon.yml",
user="root",
group="root",
mode="644",
)
need_restart |= config.changed
server.shell(
name="install postfix-mta-sts-resolver with pip",
commands=[
"python3 -m venv /usr/local/lib/postfix-mta-sts-resolver",
"/usr/local/lib/postfix-mta-sts-resolver/bin/pip install postfix-mta-sts-resolver",
],
)
systemd_unit = files.put(
name="upload mta-sts-daemon systemd unit",
src=importlib.resources.files(__package__).joinpath(
"postfix/mta-sts-daemon.service"
),
dest="/etc/systemd/system/mta-sts-daemon.service",
user="root",
group="root",
mode="644",
)
need_restart |= systemd_unit.changed
return need_restart
def _configure_postfix(domain: str, debug: bool = False) -> bool: def _configure_postfix(domain: str, debug: bool = False) -> bool:
"""Configures Postfix SMTP server.""" """Configures Postfix SMTP server."""
need_restart = False need_restart = False
@@ -221,6 +270,16 @@ def _configure_nginx(domain: str, debug: bool = False) -> bool:
) )
need_restart |= autoconfig.changed need_restart |= autoconfig.changed
mta_sts_config = files.template(
src=importlib.resources.files(__package__).joinpath("nginx/mta-sts.txt.j2"),
dest="/var/www/html/.well-known/mta-sts.txt",
user="root",
group="root",
mode="644",
config={"domain_name": domain},
)
need_restart |= mta_sts_config.changed
return need_restart return need_restart
@@ -245,7 +304,7 @@ def deploy_chatmail(mail_domain: str, mail_server: str, dkim_selector: str) -> N
) )
# Deploy acmetool to have TLS certificates. # Deploy acmetool to have TLS certificates.
deploy_acmetool(nginx_hook=True, domains=[mail_server]) deploy_acmetool(nginx_hook=True, domains=[mail_server, f"mta-sts.{mail_server}"])
apt.packages( apt.packages(
name="Install Postfix", name="Install Postfix",
@@ -276,6 +335,7 @@ def deploy_chatmail(mail_domain: str, mail_server: str, dkim_selector: str) -> N
postfix_need_restart = _configure_postfix(mail_domain, debug=debug) postfix_need_restart = _configure_postfix(mail_domain, debug=debug)
opendkim_need_restart = _configure_opendkim(mail_domain, dkim_selector) opendkim_need_restart = _configure_opendkim(mail_domain, dkim_selector)
nginx_need_restart = _configure_nginx(mail_domain) nginx_need_restart = _configure_nginx(mail_domain)
mta_sts_need_restart = _install_mta_sts_daemon()
# deploy web pages and info if we have them # deploy web pages and info if we have them
pkg_root = importlib.resources.files(__package__) pkg_root = importlib.resources.files(__package__)
@@ -291,6 +351,15 @@ def deploy_chatmail(mail_domain: str, mail_server: str, dkim_selector: str) -> N
restarted=opendkim_need_restart, restarted=opendkim_need_restart,
) )
systemd.service(
name="Start and enable MTA-STS daemon",
service="mta-sts-daemon.service",
daemon_reload=True,
running=True,
enabled=True,
restarted=mta_sts_need_restart,
)
systemd.service( systemd.service(
name="Start and enable Postfix", name="Start and enable Postfix",
service="postfix.service", service="postfix.service",

View File

@@ -46,8 +46,7 @@ def deploy_acmetool(nginx_hook=False, email="", domains=[]):
mode="644", mode="644",
) )
for domain in domains: server.shell(
server.shell( name=f"Request certificate for: { ', '.join(domains) }",
name=f"Request certificate for {domain}", commands=[f"acmetool want { ' '.join(domains)}"],
commands=[f"acmetool want {domain}"], )
)

View File

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

View File

@@ -0,0 +1,4 @@
version: STSv1
mode: enforce
mx: {{ config.domain_name }}
max_age: 2419200

View File

@@ -42,6 +42,10 @@ http {
# as directory, then fall back to displaying a 404. # as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404; try_files $uri $uri/ =404;
} }
location /new_email {
proxy_pass http://localhost:3691/;
}
} }
} }

View File

@@ -20,7 +20,7 @@ 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 KeyTable /etc/dkimkeys/KeyTable
SigningTable /etc/dkimkeys/SigningTable SigningTable refile:/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

@@ -23,6 +23,7 @@ smtpd_tls_security_level=may
smtp_tls_CApath=/etc/ssl/certs smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level=may smtp_tls_security_level=may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtp_tls_policy_maps = socketmap:inet:127.0.0.1:8461:postfix
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = {{ config.domain_name }} myhostname = {{ config.domain_name }}

View File

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

View File

@@ -0,0 +1,10 @@
[Unit]
Description=Postfix MTA-STS resolver daemon
[Service]
ExecStart=/usr/local/lib/postfix-mta-sts-resolver/bin/mta-sts-daemon
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,13 @@
host: 127.0.0.1
port: 8461
reuse_port: true
shutdown_timeout: 20
cache:
type: internal
options:
cache_size: 10000
proactive_policy_fetching:
enabled: true
default_zone:
strict_testing: false
timeout: 4

View File

@@ -15,6 +15,9 @@ _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"
_mta-sts.$CHATMAIL_DOMAIN. IN TXT "v=STSv1; id=$(date -u '+%Y%m%d%H%M')"
mta-sts.$CHATMAIL_DOMAIN. IN CNAME $CHATMAIL_DOMAIN.
_smtp._tls.$CHATMAIL_DOMAIN. IN TXT "v=TLSRPTv1;rua=mailto:$EMAIL"
EOF EOF
$SSH opendkim-genzone -F | sed 's/^;.*$//;/^$/d' $SSH opendkim-genzone -F | sed 's/^;.*$//;/^$/d'

View File

@@ -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}")

View File

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