- 新增 internal/outbound 模块:MX 查询、SMTP 出站客户端(EHLO/STARTTLS/ MAIL/RCPT/DATA/QUIT)、4xx 临时失败与 5xx 永久失败分类、8BITMIME 支持 - 新增 outbound_messages 队列表与 OutboundStore,后台 worker 指数退避重试 - 永久失败/超限退信到发件人收件箱,包含原因与目标收件人 - 外发邮件使用域名 DKIM 私钥签名(go-msgauth) - SMTP 提交集成:认证用户可发外部收件人,MAIL FROM 必须等于登录用户邮箱, 未认证外部投递明确拒绝(防开放中继) - Web 发信集成:外部收件人自动入队,附件以 multipart/mixed + base64 编码 加入邮件正文 - 每用户每分钟/每日发送限速(max_per_day=0 可禁用外部投递) - 管理后台新增外发队列页面:状态统计、失败原因、手动重试/取消 - 新增 [outbound] 配置段并更新 README / todo.md
51 lines
1.3 KiB
Go
51 lines
1.3 KiB
Go
package outbound
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
dkimgen "mail_go/internal/dkim"
|
|
|
|
msgauthdkim "github.com/emersion/go-msgauth/dkim"
|
|
)
|
|
|
|
func TestSignDKIMAndVerify(t *testing.T) {
|
|
privPEM, pubPEM, err := dkimgen.GenerateKeyPair()
|
|
if err != nil {
|
|
t.Fatalf("GenerateKeyPair: %v", err)
|
|
}
|
|
_ = pubPEM
|
|
|
|
raw := []byte("From: kevin@lmve.net\r\nTo: someone@example.com\r\nSubject: hello\r\n\r\nbody\r\n")
|
|
|
|
signed, err := SignDKIM(raw, "lmve.net", "default", privPEM)
|
|
if err != nil {
|
|
t.Fatalf("SignDKIM: %v", err)
|
|
}
|
|
if !strings.Contains(string(signed), "DKIM-Signature") {
|
|
t.Fatalf("signed message missing DKIM-Signature header")
|
|
}
|
|
|
|
verifications, err := msgauthdkim.Verify(strings.NewReader(string(signed)))
|
|
if err != nil {
|
|
t.Fatalf("dkim.Verify: %v", err)
|
|
}
|
|
if len(verifications) != 1 {
|
|
t.Fatalf("expected 1 signature, got %d", len(verifications))
|
|
}
|
|
if verifications[0].Domain != "lmve.net" {
|
|
t.Fatalf("unexpected signature: %+v", verifications[0])
|
|
}
|
|
}
|
|
|
|
func TestSignDKIMEmptyKeyReturnsUnsigned(t *testing.T) {
|
|
raw := []byte("From: kevin@lmve.net\r\nTo: someone@example.com\r\nSubject: hello\r\n\r\nbody\r\n")
|
|
out, err := SignDKIM(raw, "lmve.net", "default", "")
|
|
if err != nil {
|
|
t.Fatalf("SignDKIM with empty key should not fail: %v", err)
|
|
}
|
|
if string(out) != string(raw) {
|
|
t.Fatalf("message changed despite empty key")
|
|
}
|
|
}
|