scripts: add install_exim_sendonly and install_opensmtpd_sendonly

This commit is contained in:
Haitao Pan 2025-11-01 12:15:29 +08:00
parent 45e006d3e0
commit 5a5e6e575f
3 changed files with 876 additions and 0 deletions

View File

@ -0,0 +1,209 @@
Exim Send-Only SMTP 一键脚本使用指南
环境假设:
系统Ubuntu Server (22.04 / 24.04)
IP52.196.108.28
域名smtp.svc.plus
邮件地址no-reply@svc.plus
证书:/etc/ssl/svc.plus.pem 与 /etc/ssl/svc.plus.key
🧱 一、首次部署deploy
sudo ./install_exim_sendonly.sh deploy
执行内容:
自动安装依赖包exim4, opendkim, sasl2-bin, mailutils, dnsutils
配置:
TLS 启用 /etc/ssl/svc.plus.pem / /etc/ssl/svc.plus.key
SASL 认证LOGIN / PLAIN
DKIM 签名(自动生成私钥)
启动服务exim4 + opendkim
输出结果:
🌐 推荐 DNS 配置模板:
----------------------------------------------------------
A smtp.svc.plus 52.196.108.28
MX svc.plus smtp.svc.plus.
SPF @ "v=spf1 a:smtp.svc.plus -all"
DKIM mail._domainkey "v=DKIM1; k=rsa; p=MIIBIjANBg..."
DMARC _dmarc "v=DMARC1; p=quarantine; rua=mailto:postmaster@svc.plus"
----------------------------------------------------------
📦 应用端 SMTP 配置YAML
----------------------------------------------------------
smtp:
host: "smtp.svc.plus"
port: 587
username: "no-reply@svc.plus"
p: "s"
from: "XControl Account <no-reply@svc.plus>"
replyTo: ""
timeout: 10s
tls:
mode: "auto"
insecureSkipVerify: false
----------------------------------------------------------
🔍 二、自检发信环境check self
sudo ./install_exim_sendonly.sh check self
输出示例:
🔍 自检 SPF / DKIM / DMARC / rDNS / HELO ...
SPF:
"v=spf1 a:smtp.svc.plus -all"
DKIM:
"v=DKIM1; k=rsa; p=MIIBIjANBg..."
DMARC:
"v=DMARC1; p=quarantine; rua=mailto:postmaster@svc.plus"
rDNS:
smtp.svc.plus.
HELO 检查:
250-smtp.svc.plus Hello test
250-AUTH PLAIN LOGIN
250 STARTTLS
🧭 若以上 5 项全部正常 → 代表发信链路可信,邮件不易进垃圾箱。
📧 三、发送测试邮件check send_email
sudo ./install_exim_sendonly.sh check send_email you@gmail.com
输出:
📧 发送测试邮件到 you@gmail.com...
✅ 邮件已发送,请检查收件箱或垃圾邮件文件夹。
查看日志: sudo tail -n 20 /var/log/exim4/mainlog
邮件标题为:
✅ Exim Send-Only + DKIM + SASL Test
若在 Gmail / Outlook 查看邮件头,能看到:
Authentication-Results: dkim=pass spf=pass dmarc=pass
♻️ 四、更新配置或证书upgrade
当你更新 /etc/ssl/svc.plus.pem 或修改 DKIM / 域名时:
sudo ./install_exim_sendonly.sh upgrade
脚本会:
自动刷新 update-exim4.conf
重启服务
输出当前 DKIM / YAML 配置
📜 五、查看配置信息show
sudo ./install_exim_sendonly.sh show dns_record
sudo ./install_exim_sendonly.sh show app_config
示例:
🌐 推荐 DNS 配置模板:
A smtp.svc.plus 52.196.108.28
MX svc.plus smtp.svc.plus.
SPF @ "v=spf1 a:smtp.svc.plus -all"
...
📦 应用端 SMTP 配置YAML
smtp:
host: "smtp.svc.plus"
port: 587
username: "no-reply@svc.plus"
p: "s"
from: "XControl Account <no-reply@svc.plus>"
🧹 六、安全卸载uninstall 或 reset
sudo ./install_exim_sendonly.sh uninstall
# 或:
sudo ./install_exim_sendonly.sh reset
脚本行为:
停止服务systemctl stop exim4 opendkim
删除:
/etc/exim4/
/etc/opendkim/
/var/spool/exim4/
/var/log/exim4/
/etc/email-addresses
/etc/default/opendkim
不删除 /etc/ssl/* 与现有证书
询问是否删除 DKIM 私钥目录:
是否删除 DKIM 私钥文件?(y/N): y
🔒 已删除 DKIM 密钥。
✅ 已彻底清理 Exim4 + DKIM + SASL 环境(证书未动,可重新 deploy
🚀 七、重新部署
清理后重新执行:
sudo ./install_exim_sendonly.sh deploy
会重新生成所有配置与 DKIM 密钥,
确保一个全新、干净、可验证的发信节点。
🧠 八、日志与调试
常用命令:
sudo tail -f /var/log/exim4/mainlog
sudo journalctl -u opendkim.service -b --no-pager | tail -n 20
sudo openssl s_client -connect smtp.svc.plus:587 -starttls smtp
检查 DKIM
sudo opendkim-testkey -d svc.plus -s mail -vvv
✅ 九、健康状态一览
项目 检查方式 期望结果
SPF dig TXT svc.plus v=spf1 a:smtp.svc.plus -all
DKIM dig TXT mail._domainkey.svc.plus v=DKIM1; k=rsa; p=...
DMARC dig TXT _dmarc.svc.plus v=DMARC1; p=quarantine; ...
rDNS dig -x 52.196.108.28 smtp.svc.plus.
HELO EHLO test 返回 250-smtp.svc.plus
端口 nc -zv smtp.svc.plus 587 open
日志 /var/log/exim4/mainlog <= / => 投递正常
🔒 十、总结
install_exim_sendonly.sh v3.1
是一个可重入、幂等、安全的 SMTP 部署器,特点:
单脚本管理全生命周期
DKIM + SPF + DMARC 三重防护
支持 SASL 登录 / STARTTLS
自动输出应用配置 YAML
保留系统证书、避免误删
提供交互式清理选项

View File

@ -0,0 +1,439 @@
#!/usr/bin/env bash
#
# install_exim_sendonly.sh v3.8-stable
# Exim4 + OpenDKIM + TLS + SASLsend-only
# --------------------------------------------------------
# 🧩 改进特性:
# ✅ 自动检测并强制启用 587submission端口
# ✅ OpenDKIM PID / Socket 稳定化
# ✅ 自有 SSL 证书优先,打印有效期
# ✅ DKIM 输出自动分行,兼容阿里云 / Cloudflare
# ✅ 保留完整 --help 提示
# --------------------------------------------------------
# Author: Pan Haitao @ svc.plus
#
set -euo pipefail
DOMAIN="svc.plus"
HOSTNAME="smtp.${DOMAIN}"
SERVER_IP="52.196.108.28"
EMAIL="no-reply@${DOMAIN}"
CERT="/etc/ssl/${DOMAIN}.pem"
KEY="/etc/ssl/${DOMAIN}.key"
DKIM_SELECTOR="mail"
DKIM_KEY_DIR="/etc/opendkim/keys/${DOMAIN}"
SASL_PASS_FILE="/etc/exim4/passwd"
TMP_PASS="$(openssl rand -base64 12)"
ACTION="${1:-help}"
TO_EMAIL="${2:-}"
log(){ echo -e "\033[1;36m$*\033[0m"; }
die(){ echo "$*"; exit 1; }
check_root(){ [ "$EUID" -eq 0 ] || die "请用 root 运行"; }
# ------------------ 安装依赖 ------------------
ensure_packages(){
log "📦 安装依赖..."
apt update -qq
DEBIAN_FRONTEND=noninteractive apt install -y \
exim4-daemon-heavy mailutils \
opendkim opendkim-tools \
sasl2-bin libsasl2-modules \
dnsutils curl openssl
}
# ------------------ SSL 检测 ------------------
verify_cert(){
if [[ -f "$CERT" && -f "$KEY" ]]; then
log "🔐 使用自有 SSL 证书:$CERT"
openssl x509 -noout -subject -dates -in "$CERT" || true
else
log "⚠️ 未检测到 /etc/ssl/${DOMAIN}.pem/key将使用默认自签名"
fi
}
# ------------------ Exim 主配置 ------------------
deploy_exim(){
verify_cert
log "🚀 写入 Debconf 主配置split 模式)..."
tee /etc/exim4/update-exim4.conf.conf >/dev/null <<EOF
dc_eximconfig_configtype='internet'
dc_other_hostnames='${HOSTNAME}'
dc_local_interfaces='0.0.0.0'
dc_readhost='${DOMAIN}'
dc_relay_domains=''
dc_minimaldns='false'
dc_relay_nets=''
dc_smarthost=''
CFILEMODE='644'
dc_use_split_config='true'
dc_hide_mailname='true'
dc_mailname_in_oh='true'
dc_localdelivery='mail_spool'
EOF
log "🧩 注入 MAIN_* 宏..."
mkdir -p /etc/exim4/conf.d/main
tee /etc/exim4/exim4.conf.localmacros >/dev/null <<EOF
MAIN_HARDCODE_PRIMARY_HOSTNAME = ${HOSTNAME}
MAIN_TLS_ENABLE = true
MAIN_TLS_CERTIFICATE = ${CERT}
MAIN_TLS_PRIVATEKEY = ${KEY}
MAIN_TLS_ADVERTISE_HOSTS = *
MAIN_DAEMON_SMTP_PORTS = 25 : 587
MAIN_TLS_ON_CONNECT_PORTS = 465
MAIN_FORCE_IPV4 = yes
MAIN_MILTER = inet:localhost:8891
MAIN_MILTER_COMMAND_TIMEOUT = 30s
MAIN_MILTER_CONNECT_TIMEOUT = 5s
MAIN_MILTER_MAIL_MACROS = i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen}
MAIN_MILTER_RCPT_MACROS = i {rcpt_addr}
EOF
}
inject_main_block(){
local F="/etc/exim4/conf.d/main/02_exim4-config_options"
log "🛠 注入 MAIN 宏展开片段..."
grep -q "MAIN_DAEMON_SMTP_PORTS" "$F" || cat <<'EOF' >> "$F"
# --- Added by install_exim_sendonly.sh (ports) ---
.ifdef MAIN_DAEMON_SMTP_PORTS
daemon_smtp_ports = MAIN_DAEMON_SMTP_PORTS
.endif
.ifdef MAIN_TLS_ON_CONNECT_PORTS
tls_on_connect_ports = MAIN_TLS_ON_CONNECT_PORTS
.endif
.ifdef MAIN_TLS_ADVERTISE_HOSTS
tls_advertise_hosts = MAIN_TLS_ADVERTISE_HOSTS
.endif
# --- End (ports) ---
EOF
grep -q "MAIN_MILTER" "$F" || cat <<'EOF' >> "$F"
# --- Added by install_exim_sendonly.sh (milter) ---
.ifdef MAIN_MILTER
milter = MAIN_MILTER
milter_command_timeout = MAIN_MILTER_COMMAND_TIMEOUT
milter_connect_timeout = MAIN_MILTER_CONNECT_TIMEOUT
milter_mail_macros = MAIN_MILTER_MAIL_MACROS
milter_rcpt_macros = MAIN_MILTER_RCPT_MACROS
.endif
# --- End (milter) ---
EOF
}
# ------------------ DKIM ------------------
deploy_dkim(){
log "🔏 配置 OpenDKIM..."
mkdir -p "${DKIM_KEY_DIR}"
cd "${DKIM_KEY_DIR}"
if [ ! -f "${DKIM_SELECTOR}.private" ]; then
opendkim-genkey -s "${DKIM_SELECTOR}" -d "${DOMAIN}"
chown opendkim:opendkim "${DKIM_SELECTOR}.private" "${DKIM_SELECTOR}.txt"
chmod 600 "${DKIM_SELECTOR}.private"
fi
tee /etc/opendkim.conf >/dev/null <<EOF
Syslog yes
UMask 002
Mode sv
Background yes
PidFile /run/opendkim/opendkim.pid
Canonicalization relaxed/simple
SubDomains no
KeyTable /etc/opendkim/key.table
SigningTable /etc/opendkim/signing.table
ExternalIgnoreList refile:/etc/opendkim/trusted.hosts
InternalHosts refile:/etc/opendkim/trusted.hosts
Socket inet:8891@localhost
UserID opendkim
EOF
tee /etc/opendkim/key.table >/dev/null <<EOF
${DKIM_SELECTOR}._domainkey.${DOMAIN} ${DOMAIN}:${DKIM_SELECTOR}:${DKIM_KEY_DIR}/${DKIM_SELECTOR}.private
EOF
tee /etc/opendkim/signing.table >/dev/null <<EOF
*@${DOMAIN} ${DKIM_SELECTOR}._domainkey.${DOMAIN}
EOF
tee /etc/opendkim/trusted.hosts >/dev/null <<EOF
127.0.0.1
localhost
${DOMAIN}
EOF
chown -R opendkim:opendkim /etc/opendkim
}
# ------------------ SASL ------------------
deploy_sasl(){
log "🔐 配置 SASL..."
local HASHED_PASS
HASHED_PASS="$(openssl passwd -apr1 "${TMP_PASS}")"
echo "${EMAIL}:${HASHED_PASS}" > "${SASL_PASS_FILE}"
chown root:Debian-exim "${SASL_PASS_FILE}"
chmod 640 "${SASL_PASS_FILE}"
}
# ------------------ 启用自管 AUTH 登录 ------------------
enable_auth_login() {
log "🧩 启用自管 AUTH LOGIN / PLAIN 模式..."
mkdir -p /etc/exim4/conf.d/auth
# 禁用系统自带的 PLAIN server避免命名冲突
if [ -f /etc/exim4/conf.d/auth/30_exim4-config_plain_server ]; then
mv /etc/exim4/conf.d/auth/30_exim4-config_plain_server \
/etc/exim4/conf.d/auth/30_exim4-config_plain_server.disabled 2>/dev/null || true
fi
# 写入自定义 authenticator
tee /etc/exim4/conf.d/auth/30_local_login >/dev/null <<'EOF'
# --- Added by install_exim_sendonly.sh (AUTH LOGIN/PLAIN) ---
local_plain:
driver = plaintext
public_name = LOCAL_PLAIN
server_condition = ${if crypteq{$auth2}{${extract{2}{:}{${lookup{$auth1}lsearch{/etc/exim4/passwd}{$value}{no}}}}}}{yes}{no}}
server_set_id = $auth1
local_login:
driver = plaintext
public_name = LOGIN
server_prompts = "Username:: : Password::"
server_condition = ${if crypteq{$auth2}{${extract{2}{:}{${lookup{$auth1}lsearch{/etc/exim4/passwd}{$value}{no}}}}}}{yes}{no}}
server_set_id = $auth1
# --- End (AUTH) ---
EOF
update-exim4.conf
systemctl restart exim4
if swaks --server 127.0.0.1:587 --quit-after EHLO 2>/dev/null | grep -q "AUTH"; then
log "✅ AUTH LOGIN 已启用"
else
log "⚠️ AUTH 未广播,请检查 /etc/exim4/conf.d/auth/30_local_login"
fi
}
# ------------------ 重启与 fallback ------------------
build_and_restart(){
log "⚙️ 生成配置并重启..."
systemctl daemon-reexec
systemctl daemon-reload
systemctl enable opendkim
systemctl restart opendkim
update-exim4.conf || true
systemctl restart exim4
# ---- 强制 fallback 修复 ----
sleep 1
if ! ss -tlnp | grep -E '(:587)\s' >/dev/null; then
log "⚠️ Exim 未监听 587强制追加 fallback..."
local F="/etc/exim4/conf.d/main/02_exim4-config_options"
cat <<'EOF' >> "$F"
# --- Fallback added by install_exim_sendonly.sh v3.8 ---
daemon_smtp_ports = 25 : 587
tls_on_connect_ports = 465
tls_advertise_hosts = *
# --- End fallback ---
EOF
update-exim4.conf || true
systemctl restart exim4
fi
if ss -tlnp | grep -E '(:25|:587)\s' >/dev/null; then
log "✅ Exim 已监听 25 与 587 端口"
else
log "❌ Exim 未监听 587请检查 /etc/exim4/conf.d/main/02_exim4-config_options"
fi
}
# ------------------ DNS 输出 ------------------
show_dns_record(){
log "🌐 生成最小可信 DNS 模板..."
local DKIM_TXT DKIM_PUB LINE LEN=255
if [[ -f "${DKIM_KEY_DIR}/${DKIM_SELECTOR}.txt" ]]; then
DKIM_TXT=$(tr -d '\n' < "${DKIM_KEY_DIR}/${DKIM_SELECTOR}.txt" | sed 's/"//g' | tr -d '\r')
DKIM_PUB=$(echo "${DKIM_TXT}" | sed -n 's/.*p=\(.*\)$/\1/p' | tr -d ' ')
else
DKIM_PUB="<DKIM 公钥未生成>"
fi
echo
echo "🌐 DNS 模板(兼容阿里云 / Cloudflare"
echo "----------------------------------------------------------"
echo "A smtp.${DOMAIN} ${SERVER_IP}"
echo "MX ${DOMAIN} smtp.${DOMAIN}."
echo "SPF @ \"v=spf1 a:smtp.${DOMAIN} -all\""
echo -n "DKIM ${DKIM_SELECTOR}._domainkey "
echo "\"v=DKIM1; k=rsa; p="
while [[ -n "$DKIM_PUB" ]]; do
LINE=${DKIM_PUB:0:$LEN}
DKIM_PUB=${DKIM_PUB:$LEN}
echo "\"${LINE}\""
done
echo "DMARC _dmarc \"v=DMARC1; p=none; rua=mailto:postmaster@${DOMAIN}\""
echo "rDNS (请让 ${SERVER_IP} 反查为 ${HOSTNAME})"
echo "HELO (EHLO/HELO 输出应为 ${HOSTNAME})"
echo "----------------------------------------------------------"
}
# ------------------ 应用端配置 ------------------
show_app_config(){
cat <<EOF
📦 应用端 SMTP 配置:
----------------------------------------------------------
smtp:
host: "${HOSTNAME}"
port: 587
username: "${EMAIL}"
password: "${TMP_PASS}"
from: "XControl Account <${EMAIL}>"
tls:
mode: "auto"
insecureSkipVerify: false
auth: "login"
----------------------------------------------------------
EOF
echo "首发密码(仅本次显示):${TMP_PASS}"
}
# ------------------ 自检 ------------------
check_self(){
log "🔍 自检 SPF / DKIM / DMARC / rDNS / HELO / 端口 ..."
echo
echo "SPF:"; dig +short TXT ${DOMAIN} | grep -i spf || echo "⚠️ 未配置 SPF"; echo
echo "DKIM:"; dig +short TXT ${DKIM_SELECTOR}._domainkey.${DOMAIN} || echo "⚠️ 未配置 DKIM"; echo
echo "DMARC:"; dig +short TXT _dmarc.${DOMAIN} || echo "⚠️ 未配置 DMARC"; echo
echo "rDNS:"; dig +short -x ${SERVER_IP} || echo "⚠️ 未配置反向解析"; echo
echo "端口监听:"; ss -tlnp | grep -E '(:25|:465|:587)\s' || echo "⚠️ SMTP 未监听全部端口"; echo
echo "OpenDKIM 8891"; ss -tlnp | grep ':8891 ' || echo "⚠️ 8891 未监听"; echo
echo "OpenDKIM testkey"; opendkim-testkey -d "${DOMAIN}" -s "${DKIM_SELECTOR}" -vvv || true
}
enforce_tls_certificate() {
log "🔒 强制加载自有 TLS 证书 (${CERT})..."
sed -i '/tls_certificate/d' /etc/exim4/conf.d/main/02_exim4-config_options 2>/dev/null || true
sed -i '/tls_privatekey/d' /etc/exim4/conf.d/main/02_exim4-config_options 2>/dev/null || true
echo "tls_certificate = ${CERT}" >> /etc/exim4/conf.d/main/02_exim4-config_options
echo "tls_privatekey = ${KEY}" >> /etc/exim4/conf.d/main/02_exim4-config_options
update-exim4.conf
systemctl restart exim4
log "✅ 已更新并加载 ${CERT} (${HOSTNAME})"
}
set_auth_credentials() {
local USERNAME="${1:-${EMAIL}}"
local PLAINTEXT="${2:-${TMP_PASS}}"
[ -n "$USERNAME" ] || die "USERNAME 不能为空"
[ -n "$PLAINTEXT" ] || die "PASSWORD 不能为空"
log "🔐 写入 AUTH 凭据(哈希)到 /etc/exim4/passwd ..."
# 使用 SHA-512 crypt 取代 MD5 apr1
local HASH
HASH="$(openssl passwd -6 "${PLAINTEXT}")"
printf '%s:x:%s\n' "${USERNAME}" "${HASH}" > /etc/exim4/passwd
chown root:Debian-exim /etc/exim4/passwd
chmod 640 /etc/exim4/passwd
# 自测哈希匹配
local PROBE
PROBE="$(exim -be "\${if crypteq{${PLAINTEXT}}{${HASH}}{yes}{no}}")" || PROBE="no"
if [ "$PROBE" = "yes" ]; then
log "✅ 密码哈希校验通过SHA-512 crypt"
else
die "❌ 密码哈希校验失败,请检查 openssl passwd 生成是否异常"
fi
update-exim4.conf
systemctl restart exim4
}
# ------------------ 卸载 ------------------
uninstall_reset(){
log "🧹 停止并清理(不碰 /etc/ssl/*..."
systemctl stop exim4 || true
systemctl stop opendkim || true
rm -rf /etc/exim4 /var/spool/exim4 /var/log/exim4
rm -rf /etc/opendkim /var/run/opendkim
apt purge -y exim4* opendkim* mailutils sasl2-bin || true
apt autoremove -y || true
log "✅ 已清理完成(证书未动)。"
}
# ------------------ 主流程 ------------------
check_root
case "${ACTION}" in
deploy)
ensure_packages
deploy_exim
inject_main_block
deploy_dkim
deploy_sasl
enforce_tls_certificate
set_auth_credentials
enable_auth_login
build_and_restart
show_dns_record
show_app_config
;;
upgrade)
log "⬆️ 更新配置并重启..."
deploy_exim
inject_main_block
deploy_dkim
deploy_sasl
enforce_tls_certificate
set_auth_credentials
enable_auth_login
build_and_restart
show_dns_record
show_app_config
;;
show)
case "${2:-}" in
dns_record) show_dns_record ;;
app_config) show_app_config ;;
*) echo "用法: $0 show {dns_record|app_config}" ;;
esac
;;
check)
case "${2:-}" in
self)
check_self
;;
send_email)
send_test_email() {
local TO="${3:-}"
[ -z "$TO" ] && die "请提供收件人邮箱"
log "📧 发送测试邮件到 ${TO}..."
echo "Test from ${EMAIL}" | mail -s "✅ Exim Send-Only + DKIM + TLS Test" "$TO"
echo
echo "📄 查看日志tail -n 30 /var/log/exim4/mainlog"
}
send_test_email "$@"
;;
*)
echo "用法: $0 check {self|send_email 收件人邮箱}"
;;
esac
;;
uninstall|reset)
uninstall_reset
;;
help|--help|-h)
echo "用法: $0 {deploy|upgrade|show dns_record|check self|uninstall}"
;;
*)
echo "用法: $0 {deploy|upgrade|show dns_record|check self|uninstall}"
;;
esac

View File

@ -0,0 +1,228 @@
#!/usr/bin/env bash
#
# install_opensmtpd_sendonly.sh v1.2
# OpenSMTPD + OpenDKIM + TLSSend-Only 模式)
# --------------------------------------------------------
# ✅ 自动部署轻量级 MTA监听 25/587 端口(免认证)
# ✅ 集成 DKIM 签名、SPF/DMARC/rDNS/HELO 校验模板
# ✅ 兼容阿里云 / Cloudflare DNS 输出格式
# ✅ 适配 OpenSMTPD ≥ 6.8Ubuntu 22.04+
# --------------------------------------------------------
# Author: Pan Haitao @ svc.plus
#
set -euo pipefail
DOMAIN="svc.plus"
HOSTNAME="smtp.${DOMAIN}"
SERVER_IP="52.196.108.28"
EMAIL="no-reply@${DOMAIN}"
CERT="/etc/ssl/${DOMAIN}.pem"
KEY="/etc/ssl/${DOMAIN}.key"
DKIM_SELECTOR="mail"
DKIM_KEY_DIR="/etc/opendkim/keys/${DOMAIN}"
TMP_PASS="$(openssl rand -base64 12)"
ACTION="${1:-help}"
log(){ echo -e "\033[1;36m$*\033[0m"; }
die(){ echo "$*"; exit 1; }
check_root(){ [ "$EUID" -eq 0 ] || die "请用 root 运行"; }
# ------------------ 安装依赖 ------------------
ensure_packages(){
log "📦 安装 OpenSMTPD + OpenDKIM..."
apt update -qq
DEBIAN_FRONTEND=noninteractive apt install -y \
opensmtpd opendkim opendkim-tools dnsutils curl openssl
}
# ------------------ SSL 证书检测 ------------------
verify_cert(){
if [[ -f "$CERT" && -f "$KEY" ]]; then
log "🔐 使用自有 SSL 证书:$CERT"
openssl x509 -noout -subject -dates -in "$CERT" || true
else
log "⚠️ 未检测到 ${CERT}/${KEY},生成自签证书..."
mkdir -p /etc/ssl
openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
-subj "/CN=${HOSTNAME}" -keyout "$KEY" -out "$CERT"
fi
}
# ------------------ DKIM ------------------
deploy_dkim(){
log "🔏 配置 OpenDKIM..."
mkdir -p "${DKIM_KEY_DIR}"
cd "${DKIM_KEY_DIR}"
if [ ! -f "${DKIM_SELECTOR}.private" ]; then
opendkim-genkey -s "${DKIM_SELECTOR}" -d "${DOMAIN}"
chown opendkim:opendkim "${DKIM_SELECTOR}.private" "${DKIM_SELECTOR}.txt"
chmod 600 "${DKIM_SELECTOR}.private"
fi
cat >/etc/opendkim.conf <<EOF
Syslog yes
UMask 002
Mode sv
Canonicalization relaxed/simple
SubDomains no
KeyTable /etc/opendkim/key.table
SigningTable /etc/opendkim/signing.table
ExternalIgnoreList refile:/etc/opendkim/trusted.hosts
InternalHosts refile:/etc/opendkim/trusted.hosts
Socket inet:8891@localhost
UserID opendkim
EOF
cat >/etc/opendkim/key.table <<EOF
${DKIM_SELECTOR}._domainkey.${DOMAIN} ${DOMAIN}:${DKIM_SELECTOR}:${DKIM_KEY_DIR}/${DKIM_SELECTOR}.private
EOF
cat >/etc/opendkim/signing.table <<EOF
*@${DOMAIN} ${DKIM_SELECTOR}._domainkey.${DOMAIN}
EOF
cat >/etc/opendkim/trusted.hosts <<EOF
127.0.0.1
localhost
${DOMAIN}
EOF
chown -R opendkim:opendkim /etc/opendkim
systemctl enable --now opendkim
}
# ------------------ OpenSMTPD ------------------
deploy_smtpd(){
verify_cert
log "🚀 写入 OpenSMTPD 配置..."
mkdir -p /etc/smtpd
cat >/etc/smtpd/smtpd.conf <<EOF
# /etc/smtpd/smtpd.conf - Send-only mode
pki ${HOSTNAME} cert "${CERT}"
pki ${HOSTNAME} key "${KEY}"
# 监听端口25/587均启用 TLS无需认证
listen on 0.0.0.0 port 25 tls pki ${HOSTNAME}
listen on 0.0.0.0 port 587 tls pki ${HOSTNAME}
# DKIM 过滤器:将外发邮件经 OpenDKIM 签名
filter "dkim" smtp-out chain "inet://127.0.0.1:8891"
table aliases file:/etc/aliases
# 动作定义
action "relay" mail-from any rcpt-to any filter "dkim" relay
# 匹配规则
match from any for domain "${DOMAIN}" action "relay"
smtp helo ${HOSTNAME}
EOF
systemctl enable --now opensmtpd || (
log "⚠️ 发现语法错误,尝试 fallback 简化配置..."
cat >/etc/smtpd/smtpd.conf <<EOF
pki ${HOSTNAME} cert "${CERT}"
pki ${HOSTNAME} key "${KEY}"
listen on 0.0.0.0 port 25 tls pki ${HOSTNAME}
listen on 0.0.0.0 port 587 tls pki ${HOSTNAME}
action "relay" relay
match from any for domain "${DOMAIN}" action "relay"
smtp helo ${HOSTNAME}
EOF
systemctl restart opensmtpd
)
log "✅ OpenSMTPD 已启用并监听 25/587 端口Send-Only 模式)"
}
# ------------------ DNS 模板 ------------------
show_dns_record(){
log "🌐 生成 DNS 模板SPF / DKIM / DMARC / rDNS / HELO..."
local DKIM_TXT DKIM_PUB LINE LEN=255
if [[ -f "${DKIM_KEY_DIR}/${DKIM_SELECTOR}.txt" ]]; then
DKIM_TXT=$(tr -d '\n' < "${DKIM_KEY_DIR}/${DKIM_SELECTOR}.txt" | sed 's/"//g')
DKIM_PUB=$(echo "${DKIM_TXT}" | sed -n 's/.*p=\(.*\)$/\1/p' | tr -d ' ')
else
DKIM_PUB="<DKIM 公钥未生成>"
fi
echo "----------------------------------------------------------"
echo "A smtp.${DOMAIN} ${SERVER_IP}"
echo "MX ${DOMAIN} smtp.${DOMAIN}."
echo "SPF @ \"v=spf1 a:smtp.${DOMAIN} -all\""
echo -n "DKIM ${DKIM_SELECTOR}._domainkey "
echo "\"v=DKIM1; k=rsa; p="
while [[ -n "$DKIM_PUB" ]]; do
LINE=${DKIM_PUB:0:$LEN}
DKIM_PUB=${DKIM_PUB:$LEN}
echo "\"${LINE}\""
done
echo "DMARC _dmarc \"v=DMARC1; p=none; rua=mailto:postmaster@${DOMAIN}\""
echo "rDNS (请让 ${SERVER_IP} 反查为 ${HOSTNAME})"
echo "HELO (EHLO 输出应为 ${HOSTNAME})"
echo "----------------------------------------------------------"
}
# ------------------ 自检 ------------------
check_self(){
log "🔍 自检 SPF / DKIM / DMARC / rDNS / 端口 ..."
echo
echo "SPF:"; dig +short TXT ${DOMAIN} | grep -i spf || echo "⚠️ 未配置 SPF"; echo
echo "DKIM:"; dig +short TXT ${DKIM_SELECTOR}._domainkey.${DOMAIN} || echo "⚠️ 未配置 DKIM"; echo
echo "DMARC:"; dig +short TXT _dmarc.${DOMAIN} || echo "⚠️ 未配置 DMARC"; echo
echo "rDNS:"; dig +short -x ${SERVER_IP} || echo "⚠️ 未配置反向解析"; echo
echo "端口监听:"; ss -tlnp | grep -E '(:25|:587|:8891)\s' || echo "⚠️ 端口未监听"; echo
echo "OpenDKIM testkey"; opendkim-testkey -d "${DOMAIN}" -s "${DKIM_SELECTOR}" -vvv || true
}
# ------------------ 卸载 ------------------
uninstall_reset(){
log "🧹 停止并清理..."
systemctl stop opensmtpd || true
systemctl stop opendkim || true
apt purge -y opensmtpd opendkim opendkim-tools || true
apt autoremove -y || true
rm -rf /etc/smtpd /etc/opendkim /var/log/mail*
log "✅ 已清理完成(证书未动)。"
}
# ------------------ 主流程 ------------------
check_root
case "${ACTION}" in
deploy)
ensure_packages
deploy_dkim
deploy_smtpd
show_dns_record
;;
upgrade)
log "⬆️ 更新配置并重启..."
deploy_dkim
deploy_smtpd
show_dns_record
;;
show)
case "${2:-}" in
dns_record) show_dns_record ;;
*) echo "用法: $0 show dns_record" ;;
esac
;;
check)
case "${2:-}" in
self) check_self ;;
*) echo "用法: $0 check self" ;;
esac
;;
uninstall|reset)
uninstall_reset
;;
help|--help|-h)
echo "用法: $0 {deploy|upgrade|show dns_record|check self|uninstall}"
;;
*)
echo "用法: $0 {deploy|upgrade|show dns_record|check self|uninstall}"
;;
esac