This commit is contained in:
jinnrry 2023-09-09 17:57:15 +08:00
parent 9c3dccd7b1
commit e6ddfba941
12 changed files with 130 additions and 85 deletions

0
build.sh Normal file → Executable file
View File

View File

@ -38,7 +38,7 @@ type Config struct {
//go:embed tables/*
var tableConfig embed.FS
const Version = "2.2.1"
const Version = "2.2.2"
const DBTypeMySQL = "mysql"
const DBTypeSQLite = "sqlite"

View File

@ -33,8 +33,8 @@ func HttpStart() {
httpServer = &http.Server{
Addr: fmt.Sprintf(":%d", HttpPort),
Handler: mux,
ReadTimeout: time.Second * 60,
WriteTimeout: time.Second * 60,
ReadTimeout: time.Second * 90,
WriteTimeout: time.Second * 90,
}
} else {
fe, err := fs.Sub(local, "dist")
@ -64,8 +64,8 @@ func HttpStart() {
httpServer = &http.Server{
Addr: fmt.Sprintf(":%d", HttpPort),
Handler: session.Instance.LoadAndSave(mux),
ReadTimeout: time.Second * 60,
WriteTimeout: time.Second * 60,
ReadTimeout: time.Second * 90,
WriteTimeout: time.Second * 90,
}
}

View File

@ -1,19 +1,14 @@
package http_server
import (
"bytes"
"embed"
"encoding/hex"
"encoding/json"
"fmt"
log "github.com/sirupsen/logrus"
"github.com/spf13/cast"
"io/fs"
olog "log"
"math/rand"
"net"
"net/http"
"os"
"pmail/config"
"pmail/controllers"
"pmail/controllers/email"
@ -22,6 +17,7 @@ import (
"pmail/models"
"pmail/session"
"pmail/utils/context"
"pmail/utils/id"
"time"
)
@ -80,8 +76,8 @@ func HttpsStart() {
httpsServer = &http.Server{
Addr: fmt.Sprintf(":%d", HttpsPort),
Handler: session.Instance.LoadAndSave(mux),
ReadTimeout: time.Second * 60,
WriteTimeout: time.Second * 60,
ReadTimeout: time.Second * 90,
WriteTimeout: time.Second * 90,
ErrorLog: nullLog,
}
err = httpsServer.ListenAndServeTLS("config/ssl/public.crt", "config/ssl/private.key")
@ -97,27 +93,6 @@ func HttpsStop() {
}
}
func genLogID() string {
r := rand.New(rand.NewSource(time.Now().UnixMicro()))
if ip == "" {
ip = getLocalIP()
}
now := time.Now()
timestamp := uint32(now.Unix())
timeNano := now.UnixNano()
pid := os.Getpid()
b := bytes.Buffer{}
b.WriteString(hex.EncodeToString(net.ParseIP(ip).To4()))
b.WriteString(fmt.Sprintf("%x", timestamp&0xffffffff))
b.WriteString(fmt.Sprintf("%04x", timeNano&0xffff))
b.WriteString(fmt.Sprintf("%04x", pid&0xffff))
b.WriteString(fmt.Sprintf("%06x", r.Int31n(1<<24)))
b.WriteString("b0")
return b.String()
}
// 注入context
func contextIterceptor(h controllers.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
@ -127,7 +102,7 @@ func contextIterceptor(h controllers.HandlerFunc) http.HandlerFunc {
ctx := &context.Context{}
ctx.Context = r.Context()
ctx.SetValue(context.LogID, genLogID())
ctx.SetValue(context.LogID, id.GenLogID())
lang := r.Header.Get("Lang")
if lang == "" {
lang = "en"

View File

@ -3,7 +3,6 @@ package http_server
import (
"fmt"
"io/fs"
"net"
"net/http"
"pmail/config"
"pmail/controllers"
@ -49,21 +48,3 @@ func SetupStop() {
panic(err)
}
}
func getLocalIP() string {
ip := "127.0.0.1"
addrs, err := net.InterfaceAddrs()
if err != nil {
return ip
}
for _, a := range addrs {
if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
ip = ipnet.IP.String()
break
}
}
}
return ip
}

View File

@ -22,7 +22,10 @@ func (l *logFormatter) Format(entry *log.Entry) ([]byte, error) {
b.WriteString(fmt.Sprintf("[%s]", entry.Level.String()))
b.WriteString(fmt.Sprintf("[%s]", entry.Time.Format("2006-01-02 15:04:05")))
if entry.Context != nil {
b.WriteString(fmt.Sprintf("[%s]", entry.Context.(*context.Context).GetValue(context.LogID)))
ctx := entry.Context.(*context.Context)
if ctx != nil {
b.WriteString(fmt.Sprintf("[%s]", ctx.GetValue(context.LogID)))
}
}
b.WriteString(fmt.Sprintf("[%s:%d]", entry.Caller.File, entry.Caller.Line))
b.WriteString(entry.Message)

View File

@ -17,7 +17,7 @@ import (
func GetAllRules(ctx *context.Context) []*dto.Rule {
var res []*models.Rule
var err error
if ctx == nil {
if ctx == nil || ctx.UserID == 0 {
err = db.Instance.Select(&res, "select * from rule order by sort desc")
} else {
err = db.Instance.Select(&res, db.WithContext(ctx, "select * from rule where user_id=? order by sort desc"), ctx.UserID)
@ -60,6 +60,8 @@ func MatchRule(ctx *context.Context, rule *dto.Rule, email *parsemail.Email) boo
}
func DoRule(ctx *context.Context, rule *dto.Rule, email *parsemail.Email) {
log.WithContext(ctx).Debugf("执行规则:%s", rule.Name)
switch rule.Action {
case dto.READ:
email.IsRead = 1
@ -70,8 +72,7 @@ func DoRule(ctx *context.Context, rule *dto.Rule, email *parsemail.Email) {
log.WithContext(ctx).Errorf("Forward Error! loop forwarding!")
return
}
err := send.Forward(nil, email, rule.Params)
err := send.Forward(ctx, email, rule.Params)
if err != nil {
log.WithContext(ctx).Errorf("Forward Error:%v", err)
}

View File

@ -20,7 +20,7 @@ func GetDatabaseSettings(ctx *context.Context) (string, string, error) {
}
if configData.DbType == "" && configData.DbDSN == "" {
return config.DBTypeSQLite, "./pmail.db", nil
return config.DBTypeSQLite, "./config/pmail.db", nil
}
return configData.DbType, configData.DbDSN, nil

View File

@ -14,18 +14,24 @@ import (
"pmail/hooks"
"pmail/services/rule"
"pmail/utils/async"
"pmail/utils/context"
"pmail/utils/id"
"strings"
"time"
)
func (s *Session) Data(r io.Reader) error {
ctx := &context.Context{}
ctx.SetValue(context.LogID, id.GenLogID())
log.WithContext(ctx).Debugf("收到邮件")
emailData, err := io.ReadAll(r)
if err != nil {
log.Error("邮件内容无法读取", err)
log.WithContext(ctx).Error("邮件内容无法读取", err)
return err
}
as1 := async.New(nil)
as1 := async.New(ctx)
for _, hook := range hooks.HookList {
if hook == nil {
continue
@ -36,7 +42,7 @@ func (s *Session) Data(r io.Reader) error {
}
as1.Wait()
log.Infof("邮件原始内容: %s", emailData)
log.WithContext(ctx).Infof("邮件原始内容: %s", emailData)
var dkimStatus, SPFStatus bool
@ -46,7 +52,7 @@ func (s *Session) Data(r io.Reader) error {
email := parsemail.NewEmailFromReader(bytes.NewReader(emailData))
if err != nil {
log.Fatalf("邮件内容解析失败! Error : %v \n", err)
log.WithContext(ctx).Errorf("邮件内容解析失败! Error : %v \n", err)
}
SPFStatus = spfCheck(s.RemoteAddress.String(), email.Sender, email.Sender.EmailAddress)
@ -61,16 +67,17 @@ func (s *Session) Data(r io.Reader) error {
// 垃圾过滤
if config.Instance.SpamFilterLevel == 1 && !SPFStatus && !dkimStatus {
log.Infoln("垃圾邮件,拒信")
log.WithContext(ctx).Infoln("垃圾邮件,拒信")
return nil
}
if config.Instance.SpamFilterLevel == 2 && !SPFStatus {
log.Infoln("垃圾邮件,拒信")
log.WithContext(ctx).Infoln("垃圾邮件,拒信")
return nil
}
log.WithContext(ctx).Debugf("开始执行插件!")
as2 := async.New(nil)
as2 := async.New(ctx)
for _, hook := range hooks.HookList {
if hook == nil {
continue
@ -81,13 +88,15 @@ func (s *Session) Data(r io.Reader) error {
}
as2.Wait()
log.WithContext(ctx).Debugf("开始执行邮件规则!")
// 执行邮件规则
rs := rule.GetAllRules(nil)
rs := rule.GetAllRules(ctx)
for _, r := range rs {
if rule.MatchRule(nil, r, email) {
rule.DoRule(nil, r, email)
if rule.MatchRule(ctx, r, email) {
rule.DoRule(ctx, r, email)
}
}
log.WithContext(ctx).Debugf("开始入库!")
if email == nil {
return nil
@ -116,7 +125,7 @@ func (s *Session) Data(r io.Reader) error {
)
if err != nil {
log.Println("mysql insert error:", err.Error())
log.WithContext(ctx).Println("mysql insert error:", err.Error())
}
return nil

57
server/utils/id/logid.go Normal file
View File

@ -0,0 +1,57 @@
package id
import (
"bytes"
"encoding/hex"
"fmt"
"math/rand"
"net"
"os"
"time"
)
var ip_instance string
func getLocalIP() string {
if ip_instance != "" {
return ip_instance
}
ip := "127.0.0.1"
addrs, err := net.InterfaceAddrs()
if err != nil {
ip_instance = ip
return ip
}
for _, a := range addrs {
if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
ip = ipnet.IP.String()
break
}
}
}
ip_instance = ip
return ip
}
func GenLogID() string {
r := rand.New(rand.NewSource(time.Now().UnixMicro()))
ip := getLocalIP()
now := time.Now()
timestamp := uint32(now.Unix())
timeNano := now.UnixNano()
pid := os.Getpid()
b := bytes.Buffer{}
b.WriteString(hex.EncodeToString(net.ParseIP(ip).To4()))
b.WriteString(fmt.Sprintf("%x", timestamp&0xffffffff))
b.WriteString(fmt.Sprintf("%04x", timeNano&0xffff))
b.WriteString(fmt.Sprintf("%04x", pid&0xffff))
b.WriteString(fmt.Sprintf("%06x", r.Int31n(1<<24)))
b.WriteString("b0")
return b.String()
}

View File

@ -22,6 +22,7 @@ type mxDomain struct {
// Forward 转发邮件
func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) error {
log.WithContext(ctx).Debugf("开始转发邮件")
b := e.ForwardBuildBytes(ctx, forwardAddress)
var to []*parsemail.User
@ -66,15 +67,19 @@ func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) er
tos := tos
as.WaitProcess(func(p any) {
// 先使用smtps协议尝试
err := smtp.SendMailWithTls("", domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTPS Send Error! Error:%+v", err)
log.WithContext(ctx).Warnf("SMTPS on 465 Send Error! Error:%+v", err)
// smtps发送失败,尝试smtp
err = smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTP Send Error! Error:%+v", err)
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
} else {
log.WithContext(ctx).Infof("SMTP Send Success !")
}
} else {
log.WithContext(ctx).Infof("SMTPS on 465 Send Success !")
}
// 重新选取证书域名
@ -83,15 +88,21 @@ func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) er
if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is {
if hostnameErr.Certificate != nil {
certificateHostName := hostnameErr.Certificate.DNSNames
// 先使用smtps协议尝试
// 再使用smtps 465端口 尝试
err = smtp.SendMailWithTls(domainMatch(domain.domain, certificateHostName), domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTPS Send Error! Error:%+v", err)
log.WithContext(ctx).Warnf("SMTPS on 465 Send Error! Error:%+v", err)
// smtps发送失败,尝试smtp
err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTP Send Error! Error:%+v", err)
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
} else {
log.WithContext(ctx).Infof("SMTP Send Success !")
}
} else {
log.WithContext(ctx).Infof("SMTPS Send Success !")
}
}
}
@ -102,7 +113,6 @@ func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) er
log.WithContext(ctx).Errorf("%v 邮件投递失败%+v", tos, err)
for _, user := range tos {
errEmailAddress = append(errEmailAddress, user.EmailAddress)
}
}
errMap[domain.domain] = err
@ -160,15 +170,19 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
tos := tos
as.WaitProcess(func(p any) {
// 先使用smtps协议尝试
err := smtp.SendMailWithTls("", domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTPS Send Error! Error:%+v", err)
log.WithContext(ctx).Warnf("SMTPS on 465 Send Error! Error:%+v", err)
// smtps发送失败,尝试smtp
err = smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTP Send Error! Error:%+v", err)
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
} else {
log.WithContext(ctx).Infof("SMTP Send Success !")
}
} else {
log.WithContext(ctx).Infof("SMTPS Send Success !")
}
// 重新选取证书域名
@ -177,15 +191,20 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is {
if hostnameErr.Certificate != nil {
certificateHostName := hostnameErr.Certificate.DNSNames
// 先使用smtps协议尝试
err = smtp.SendMailWithTls(domainMatch(domain.domain, certificateHostName), domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTPS Send Error! Error:%+v", err)
log.WithContext(ctx).Warnf("SMTPS on 465 Send Error! Error:%+v", err)
// smtps发送失败,尝试smtp
err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTP Send Error! Error:%+v", err)
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
} else {
log.WithContext(ctx).Infof("SMTP Send Success !")
}
} else {
log.WithContext(ctx).Infof("SMTPS Send Success !")
}
}
}
@ -196,7 +215,6 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
log.WithContext(ctx).Errorf("%v 邮件投递失败%+v", tos, err)
for _, user := range tos {
errEmailAddress = append(errEmailAddress, user.EmailAddress)
}
}
errMap[domain.domain] = err

View File

@ -29,6 +29,7 @@ import (
"net/smtp"
"net/textproto"
"strings"
"time"
)
// A Client represents a client connection to an SMTP server.
@ -54,7 +55,7 @@ type Client struct {
// Dial returns a new Client connected to an SMTP server at addr.
// The addr must include a port, as in "mail.example.com:smtp".
func Dial(addr string) (*Client, error) {
conn, err := net.Dial("tcp", addr)
conn, err := net.DialTimeout("tcp", addr, 2*time.Second)
if err != nil {
return nil, err
}
@ -70,7 +71,7 @@ func DialTls(addr, domain string) (*Client, error) {
ServerName: domain,
}
conn, err := tls.Dial("tcp", addr, tlsconfig)
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: 2 * time.Second}, "tcp", addr, tlsconfig)
if err != nil {
return nil, err
}