Compare commits

..

1 Commits

Author SHA1 Message Date
Keonik1
5fbbd6b5bd Add a script for creating DNS records in Cloudflare 2025-10-25 00:08:50 +03:00
11 changed files with 218 additions and 42 deletions

View File

@@ -1 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Mutual Help Chat Group
url: https://i.delta.chat/#6CBFF8FFD505C0FDEA20A66674F2916EA8FBEE99&a=invitebot%40nine.testrun.org&g=Chatmail%20Mutual%20Help&x=7sFF7Ik50pWv6J1z7RVC5527&i=X69wTFfvCfs3d-JzqP0kVA3i&s=ibp-447dU-wUq-52QanwAtWc
about: If you have troubles setting up the relay server, feel free to ask here.

View File

@@ -2,15 +2,12 @@
## untagged
- acmetool: use ECDSA keys instead of RSA
([#689](https://github.com/chatmail/relay/pull/689))
- QoL: Add a script for creating DNS records in Cloudflare
([#692](https://github.com/chatmail/relay/pull/692))
- Require TLS 1.2 for outgoing SMTP connections
([#685](https://github.com/chatmail/relay/pull/685))
- require STARTTLS for incoming port 25 connections
([#684](https://github.com/chatmail/relay/pull/684))
- filtermail: run CPU-intensive handle_DATA in a thread pool executor
([#676](https://github.com/chatmail/relay/pull/676))
@@ -30,7 +27,7 @@
([#650](https://github.com/chatmail/relay/pull/650))
- filtermail: accept mails from Protonmail
([#616](https://github.com/chatmail/relay/pull/616))
([#616](https://github.com/chatmail/relay/pull/655))
- Ignore all RCPT TO: parameters
([#651](https://github.com/chatmail/relay/pull/651))
@@ -63,7 +60,7 @@
to only do a single iteration over sometimes millions of messages
instead of doing "find" commands that iterate 9 times over the messages.
Provide an "fsreport" CLI for more fine grained analysis of message files.
([#637](https://github.com/chatmail/relay/pull/637))
([#637](https://github.com/chatmail/relay/pull/632))
## 1.7.0 2025-09-11

View File

@@ -69,6 +69,20 @@ Please substitute it with your own domain.
mta-sts.chat.example.com. 3600 IN CNAME chat.example.com.
```
> [!note]
> If you use Cloudflare as your DNS server, you can use a script that will automatically create all the necessary DNS records!
> To do this, you need to [create an API token](https://dash.cloudflare.com/profile/api-tokens)
> and execute the following commands in the console after you clone the repository (step 2):
> ```bash
> CLOUDFLARE_API_KEY="dsfkljhfkjldwsnfkjldsnf" # REPLACE TO YOURS
> ZONE_ID="sdkjbfbnjkdsbfjkdsbkjfbds" # REPLACE TO YOURS
> CHATMAIL_FULL_DNS_NAME="chat.example.com" # REPLACE TO YOURS
> CHATMAIL_PUBLIC_IP="198.51.100.5" # REPLACE TO YOURS
> # IPV6_ENABLED="true" # (optional) by default 'false'
> # CHATMAIL_PUBLIC_IPv6="2001:db8::5" # (optional) REPLACE TO YOURS
> ./scripts/create_cloudflare_records.sh
> ```
2. On your local PC, clone the repository and bootstrap the Python virtualenv.
```
@@ -180,10 +194,6 @@ The components of chatmail are:
- [Iroh relay](https://www.iroh.computer/docs/concepts/relay)
which helps client devices to establish Peer-to-Peer connections
- [TURN](https://github.com/chatmail/chatmail-turn)
to enable relay users to start webRTC calls
even if a p2p connection can't be established
- and the chatmaild services, explained in the next section:
### chatmaild
@@ -308,8 +318,6 @@ Chatmail address creation will be denied while this file is present.
[Nginx](https://www.nginx.com/) listens on port 8443 (HTTPS-ALT) and 443 (HTTPS).
Port 443 multiplexes HTTPS, IMAP and SMTP using ALPN to redirect connections to ports 8443, 465 or 993.
[acmetool](https://hlandau.github.io/acmetool/) listens on port 80 (HTTP).
[chatmail-turn](https://github.com/chatmail/chatmail-turn) listens on UDP port 3478 (STUN/TURN),
and temporarily opens UDP ports when users request them. UDP port range is not restricted, any free port may be allocated.
chatmail-core based apps will, however, discover all ports and configurations
automatically by reading the [autoconfig XML file](https://www.ietf.org/archive/id/draft-bucksch-autoconfig-00.html) from the chatmail relay server.

View File

@@ -338,9 +338,9 @@ def _install_dovecot_package(package: str, arch: str):
match (package, arch):
case ("core", "amd64"):
sha256 = "dd060706f52a306fa863d874717210b9fe10536c824afe1790eec247ded5b27d"
sha256 = "43f593332e22ac7701c62d58b575d2ca409e0f64857a2803be886c22860f5587"
case ("core", "arm64"):
sha256 = "e7548e8a82929722e973629ecc40fcfa886894cef3db88f23535149e7f730dc9"
sha256 = "4d21eba1a83f51c100f08f2e49f0c9f8f52f721ebc34f75018e043306da993a7"
case ("imapd", "amd64"):
sha256 = "8d8dc6fc00bbb6cdb25d345844f41ce2f1c53f764b79a838eb2a03103eebfa86"
case ("imapd", "arm64"):

View File

@@ -1,8 +1,7 @@
request:
provider: https://acme-v02.api.letsencrypt.org/directory
key:
type: ecdsa
ecdsa-curve: nistp256
type: rsa
challenge:
webroot-paths:
- /var/www/html/.well-known/acme-challenge

View File

@@ -70,12 +70,6 @@ userdb {
# Mailboxes are stored in the "mail" directory of the vmail user home.
mail_location = maildir:{{ config.mailboxes_dir }}/%u
# index/cache files are not very useful for chatmail relay operations
# but it's not clear how to disable them completely.
# According to https://doc.dovecot.org/2.3/settings/advanced/#core_setting-mail_cache_max_size
# if the cache file becomes larger than the specified size, it is truncated by dovecot
mail_cache_max_size = 500K
namespace inbox {
inbox = yes

View File

@@ -25,6 +25,7 @@ smtp_tls_security_level=verify
# <https://www.postfix.org/postconf.5.html#smtp_tls_servername>
smtp_tls_servername = hostname
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtp_tls_policy_maps = inline:{nauta.cu=may}
smtp_tls_protocols = >=TLSv1.2
smtpd_tls_protocols = >=TLSv1.2

View File

@@ -14,7 +14,6 @@ smtp inet n - y - - smtpd -v
{%- else %}
smtp inet n - y - - smtpd
{%- endif %}
-o smtpd_tls_security_level=encrypt
-o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port_incoming }}
submission inet n - y - 5000 smtpd
-o syslog_name=postfix/submission

View File

@@ -1,5 +1,5 @@
import queue
import smtplib
import socket
import threading
import pytest
@@ -91,23 +91,25 @@ def test_concurrent_logins_same_account(
def test_no_vrfy(chatmail_config):
domain = chatmail_config.mail_domain
s = smtplib.SMTP(domain)
s.starttls()
s.putcmd("vrfy", f"wrongaddress@{chatmail_config.mail_domain}")
result = s.getreply()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(10)
try:
sock.connect((domain, 25))
except socket.timeout:
pytest.skip(f"port 25 not reachable for {domain}")
banner = sock.recv(1024)
print(banner)
sock.send(b"VRFY wrongaddress@%s\r\n" % (chatmail_config.mail_domain.encode(),))
result = sock.recv(1024)
print(result)
s.putcmd("vrfy", f"echo@{chatmail_config.mail_domain}")
result2 = s.getreply()
sock.send(b"VRFY echo@%s\r\n" % (chatmail_config.mail_domain.encode(),))
result2 = sock.recv(1024)
print(result2)
assert result[0] == result2[0] == 252
assert result[1][0:6] == result2[1][0:6] == b"2.0.0 "
s.putcmd("vrfy", "wrongaddress")
result = s.getreply()
assert result[0:10] == result2[0:10]
sock.send(b"VRFY wrongaddress\r\n")
result = sock.recv(1024)
print(result)
s.putcmd("vrfy", "echo")
result2 = s.getreply()
sock.send(b"VRFY echo\r\n")
result2 = sock.recv(1024)
print(result2)
assert result[0] == result2[0] == 252
assert result[1][0:6] == result2[1][0:6] == b"2.0.0 "
assert result[0:10] == result2[0:10] == b"252 2.0.0 "

View File

@@ -143,7 +143,6 @@ def test_reject_missing_dkim(cmsetup, maildata, from_addr):
"encrypted.eml", from_addr=from_addr, to_addr=recipient.addr
).as_string()
conn = smtplib.SMTP(cmsetup.maildomain, 25, timeout=10)
conn.starttls()
with conn as s:
with pytest.raises(smtplib.SMTPDataError, match="No valid DKIM signature"):

View File

@@ -0,0 +1,173 @@
#!/bin/bash
# go to https://dash.cloudflare.com/profile/api-tokens
# "create token" -> "Edit zone DNS"
## optionaly: rename token
## set your zone
# "continue to summary" -> "create token"
# copy your created token
CLOUDFLARE_API_KEY=${CLOUDFLARE_API_KEY}
ZONE_ID=${ZONE_ID}
CHATMAIL_FULL_DNS_NAME=${CHATMAIL_FULL_DNS_NAME}
CHATMAIL_PUBLIC_IP=${CHATMAIL_PUBLIC_IP}
IPV6_ENABLED=${IPV6_ENABLED:-false}
CHATMAIL_PUBLIC_IPv6=${CHATMAIL_PUBLIC_IPv6}
#####################
# why 'proxied' is 'false'?
# I suppose that if Cloudflare is blocked in a country, clients cannot use Deltachat without a VPN.
#####################
PROXIED=${PROXIED:-"false"}
check_variables() {
required_vars=(
CLOUDFLARE_API_KEY
ZONE_ID
CHATMAIL_FULL_DNS_NAME
CHATMAIL_PUBLIC_IP
)
missing_vars=()
for var in "${required_vars[@]}"; do
if [ -z "${!var}" ]; then
missing_vars+=("$var")
fi
done
if [ ${#missing_vars[@]} -ne 0 ]; then
echo "❌ Error: this variables not set or empty:"
for var in "${missing_vars[@]}"; do
echo " - $var"
done
echo "Please execute command 'export var_name=\"var_value\"' and restart script."
exit 1
fi
}
create_record() {
local data=$1
curl https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer ${CLOUDFLARE_API_KEY}" \
-d "$1"
}
generate_post_data_a_aaaa_record()
{
local name=$1
local type=${2:-"A"}
cat <<EOF
{
"name": "${name}",
"ttl": 3600,
"type": "${type}",
"comment": "",
"content": "${CHATMAIL_PUBLIC_IP}",
"proxied": ${PROXIED}
}
EOF
}
generate_post_data_cname_record()
{
local name=$1
cat <<EOF
{
"name": "${name}",
"ttl": 3600,
"type": "CNAME",
"comment": "",
"content": "${CHATMAIL_FULL_DNS_NAME}",
"proxied": ${PROXIED}
}
EOF
}
generate_post_data_mx_record()
{
local name=$1
cat <<EOF
{
"name": "${name}",
"ttl": 120,
"type": "MX",
"comment": "",
"content": "${CHATMAIL_FULL_DNS_NAME}",
"priority": 10,
"proxied": ${PROXIED}
}
EOF
}
generate_post_data_txt_record()
{
local name=$1
local content=$2
cat <<EOF
{
"name": "${name}",
"ttl": 120,
"type": "TXT",
"comment": "",
"content": "$content",
"proxied": ${PROXIED}
}
EOF
}
generate_post_data_srv_record()
{
local name=$1
local port=$2
cat <<EOF
{
"name": "${name}",
"ttl": 120,
"type": "SRV",
"comment": "",
"data": {
"port": $port,
"priority": 0,
"target": "${CHATMAIL_FULL_DNS_NAME}",
"weight": 1
},
"proxied": ${PROXIED}
}
EOF
}
check_variables
# A records
create_record "$(generate_post_data_a_record "$CHATMAIL_FULL_DNS_NAME" "A")"
create_record "$(generate_post_data_a_record "*.$CHATMAIL_FULL_DNS_NAME" "A")"
# AAAA records
if [ $IPV6_ENABLED = true ]; then # note: I don't have an IPv6 address, so this part hasn't been tested!
create_record "$(generate_post_data_a_record "$CHATMAIL_FULL_DNS_NAME" "AAAA")"
# create_record "$(generate_post_data_a_record "*.$CHATMAIL_FULL_DNS_NAME" "AAAA")"
fi
# CNAME records
create_record "$(generate_post_data_cname_record "mta-sts.$CHATMAIL_FULL_DNS_NAME")"
create_record "$(generate_post_data_cname_record "www.$CHATMAIL_FULL_DNS_NAME")"
# MX records
create_record "$(generate_post_data_mx_record "$CHATMAIL_FULL_DNS_NAME")"
# TXT records
create_record "$(generate_post_data_txt_record "$CHATMAIL_FULL_DNS_NAME" '\"v=spf1 a ~all\"')"
create_record "$(generate_post_data_txt_record "_dmarc.$CHATMAIL_FULL_DNS_NAME" '\"v=DMARC1;p=reject;adkim=s;aspf=s\"')"
create_record "$(generate_post_data_txt_record "_adsp._domainkey.$CHATMAIL_FULL_DNS_NAME" '\"dkim=discardable\"')"
create_record "$(generate_post_data_txt_record "opendkim._domainkey.$CHATMAIL_FULL_DNS_NAME" '\"v=DKIM1;k=rsa;p=;s=email;t=s\"')"
create_record "$(generate_post_data_txt_record "_mta-sts.$CHATMAIL_FULL_DNS_NAME" '\"v=STSv1; id='"$(date +"%Y%m%d%H%M")"'\"')"
# SRV records
create_record "$(generate_post_data_srv_record "_imap._tcp.$CHATMAIL_FULL_DNS_NAME" "143")"
create_record "$(generate_post_data_srv_record "_imaps._tcp.$CHATMAIL_FULL_DNS_NAME" "993")"
create_record "$(generate_post_data_srv_record "_submission._tcp.$CHATMAIL_FULL_DNS_NAME" "587")"
create_record "$(generate_post_data_srv_record "_submissions._tcp.$CHATMAIL_FULL_DNS_NAME" "465")"