mirror of
https://github.com/chatmail/relay.git
synced 2026-05-18 01:08:59 +00:00
Compare commits
8 Commits
nsd
...
hpk/readme
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5c14e20a7 | ||
|
|
1ba3c410dd | ||
|
|
3a9db729f8 | ||
|
|
7eb86cba34 | ||
|
|
5633c0612e | ||
|
|
d5912b909c | ||
|
|
f75eb0658c | ||
|
|
7c5ec1e0df |
85
README.md
85
README.md
@@ -1,26 +1,61 @@
|
|||||||
# Chat Mail server configuration
|
# Chatmail instances optimized for Delta Chat apps
|
||||||
|
|
||||||
This repository setups a ready-to-go chatmail instance
|
This repository helps to setup a ready-to-use chatmail instance
|
||||||
comprised of a minimal setup of the battle-tested
|
comprised of a minimal setup of the battle-tested
|
||||||
[postfix smtp server](https://www.postfix.org) and [dovecot imap server](https://www.dovecot.org).
|
[postfix smtp](https://www.postfix.org) and [dovecot imap](https://www.dovecot.org) services.
|
||||||
|
|
||||||
## Getting started
|
The setup is designed and optimized for providing chatmail accounts
|
||||||
|
for use by [Delta Chat apps](https://delta.chat).
|
||||||
|
|
||||||
1. prepare your local system:
|
Chatmail accounts are automatically created by a first login,
|
||||||
|
after which the initially specified password is required for using them.
|
||||||
|
|
||||||
|
## Getting Started deploying your own chatmail instance
|
||||||
|
|
||||||
|
1. Prepare your local (presumably Linux) system:
|
||||||
|
|
||||||
scripts/init.sh
|
scripts/init.sh
|
||||||
|
|
||||||
2. setup a domain with `A` and `AAAA` records for your chatmail server
|
2. Setup a domain with `A` and `AAAA` records for your chatmail server.
|
||||||
|
|
||||||
3. set environment variable to the chatmail domain you want to setup:
|
3. Set environment variable to the chatmail domain you want to setup:
|
||||||
|
|
||||||
export CHATMAIL_DOMAIN=c1.testrun.org # replace with your host
|
export CHATMAIL_DOMAIN=c1.testrun.org # replace with your host
|
||||||
|
|
||||||
4. run the deploy of the chat mail instance:
|
4. Deploy the chat mail instance to your chatmail server:
|
||||||
|
|
||||||
scripts/deploy.sh
|
scripts/deploy.sh
|
||||||
|
|
||||||
5. run `scripts/generate-dns-zone.sh` and create the generated DNS records at your DNS provider
|
This script uses `pyinfra` and `ssh` to setup packages and configure
|
||||||
|
the chatmail instance on your remote server.
|
||||||
|
|
||||||
|
5. Run `scripts/generate-dns-zone.sh` and
|
||||||
|
transfer the generated DNS records at your DNS provider
|
||||||
|
|
||||||
|
6. 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
|
||||||
|
|
||||||
|
Postfix listens on ports 25 (smtp) and 587 (submission) and 465 (submissions).
|
||||||
|
Dovecot listens on ports 143(imap) and 993 (imaps).
|
||||||
|
|
||||||
|
Delta Chat will, however, discover all ports and configurations
|
||||||
|
automatically by reading the `autoconfig.xml` file from the chatmail instance.
|
||||||
|
|
||||||
|
|
||||||
|
## Emergency Commands to disable automatic account creation
|
||||||
|
|
||||||
|
If you need to stop account creation,
|
||||||
|
e.g. because some script is wildly creating accounts, run:
|
||||||
|
|
||||||
|
touch /etc/chatmail-nocreate
|
||||||
|
|
||||||
|
While this file is present, account creation will be blocked.
|
||||||
|
|
||||||
|
|
||||||
## Running tests and benchmarks (offline and online)
|
## Running tests and benchmarks (offline and online)
|
||||||
|
|
||||||
@@ -35,28 +70,26 @@ comprised of a minimal setup of the battle-tested
|
|||||||
|
|
||||||
scripts/bench.sh
|
scripts/bench.sh
|
||||||
|
|
||||||
## Running tests (offline and online)
|
|
||||||
|
|
||||||
```
|
## Development Background for chatmail instances
|
||||||
## Dovecot/Postfix configuration
|
|
||||||
|
|
||||||
### Ports
|
This repository drives the development of "chatmail instances",
|
||||||
|
comprised of minimal setups of
|
||||||
|
|
||||||
Postfix listens on ports 25 (smtp) and 587 (submission) and 465 (submissions).
|
- [postfix smtp server](https://www.postfix.org)
|
||||||
Dovecot listens on ports 143(imap) and 993 (imaps).
|
- [dovecot imap server](https://www.dovecot.org)
|
||||||
|
|
||||||
## DNS
|
as well as two custom services that are integrated with these two:
|
||||||
|
|
||||||
For DKIM you must add a DNS entry as found in /etc/opendkim/selector.txt on your chatmail instance.
|
- `chatmaild/src/chatmaild/dictproxy.py` implements
|
||||||
The above `scripts/deploy.sh` prints out the DKIM selector and DNS entry you
|
create-on-login account creation semantics and is used
|
||||||
need to setup with your DNS provider.
|
by Dovecot during login authentication and by Postfix
|
||||||
|
which in turn uses Dovecot SASL to authenticate users
|
||||||
|
to send mails for them.
|
||||||
|
|
||||||
|
- `chatmaild/src/chatmaild/filtermail.py` prevents
|
||||||
|
unencrypted e-mail from leaving the chatmail instance
|
||||||
|
and is integrated into postfix's outbound mail pipelines.
|
||||||
|
|
||||||
## Emergency Commands
|
|
||||||
|
|
||||||
If you need to stop account creation,
|
|
||||||
e.g. because some script is wildly creating accounts,
|
|
||||||
just run `touch /tmp/nocreate`.
|
|
||||||
You can remove the file
|
|
||||||
as soon as the attacker was banned
|
|
||||||
by different means.
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,15 +21,27 @@ def encrypt_password(password: str):
|
|||||||
return "{SHA512-CRYPT}" + passhash
|
return "{SHA512-CRYPT}" + passhash
|
||||||
|
|
||||||
|
|
||||||
def create_user(db, user, password):
|
def check_password(password) -> bool:
|
||||||
|
"""Check password policy"""
|
||||||
|
if len(password) < 10:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def create_user(db, user, encrypted_password):
|
||||||
if os.path.exists(NOCREATE_FILE):
|
if os.path.exists(NOCREATE_FILE):
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"Didn't create account: {NOCREATE_FILE} exists. Delete the file to enable account creation."
|
f"Didn't create account: {NOCREATE_FILE} exists. Delete the file to enable account creation."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
with db.write_transaction() as conn:
|
with db.write_transaction() as conn:
|
||||||
conn.create_user(user, password)
|
conn.create_user(user, encrypted_password)
|
||||||
return dict(home=f"/home/vmail/{user}", uid="vmail", gid="vmail", password=password)
|
return dict(
|
||||||
|
home=f"/home/vmail/{user}",
|
||||||
|
uid="vmail",
|
||||||
|
gid="vmail",
|
||||||
|
password=encrypted_password,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_user_data(db, user):
|
def get_user_data(db, user):
|
||||||
@@ -48,6 +60,9 @@ def lookup_userdb(db, user):
|
|||||||
def lookup_passdb(db, user, password):
|
def lookup_passdb(db, user, password):
|
||||||
userdata = get_user_data(db, user)
|
userdata = get_user_data(db, user)
|
||||||
if not userdata:
|
if not userdata:
|
||||||
|
if not check_password(password):
|
||||||
|
logging.warning("Attempt to create an account with a weak password.")
|
||||||
|
return
|
||||||
return create_user(db, user, encrypt_password(password))
|
return create_user(db, user, encrypt_password(password))
|
||||||
userdata["password"] = userdata["password"].strip()
|
userdata["password"] = userdata["password"].strip()
|
||||||
return userdata
|
return userdata
|
||||||
|
|||||||
@@ -183,6 +183,17 @@ def _configure_dovecot(mail_server: str, debug: bool = False) -> bool:
|
|||||||
mode="644",
|
mode="644",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# as per https://doc.dovecot.org/configuration_manual/os/
|
||||||
|
# it is recommended to set the following inotify limits
|
||||||
|
for name in ("max_user_instances", "max_user_watches"):
|
||||||
|
key = f"fs.inotify.{name}"
|
||||||
|
server.sysctl(
|
||||||
|
name=f"Change {key}",
|
||||||
|
key=key,
|
||||||
|
value=65535,
|
||||||
|
persist=True,
|
||||||
|
)
|
||||||
|
|
||||||
return need_restart
|
return need_restart
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -118,6 +118,24 @@ service auth-worker {
|
|||||||
user = vmail
|
user = vmail
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service imap-login {
|
||||||
|
# High-security mode.
|
||||||
|
# Each process serves a single connection and exits afterwards.
|
||||||
|
# This is the default, but we set it explicitly to be sure.
|
||||||
|
# See <https://doc.dovecot.org/admin_manual/login_processes/#high-security-mode> for details.
|
||||||
|
service_count = 1
|
||||||
|
|
||||||
|
# Inrease the number of simultaneous connections.
|
||||||
|
#
|
||||||
|
# As of Dovecot 2.3.19.1 the default is 100 processes.
|
||||||
|
# Combined with `service_count = 1` it means only 100 connections
|
||||||
|
# can be handled simultaneously.
|
||||||
|
process_limit = 10000
|
||||||
|
|
||||||
|
# Avoid startup latency for new connections.
|
||||||
|
process_min_avail = 10
|
||||||
|
}
|
||||||
|
|
||||||
ssl = required
|
ssl = required
|
||||||
ssl_cert = </var/lib/acme/live/{{ config.hostname }}/fullchain
|
ssl_cert = </var/lib/acme/live/{{ config.hostname }}/fullchain
|
||||||
ssl_key = </var/lib/acme/live/{{ config.hostname }}/privkey
|
ssl_key = </var/lib/acme/live/{{ config.hostname }}/privkey
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
online-tests/venv/bin/pytest online-tests/benchmark.py -vrx
|
venv/bin/pytest online-tests/benchmark.py -vrx
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ def test_basic(db):
|
|||||||
chatmaild.dictproxy.NOCREATE_FILE = "/tmp/nocreate"
|
chatmaild.dictproxy.NOCREATE_FILE = "/tmp/nocreate"
|
||||||
if os.path.exists(chatmaild.dictproxy.NOCREATE_FILE):
|
if os.path.exists(chatmaild.dictproxy.NOCREATE_FILE):
|
||||||
os.remove(chatmaild.dictproxy.NOCREATE_FILE)
|
os.remove(chatmaild.dictproxy.NOCREATE_FILE)
|
||||||
lookup_passdb(db, "link2xt@c1.testrun.org", "asdf")
|
lookup_passdb(db, "link2xt@c1.testrun.org", "Pieg9aeToe3eghuthe5u")
|
||||||
data = get_user_data(db, "link2xt@c1.testrun.org")
|
data = get_user_data(db, "link2xt@c1.testrun.org")
|
||||||
assert data
|
assert data
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ def test_nocreate_file(db):
|
|||||||
with open(chatmaild.dictproxy.NOCREATE_FILE, "w+") as f:
|
with open(chatmaild.dictproxy.NOCREATE_FILE, "w+") as f:
|
||||||
f.write("")
|
f.write("")
|
||||||
assert os.path.exists(chatmaild.dictproxy.NOCREATE_FILE)
|
assert os.path.exists(chatmaild.dictproxy.NOCREATE_FILE)
|
||||||
lookup_passdb(db, "newuser1@something.org", "kajdlqweqwe")
|
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")
|
||||||
os.remove(chatmaild.dictproxy.NOCREATE_FILE)
|
os.remove(chatmaild.dictproxy.NOCREATE_FILE)
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ def test_login_basic_functioning(imap_or_smtp, gencreds, lp):
|
|||||||
with pytest.raises(imap_or_smtp.AuthError):
|
with pytest.raises(imap_or_smtp.AuthError):
|
||||||
imap_or_smtp.login(user, password + "wrong")
|
imap_or_smtp.login(user, password + "wrong")
|
||||||
|
|
||||||
|
lp.sec(f"creating users with a short password is not allowed")
|
||||||
|
user, _password = gencreds()
|
||||||
|
with pytest.raises(imap_or_smtp.AuthError):
|
||||||
|
imap_or_smtp.login(user, "admin")
|
||||||
|
|
||||||
|
|
||||||
def test_login_same_password(imap_or_smtp, gencreds):
|
def test_login_same_password(imap_or_smtp, gencreds):
|
||||||
"""Test two different users logging in with the same password
|
"""Test two different users logging in with the same password
|
||||||
|
|||||||
Reference in New Issue
Block a user