fix(submission): 调整 SMTP 提交与发件授权逻辑。

- 启用 SMTP submission 时改为强制使用可读证书文件,并禁止使用容器自带测试证书。
- 收紧发件人校验,拒绝未授权的 From 地址,并支持别名多目标授权。
- 优化发送队列重入与重复消息处理,避免重复记录冲突。
- 让提交证书按需热加载,并稳定 MIME 头序列化顺序。
- 更新部署示例与文档,明确 submission 的证书配置要求。
This commit is contained in:
LanQin_
2026-06-24 13:10:24 +08:00
parent 2476fe0c19
commit d1876691dd
10 changed files with 345 additions and 75 deletions
+4 -4
View File
@@ -28,7 +28,7 @@ LANQIN_PUBLIC_BASE_URL=https://mail.example.com
# 邮件客户端 TLS 证书。用于 SMTP 465/587、IMAP 993、POP3 995。
# 生产环境请挂载真实证书,并确保域名包含 LANQIN_PUBLIC_HOSTNAME。
# 留空时会使用容器自带 localhost 自签证书,第三方客户端会提示证书不匹配
# 留空时 Dovecot/Postfix 会使用容器自带 localhost 自签证书LanQin API 的 SMTP submission 不会启用
LANQIN_TLS_CERT_FILE=
LANQIN_TLS_KEY_FILE=
@@ -97,9 +97,9 @@ LANQIN_SMTP_PASSWORD=
# 外部 SMTP 要求 STARTTLS / TLS 时改 true;本机 Postfix 默认 false。
LANQIN_SMTP_REQUIRE_TLS=false
# 第三方客户端 SMTP 提交,由 LanQin API 监听 587/465。
LANQIN_SUBMISSION_ADDR=:587
LANQIN_SUBMISSION_TLS_ADDR=:465
# 第三方客户端 SMTP 提交,由 LanQin API 监听 587/465;启用前必须配置可读 TLS 证书
LANQIN_SUBMISSION_ADDR=
LANQIN_SUBMISSION_TLS_ADDR=
LANQIN_SUBMISSION_MAX_MESSAGE_MB=35
# =========================
+3 -1
View File
@@ -134,13 +134,15 @@ docker compose -f docker-compose.stack.yml -f docker-compose.stack.build.yml up
## 邮件客户端 TLS 证书
Web 站点可以由宿主机 Nginx / 宝塔反代到容器 `80`,但 SMTP/IMAP/POP3 端口不会使用 Web 反代的证书。
如果第三方客户端连接 `465/587/993/995` 时提示证书是 `localhost`,说明 LanQin API 或 Dovecot 仍在使用容器自带的测试证书。
如果第三方客户端连接 `993/995` 时提示证书是 `localhost`,说明 Dovecot 仍在使用容器自带的测试证书。LanQin API 的 SMTP `465/587` submission 不会使用自签测试证书;启用前必须配置可读的真实证书。
生产环境请把域名证书挂载进容器,并在 `.env` 指向证书文件:
```env
LANQIN_TLS_CERT_FILE=/certs/fullchain.pem
LANQIN_TLS_KEY_FILE=/certs/privkey.pem
LANQIN_SUBMISSION_ADDR=:587
LANQIN_SUBMISSION_TLS_ADDR=:465
```
单容器示例:
+10 -2
View File
@@ -7,8 +7,8 @@ set -eu
: "${LANQIN_ADDR:=127.0.0.1:8080}"
: "${LANQIN_SMTP_HOST:=127.0.0.1}"
: "${LANQIN_SMTP_PORT:=25}"
: "${LANQIN_SUBMISSION_ADDR:=:587}"
: "${LANQIN_SUBMISSION_TLS_ADDR:=:465}"
: "${LANQIN_SUBMISSION_ADDR:=}"
: "${LANQIN_SUBMISSION_TLS_ADDR:=}"
: "${LANQIN_SUBMISSION_MAX_MESSAGE_MB:=35}"
: "${LANQIN_MAILDIR_ROOT:=/var/mail/vhosts}"
: "${LANQIN_TLS_CERT_FILE:=}"
@@ -40,10 +40,18 @@ if [ -n "$LANQIN_TLS_CERT_FILE" ] || [ -n "$LANQIN_TLS_KEY_FILE" ]; then
if [ -f "$LANQIN_TLS_CERT_FILE" ] && [ -f "$LANQIN_TLS_KEY_FILE" ]; then
TLS_CERT="$LANQIN_TLS_CERT_FILE"
TLS_KEY="$LANQIN_TLS_KEY_FILE"
: "${LANQIN_SUBMISSION_ADDR:=:587}"
: "${LANQIN_SUBMISSION_TLS_ADDR:=:465}"
else
echo "warning: LANQIN_TLS_CERT_FILE/LANQIN_TLS_KEY_FILE not readable; using snakeoil localhost certificate" >&2
fi
fi
if [ -n "$LANQIN_SUBMISSION_ADDR$LANQIN_SUBMISSION_TLS_ADDR" ] && { [ "$TLS_CERT" = "/etc/ssl/certs/ssl-cert-snakeoil.pem" ] || [ "$TLS_KEY" = "/etc/ssl/private/ssl-cert-snakeoil.key" ]; }; then
echo "warning: SMTP submission disabled because LANQIN_TLS_CERT_FILE/LANQIN_TLS_KEY_FILE are not configured with readable certificate files" >&2
LANQIN_SUBMISSION_ADDR=""
LANQIN_SUBMISSION_TLS_ADDR=""
fi
export LANQIN_SUBMISSION_ADDR LANQIN_SUBMISSION_TLS_ADDR
postconf -e "myhostname = ${LANQIN_PUBLIC_HOSTNAME}"
postconf -e "myorigin = ${LANQIN_PUBLIC_HOSTNAME}"
+2
View File
@@ -5,6 +5,8 @@ services:
environment:
LANQIN_SMTP_HOST: ${LANQIN_STACK_SMTP_HOST:-postfix}
LANQIN_SMTP_PORT: ${LANQIN_STACK_SMTP_PORT:-25}
LANQIN_SUBMISSION_ADDR: ${LANQIN_SUBMISSION_ADDR:-}
LANQIN_SUBMISSION_TLS_ADDR: ${LANQIN_SUBMISSION_TLS_ADDR:-}
volumes:
- ./data:/data
- ./mail:/var/mail/vhosts:ro