- 会话 cookie 增加 Secure 标志;新增 [web].cookie_secure 配置 (默认 true,仅本地 HTTP 调试关闭;缺失字段按安全默认处理) - SMTP/IMAP/POP3 认证接入封禁体系(store.RecordAuthFailure 与 Web 共用 ban_entries):失败计数达 ban.max_fail_attempts 即封禁 IP, 已封禁 IP 拒绝认证,堵住协议层暴力破解 - 附件存储路径遍历防护重写:FullPath 白名单校验(UUID 文件名格式) + baseDir 前缀兜底,非法路径返回错误;Save 扩展名白名单化 - 初始管理员不再使用 admin/admin:密码取 MAILGO_ADMIN_PASSWORD 或 随机生成并打印一次;新增 MustChangePassword 首登强制改密 (管理员重置密码同样触发) - 外发中继默认验证 TLS 证书(保护 AUTH 凭据,防 MITM),直投 MX 保持机会式 TLS;新增 outbound.relay_tls_insecure 开关(默认 false) - 新增安全响应头中间件:HSTS、X-Frame-Options DENY、nosniff、 Referrer-Policy、基础 CSP(frame-ancestors 'none' 防点击劫持, connect-src/form-action 'self' 防数据外泄) - LDAP/OAuth 登录错误统一为通用文案,原始错误只写日志, 不再回显邮箱/内部细节(防用户枚举与信息泄露) - 新增 25 个回归测试:cookie 标志、封禁阈值、路径遍历用例、 中继 TLS 验证(自签证书 STARTTLS 集成)、安全头、OAuth 文案 部署注意:升级后所有会话失效需重新登录;若直接以 HTTP 提供 服务需显式配置 cookie_secure = false。
44 lines
1.2 KiB
Go
44 lines
1.2 KiB
Go
package store
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"time"
|
|
|
|
"mail_go/internal/db"
|
|
)
|
|
|
|
// ClientIPFromAddr 从 net.Addr 提取客户端 IP 字符串(去掉端口)。
|
|
// 解析失败返回空字符串,调用方应据此跳过封禁逻辑(不误封)。
|
|
func ClientIPFromAddr(addr net.Addr) string {
|
|
if addr == nil {
|
|
return ""
|
|
}
|
|
host, _, err := net.SplitHostPort(addr.String())
|
|
if err != nil {
|
|
return addr.String()
|
|
}
|
|
return host
|
|
}
|
|
|
|
// RecordAuthFailure 记录一次协议层(SMTP/IMAP/POP3)认证失败:
|
|
// 失败计数累加,达到 maxFail 阈值时封禁该 IP(封禁时长 minutes 分钟)。
|
|
// 返回 (是否触发封禁, 当前失败计数)。Web 登录的封禁逻辑在
|
|
// handlers.AuthHandler 中,与这里独立。
|
|
func (s *Stores) RecordAuthFailure(ip string, maxFail int, minutes int) (banned bool, failCount int) {
|
|
if ip == "" || maxFail <= 0 {
|
|
return false, 0
|
|
}
|
|
failCount, _ = s.Bans.IncrementFail(ip)
|
|
if failCount >= maxFail {
|
|
_ = s.Bans.Create(&db.BanEntry{
|
|
IPAddress: ip,
|
|
Reason: fmt.Sprintf("邮件协议认证失败次数过多 (%d次)", failCount),
|
|
FailCount: failCount,
|
|
ExpiresAt: time.Now().Add(time.Duration(minutes) * time.Minute),
|
|
})
|
|
return true, failCount
|
|
}
|
|
return false, failCount
|
|
}
|