You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

156 lines
3.0 KiB

package gost
import (
"fmt"
"net"
"regexp"
"slices"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/go-log/log"
)
// 25 SMTP 服务器发邮件
// 465 SMTP SSL 客户端发邮件
// 587 SMTP Submission 客户端发邮件
// 143 IMAP 收邮件
// 993 IMAP SSL 收邮件
// 110 POP3 收邮件
// 995 POP3 SSL 收邮件
var mailPorts = []string{"25", "465", "587", "143", "993", "110", "995", "2525"}
type EmailACL struct {
emails map[string]struct{}
domains map[string]struct{}
regex []*regexp.Regexp
}
var emailACL atomic.Value
func LoadEmailACL(list []string, regexList []string) error {
acl := &EmailACL{
emails: map[string]struct{}{},
domains: map[string]struct{}{},
}
for _, v := range list {
v = strings.ToLower(strings.TrimSpace(v))
if strings.HasPrefix(v, "@") {
domain := strings.TrimPrefix(v, "@")
acl.domains[domain] = struct{}{}
} else {
acl.emails[v] = struct{}{}
}
}
for _, r := range regexList {
re, err := regexp.Compile(r)
if err != nil {
return err
}
acl.regex = append(acl.regex, re)
}
emailACL.Store(acl)
return nil
}
func IsEmailAllowed(email string) bool {
acl := emailACL.Load().(*EmailACL)
email = strings.ToLower(strings.TrimSpace(email))
// 白名单为空 → 拒绝所有
if len(acl.emails) == 0 && len(acl.domains) == 0 && len(acl.regex) == 0 {
return false
}
// 精确匹配
if _, ok := acl.emails[email]; ok {
return true
}
// domain 匹配
parts := strings.Split(email, "@")
if len(parts) == 2 {
domain := parts[1]
if _, ok := acl.domains[domain]; ok {
return true
}
}
// regex
for _, r := range acl.regex {
if r.MatchString(email) {
return true
}
}
return false
}
func CheckMailFrom(email string) error {
if !IsEmailAllowed(email) {
log.Logf("smtp blocked email: %s", email)
return fmt.Errorf("550 sender not allowed")
}
return nil
}
func CheckMailTo(address string) error {
if address == "" {
return nil
}
_, port, err := net.SplitHostPort(address)
if err != nil {
return err
}
if !slices.Contains(mailPorts, port) {
return nil
}
allowed := false
for _, h := range config.Auth.EmailWhiteList {
if strings.EqualFold(h, address) || strings.HasSuffix(address, h) {
allowed = true
break
}
}
if !allowed {
// 记录尝试连接非法 SMTP
fmt.Printf("SMTP access blocked to %s", address)
return fmt.Errorf("SMTP to this destination is forbidden")
}
return nil
}
type RateLimit struct {
count int
lastTime time.Time
}
var rateLimitMap sync.Map // key: ip/user, value: *RateLimit
func CheckRateLimit(ip net.IP, user string, maxPerMinute int) bool {
key := ip.String() + ":" + user
now := time.Now()
v, _ := rateLimitMap.LoadOrStore(key, &RateLimit{
count: 0,
lastTime: now,
})
rl := v.(*RateLimit)
// 超过一分钟窗口 → 重置计数
if now.Sub(rl.lastTime) > time.Minute {
rl.count = 0
rl.lastTime = now
}
if rl.count >= maxPerMinute {
return false
}
rl.count++
return true
}
// if !CheckRateLimit(clientIP, username, 50) {
// return fmt.Errorf("451 Too many messages, rate limit exceeded")
// }