accounts/scripts/install_opensmtpd_sendonly.sh
2025-11-01 18:10:46 +08:00

306 lines
9.3 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 运行"; }
# ------------------ 应用端配置 ------------------
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_send_email() {
local SMTP_HOST="${HOSTNAME:-smtp.svc.plus}"
local SMTP_PORT=587
local SMTP_USER="${EMAIL:-no-reply@svc.plus}"
local SMTP_PASS="${TMP_PASS:-$(grep -m1 "${EMAIL:-no-reply@svc.plus}" /etc/smtpd/auth 2>/dev/null | awk -F: '{print $2}' || echo '')}"
local TEST_TO="${1:-${EMAIL:-no-reply@svc.plus}}"
local SUBJECT="📨 SMTP Deliverability Test — $(date '+%Y-%m-%d %H:%M:%S')"
local BODY="✅ Automated deliverability test from ${SMTP_HOST}
Environment:
- HELO: $(hostname -f)
- Source IP: $(curl -s ifconfig.me 2>/dev/null || echo 'unknown')
- TLS: STARTTLS on ${SMTP_PORT}
- Auth: LOGIN (${SMTP_USER})
If you received this message intact, DKIM/DMARC/SPF validation succeeded."
echo
echo "🔍 Testing outbound mail via ${SMTP_HOST}:${SMTP_PORT}"
echo "-------------------------------------------------------------"
# 检测 swaks 是否支持 --hide-password
local SWAKS_HIDE=""
if swaks --help 2>&1 | grep -q -- '--hide-password'; then
SWAKS_HIDE="--hide-password"
fi
swaks --server "${SMTP_HOST}:${SMTP_PORT}" \
--tls --protocol ESMTP \
--auth LOGIN \
--auth-user "${SMTP_USER}" \
--auth-password "${SMTP_PASS}" \
--from "${SMTP_USER}" \
--to "${TEST_TO}" \
--header "From: XControl Mail System <${SMTP_USER}>" \
--header "Reply-To: ${SMTP_USER}" \
--header "Subject: ${SUBJECT}" \
--body "${BODY}" \
--timeout 15 ${SWAKS_HIDE} --quit-after "."
echo "-------------------------------------------------------------"
echo "✅ Check inbox (${TEST_TO}) for DKIM/SPF validation results."
}
# ------------------ 安装依赖 ------------------
ensure_packages(){
log "📦 安装 OpenSMTPD + OpenDKIM..."
apt update -qq
DEBIAN_FRONTEND=noninteractive apt install -y \
opensmtpd opendkim opendkim-tools dnsutils curl openssl swaks
}
# ------------------ 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 配置 (仅启用 587 / STARTTLS)..."
mkdir -p /etc/smtpd
cat >/etc/smtpd/smtpd.conf <<EOF
pki ${HOSTNAME} cert "${CERT}"
pki ${HOSTNAME} key "${KEY}"
listen on 0.0.0.0 port 587
listen on ::0 port 587
# 启用 TLS 支持STARTTLS
tls enable
pki ${HOSTNAME}
# 允许本机及 svc.plus 域发信
accept from local for any relay
accept from any for domain "${DOMAIN}" relay
EOF
# 若系统自带默认配置,先禁用
if [ -f /etc/smtpd.conf ]; then
log "⚙️ 检测到系统默认 /etc/smtpd.conf自动禁用..."
mv /etc/smtpd.conf /etc/smtpd.conf.disabled 2>/dev/null || true
fi
ln -sf /etc/smtpd/smtpd.conf /etc/smtpd.conf
# 语法校验
if ! smtpd -n -f /etc/smtpd/smtpd.conf > /tmp/smtpd_check.log 2>&1; then
log "⚠️ 配置语法检测失败:"
cat /tmp/smtpd_check.log
die "配置无效,已终止启动。"
fi
# 启动并启用服务
systemctl enable --now opensmtpd || die "❌ 启动 opensmtpd 失败"
sleep 1
# 若仍监听 25 端口则强制关闭
if ss -tlnp | grep -qE ':25\s'; then
log "🚫 检测到 25 端口仍在监听,强制关闭..."
fuser -k 25/tcp 2>/dev/null || true
systemctl restart opensmtpd
fi
# 再次确认状态
if ss -tlnp | grep -qE ':587\s'; then
log "✅ OpenSMTPD 6.8 已启用并监听 587 端口STARTTLS Send-Only 模式)"
else
die "❌ 端口 587 未成功监听请检查日志journalctl -xeu opensmtpd.service"
fi
}
# ------------------ 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 ;;
app_config) show_app_config ;;
*) echo "用法: $0 show {dns_record|app_config}" ;;
esac
;;
check)
case "${2:-}" in
self) check_self ;;
send_email) check_send_email ;;
*) echo "用法: $0 check {self|send_email}" ;;
esac
;;
uninstall|reset)
uninstall_reset
;;
help|--help|-h)
echo "用法: $0 {deploy|upgrade|show {dns_record|app_config}|check {self|send_email}|uninstall}"
;;
*)
echo "用法: $0 {deploy|upgrade|show {dns_record|app_config}|check {self|send_email}|uninstall}"
;;
esac