# ============================================================================
# SPF 记录模板
#
# 用法：把下面「选一个」的那一段原样填进你的 DNS。
#       TXT 记录名 = 你的域（或子域），TTL 300（改期先降到 300，稳定后再调高）。
#       一个域只能有一条 v=spf1 记录 —— 多一条 = 全部作废。
#
# 硬配额（超了就 permerror，不是「效果打折」）：
#   · DNS 查询 ≤ 10 次  （include / a / mx / ptr / exists / redirect 各计 1，递归累加）
#   · void 查询 ≤ 2 次  （NXDOMAIN 或空答案）
#   · ip4 / ip6 / all / exp 计 0 次
# ============================================================================


# ---------------------------------------------------------------------------
# 场景 A：只有一台自己的服务器在发信（最简单，也最安全）
# ---------------------------------------------------------------------------
v=spf1 ip4:203.0.113.10 ip4:203.0.113.11 -all
#
# 判据：这两个 IP 就是你服务器的出口 IP，没有别的发信源。
# 收紧到 -all 之前必须确认这一条 —— 见文末检查表。


# ---------------------------------------------------------------------------
# 场景 B：全部走第三方 ESP（自己没有发信 IP）
# ---------------------------------------------------------------------------
v=spf1 include:_spf.esp.example -all
#
# 注意：include 里的 all 会被忽略，只取其中的 ip4/a/mx 机制是否匹配。
#       所以 include 不能用来「把别人的策略搬过来」。


# ---------------------------------------------------------------------------
# 场景 C：混合（自建 + 两个 ESP + 一个 SaaS 验证邮件）
# ---------------------------------------------------------------------------
v=spf1
  ip4:203.0.113.10
  include:_spf.esp-one.example
  include:_spf.esp-two.example
  include:spf.saas-verification.example
  -all
#
# 实际写 DNS 时是一整行（上面的换行只是为了看清结构）：
#   v=spf1 ip4:203.0.113.10 include:_spf.esp-one.example include:_spf.esp-two.example include:spf.saas-verification.example -all
#
# 这一条已经可能有 8~10 次查询了。加任何一个 include 之前先跑：
#   python scripts/auth_audit.py --domain your.com
# 看「SPF 查询次数 x/10」。


# ---------------------------------------------------------------------------
# 场景 D：观测期（还不知道全部发信源，先软失败）
# ---------------------------------------------------------------------------
v=spf1 ip4:203.0.113.10 include:_spf.esp.example ~all
#
# ~all（软失败）= 不在列表里的发信源「标记为可疑，但通常仍然收下」。
# 这是收集清单的正确姿势：既能拿到聚合报告里的失败源，又不会当场丢信。
# 拿到 30 天完整清单之后，再考虑切 -all（见文末检查表）。


# ---------------------------------------------------------------------------
# 场景 E：这个域根本不发信（只收信）
# ---------------------------------------------------------------------------
v=spf1 -all
#
# 明确声明「本域不授权任何 IP 发信」。这是最强的一种，也是最少人配的一种。
# 适用于：纯收信的企业域、parked 域、只做品牌保护的域。
# 配合 DMARC p=reject 效果最好 —— 任何冒用都会当场被拒。


# ---------------------------------------------------------------------------
# 场景 F：把发信集中到一个子域（推荐给营销邮件）
# ---------------------------------------------------------------------------
# 主域：          v=spf1 -all
# mail.your.com： v=spf1 include:_spf.esp.example -all
# _dmarc.mail.your.com： v=DMARC1; p=reject; ...
#
# 好处：营销邮件的声誉风险隔离在子域，主域（发验证码的）不受牵连。
# 代价：多一套记录要维护，而且子域必须有自己的一套 DKIM。


# ============================================================================
# ❌ 常见错误写法（每一条都有人真的这么配过）
# ============================================================================

# ❌ 两条记录并存 —— 全部作废
#   v=spf1 ip4:203.0.113.10 ~all
#   v=spf1 include:_spf.esp.example ~all

# ❌ 裸 all = +all = 放行全世界
#   v=spf1 ip4:203.0.113.10 all

# ❌ 保留 ptr
#   v=spf1 ptr ip4:203.0.113.10 -all
#   RFC 7208 §5.5 明确 SHOULD NOT be published：慢、不可靠、占 1 次配额，
#   而且不会让本来该失败的验证通过。

# ❌ CIDR 写反了位置
#   v=spf1 ip4/24:203.0.113.0 -all     ← 错
#   v=spf1 ip4:203.0.113.0/24 -all     ← 对
#   v=spf1 a/24 -all                   ← CIDR 贴在机制名后面，这是对的

# ❌ redirect 与 all 都写，以为会叠加
#   v=spf1 redirect=_spf.example.net ~all
#   redirect 只在没有任何机制匹配时才生效，且结果原样采用，
#   后面那个 ~all 实际上永远用不到。

# ❌ 指向一个已经没有 SPF 记录的域（吃 void 配额）
#   v=spf1 include:_spf.半年前跑路的服务商.com -all


# ============================================================================
# `-all` 收紧前置检查表
#
# 从 ~all 改到 -all 会让所有「没被授权」的发信源立刻被拒。
# 下面每一条都打勾之前不要改：
# ============================================================================

# [ ] 聚合报告（rua）已连续收到 30 天以上，且全部 source_ip 都认得出来
# [ ] 每个不认识的 IP 都查过 RDAP：是自己的云账号 → 加进 SPF；
#     完全陌生 → 按冒用处理（别加！）
# [ ] 每个认领的源都用 --send-ip 验证过
# [ ] 交易类邮件（验证码 / 订单 / 告警）单独挑一台机器实测投递到
#     Gmail + Outlook + QQ 邮箱，三处都进收件箱
# [ ] 确认没有 ptr、没有裸 all、没有 +all
# [ ] 确认记录只有一条
# [ ] 原记录原文已保存（回滚要用的就是它）
# [ ] TTL 已降到 300（回滚时间 = TTL，3600 意味着改错要等一小时）

# 收尾验证：
#   dig +short TXT your.com | tr -d '"' | grep -c '^v=spf1'      # 必须是 1
#   python scripts/auth_audit.py --domain your.com --send-ip <每个出口IP>


# ============================================================================
# RFC 9989 迁移备注（SPF 本身没变，但相关判据变了）
# ============================================================================
#
# · SPF 对齐只认 MAIL FROM —— HELO 回退在 DMARC 层被删除了。
#   所以 HELO 域和 MAIL FROM 域不一致时，SPF pass 也可能不算对齐。
#
# · 单条 TXT 建议控制在 450 字符以内（超过会被拆成多段，且部分老设备处理不好）。
#
# · exp= 不消耗查询配额，但会把「为什么被拒」的解释文本放在一个 TXT 里，
#   实际使用者极少。除非你有明确需求，否则不要加。
