From 9b19268c00977426e0859c394caf11961bcab4d6 Mon Sep 17 00:00:00 2001 From: smail Date: Mon, 31 Aug 2026 00:34:30 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E6=94=B6=E6=95=9B=E9=82=AE?= =?UTF-8?q?=E4=BB=B6=E7=AD=96=E7=95=A5=E8=AF=BB=E5=8F=96=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E6=97=B6=E5=BF=AB=E7=85=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/config/runtime_access.go | 42 +++++++++++------ server/config/runtime_access_test.go | 46 +++++++++++++++++++ server/controllers/email/send.go | 14 +++--- server/controllers/user.go | 9 ++-- .../listen/smtp_server/audit_persistence.go | 23 +++++----- server/services/rule/rule.go | 9 ++-- 6 files changed, 99 insertions(+), 44 deletions(-) diff --git a/server/config/runtime_access.go b/server/config/runtime_access.go index 2a6e1b1..81593b1 100644 --- a/server/config/runtime_access.go +++ b/server/config/runtime_access.go @@ -161,6 +161,27 @@ func CurrentMailDomains() MailDomains { return domains } +func CurrentEmailPolicy() EmailConfig { + manager := CurrentManager() + if manager != nil { + runtime := manager.Snapshot() + if runtime.Generation != 0 { + return runtime.Email.Policy + } + } + + cfg := CurrentConfig() + if cfg == nil { + return EmailConfig{} + } + return EmailConfig{ + SMTPPort: cfg.SMTPPort, + SpamFilterLevel: cfg.SpamFilterLevel, + LoginVerificationPolicy: cfg.LoginPolicy, + ExternalRecoveryEnabled: cfg.ExternalRecoveryEnabled, + } +} + type SMTPRuntimeSettings struct { Relay SMTPConfig Password string @@ -213,22 +234,15 @@ func CurrentSMTPRuntimeSettings() (SMTPRuntimeSettings, error) { func CurrentMailPorts() MailPorts { ports := MailPorts{SMTP: 25, IMAP: 143, POP3: 110} - manager := CurrentManager() - if manager == nil { - return ports + policy := CurrentEmailPolicy() + if policy.SMTPPort > 0 && policy.SMTPPort <= 65535 { + ports.SMTP = policy.SMTPPort } - runtime := manager.Snapshot() - if runtime.Generation == 0 { - return ports + if policy.IMAPPort > 0 && policy.IMAPPort <= 65535 { + ports.IMAP = policy.IMAPPort } - if runtime.Email.Policy.SMTPPort > 0 && runtime.Email.Policy.SMTPPort <= 65535 { - ports.SMTP = runtime.Email.Policy.SMTPPort - } - if runtime.Email.Policy.IMAPPort > 0 && runtime.Email.Policy.IMAPPort <= 65535 { - ports.IMAP = runtime.Email.Policy.IMAPPort - } - if runtime.Email.Policy.POP3Port > 0 && runtime.Email.Policy.POP3Port <= 65535 { - ports.POP3 = runtime.Email.Policy.POP3Port + if policy.POP3Port > 0 && policy.POP3Port <= 65535 { + ports.POP3 = policy.POP3Port } return ports } diff --git a/server/config/runtime_access_test.go b/server/config/runtime_access_test.go index 8cff24b..0d6308b 100644 --- a/server/config/runtime_access_test.go +++ b/server/config/runtime_access_test.go @@ -75,6 +75,52 @@ func TestCurrentMailPortsUsesSafeDefaultsWithoutRuntimeManager(t *testing.T) { } } +func TestCurrentEmailPolicyReadsPartitionSnapshot(t *testing.T) { + previousManager := CurrentManager() + defer SetRuntimeManager(previousManager) + + runtime := validRuntimeConfig() + runtime.Email.Policy = EmailConfig{ + SMTPPort: 2525, + IMAPPort: 1143, + POP3Port: 1110, + MaxMessageBytes: 8 << 20, + SpamFilterLevel: 2, + LoginVerificationPolicy: "new_device", + ExternalRecoveryEnabled: true, + } + SetRuntimeManager(&ConfigManager{snapshots: runtimeAccessSnapshotStore{runtime: runtime}}) + + got := CurrentEmailPolicy() + if got != runtime.Email.Policy { + t.Fatalf("email policy = %+v, want partition snapshot", got) + } +} + +func TestCurrentEmailPolicyFallsBackToLegacyConfig(t *testing.T) { + previousManager := CurrentManager() + previousInstance := Instance + defer func() { + SetRuntimeManager(previousManager) + Instance = previousInstance + PublishConfigSnapshot(previousInstance) + }() + SetRuntimeManager(nil) + + Instance = &Config{ + SMTPPort: 2525, + SpamFilterLevel: 2, + LoginPolicy: "new_device", + ExternalRecoveryEnabled: true, + } + PublishConfigSnapshot(Instance) + + got := CurrentEmailPolicy() + if got.SMTPPort != 2525 || got.IMAPPort != 0 || got.POP3Port != 0 || got.MaxMessageBytes != 0 || got.SpamFilterLevel != 2 || got.LoginVerificationPolicy != "new_device" || !got.ExternalRecoveryEnabled { + t.Fatalf("legacy email policy = %+v, want mapped fallback", got) + } +} + func TestCurrentMailPortsReadsEmailPolicySnapshot(t *testing.T) { previousManager := CurrentManager() defer SetRuntimeManager(previousManager) diff --git a/server/controllers/email/send.go b/server/controllers/email/send.go index 0f8b5bd..0d57dc9 100644 --- a/server/controllers/email/send.go +++ b/server/controllers/email/send.go @@ -60,8 +60,8 @@ type attachment struct { } func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) { - cfg := config.CurrentConfig() - if cfg == nil || cfg.Domain == "" || len(cfg.Domains) == 0 { + domains := config.CurrentMailDomains() + if domains.Primary == "" || len(domains.Accepted) == 0 { response.NewErrorResponse(response.ServerError, "服务配置不可用", "").FPrint(w) return } @@ -73,17 +73,17 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) { } log.WithContext(ctx).Infof("发送邮件") - if reqData.From.Email != "" && !validation.IsAuthorizedSender(reqData.From.Email, ctx.UserAccount, ctx.IsAdmin, cfg.Domains) { + if reqData.From.Email != "" && !validation.IsAuthorizedSender(reqData.From.Email, ctx.UserAccount, ctx.IsAdmin, domains.Accepted) { response.NewErrorResponse(response.ParamsError, "\u53d1\u4ef6\u4eba\u5730\u5740\u4e0d\u5141\u8bb8", "").FPrint(w) return } - if reqData.Sender.Email != "" && !validation.IsAuthorizedSender(reqData.Sender.Email, ctx.UserAccount, ctx.IsAdmin, cfg.Domains) { + if reqData.Sender.Email != "" && !validation.IsAuthorizedSender(reqData.Sender.Email, ctx.UserAccount, ctx.IsAdmin, domains.Accepted) { response.NewErrorResponse(response.ParamsError, "\u53d1\u9001\u8005\u5730\u5740\u4e0d\u5141\u8bb8", "").FPrint(w) return } if reqData.From.Email == "" { - reqData.From.Email = ctx.UserAccount + "@" + cfg.Domain + reqData.From.Email = ctx.UserAccount + "@" + domains.Primary } if reqData.From.Email == "" { @@ -192,7 +192,7 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) { } log.WithContext(ctx).Debugf("插件执行--SendBefore End") - if e.From == nil || e.Sender == nil || !validation.IsAuthorizedSender(e.From.EmailAddress, ctx.UserAccount, ctx.IsAdmin, cfg.Domains) || !validation.IsAuthorizedSender(e.Sender.EmailAddress, ctx.UserAccount, ctx.IsAdmin, cfg.Domains) { + if e.From == nil || e.Sender == nil || !validation.IsAuthorizedSender(e.From.EmailAddress, ctx.UserAccount, ctx.IsAdmin, domains.Accepted) || !validation.IsAuthorizedSender(e.Sender.EmailAddress, ctx.UserAccount, ctx.IsAdmin, domains.Accepted) { response.NewErrorResponse(response.ParamsError, "\u63d2\u4ef6\u4fee\u6539\u4e86\u672a\u6388\u6743\u7684\u53d1\u4ef6\u4eba", "").FPrint(w) return } @@ -208,7 +208,7 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) { return } - msgID, err := parsemail.GenerateMsgID(cfg.Domain) + msgID, err := parsemail.GenerateMsgID(domains.Primary) if err != nil { log.WithContext(ctx).WithError(err).Error("generate outgoing message ID failed") response.NewErrorResponse(response.ServerError, i18n.GetText(ctx.Lang, "send_fail"), "").FPrint(w) diff --git a/server/controllers/user.go b/server/controllers/user.go index f67f93c..bb6fae5 100644 --- a/server/controllers/user.go +++ b/server/controllers/user.go @@ -139,14 +139,13 @@ func UserList(ctx *context.Context, w http.ResponseWriter, req *http.Request) { } func Info(ctx *context.Context, w http.ResponseWriter, req *http.Request) { - cfg := config.CurrentConfig() - if cfg == nil || cfg.Domain == "" { + mailDomains := config.CurrentMailDomains() + if mailDomains.Primary == "" { response.NewErrorResponse(response.ServerError, "服务配置不可用", "").FPrint(w) return } - domains := cfg.Domains - domains = array.Difference(domains, []string{cfg.Domain}) - domains = append([]string{cfg.Domain}, domains...) + domains := array.Difference(mailDomains.Accepted, []string{mailDomains.Primary}) + domains = append([]string{mailDomains.Primary}, domains...) result := map[string]any{ "account": ctx.UserAccount, "name": ctx.UserName, diff --git a/server/listen/smtp_server/audit_persistence.go b/server/listen/smtp_server/audit_persistence.go index e2f7973..36ccf83 100644 --- a/server/listen/smtp_server/audit_persistence.go +++ b/server/listen/smtp_server/audit_persistence.go @@ -140,14 +140,16 @@ func persistInitialAudit(ctx *context.Context, modelEmail models.Email, userIDs } func resolveIncomingUsers(ctx *context.Context, email *parsemail.Email, emailType int, reallyTo []string, SPFStatus, dkimStatus bool) ([]*models.User, bool, error) { - cfg := config.CurrentConfig() - if cfg == nil { - return nil, false, stderrors.New("runtime configuration is unavailable") - } if emailType != int(consts.EmailTypeReceive) { return nil, false, nil } + policy := config.CurrentEmailPolicy() + domains := config.CurrentMailDomains() + if len(domains.Accepted) == 0 { + return nil, false, stderrors.New("runtime configuration is unavailable") + } + accounts := incomingAccounts(email, reallyTo) var users []*models.User if len(accounts) > 0 { @@ -164,9 +166,9 @@ func resolveIncomingUsers(ctx *context.Context, email *parsemail.Email, emailTyp } if DropUnknownRecipientEmails && - ((cfg.SpamFilterLevel == 1 && !SPFStatus && !dkimStatus) || - (cfg.SpamFilterLevel == 2 && !SPFStatus) || - (cfg.SpamFilterLevel == 3 && !dkimStatus)) { + ((policy.SpamFilterLevel == 1 && !SPFStatus && !dkimStatus) || + (policy.SpamFilterLevel == 2 && !SPFStatus) || + (policy.SpamFilterLevel == 3 && !dkimStatus)) { log.WithContext(ctx).Infoln("垃圾邮件,拒信") log.WithContext(ctx).Infof("收件人不存在且DKIM验证失败,丢弃邮件: recipient_count=%d", len(accounts)) return nil, true, nil @@ -183,10 +185,7 @@ func resolveIncomingUsers(ctx *context.Context, email *parsemail.Email, emailTyp } func incomingAccounts(email *parsemail.Email, reallyTo []string) []string { - cfg := config.CurrentConfig() - if cfg == nil { - return nil - } + domains := config.CurrentMailDomains() var accounts []string if len(reallyTo) > 0 { for _, recipient := range reallyTo { @@ -195,7 +194,7 @@ func incomingAccounts(email *parsemail.Email, reallyTo []string) []string { continue } name, domain := account.GetDomainAccount() - if array.InArray(domain, cfg.Domains) && name != "" { + if array.InArray(domain, domains.Accepted) && name != "" { accounts = append(accounts, strings.ToLower(name)) } } diff --git a/server/services/rule/rule.go b/server/services/rule/rule.go index 66a059e..ea8aa60 100644 --- a/server/services/rule/rule.go +++ b/server/services/rule/rule.go @@ -190,16 +190,13 @@ func forwardToLocalUser(ctx *context.Context, email *parsemail.Email, account, f } func isLocalDomain(domain string) bool { - cfg := config.CurrentConfig() - if cfg == nil { - return false - } - for _, localDomain := range cfg.Domains { + domains := config.CurrentMailDomains() + for _, localDomain := range domains.Accepted { if strings.EqualFold(domain, localDomain) { return true } } - return strings.EqualFold(domain, cfg.Domain) + return strings.EqualFold(domain, domains.Primary) } func doMove(ctx *context.Context, rule *dto.Rule, email *parsemail.Email) error {