mirror of
https://github.com/chatmail/relay.git
synced 2026-05-11 16:34:39 +00:00
Compare commits
13 Commits
hpk/shift-
...
repackage
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d3ffd8f4e | ||
|
|
a24f1e8393 | ||
|
|
f84692a07a | ||
|
|
4badc7c8d6 | ||
|
|
4b82fd6f77 | ||
|
|
9da375cf5c | ||
|
|
8086e2ee2f | ||
|
|
1b88e0d9d0 | ||
|
|
db6df34703 | ||
|
|
a907da9907 | ||
|
|
14649c37fd | ||
|
|
28fe373489 | ||
|
|
1942ad3cef |
59
README.md
59
README.md
@@ -4,7 +4,58 @@ This package deploys Postfix and Dovecot servers, including OpenDKIM for DKIM si
|
||||
|
||||
Postfix uses Dovecot for authentication as described in <https://www.postfix.org/SASL_README.html#server_dovecot>
|
||||
|
||||
## Ports
|
||||
## Getting started
|
||||
|
||||
prepare:
|
||||
|
||||
pip install -e chatmail-infra
|
||||
|
||||
|
||||
then run with pyinfra command line tool:
|
||||
|
||||
CHATMAIL_DOMAIN=c1.testrun.org pyinfra --ssh-user root c1.testrun.org deploy.py
|
||||
|
||||
|
||||
## Structure (wip)
|
||||
```
|
||||
|
||||
# package doveauth tool and deploy chatmail server to a envvar-specified ssh-reachable host
|
||||
deploy.py
|
||||
|
||||
# chatmail pyinfra deploy package
|
||||
chatmail-pyinfra
|
||||
pyproject.toml
|
||||
chatmail/__init__ ...
|
||||
|
||||
# tests against the deployed system
|
||||
tests/test_online_test.py
|
||||
|
||||
# doveauth tool used by dovecot's auth mechanism on the host system
|
||||
doveauth
|
||||
README.md
|
||||
pyproject.toml
|
||||
doveauth.py
|
||||
doveauth.lua
|
||||
test_doveauth.py
|
||||
|
||||
# lmtp server to block (outgoing) unencrypted messages
|
||||
filtermail
|
||||
README.md
|
||||
pyproject.toml
|
||||
....
|
||||
|
||||
|
||||
# scripts for setup/development/deployment
|
||||
|
||||
scripts/
|
||||
init.sh # create venv/other perequires
|
||||
deploy.sh # run pyinfra based deploy of everything
|
||||
|
||||
```
|
||||
|
||||
## Dovecot/Postfix configuration
|
||||
|
||||
### Ports
|
||||
|
||||
Postfix listens on ports 25 (smtp) and 587 (submission) and 465 (submissions).
|
||||
Dovecot listens on ports 143(imap) and 993 (imaps).
|
||||
@@ -12,9 +63,3 @@ Dovecot listens on ports 143(imap) and 993 (imaps).
|
||||
## DNS
|
||||
|
||||
For DKIM you must add a DNS entry as in /etc/opendkim/selector.txt (where selector is the opendkim_selector configured in the chatmail inventory).
|
||||
|
||||
## Run with pyinfra
|
||||
|
||||
```
|
||||
CHATMAIL_DOMAIN=c1.testrun.org pyinfra c1.testrun.org deploy.py
|
||||
```
|
||||
|
||||
11
c1-inv.py
11
c1-inv.py
@@ -1,11 +0,0 @@
|
||||
|
||||
chatmail = [
|
||||
(
|
||||
"c1.testrun.org",
|
||||
{
|
||||
"ssh_user": "root",
|
||||
"domain": "c1.testrun.org",
|
||||
"dkim_selector": "2023",
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -8,7 +8,9 @@ end
|
||||
-- call out to python program to actually manage authentication for dovecot
|
||||
|
||||
function chatctl_verify(user, password)
|
||||
local handle = io.popen("python doveauth.py hexauth "..escape(user).." "..escape(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)
|
||||
@@ -16,32 +18,27 @@ end
|
||||
|
||||
function chatctl_lookup(user)
|
||||
assert(user)
|
||||
local handle = io.popen("python doveauth.py hexlookup "..escape(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 {homedir=res.homedir, uid=res.uid, gid=res.gid}
|
||||
return {home=res.home, uid=res.uid, gid=res.gid}
|
||||
end
|
||||
|
||||
|
||||
function auth_passdb_verify(request, password)
|
||||
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_passdb_lookup(request)
|
||||
local res = chatctl_lookup(request.user)
|
||||
if res.status == "ok" then
|
||||
return dovecot.auth.PASSDB_RESULT_OK, get_extra_dovecot_output(res)
|
||||
end
|
||||
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "no such user"
|
||||
end
|
||||
|
||||
function auth_userdb_lookup(request)
|
||||
local res = chatctl_lookup(request.user)
|
||||
@@ -4,21 +4,17 @@ import sys
|
||||
|
||||
|
||||
def get_user_data(user):
|
||||
if user == b"link2xt@instant2.testrun.org":
|
||||
if user == "link2xt@c1.testrun.org":
|
||||
return dict(
|
||||
homedir="/home/vmail/link2xt",
|
||||
uid="vmail",
|
||||
gid="vmail",
|
||||
password=b"Ahyei6ie",
|
||||
password="Ahyei6ie",
|
||||
)
|
||||
return {}
|
||||
|
||||
|
||||
def create_user(user, password):
|
||||
assert isinstance(password, bytes)
|
||||
return dict(
|
||||
homedir=f"/home/vmail/{user}", uid="vmail", gid="vmail", password=password
|
||||
)
|
||||
return dict(home=f"/home/vmail/{user}", uid="vmail", gid="vmail", password=password)
|
||||
|
||||
|
||||
def verify_user(user, password):
|
||||
@@ -51,11 +47,11 @@ def dump_result(res):
|
||||
|
||||
if __name__ == "__main__":
|
||||
if sys.argv[1] == "hexauth":
|
||||
login = base64.b16decode(sys.argv[2])
|
||||
password = base64.b16decode(sys.argv[3])
|
||||
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])
|
||||
login = base64.b16decode(sys.argv[2]).decode()
|
||||
res = lookup_user(login)
|
||||
dump_result(res)
|
||||
@@ -4,6 +4,11 @@ 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
|
||||
@@ -15,19 +15,19 @@ dovecot = {
|
||||
|
||||
-- Tests for testing the lua<->python interaction
|
||||
|
||||
function test_passdb_verify_ok(user, password)
|
||||
local res, extra = auth_passdb_verify({user=user}, password)
|
||||
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_passdb_verify_ok "..user.." "..password)
|
||||
print("OK test_password_verify_ok "..user.." "..password)
|
||||
end
|
||||
|
||||
function test_passdb_verify_mismatch(user, password)
|
||||
local res = auth_passdb_verify({user=user}, password)
|
||||
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_passdb_verify_mismatch "..user.." "..password)
|
||||
print("OK test_password_verify_mismatch "..user.." "..password)
|
||||
end
|
||||
|
||||
function test_userdb_lookup_ok(user)
|
||||
@@ -67,10 +67,12 @@ function test_split_chatctl()
|
||||
end
|
||||
|
||||
test_split_chatctl()
|
||||
test_passdb_verify_ok("link2xt@instant2.testrun.org", "Ahyei6ie")
|
||||
test_passdb_verify_mismatch("link2xt@instant2.testrun.org", "Aqwlek")
|
||||
test_userdb_lookup_ok("link2xt@instant2.testrun.org")
|
||||
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")
|
||||
test_passdb_lookup_ok("link2xt@instant2.testrun.org")
|
||||
test_passdb_lookup_mismatch("llqkwjelqwe@xyz.org")
|
||||
|
||||
-- probably not needed by dovecot?
|
||||
-- test_passdb_lookup_ok("link2xt@c1.testrun.org")
|
||||
-- test_passdb_lookup_mismatch("llqkwjelqwe@xyz.org")
|
||||
|
||||
@@ -5,15 +5,15 @@ from doveauth import get_user_data, verify_user
|
||||
|
||||
|
||||
def test_basic():
|
||||
data = get_user_data(b"link2xt@instant2.testrun.org")
|
||||
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(b"newuser1@something.org", b"kajdlkajsldk12l3kj1983")
|
||||
res = verify_user("newuser1@something.org", "kajdlkajsldk12l3kj1983")
|
||||
assert res["status"] == "ok"
|
||||
res = verify_user(b"newuser1@something.org", b"kajdlqweqwe")
|
||||
res = verify_user("newuser1@something.org", "kajdlqweqwe")
|
||||
assert res["status"] == "fail"
|
||||
|
||||
|
||||
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)
|
||||
@@ -8,7 +8,11 @@ def main():
|
||||
mail_server = os.getenv("CHATMAIL_SERVER", mail_domain)
|
||||
dkim_selector = os.getenv("CHATMAIL_DKIM_SELECTOR", "2023")
|
||||
|
||||
assert mail_domain
|
||||
assert mail_server
|
||||
assert dkim_selector
|
||||
|
||||
deploy_chatmail(mail_domain, mail_server, dkim_selector)
|
||||
|
||||
|
||||
main()
|
||||
main()
|
||||
|
||||
39
plan.txt
Normal file
39
plan.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
# Chat-mail server development (up until Oct 18th)
|
||||
|
||||
## Dovecot goals/steps
|
||||
|
||||
1. create-user-on-login ("doveauth")
|
||||
- repackage so that "doveauth" does not come from a hard-coded path
|
||||
- persistence of accounts
|
||||
|
||||
2. per-user quota (adaptive)
|
||||
|
||||
3. automatic expiry of messages older than M days
|
||||
|
||||
4. automatic expiry of users that haven't logged in for N days
|
||||
|
||||
|
||||
## Postfix goals/steps
|
||||
|
||||
1. block all outgoing mails with our own LMTP program
|
||||
|
||||
2. only allow (outgoing) mails if secure-join or autocrypt-pgp-encrypted format
|
||||
(probably via an lmtp service)
|
||||
|
||||
3. basic outgoing send rate/limits (depending on "account-rating")
|
||||
|
||||
|
||||
## online tests (first with plain python/pytest)
|
||||
|
||||
- write tests for dovecot login (exists)
|
||||
- write tests for postfix logins
|
||||
- write A<>B send/receive tests
|
||||
|
||||
|
||||
## Delta Chat
|
||||
|
||||
1. qr code that defines access to a chatmail instance (like mailadm but without http etc.)
|
||||
|
||||
2. support for creating username/password and verifying login works
|
||||
|
||||
|
||||
3
scripts/deploy.sh
Executable file
3
scripts/deploy.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
export CHATMAIL_DOMAIN="${1:-c1.testrun.org}"
|
||||
chatmail-pyinfra/venv/bin/pyinfra --ssh-user root "$CHATMAIL_DOMAIN" deploy.py
|
||||
4
scripts/init.sh
Executable file
4
scripts/init.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
python3 -m venv chatmail-pyinfra/venv
|
||||
chatmail-pyinfra/venv/bin/pip install pyinfra
|
||||
chatmail-pyinfra/venv/bin/pip install -e chatmail-pyinfra
|
||||
Reference in New Issue
Block a user