Compare commits

..

1 Commits

Author SHA1 Message Date
link2xt
d407c2ad6a 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 04:30:52 +00:00
24 changed files with 70 additions and 438 deletions

View File

@@ -32,17 +32,10 @@ after which the initially specified password is required for using them.
5. Run `scripts/generate-dns-zone.sh` and 5. Run `scripts/generate-dns-zone.sh` and
transfer the generated DNS records at your DNS provider transfer the generated DNS records at your DNS provider
6. Start a Delta Chat app and create a new account
### Home page and getting started for users by typing an e-mail address with an arbitrary username
and `@<your-chatmail-domain>` appended.
- The `deploy.sh` script deploys a default `index.html` Use an at least 10-character random password.
along with a QR code that users can click to
create accounts on the chatmail provider.
- Start a Delta Chat app and create a new account
by typing an e-mail address with an arbitrary username
and `@<your-chatmail-domain>` appended.
Use an at least 10-character random password.
### Ports ### Ports
@@ -88,11 +81,10 @@ 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/doveauth.py` implements - `chatmaild/src/chatmaild/dictproxy.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](https://doc.dovecot.org/configuration_manual/authentication/dict/#complete-example-for-authenticating-via-a-unix-socket) which in turn uses Dovecot SASL to authenticate users
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,18 +6,15 @@ build-backend = "setuptools.build_meta"
name = "chatmaild" name = "chatmaild"
version = "0.1" version = "0.1"
dependencies = [ dependencies = [
"aiosmtpd", "aiosmtpd"
] ]
[project.scripts] [project.scripts]
doveauth = "chatmaild.doveauth:main" doveauth-dictproxy = "chatmaild.dictproxy: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

@@ -116,7 +116,7 @@ def handle_dovecot_request(msg, db, mail_domain):
class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer): class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer):
request_queue_size = 100 pass
def main(): def main():
@@ -128,20 +128,14 @@ def main():
class Handler(StreamRequestHandler): class Handler(StreamRequestHandler):
def handle(self): def handle(self):
try: 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 = handle_dovecot_request(msg, db, mail_domain) if res:
if res: self.wfile.write(res.encode("ascii"))
self.wfile.write(res.encode("ascii")) self.wfile.flush()
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 /run/dovecot/doveauth.socket vmail /home/vmail/passdb.sqlite ExecStart=/usr/local/bin/doveauth-dictproxy /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.WARN) logging.basicConfig(level=logging.INFO)
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

@@ -1,28 +0,0 @@
#!/usr/bin/python3
""" CGI script for creating new accounts. """
import json
import random
mailname_path = "/etc/mailname"
def create_newemail_dict(domain):
alphanumeric = "abcdefghijklmnopqrstuvwxyz1234567890"
user = "".join(random.choices(alphanumeric, k=9))
password = "".join(random.choices(alphanumeric, k=12))
return dict(email=f"{user}@{domain}", password=f"{password}")
def print_new_account():
domain = open(mailname_path).read().strip()
creds = create_newemail_dict(domain=domain)
print("Content-Type: application/json")
print("")
print(json.dumps(creds))
if __name__ == "__main__":
print_new_account()

View File

@@ -7,7 +7,6 @@ name = "deploy-chatmail"
version = "0.1" version = "0.1"
dependencies = [ dependencies = [
"pyinfra", "pyinfra",
"qrcode",
] ]
[tool.pytest.ini_options] [tool.pytest.ini_options]

View File

@@ -7,11 +7,8 @@ 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
from .genqr import gen_qr_png_data
def _install_chatmaild() -> None: def _install_chatmaild() -> None:
chatmaild_filename = "chatmaild-0.1.tar.gz" chatmaild_filename = "chatmaild-0.1.tar.gz"
@@ -27,8 +24,8 @@ def _install_chatmaild() -> None:
) )
apt.packages( apt.packages(
name="apt install python3-aiosmtpd python3-pip python3-venv", name="apt install python3-aiosmtpd",
packages=["python3-aiosmtpd", "python3-pip", "python3-venv"], packages=["python3-aiosmtpd", "python3-pip"],
) )
# --no-deps because aiosmtplib is installed with `apt`. # --no-deps because aiosmtplib is installed with `apt`.
@@ -37,19 +34,8 @@ 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,
)
# install systemd units
for fn in ( for fn in (
"doveauth", "doveauth-dictproxy",
"filtermail", "filtermail",
): ):
files.put( files.put(
@@ -137,44 +123,6 @@ 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
@@ -273,46 +221,6 @@ 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
# install CGI newemail script
#
cgi_dir = "/usr/lib/cgi-bin"
files.directory(
name=f"Ensure {cgi_dir} exists",
path=cgi_dir,
user="root",
group="root",
)
files.put(
name="Upload cgi newemail.py script",
src=importlib.resources.files("chatmaild").joinpath("newemail.py").open("rb"),
dest=f"{cgi_dir}/newemail.py",
user="root",
group="root",
mode="755",
)
qr_data = gen_qr_png_data(domain)
files.put(
name="Upload QR code for account creation",
src=qr_data,
dest="/var/www/html/qrcode.png",
user="root",
group="root",
mode="644",
)
return need_restart return need_restart
@@ -337,7 +245,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, f"mta-sts.{mail_server}"]) deploy_acmetool(nginx_hook=True, domains=[mail_server])
apt.packages( apt.packages(
name="Install Postfix", name="Install Postfix",
@@ -362,17 +270,11 @@ def deploy_chatmail(mail_domain: str, mail_server: str, dkim_selector: str) -> N
packages=["nginx"], packages=["nginx"],
) )
apt.packages(
name="Install fcgiwrap",
packages=["fcgiwrap"],
)
_install_chatmaild() _install_chatmaild()
debug = False debug = False
dovecot_need_restart = _configure_dovecot(mail_server, debug=debug) dovecot_need_restart = _configure_dovecot(mail_server, debug=debug)
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)
mta_sts_need_restart = _install_mta_sts_daemon()
nginx_need_restart = _configure_nginx(mail_domain) nginx_need_restart = _configure_nginx(mail_domain)
# deploy web pages and info if we have them # deploy web pages and info if we have them
@@ -380,16 +282,6 @@ def deploy_chatmail(mail_domain: str, mail_server: str, dkim_selector: str) -> N
www_path = pkg_root.joinpath(f"../../../www/{mail_domain}").resolve() www_path = pkg_root.joinpath(f"../../../www/{mail_domain}").resolve()
if www_path.is_dir(): if www_path.is_dir():
files.rsync(f"{www_path}/", "/var/www/html", flags=["-avz"]) files.rsync(f"{www_path}/", "/var/www/html", flags=["-avz"])
else:
index_path = www_path.parent.joinpath("default/index.html.j2")
files.template(
src=index_path,
dest="/var/www/html/index.html",
user="root",
group="root",
mode="644",
config={"mail_domain": mail_domain},
)
systemd.service( systemd.service(
name="Start and enable OpenDKIM", name="Start and enable OpenDKIM",
@@ -399,15 +291,6 @@ 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,7 +46,8 @@ def deploy_acmetool(nginx_hook=False, email="", domains=[]):
mode="644", mode="644",
) )
server.shell( for domain in domains:
name=f"Request certificate for: { ', '.join(domains) }", server.shell(
commands=[f"acmetool want { ' '.join(domains)}"], name=f"Request certificate for {domain}",
) commands=[f"acmetool want {domain}"],
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,82 +0,0 @@
import importlib
import qrcode
import os
from PIL import ImageFont, ImageDraw, Image
import io
def gen_qr_png_data(maildomain):
url = f"DCACCOUNT:https://{maildomain}/cgi-bin/newemail.py"
image = gen_qr(maildomain, url)
temp = io.BytesIO()
image.save(temp, format="png")
temp.seek(0)
return temp
def gen_qr(maildomain, url):
# taken and modified from
# https://github.com/deltachat/mailadm/blob/master/src/mailadm/gen_qr.py
info = f"{maildomain} invite code"
# load QR code
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_H,
box_size=1,
border=1,
)
qr.add_data(url)
qr.make(fit=True)
qr_img = qr.make_image(fill_color="black", back_color="white")
# paint all elements
ttf_path = str(
importlib.resources.files(__package__).joinpath("data/opensans-regular.ttf")
)
logo_red_path = str(
importlib.resources.files(__package__).joinpath("data/delta-chat-bw.png")
)
assert os.path.exists(ttf_path), ttf_path
font_size = 16
font = ImageFont.truetype(font=ttf_path, size=font_size)
num_lines = (info).count("\n") + 1
size = width = 384
qr_padding = 6
text_height = font_size * num_lines
height = size + text_height + qr_padding * 2
image = Image.new("RGBA", (width, height), "white")
draw = ImageDraw.Draw(image)
qr_final_size = width - (qr_padding * 2)
# draw text
if hasattr(font, "getsize"):
info_pos = (width - font.getsize(info.strip())[0]) // 2
else:
info_pos = (width - font.getbbox(info.strip())[3]) // 2
draw.multiline_text(
(info_pos, size - qr_padding // 2), info, font=font, fill="black", align="right"
)
# paste QR code
image.paste(
qr_img.resize((qr_final_size, qr_final_size), resample=Image.NEAREST),
(qr_padding, qr_padding),
)
# background delta logo
logo2_img = Image.open(logo_red_path)
logo2_width = int(size / 6)
logo2 = logo2_img.resize((logo2_width, logo2_width), resample=Image.NEAREST)
pos = int((size / 2) - (logo2_width / 2))
image.paste(logo2, (pos, pos), mask=logo2)
return image

View File

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

View File

@@ -26,6 +26,8 @@ http {
gzip on; gzip on;
server { server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server; listen 443 ssl default_server;
listen [::]:443 ssl default_server; listen [::]:443 ssl default_server;
@@ -40,16 +42,6 @@ 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;
} }
# add cgi-bin support
include /usr/share/doc/fcgiwrap/examples/nginx.conf;
} }
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
} }

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 refile:/etc/dkimkeys/SigningTable 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

@@ -23,7 +23,6 @@ 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,7 +32,6 @@ 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
@@ -47,7 +46,6 @@ 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

@@ -1,10 +0,0 @@
[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

@@ -1,13 +0,0 @@
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,9 +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 128 issue "letsencrypt.org;accounturi=$ACME_ACCOUNT_URL" $CHATMAIL_DOMAIN. IN CAA 0 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.doveauth import chatmaild.dictproxy
from chatmaild.doveauth import get_user_data, lookup_passdb, handle_dovecot_request from chatmaild.dictproxy 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.doveauth, "NOCREATE_FILE", str(p)) monkeypatch.setattr(chatmaild.dictproxy, "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,31 +60,27 @@ def test_handle_dovecot_request(db):
assert userdata["password"].startswith("{SHA512-CRYPT}") assert userdata["password"].startswith("{SHA512-CRYPT}")
def test_100_concurrent_lookups_different_accounts(db, gencreds): def test_100_concurrent_lookups(db):
num_threads = 100 num = 100
req_per_thread = 5 dbs = [Database(db.path) for i in range(num)]
print(f"created {num} databases")
results = queue.Queue() results = queue.Queue()
def lookup(db): def lookup(db):
for i in range(req_per_thread): try:
addr, password = gencreds() lookup_passdb(db, "something@c1.testrun.org", "Pieg9aeToe3eghuthe5u")
try: except Exception:
lookup_passdb(db, addr, password) results.put(traceback.format_exc())
except Exception: else:
results.put(traceback.format_exc()) results.put(None)
else:
results.put(None)
threads = [] threads = [threading.Thread(target=lookup, args=(db,), daemon=True) for db in dbs]
for i in range(num_threads):
thread = threading.Thread(target=lookup, args=(db,), daemon=True)
threads.append(thread)
print(f"created {num_threads} threads, starting them and waiting for results") print(f"created {num} threads, starting them and waiting for results")
for thread in threads: for thread in threads:
thread.start() thread.start()
for i in range(num_threads * req_per_thread): for _ in dbs:
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

@@ -1,28 +0,0 @@
import json
import chatmaild
from chatmaild.newemail import create_newemail_dict, print_new_account
def test_create_newemail_dict():
ac1 = create_newemail_dict(domain="example.org")
assert "@" in ac1["email"]
assert len(ac1["password"]) >= 10
ac2 = create_newemail_dict(domain="example.org")
assert ac1["email"] != ac2["email"]
assert ac1["password"] != ac2["password"]
def test_print_new_account(capsys, monkeypatch, maildomain, tmpdir):
p = tmpdir.join("mailname")
p.write(maildomain)
monkeypatch.setattr(chatmaild.newemail, "mailname_path", str(p))
print_new_account()
out, err = capsys.readouterr()
lines = out.split("\n")
assert lines[0] == "Content-Type: application/json"
assert not lines[1]
dic = json.loads(lines[2])
assert dic["email"].endswith(f"@{maildomain}")
assert len(dic["password"]) >= 10

View File

@@ -1,31 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>chatmail instance</title>
</head>
<body>
<h1>Welcome to {{ config.mail_domain }}!</h1>
<h2>Getting started</h2>
<ol>
<li>Install <a href="https://get.delta.chat">https://get.delta.chat</a></li>
<li>Scan or Tap on the invite QR code</li>
<li>Choose Nickname and Avatar</li>
<li>Setup contact with others using <a href="https://delta.chat/en/help#howtoe2ee">
guaranteed end-to-end encryption via QR code scans</a>
</li>
</ol>
<a href="DCACCOUNT:https://{{ config.mail_domain }}/cgi-bin/newemail.py">
<img class="section" src="qrcode.png" />
</a>
<h2>Constraints</h2>
<ul>
<li>You can only send encrypted mails to anyone outside {{config.mail_domain }} </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>
</body>
</html>

View File

@@ -20,14 +20,15 @@
box-sizing: border-box; box-sizing: border-box;
padding: 9px; padding: 9px;
font-size: 18px; font-size: 18px;
font-family: "Swansea", "Helvetica", sans-serif; font-family: "Courier New", monospace;
color: black; color: white;
} background-position: left top;
a { background-image: url(collage-bg.png);
color: black; background-repeat: no-repeat;
background-size: 100% 100%;
} }
h1, h2, h3 { h1, h2, h3 {
font-size: 18px; font-size: 16px;
font-weight: bold; font-weight: bold;
} }
</style> </style>
@@ -36,45 +37,24 @@
<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>Dear Delta Chat users and newcomers,</h1> <h1>welcome to nine.testrun.org</h1>
<p> <p>
welcome to the first public "chat-mail instance", to get an account,
a small and lean e-mail provider for smooth chatting. invent a word with <i>exactly</i> nine characters
Install Delta Chat and then and append @nine.testrun.org to it.
Tap or scan this QR code to obtain a random e-mail address: eg. <b>hellofits@nine.testrun.org</b>
<a href="DCACCOUNT:https://nine.testrun.org/cgi-bin/newemail.py">
<img with=300 src="qrcode.png" /></a>
</p> </p>
<p> <p>
Alternatively, you can manually invent an e-mail address: if the email address is not yet taken, you'll get that account.
<ul> the first login sets your password.
<li>Tap "LOG INTO YOUR E-MAIL ACCOUNT".</li> that's it.
<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> </div>
<img class="section" src="collage-down.png" /> <img class="section" src="collage-down.png" />
<div class="section text">
<h2>What's behind it, how does it operate?</h2> <h1>faq</h1>
<p>nine.testrun.org is run <p><i>why are other email providers 1000 times more complicated?</i></p>
by a small group of devs and sysadmins, reachable via root@. <p>because they want to for $reasons</p>
They want to keep this instance running at least until end 2024.
Current limits:
<ul>
<li>Un-encrypted mails can not leave the chat-mail instance.</li>
<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>