mirror of
https://github.com/chatmail/relay.git
synced 2026-05-19 04:18:09 +00:00
works
This commit is contained in:
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
name = "chatmaild"
|
name = "chatmaild"
|
||||||
version = "0.1"
|
version = "0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aiosmtpd"
|
"aiosmtpd",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
28
chatmaild/src/chatmaild/newemail.py
Normal file
28
chatmaild/src/chatmaild/newemail.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/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()
|
||||||
@@ -7,6 +7,7 @@ name = "deploy-chatmail"
|
|||||||
version = "0.1"
|
version = "0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pyinfra",
|
"pyinfra",
|
||||||
|
"qrcode",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ from pyinfra.facts.files import File
|
|||||||
from pyinfra.facts.systemd import SystemdEnabled
|
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"
|
||||||
@@ -44,6 +46,8 @@ def _install_chatmaild() -> None:
|
|||||||
enabled=False,
|
enabled=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# install systemd units
|
||||||
|
|
||||||
for fn in (
|
for fn in (
|
||||||
"doveauth",
|
"doveauth",
|
||||||
"filtermail",
|
"filtermail",
|
||||||
@@ -279,6 +283,34 @@ def _configure_nginx(domain: str, debug: bool = False) -> bool:
|
|||||||
)
|
)
|
||||||
need_restart |= mta_sts_config.changed
|
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=f"Upload cgi newemail.py script",
|
||||||
|
src=importlib.resources.files("chatmaild").joinpath(f"newemail.py").open("rb"),
|
||||||
|
dest=f"{cgi_dir}/newemail.py",
|
||||||
|
user="root",
|
||||||
|
group="root",
|
||||||
|
mode="755",
|
||||||
|
)
|
||||||
|
|
||||||
|
files.put(
|
||||||
|
name=f"Upload QR code for account creation",
|
||||||
|
src=gen_qr_png_data(domain),
|
||||||
|
dest=f"/var/www/html/qrcode.png",
|
||||||
|
user="root",
|
||||||
|
group="root",
|
||||||
|
mode="644",
|
||||||
|
)
|
||||||
|
|
||||||
return need_restart
|
return need_restart
|
||||||
|
|
||||||
|
|
||||||
@@ -328,19 +360,26 @@ 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)
|
||||||
nginx_need_restart = _configure_nginx(mail_domain)
|
|
||||||
mta_sts_need_restart = _install_mta_sts_daemon()
|
mta_sts_need_restart = _install_mta_sts_daemon()
|
||||||
|
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
|
||||||
pkg_root = importlib.resources.files(__package__)
|
pkg_root = importlib.resources.files(__package__)
|
||||||
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 not www_path.is_dir():
|
||||||
files.rsync(f"{www_path}/", "/var/www/html", flags=["-avz"])
|
www_path = pkg_root.joinpath(f"../../../www/default").resolve()
|
||||||
|
|
||||||
|
files.rsync(f"{www_path}/", "/var/www/html", flags=["-avz"])
|
||||||
|
|
||||||
systemd.service(
|
systemd.service(
|
||||||
name="Start and enable OpenDKIM",
|
name="Start and enable OpenDKIM",
|
||||||
|
|||||||
BIN
deploy-chatmail/src/deploy_chatmail/data/delta-chat-bw.png
Normal file
BIN
deploy-chatmail/src/deploy_chatmail/data/delta-chat-bw.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
deploy-chatmail/src/deploy_chatmail/data/delta-chat-red.png
Normal file
BIN
deploy-chatmail/src/deploy_chatmail/data/delta-chat-red.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
deploy-chatmail/src/deploy_chatmail/data/opensans-regular.ttf
Normal file
BIN
deploy-chatmail/src/deploy_chatmail/data/opensans-regular.ttf
Normal file
Binary file not shown.
94
deploy-chatmail/src/deploy_chatmail/genqr.py
Normal file
94
deploy-chatmail/src/deploy_chatmail/genqr.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
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):
|
||||||
|
info = f"{maildomain} invite code"
|
||||||
|
|
||||||
|
steps = (
|
||||||
|
"1. Install https://get.delta.chat\n"
|
||||||
|
"2. On setup screen scan above invite QR code\n"
|
||||||
|
"3. Choose nickname & avatar\n"
|
||||||
|
"+ chat with any e-mail address ...\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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 + steps).count("\n") + 3
|
||||||
|
|
||||||
|
size = width = 384
|
||||||
|
qr_padding = 6
|
||||||
|
text_margin_right = 12
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
draw.multiline_text(
|
||||||
|
(text_margin_right, height - text_height + font_size * 1.0),
|
||||||
|
steps,
|
||||||
|
font=font,
|
||||||
|
fill="black",
|
||||||
|
align="left",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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
|
||||||
@@ -40,6 +40,21 @@ 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 /cgi-bin/ {
|
||||||
|
# Set the root to /usr/lib (inside this location this means that we are
|
||||||
|
# giving access to the files under /usr/lib/cgi-bin)
|
||||||
|
root /usr/lib;
|
||||||
|
|
||||||
|
# Fastcgi socket
|
||||||
|
fastcgi_pass unix:/var/run/fcgiwrap.socket;
|
||||||
|
|
||||||
|
# Fastcgi parameters, include the standard ones
|
||||||
|
include /etc/nginx/fastcgi_params;
|
||||||
|
|
||||||
|
# Adjust non standard parameters (SCRIPT_FILENAME)
|
||||||
|
# fastcgi_param SCRIPT_FILENAME /usr/lib$fastcgi_script_name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
server {
|
server {
|
||||||
listen 80 default_server;
|
listen 80 default_server;
|
||||||
@@ -48,5 +63,8 @@ http {
|
|||||||
|
|
||||||
return 301 https://$host$request_uri;
|
return 301 https://$host$request_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
28
tests/chatmaild/test_newmail.py
Normal file
28
tests/chatmaild/test_newmail.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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
|
||||||
25
www/default/index.html
Normal file
25
www/default/index.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>chatmail instance</title>
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome to Chatmail!</h1>
|
||||||
|
<h2>Scan this invite QR code from any Delta Chat app</h2>
|
||||||
|
<img class="section" src="qrcode.png" />
|
||||||
|
|
||||||
|
<h2>Properties / Constraints</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Un-encrypted mails can not leave the chat-mail domain.</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>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user