继续收敛邮件策略读取运行时快照
Some checks are pending
Security Checks / security (push) Waiting to run

This commit is contained in:
smail 2026-08-31 00:34:30 +08:00
parent d4c3994102
commit 9b19268c00
6 changed files with 99 additions and 44 deletions

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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,

View File

@ -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))
}
}

View File

@ -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 {