mirror of
https://github.com/chatmail/relay.git
synced 2026-05-19 20:38:05 +00:00
fix/rename
This commit is contained in:
30
chatmail-pyinfra/pyproject.toml
Normal file
30
chatmail-pyinfra/pyproject.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=45"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "chatmail"
|
||||
version = "0.1"
|
||||
dependencies = [
|
||||
"pyinfra",
|
||||
]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "-v -ra --strict-markers"
|
||||
|
||||
[tool.tox]
|
||||
legacy_tox_ini = """
|
||||
[tox]
|
||||
isolated_build = true
|
||||
envlist = lint
|
||||
|
||||
[testenv:lint]
|
||||
skipdist = True
|
||||
skip_install = True
|
||||
deps =
|
||||
ruff
|
||||
black
|
||||
commands =
|
||||
black --quiet --check --diff src/
|
||||
ruff src/
|
||||
"""
|
||||
200
chatmail-pyinfra/src/chatmail/__init__.py
Normal file
200
chatmail-pyinfra/src/chatmail/__init__.py
Normal file
@@ -0,0 +1,200 @@
|
||||
"""
|
||||
Chat Mail pyinfra deploy.
|
||||
"""
|
||||
import importlib.resources
|
||||
|
||||
from pyinfra import host, logger
|
||||
from pyinfra.operations import apt, files, server, systemd, python
|
||||
from pyinfra.facts.files import File
|
||||
from .acmetool import deploy_acmetool
|
||||
|
||||
|
||||
def _install_chatctl() -> None:
|
||||
"""Setup chatctl."""
|
||||
files.put(
|
||||
src=importlib.resources.files(__package__)
|
||||
.joinpath("dovecot/doveauth.py")
|
||||
.open("rb"),
|
||||
dest="/home/vmail/chatctl",
|
||||
user="vmail",
|
||||
group="vmail",
|
||||
mode="755",
|
||||
)
|
||||
|
||||
|
||||
def _configure_opendkim(domain: str, dkim_selector: str) -> bool:
|
||||
"""Configures OpenDKIM"""
|
||||
need_restart = False
|
||||
|
||||
main_config = files.template(
|
||||
src=importlib.resources.files(__package__).joinpath("opendkim/opendkim.conf"),
|
||||
dest="/etc/opendkim.conf",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
config={"domain_name": domain, "opendkim_selector": dkim_selector},
|
||||
)
|
||||
|
||||
files.directory(
|
||||
name="Add opendkim socket directory to /var/spool/postfix",
|
||||
path="/var/spool/postfix/opendkim",
|
||||
user="opendkim",
|
||||
group="opendkim",
|
||||
mode="750",
|
||||
present=True,
|
||||
)
|
||||
|
||||
if not host.get_fact(File, f"/etc/dkimkeys/{dkim_selector}.private"):
|
||||
server.shell(
|
||||
name="Generate OpenDKIM domain keys",
|
||||
commands=[
|
||||
f"opendkim-genkey -D /etc/dkimkeys -d {domain} -s {dkim_selector}"
|
||||
],
|
||||
_sudo=True,
|
||||
_sudo_user="opendkim",
|
||||
)
|
||||
|
||||
need_restart |= main_config.changed
|
||||
|
||||
return need_restart
|
||||
|
||||
|
||||
def _configure_postfix(domain: str) -> bool:
|
||||
"""Configures Postfix SMTP server."""
|
||||
need_restart = False
|
||||
|
||||
main_config = files.template(
|
||||
src=importlib.resources.files(__package__).joinpath("postfix/main.cf.j2"),
|
||||
dest="/etc/postfix/main.cf",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
config={"domain_name": domain},
|
||||
)
|
||||
need_restart |= main_config.changed
|
||||
|
||||
master_config = files.put(
|
||||
src=importlib.resources.files(__package__)
|
||||
.joinpath("postfix/master.cf")
|
||||
.open("rb"),
|
||||
dest="/etc/postfix/master.cf",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
)
|
||||
need_restart |= master_config.changed
|
||||
|
||||
return need_restart
|
||||
|
||||
|
||||
def _configure_dovecot(mail_server: str) -> bool:
|
||||
"""Configures Dovecot IMAP server."""
|
||||
need_restart = False
|
||||
|
||||
main_config = files.template(
|
||||
src=importlib.resources.files(__package__).joinpath("dovecot/dovecot.conf.j2"),
|
||||
dest="/etc/dovecot/dovecot.conf",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
config={"hostname": mail_server},
|
||||
)
|
||||
need_restart |= main_config.changed
|
||||
|
||||
# luarocks install http lpeg_patterns fifo
|
||||
auth_script = files.put(
|
||||
src=importlib.resources.files(__package__).joinpath("dovecot/doveauth.lua"),
|
||||
dest="/etc/dovecot/doveauth.lua",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
)
|
||||
need_restart |= auth_script.changed
|
||||
|
||||
return need_restart
|
||||
|
||||
|
||||
def deploy_chatmail(mail_domain: str, mail_server: str, dkim_selector: str) -> None:
|
||||
"""Deploy a chat-mail instance.
|
||||
|
||||
:param mail_domain: domain part of your future email addresses
|
||||
:param mail_server: the DNS name under which your mail server is reachable
|
||||
:param dkim_selector:
|
||||
"""
|
||||
|
||||
apt.update(name="apt update")
|
||||
server.group(name="Create vmail group", group="vmail", system=True)
|
||||
server.user(name="Create vmail user", user="vmail", group="vmail", system=True)
|
||||
|
||||
server.group(name="Create opendkim group", group="opendkim", system=True)
|
||||
server.user(
|
||||
name="Add postfix user to opendkim group for socket access",
|
||||
user="postfix",
|
||||
groups=["opendkim"],
|
||||
system=True,
|
||||
)
|
||||
|
||||
# Deploy acmetool to have TLS certificates.
|
||||
deploy_acmetool(domains=[mail_server])
|
||||
|
||||
apt.packages(
|
||||
name="Install Postfix",
|
||||
packages="postfix",
|
||||
)
|
||||
|
||||
apt.packages(
|
||||
name="Install Dovecot",
|
||||
packages=[
|
||||
"dovecot-imapd",
|
||||
"dovecot-lmtpd",
|
||||
"dovecot-auth-lua",
|
||||
],
|
||||
)
|
||||
|
||||
apt.packages(
|
||||
name="Install OpenDKIM",
|
||||
packages=[
|
||||
"opendkim",
|
||||
"opendkim-tools",
|
||||
],
|
||||
)
|
||||
|
||||
_install_chatctl()
|
||||
dovecot_need_restart = _configure_dovecot(mail_server)
|
||||
postfix_need_restart = _configure_postfix(mail_domain)
|
||||
opendkim_need_restart = _configure_opendkim(mail_domain, dkim_selector)
|
||||
|
||||
systemd.service(
|
||||
name="Start and enable OpenDKIM",
|
||||
service="opendkim.service",
|
||||
running=True,
|
||||
enabled=True,
|
||||
restarted=opendkim_need_restart,
|
||||
)
|
||||
|
||||
systemd.service(
|
||||
name="Start and enable Postfix",
|
||||
service="postfix.service",
|
||||
running=True,
|
||||
enabled=True,
|
||||
restarted=postfix_need_restart,
|
||||
)
|
||||
|
||||
systemd.service(
|
||||
name="Start and enable Dovecot",
|
||||
service="dovecot.service",
|
||||
running=True,
|
||||
enabled=True,
|
||||
restarted=dovecot_need_restart,
|
||||
)
|
||||
|
||||
def callback():
|
||||
result = server.shell(
|
||||
commands=[
|
||||
f"""sed 's/\tIN/ 600 IN/;s/\t(//;s/\"$//;s/^\t \"//g; s/ ).*//' """
|
||||
f"""/etc/dkimkeys/{dkim_selector}.txt | tr --delete '\n'"""
|
||||
]
|
||||
)
|
||||
logger.info(f"Add this TXT entry into DNS zone: {result.stdout}")
|
||||
|
||||
python.call(name="Print TXT entry for DKIM", function=callback)
|
||||
62
chatmail-pyinfra/src/chatmail/acmetool/__init__.py
Normal file
62
chatmail-pyinfra/src/chatmail/acmetool/__init__.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import importlib.resources
|
||||
|
||||
from pyinfra.operations import apt, files, systemd, server
|
||||
|
||||
|
||||
def deploy_acmetool(nginx_hook=False, email="", domains=[]):
|
||||
"""Deploy acmetool."""
|
||||
apt.packages(
|
||||
name="Install acmetool",
|
||||
packages=["acmetool"],
|
||||
)
|
||||
|
||||
files.put(
|
||||
src=importlib.resources.files(__package__).joinpath("acmetool.cron").open("rb"),
|
||||
dest="/etc/cron.d/acmetool",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
)
|
||||
|
||||
if nginx_hook:
|
||||
files.put(
|
||||
src=importlib.resources.files(__package__)
|
||||
.joinpath("acmetool.hook")
|
||||
.open("rb"),
|
||||
dest="/usr/lib/acme/hooks/nginx",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="744",
|
||||
)
|
||||
|
||||
files.template(
|
||||
src=importlib.resources.files(__package__).joinpath("response-file.yaml.j2"),
|
||||
dest="/var/lib/acme/conf/responses",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
email=email,
|
||||
)
|
||||
|
||||
service_file = files.put(
|
||||
src=importlib.resources.files(__package__)
|
||||
.joinpath("acmetool-redirector.service")
|
||||
.open("rb"),
|
||||
dest="/etc/systemd/system/acmetool-redirector.service",
|
||||
user="root",
|
||||
group="root",
|
||||
mode="644",
|
||||
)
|
||||
systemd.service(
|
||||
name="Setup acmetool-redirector service",
|
||||
service="acmetool-redirector.service",
|
||||
running=True,
|
||||
enabled=True,
|
||||
restarted=service_file.changed,
|
||||
)
|
||||
|
||||
for domain in domains:
|
||||
server.shell(
|
||||
name=f"Request certificate for {domain}",
|
||||
commands=[f"acmetool want {domain}"],
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=acmetool HTTP redirector
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=/usr/bin/acmetool redirector --service.uid=daemon
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
4
chatmail-pyinfra/src/chatmail/acmetool/acmetool.cron
Normal file
4
chatmail-pyinfra/src/chatmail/acmetool/acmetool.cron
Normal file
@@ -0,0 +1,4 @@
|
||||
SHELL=/bin/sh
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
|
||||
MAILTO=root
|
||||
20 16 * * * root /usr/bin/acmetool --batch reconcile
|
||||
5
chatmail-pyinfra/src/chatmail/acmetool/acmetool.hook
Normal file
5
chatmail-pyinfra/src/chatmail/acmetool/acmetool.hook
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
EVENT_NAME="$1"
|
||||
[ "$EVENT_NAME" = "live-updated" ] || exit 42
|
||||
systemctl restart nginx.service
|
||||
@@ -0,0 +1,2 @@
|
||||
"acme-enter-email": "{{ email }}"
|
||||
"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf": true
|
||||
57
chatmail-pyinfra/src/chatmail/dovecot/doveauth.lua
Normal file
57
chatmail-pyinfra/src/chatmail/dovecot/doveauth.lua
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
-- Escape shell argument by hex encoding it and wrapping in quotes.
|
||||
function escape(data)
|
||||
b16 = data:gsub(".", function(char) return string.format("%2X", char:byte()) end)
|
||||
return ("'"..b16.."'")
|
||||
end
|
||||
|
||||
-- call out to python program to actually manage authentication for dovecot
|
||||
|
||||
function chatctl_verify(user, password)
|
||||
local cmd = "python3 /home/vmail/chatctl hexauth "..escape(user).." "..escape(password)
|
||||
print("executing: "..cmd)
|
||||
local handle = io.popen(cmd)
|
||||
local result = handle:read("*a")
|
||||
handle:close()
|
||||
return split_chatctl(result)
|
||||
end
|
||||
|
||||
function chatctl_lookup(user)
|
||||
assert(user)
|
||||
local handle = io.popen("python3 /home/vmail/chatctl hexlookup "..escape(user))
|
||||
local result = handle:read("*a")
|
||||
handle:close()
|
||||
return split_chatctl(result)
|
||||
end
|
||||
|
||||
function get_extra_dovecot_output(res)
|
||||
return {home=res.home, uid=res.uid, gid=res.gid}
|
||||
end
|
||||
|
||||
|
||||
function auth_password_verify(request, password)
|
||||
local res = chatctl_verify(request.user, password)
|
||||
-- request:log_error("auth_password_verify "..request.user.." "..password)
|
||||
if res.status == "ok" then
|
||||
local extra = get_extra_dovecot_output(res)
|
||||
return dovecot.auth.PASSDB_RESULT_OK, get_extra_dovecot_output(res)
|
||||
end
|
||||
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, ""
|
||||
end
|
||||
|
||||
|
||||
function auth_userdb_lookup(request)
|
||||
local res = chatctl_lookup(request.user)
|
||||
if res.status == "ok" then
|
||||
return dovecot.auth.USERDB_RESULT_OK, get_extra_dovecot_output(res)
|
||||
end
|
||||
return dovecot.auth.USERDB_RESULT_USER_UNKNOWN, "no such user"
|
||||
end
|
||||
|
||||
function split_chatctl(output)
|
||||
local ret = {}
|
||||
for key, value in output:gmatch "(%w+)%s*=%s*(%w+)" do
|
||||
ret[key] = value
|
||||
end
|
||||
return ret
|
||||
end
|
||||
57
chatmail-pyinfra/src/chatmail/dovecot/doveauth.py
Normal file
57
chatmail-pyinfra/src/chatmail/dovecot/doveauth.py
Normal file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
import base64
|
||||
import sys
|
||||
|
||||
|
||||
def get_user_data(user):
|
||||
if user == "link2xt@c1.testrun.org":
|
||||
return dict(
|
||||
uid="vmail",
|
||||
gid="vmail",
|
||||
password="Ahyei6ie",
|
||||
)
|
||||
return {}
|
||||
|
||||
|
||||
def create_user(user, password):
|
||||
return dict(home=f"/home/vmail/{user}", uid="vmail", gid="vmail", password=password)
|
||||
|
||||
|
||||
def verify_user(user, password):
|
||||
userdata = get_user_data(user)
|
||||
if userdata:
|
||||
if userdata.get("password") == password:
|
||||
userdata["status"] = "ok"
|
||||
else:
|
||||
userdata["status"] = "fail"
|
||||
else:
|
||||
userdata = create_user(user, password)
|
||||
userdata["status"] = "ok"
|
||||
|
||||
return userdata
|
||||
|
||||
|
||||
def lookup_user(user):
|
||||
userdata = get_user_data(user)
|
||||
if userdata:
|
||||
userdata["status"] = "ok"
|
||||
else:
|
||||
userdata["status"] = "fail"
|
||||
return userdata
|
||||
|
||||
|
||||
def dump_result(res):
|
||||
for key, value in res.items():
|
||||
print(f"{key}={value}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if sys.argv[1] == "hexauth":
|
||||
login = base64.b16decode(sys.argv[2]).decode()
|
||||
password = base64.b16decode(sys.argv[3]).decode()
|
||||
res = verify_user(login, password)
|
||||
dump_result(res)
|
||||
elif sys.argv[1] == "hexlookup":
|
||||
login = base64.b16decode(sys.argv[2]).decode()
|
||||
res = lookup_user(login)
|
||||
dump_result(res)
|
||||
99
chatmail-pyinfra/src/chatmail/dovecot/dovecot.conf.j2
Normal file
99
chatmail-pyinfra/src/chatmail/dovecot/dovecot.conf.j2
Normal file
@@ -0,0 +1,99 @@
|
||||
## Dovecot configuration file
|
||||
|
||||
protocols = imap lmtp
|
||||
|
||||
auth_mechanisms = plain
|
||||
|
||||
auth_verbose = yes
|
||||
auth_debug = yes
|
||||
auth_debug_passwords = yes
|
||||
auth_verbose_passwords = plain
|
||||
|
||||
# Authentication for system users.
|
||||
passdb {
|
||||
driver = lua
|
||||
args = file=/etc/dovecot/doveauth.lua
|
||||
}
|
||||
userdb {
|
||||
driver = lua
|
||||
args = file=/etc/dovecot/doveauth.lua
|
||||
}
|
||||
|
||||
##
|
||||
## Mailbox locations and namespaces
|
||||
##
|
||||
|
||||
# Mailboxes are stored in the "mail" directory of the vmail user home.
|
||||
mail_location = maildir:/home/vmail/mail/%d/%u
|
||||
|
||||
namespace inbox {
|
||||
inbox = yes
|
||||
|
||||
mailbox Drafts {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox Junk {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox Trash {
|
||||
special_use = \Trash
|
||||
}
|
||||
|
||||
# For \Sent mailboxes there are two widely used names. We'll mark both of
|
||||
# them as \Sent. User typically deletes one of them if duplicates are created.
|
||||
mailbox Sent {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Sent Messages" {
|
||||
special_use = \Sent
|
||||
}
|
||||
}
|
||||
|
||||
mail_uid = vmail
|
||||
mail_gid = vmail
|
||||
mail_privileged_group = vmail
|
||||
|
||||
##
|
||||
## Mail processes
|
||||
##
|
||||
|
||||
# Enable IMAP COMPRESS (RFC 4978).
|
||||
# <https://datatracker.ietf.org/doc/html/rfc4978.html>
|
||||
protocol imap {
|
||||
mail_plugins = $mail_plugins imap_zlib
|
||||
}
|
||||
|
||||
plugin {
|
||||
imap_compress_deflate_level = 6
|
||||
}
|
||||
|
||||
service lmtp {
|
||||
user=vmail
|
||||
|
||||
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
||||
group = postfix
|
||||
mode = 0600
|
||||
user = postfix
|
||||
}
|
||||
}
|
||||
|
||||
service auth {
|
||||
unix_listener /var/spool/postfix/private/auth {
|
||||
mode = 0660
|
||||
user = postfix
|
||||
group = postfix
|
||||
}
|
||||
}
|
||||
|
||||
service auth-worker {
|
||||
# Default is root.
|
||||
# Drop privileges we don't need.
|
||||
user = $default_internal_user
|
||||
}
|
||||
|
||||
ssl = required
|
||||
ssl_cert = </var/lib/acme/live/{{ config.hostname }}/fullchain
|
||||
ssl_key = </var/lib/acme/live/{{ config.hostname }}/privkey
|
||||
ssl_dh = </usr/share/dovecot/dh.pem
|
||||
ssl_min_protocol = TLSv1.2
|
||||
ssl_prefer_server_ciphers = yes
|
||||
78
chatmail-pyinfra/src/chatmail/dovecot/test_doveauth.lua
Normal file
78
chatmail-pyinfra/src/chatmail/dovecot/test_doveauth.lua
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
require "doveauth"
|
||||
|
||||
-- simulate dovecot defined result codes
|
||||
|
||||
dovecot = {
|
||||
auth = {
|
||||
PASSDB_RESULT_OK="PASSWORD-OK",
|
||||
PASSDB_RESULT_PASSWORD_MISMATCH="PASSWORD-MISMATCH",
|
||||
USERDB_RESULT_OK="USERDB-OK",
|
||||
USERDB_RESULT_USER_UNKNOWN="USERDB-UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-- Tests for testing the lua<->python interaction
|
||||
|
||||
function test_password_verify_ok(user, password)
|
||||
local res, extra = auth_password_verify({user=user}, password)
|
||||
assert(res==dovecot.auth.PASSDB_RESULT_OK)
|
||||
assert(extra.uid == "vmail")
|
||||
assert(extra.gid == "vmail")
|
||||
-- assert(extra.homedir == "/home/vmail/link2xt")
|
||||
print("OK test_password_verify_ok "..user.." "..password)
|
||||
end
|
||||
|
||||
function test_password_verify_mismatch(user, password)
|
||||
local res = auth_password_verify({user=user}, password)
|
||||
assert(res == dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH)
|
||||
print("OK test_password_verify_mismatch "..user.." "..password)
|
||||
end
|
||||
|
||||
function test_userdb_lookup_ok(user)
|
||||
local res, extra = auth_userdb_lookup({user=user})
|
||||
assert(extra.uid == "vmail")
|
||||
assert(extra.gid == "vmail")
|
||||
assert(res == dovecot.auth.USERDB_RESULT_OK)
|
||||
print("OK test_userdb_lookup_ok "..user)
|
||||
end
|
||||
|
||||
function test_userdb_lookup_mismatch(user)
|
||||
local res, extra = auth_userdb_lookup({user=user})
|
||||
assert(res == dovecot.auth.USERDB_RESULT_USER_UNKNOWN)
|
||||
print("OK test_userdb_lookup_mismatch "..user)
|
||||
end
|
||||
|
||||
function test_passdb_lookup_ok(user)
|
||||
local res, extra = auth_passdb_lookup({user=user})
|
||||
assert(extra.uid == "vmail")
|
||||
assert(extra.gid == "vmail")
|
||||
assert(res == dovecot.auth.PASSDB_RESULT_OK)
|
||||
print("OK test_passdb_lookup_ok "..user)
|
||||
end
|
||||
|
||||
function test_passdb_lookup_mismatch(user)
|
||||
local res, extra = auth_passdb_lookup({user=user})
|
||||
assert(res == dovecot.auth.PASSDB_RESULT_USER_UNKNOWN)
|
||||
print("OK test_passdb_lookup_mismatch "..user)
|
||||
end
|
||||
|
||||
function test_split_chatctl()
|
||||
local res = split_chatctl("a=3 b=4\nc=5")
|
||||
assert(res["a"] == "3")
|
||||
assert(res["b"] == "4")
|
||||
assert(res["c"] == "5")
|
||||
print("OK test_split_chatctl")
|
||||
end
|
||||
|
||||
test_split_chatctl()
|
||||
test_password_verify_ok("link2xt@c1.testrun.org", "Ahyei6ie")
|
||||
test_password_verify_mismatch("link2xt@c1.testrun.org", "Aqwlek")
|
||||
test_userdb_lookup_ok("link2xt@c1.testrun.org")
|
||||
test_userdb_lookup_mismatch("wlekqjlew@xyz.org")
|
||||
|
||||
-- probably not needed by dovecot?
|
||||
-- test_passdb_lookup_ok("link2xt@c1.testrun.org")
|
||||
-- test_passdb_lookup_mismatch("llqkwjelqwe@xyz.org")
|
||||
|
||||
23
chatmail-pyinfra/src/chatmail/dovecot/test_doveauth.py
Normal file
23
chatmail-pyinfra/src/chatmail/dovecot/test_doveauth.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import subprocess
|
||||
import pytest
|
||||
|
||||
from doveauth import get_user_data, verify_user
|
||||
|
||||
|
||||
def test_basic():
|
||||
data = get_user_data("link2xt@c1.testrun.org")
|
||||
assert data
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="no persistence yet")
|
||||
def test_verify_or_create():
|
||||
res = verify_user("newuser1@something.org", "kajdlkajsldk12l3kj1983")
|
||||
assert res["status"] == "ok"
|
||||
res = verify_user("newuser1@something.org", "kajdlqweqwe")
|
||||
assert res["status"] == "fail"
|
||||
|
||||
|
||||
def test_lua_integration(request):
|
||||
p = request.fspath.dirpath("test_doveauth.lua")
|
||||
proc = subprocess.run(["lua", str(p)])
|
||||
assert proc.returncode == 0
|
||||
51
chatmail-pyinfra/src/chatmail/opendkim/opendkim.conf
Normal file
51
chatmail-pyinfra/src/chatmail/opendkim/opendkim.conf
Normal file
@@ -0,0 +1,51 @@
|
||||
# This is a basic configuration for signing and verifying. It can easily be
|
||||
# adapted to suit a basic installation. See opendkim.conf(5) and
|
||||
# /usr/share/doc/opendkim/examples/opendkim.conf.sample for complete
|
||||
# documentation of available configuration parameters.
|
||||
|
||||
Syslog yes
|
||||
SyslogSuccess yes
|
||||
#LogWhy no
|
||||
|
||||
# Common signing and verification parameters. In Debian, the "From" header is
|
||||
# oversigned, because it is often the identity key used by reputation systems
|
||||
# and thus somewhat security sensitive.
|
||||
Canonicalization relaxed/simple
|
||||
#Mode sv
|
||||
#SubDomains no
|
||||
OversignHeaders From
|
||||
|
||||
# Signing domain, selector, and key (required). For example, perform signing
|
||||
# for domain "example.com" with selector "2020" (2020._domainkey.example.com),
|
||||
# using the private key stored in /etc/dkimkeys/example.private. More granular
|
||||
# setup options can be found in /usr/share/doc/opendkim/README.opendkim.
|
||||
Domain {{ config.domain_name }}
|
||||
Selector {{ config.opendkim_selector }}
|
||||
KeyFile /etc/dkimkeys/{{ config.opendkim_selector }}.private
|
||||
|
||||
# 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
|
||||
# user (for example, Postfix). You may need to add user "postfix" to group
|
||||
# "opendkim" in that case.
|
||||
UserID opendkim
|
||||
UMask 007
|
||||
|
||||
# Socket for the MTA connection (required). If the MTA is inside a chroot jail,
|
||||
# it must be ensured that the socket is accessible. In Debian, Postfix runs in
|
||||
# a chroot in /var/spool/postfix, therefore a Unix socket would have to be
|
||||
# configured as shown on the last line below.
|
||||
#Socket local:/run/opendkim/opendkim.sock
|
||||
#Socket inet:8891@localhost
|
||||
#Socket inet:8891
|
||||
Socket local:/var/spool/postfix/opendkim/opendkim.sock
|
||||
|
||||
PidFile /run/opendkim/opendkim.pid
|
||||
|
||||
# Hosts for which to sign rather than verify, default is 127.0.0.1. See the
|
||||
# OPERATION section of opendkim(8) for more information.
|
||||
#InternalHosts 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12
|
||||
|
||||
# The trust anchor enables DNSSEC. In Debian, the trust anchor file is provided
|
||||
# by the package dns-root-data.
|
||||
TrustAnchorFile /usr/share/dns/root.key
|
||||
#Nameservers 127.0.0.1
|
||||
48
chatmail-pyinfra/src/chatmail/postfix/main.cf.j2
Normal file
48
chatmail-pyinfra/src/chatmail/postfix/main.cf.j2
Normal file
@@ -0,0 +1,48 @@
|
||||
myorigin = {{ config.domain_name }}
|
||||
|
||||
smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
|
||||
biff = no
|
||||
|
||||
# appending .domain is the MUA's job.
|
||||
append_dot_mydomain = no
|
||||
|
||||
# Uncomment the next line to generate "delayed mail" warnings
|
||||
#delay_warning_time = 4h
|
||||
|
||||
readme_directory = no
|
||||
|
||||
# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
|
||||
# fresh installs.
|
||||
compatibility_level = 2
|
||||
|
||||
# TLS parameters
|
||||
smtpd_tls_cert_file=/var/lib/acme/live/{{ config.domain_name }}/fullchain
|
||||
smtpd_tls_key_file=/var/lib/acme/live/{{ config.domain_name }}/privkey
|
||||
smtpd_tls_security_level=may
|
||||
|
||||
smtp_tls_CApath=/etc/ssl/certs
|
||||
smtp_tls_security_level=may
|
||||
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
|
||||
|
||||
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
|
||||
myhostname = {{ config.domain_name }}
|
||||
alias_maps = hash:/etc/aliases
|
||||
alias_database = hash:/etc/aliases
|
||||
|
||||
# Postfix does not deliver mail for any domain by itself.
|
||||
# Primary domain is listed in `virtual_mailbox_domains` instead
|
||||
# and handed over to Dovecot.
|
||||
mydestination =
|
||||
|
||||
relayhost =
|
||||
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
|
||||
mailbox_size_limit = 0
|
||||
recipient_delimiter = +
|
||||
inet_interfaces = all
|
||||
inet_protocols = all
|
||||
|
||||
virtual_transport = lmtp:unix:private/dovecot-lmtp
|
||||
virtual_mailbox_domains = {{ config.domain_name }}
|
||||
|
||||
smtpd_milters = unix:opendkim/opendkim.sock
|
||||
non_smtpd_milters = $smtpd_milters
|
||||
72
chatmail-pyinfra/src/chatmail/postfix/master.cf
Normal file
72
chatmail-pyinfra/src/chatmail/postfix/master.cf
Normal file
@@ -0,0 +1,72 @@
|
||||
#
|
||||
# Postfix master process configuration file. For details on the format
|
||||
# of the file, see the master(5) manual page (command: "man 5 master" or
|
||||
# on-line: http://www.postfix.org/master.5.html).
|
||||
#
|
||||
# Do not forget to execute "postfix reload" after editing this file.
|
||||
#
|
||||
# ==========================================================================
|
||||
# service type private unpriv chroot wakeup maxproc command + args
|
||||
# (yes) (yes) (no) (never) (100)
|
||||
# ==========================================================================
|
||||
smtp inet n - y - - smtpd
|
||||
#smtp inet n - y - 1 postscreen
|
||||
#smtpd pass - - y - - smtpd
|
||||
#dnsblog unix - - y - 0 dnsblog
|
||||
#tlsproxy unix - - y - 0 tlsproxy
|
||||
submission inet n - y - - smtpd
|
||||
-o syslog_name=postfix/submission
|
||||
-o smtpd_tls_security_level=encrypt
|
||||
-o smtpd_sasl_auth_enable=yes
|
||||
-o smtpd_sasl_type=dovecot
|
||||
-o smtpd_sasl_path=private/auth
|
||||
-o smtpd_tls_auth_only=yes
|
||||
-o smtpd_reject_unlisted_recipient=no
|
||||
-o smtpd_client_restrictions=$mua_client_restrictions
|
||||
-o smtpd_helo_restrictions=$mua_helo_restrictions
|
||||
-o smtpd_sender_restrictions=$mua_sender_restrictions
|
||||
-o smtpd_recipient_restrictions=
|
||||
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
|
||||
-o milter_macro_daemon_name=ORIGINATING
|
||||
smtps inet n - y - - smtpd
|
||||
-o syslog_name=postfix/smtps
|
||||
-o smtpd_tls_wrappermode=yes
|
||||
-o smtpd_tls_security_level=encrypt
|
||||
-o smtpd_sasl_auth_enable=yes
|
||||
-o smtpd_sasl_type=dovecot
|
||||
-o smtpd_sasl_path=private/auth
|
||||
-o smtpd_reject_unlisted_recipient=no
|
||||
-o smtpd_client_restrictions=$mua_client_restrictions
|
||||
-o smtpd_helo_restrictions=$mua_helo_restrictions
|
||||
-o smtpd_sender_restrictions=$mua_sender_restrictions
|
||||
-o smtpd_recipient_restrictions=
|
||||
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
|
||||
-o milter_macro_daemon_name=ORIGINATING
|
||||
#628 inet n - y - - qmqpd
|
||||
pickup unix n - y 60 1 pickup
|
||||
cleanup unix n - y - 0 cleanup
|
||||
qmgr unix n - n 300 1 qmgr
|
||||
#qmgr unix n - n 300 1 oqmgr
|
||||
tlsmgr unix - - y 1000? 1 tlsmgr
|
||||
rewrite unix - - y - - trivial-rewrite
|
||||
bounce unix - - y - 0 bounce
|
||||
defer unix - - y - 0 bounce
|
||||
trace unix - - y - 0 bounce
|
||||
verify unix - - y - 1 verify
|
||||
flush unix n - y 1000? 0 flush
|
||||
proxymap unix - - n - - proxymap
|
||||
proxywrite unix - - n - 1 proxymap
|
||||
smtp unix - - y - - smtp
|
||||
relay unix - - y - - smtp
|
||||
-o syslog_name=postfix/$service_name
|
||||
# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
|
||||
showq unix n - y - - showq
|
||||
error unix - - y - - error
|
||||
retry unix - - y - - error
|
||||
discard unix - - y - - discard
|
||||
local unix - n n - - local
|
||||
virtual unix - n n - - virtual
|
||||
lmtp unix - - y - - lmtp
|
||||
anvil unix - - y - 1 anvil
|
||||
scache unix - - y - 1 scache
|
||||
postlog unix-dgram n - n - 1 postlogd
|
||||
28
chatmail-pyinfra/tests/test_online_login.py
Normal file
28
chatmail-pyinfra/tests/test_online_login.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import pytest
|
||||
import imaplib
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def conn():
|
||||
return connect("c1.testrun.org")
|
||||
|
||||
|
||||
def login(conn, user, password):
|
||||
print("trying to login", user, password)
|
||||
conn.login(user, password)
|
||||
|
||||
|
||||
def connect(host):
|
||||
print(f"connecting to {host}")
|
||||
conn = imaplib.IMAP4_SSL(host)
|
||||
return conn
|
||||
|
||||
|
||||
def test_login_ok(conn):
|
||||
login(conn, "link2xt@c1.testrun.org", "Ahyei6ie")
|
||||
|
||||
|
||||
def test_login_fail(conn):
|
||||
with pytest.raises(imaplib.IMAP4.error) as excinfo:
|
||||
login(conn, "link2xt@c1.testrun.org", "qweqwe")
|
||||
assert "AUTHENTICATIONFAILED" in str(excinfo)
|
||||
Reference in New Issue
Block a user