mirror of
https://github.com/Jinnrry/PMail.git
synced 2025-02-20 11:43:09 +08:00
1、修复邮件插入失败 2、插件支持设置页面 3、修复pop3邮箱拉取权限判断 4、修复非管理员账户的附件权限判断 Co-authored-by: jinnrry <i@jinnrry.com>
321 lines
8.5 KiB
Go
321 lines
8.5 KiB
Go
package smtp_server
|
||
|
||
import (
|
||
"bytes"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"github.com/Jinnrry/pmail/config"
|
||
"github.com/Jinnrry/pmail/db"
|
||
"github.com/Jinnrry/pmail/dto/parsemail"
|
||
"github.com/Jinnrry/pmail/hooks"
|
||
"github.com/Jinnrry/pmail/hooks/framework"
|
||
"github.com/Jinnrry/pmail/models"
|
||
"github.com/Jinnrry/pmail/services/rule"
|
||
"github.com/Jinnrry/pmail/utils/async"
|
||
"github.com/Jinnrry/pmail/utils/context"
|
||
"github.com/Jinnrry/pmail/utils/errors"
|
||
"github.com/Jinnrry/pmail/utils/send"
|
||
"github.com/mileusna/spf"
|
||
log "github.com/sirupsen/logrus"
|
||
"github.com/spf13/cast"
|
||
"io"
|
||
"net"
|
||
"net/netip"
|
||
"strings"
|
||
"time"
|
||
. "xorm.io/builder"
|
||
)
|
||
|
||
func (s *Session) Data(r io.Reader) error {
|
||
|
||
ctx := s.Ctx
|
||
|
||
log.WithContext(ctx).Debugf("收到邮件")
|
||
|
||
emailData, err := io.ReadAll(r)
|
||
if err != nil {
|
||
log.WithContext(ctx).Error("邮件内容无法读取", err)
|
||
return err
|
||
}
|
||
log.WithContext(ctx).Debugf("开始执行插件ReceiveParseBefore!")
|
||
for _, hook := range hooks.HookList {
|
||
if hook == nil {
|
||
continue
|
||
}
|
||
hook.ReceiveParseBefore(ctx, &emailData)
|
||
}
|
||
log.WithContext(ctx).Debugf("开始执行插件ReceiveParseBefore End!")
|
||
|
||
email := parsemail.NewEmailFromReader(s.To, bytes.NewReader(emailData), len(emailData))
|
||
|
||
if s.From != "" {
|
||
from := parsemail.BuilderUser(s.From)
|
||
if email.From == nil {
|
||
email.From = from
|
||
}
|
||
if email.From.EmailAddress != from.EmailAddress {
|
||
// 协议中的from和邮件内容中的from不匹配,当成垃圾邮件处理
|
||
//log.WithContext(s.Ctx).Infof("垃圾邮件,拒信")
|
||
//return nil
|
||
}
|
||
}
|
||
|
||
// 判断是收信还是转发,只要是登陆了,都当成转发处理
|
||
if s.Ctx.UserID > 0 {
|
||
account, _ := email.From.GetDomainAccount()
|
||
if account != ctx.UserAccount && !ctx.IsAdmin {
|
||
return errors.New("No Auth")
|
||
}
|
||
|
||
log.WithContext(ctx).Debugf("开始执行插件SendBefore!")
|
||
for _, hook := range hooks.HookList {
|
||
if hook == nil {
|
||
continue
|
||
}
|
||
hook.SendBefore(ctx, email)
|
||
}
|
||
log.WithContext(ctx).Debugf("开始执行插件SendBefore!End")
|
||
|
||
if email == nil {
|
||
return nil
|
||
}
|
||
|
||
// 转发
|
||
_, err := saveEmail(ctx, len(emailData), email, s.Ctx.UserID, 1, true, true)
|
||
if err != nil {
|
||
log.WithContext(ctx).Errorf("Email Save Error %v", err)
|
||
}
|
||
|
||
errMsg := ""
|
||
err, sendErr := send.Send(ctx, email)
|
||
|
||
log.WithContext(ctx).Debugf("插件执行--SendAfter")
|
||
|
||
as3 := async.New(ctx)
|
||
for _, hook := range hooks.HookList {
|
||
if hook == nil {
|
||
continue
|
||
}
|
||
as3.WaitProcess(func(hk any) {
|
||
hk.(framework.EmailHook).SendAfter(ctx, email, sendErr)
|
||
}, hook)
|
||
}
|
||
as3.Wait()
|
||
log.WithContext(ctx).Debugf("插件执行--SendAfter")
|
||
|
||
if err != nil {
|
||
errMsg = err.Error()
|
||
_, err := db.Instance.Exec(db.WithContext(ctx, "update email set status =2 ,error=? where id = ? "), errMsg, email.MessageId)
|
||
if err != nil {
|
||
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
||
}
|
||
_, err = db.Instance.Exec(db.WithContext(ctx, "update user_email set status =2 where email_id = ? "), email.MessageId)
|
||
if err != nil {
|
||
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
||
}
|
||
|
||
} else {
|
||
_, err := db.Instance.Exec(db.WithContext(ctx, "update email set status =1 where id = ? "), email.MessageId)
|
||
if err != nil {
|
||
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
||
}
|
||
_, err = db.Instance.Exec(db.WithContext(ctx, "update user_email set status =1 where email_id = ? "), email.MessageId)
|
||
if err != nil {
|
||
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
||
}
|
||
}
|
||
|
||
} else {
|
||
// 收件
|
||
|
||
var dkimStatus, SPFStatus bool
|
||
|
||
// DKIM校验
|
||
dkimStatus = parsemail.Check(bytes.NewReader(emailData))
|
||
|
||
SPFStatus = spfCheck(s.RemoteAddress.String(), email.Sender, email.Sender.EmailAddress)
|
||
|
||
log.WithContext(ctx).Debugf("开始执行插件ReceiveParseAfter!")
|
||
for _, hook := range hooks.HookList {
|
||
if hook == nil {
|
||
continue
|
||
}
|
||
hook.ReceiveParseAfter(ctx, email)
|
||
}
|
||
log.WithContext(ctx).Debugf("开始执行插件ReceiveParseAfter!End")
|
||
|
||
// 垃圾过滤
|
||
if config.Instance.SpamFilterLevel == 1 && !SPFStatus && !dkimStatus {
|
||
log.WithContext(ctx).Infoln("垃圾邮件,拒信")
|
||
return nil
|
||
}
|
||
|
||
if config.Instance.SpamFilterLevel == 2 && !SPFStatus {
|
||
log.WithContext(ctx).Infoln("垃圾邮件,拒信")
|
||
return nil
|
||
}
|
||
|
||
users, _ := saveEmail(ctx, len(emailData), email, 0, 0, SPFStatus, dkimStatus)
|
||
|
||
if email.MessageId > 0 {
|
||
log.WithContext(ctx).Debugf("开始执行邮件规则!")
|
||
for _, user := range users {
|
||
// 执行邮件规则
|
||
rs := rule.GetAllRules(ctx, user.ID)
|
||
for _, r := range rs {
|
||
if rule.MatchRule(ctx, r, email) {
|
||
rule.DoRule(ctx, r, email)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
log.WithContext(ctx).Debugf("开始执行插件ReceiveSaveAfter!")
|
||
var ue []*models.UserEmail
|
||
err = db.Instance.Table(&models.UserEmail{}).Where("email_id=?", email.MessageId).Find(&ue)
|
||
if err != nil {
|
||
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
||
}
|
||
as3 := async.New(ctx)
|
||
for _, hook := range hooks.HookList {
|
||
if hook == nil {
|
||
continue
|
||
}
|
||
as3.WaitProcess(func(hk any) {
|
||
hk.(framework.EmailHook).ReceiveSaveAfter(ctx, email, ue)
|
||
}, hook)
|
||
}
|
||
as3.Wait()
|
||
log.WithContext(ctx).Debugf("开始执行插件ReceiveSaveAfter!End")
|
||
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func saveEmail(ctx *context.Context, size int, email *parsemail.Email, sendUserID int, emailType int, SPFStatus, dkimStatus bool) ([]*models.User, error) {
|
||
var dkimV, spfV int8
|
||
if dkimStatus {
|
||
dkimV = 1
|
||
}
|
||
if SPFStatus {
|
||
spfV = 1
|
||
}
|
||
|
||
log.WithContext(ctx).Debugf("开始入库!")
|
||
|
||
if email == nil {
|
||
return nil, nil
|
||
}
|
||
|
||
modelEmail := models.Email{
|
||
Type: cast.ToInt8(emailType),
|
||
Subject: email.Subject,
|
||
ReplyTo: json2string(email.ReplyTo),
|
||
FromName: email.From.Name,
|
||
FromAddress: email.From.EmailAddress,
|
||
To: json2string(email.To),
|
||
Bcc: json2string(email.Bcc),
|
||
Cc: json2string(email.Cc),
|
||
Text: sql.NullString{String: string(email.Text), Valid: true},
|
||
Html: sql.NullString{String: string(email.HTML), Valid: true},
|
||
Sender: json2string(email.Sender),
|
||
Attachments: json2string(email.Attachments),
|
||
Size: email.Size,
|
||
SPFCheck: spfV,
|
||
DKIMCheck: dkimV,
|
||
SendUserID: sendUserID,
|
||
SendDate: time.Now(),
|
||
Status: cast.ToInt8(email.Status),
|
||
CreateTime: time.Now(),
|
||
CronSendTime: time.Now(),
|
||
}
|
||
|
||
_, err := db.Instance.Insert(&modelEmail)
|
||
|
||
if err != nil {
|
||
log.WithContext(ctx).Errorf("db insert error:%+v", err.Error())
|
||
}
|
||
|
||
if modelEmail.Id > 0 {
|
||
email.MessageId = cast.ToInt64(modelEmail.Id)
|
||
}
|
||
// 收信人信息
|
||
var users []*models.User
|
||
|
||
// 如果是收信
|
||
if emailType == 0 {
|
||
// 找到收信人id
|
||
accounts := []string{}
|
||
for _, user := range append(append(email.To, email.Cc...), email.Bcc...) {
|
||
account, _ := user.GetDomainAccount()
|
||
if account != "" {
|
||
accounts = append(accounts, account)
|
||
}
|
||
}
|
||
|
||
where, params, _ := ToSQL(In("account", accounts))
|
||
|
||
err = db.Instance.Table(&models.User{}).Where(where, params...).Find(&users)
|
||
if err != nil {
|
||
log.WithContext(ctx).Errorf("db Select error:%+v", err.Error())
|
||
}
|
||
|
||
if len(users) > 0 {
|
||
for _, user := range users {
|
||
ue := models.UserEmail{EmailID: modelEmail.Id, UserID: user.ID, Status: cast.ToInt8(email.Status)}
|
||
_, err = db.Instance.Insert(&ue)
|
||
if err != nil {
|
||
log.WithContext(ctx).Errorf("db insert error:%+v", err.Error())
|
||
}
|
||
}
|
||
} else {
|
||
users = append(users, &models.User{ID: 1})
|
||
// 当邮件找不到收件人的时候,邮件全部丢给管理员账号
|
||
// id = 1的账号直接当成管理员账号处理
|
||
ue := models.UserEmail{EmailID: modelEmail.Id, UserID: 1, Status: cast.ToInt8(email.Status)}
|
||
_, err = db.Instance.Insert(&ue)
|
||
if err != nil {
|
||
log.WithContext(ctx).Errorf("db insert error:%+v", err.Error())
|
||
}
|
||
}
|
||
|
||
} else {
|
||
ue := models.UserEmail{EmailID: modelEmail.Id, UserID: ctx.UserID}
|
||
_, err = db.Instance.Insert(&ue)
|
||
if err != nil {
|
||
log.WithContext(ctx).Errorf("db insert error:%+v", err.Error())
|
||
}
|
||
}
|
||
|
||
return users, nil
|
||
}
|
||
|
||
func json2string(d any) string {
|
||
by, _ := json.Marshal(d)
|
||
return string(by)
|
||
}
|
||
|
||
func spfCheck(remoteAddress string, sender *parsemail.User, senderString string) bool {
|
||
//spf校验
|
||
ipAddress, _ := netip.ParseAddrPort(remoteAddress)
|
||
|
||
ip := net.ParseIP(ipAddress.Addr().String())
|
||
if ip.IsPrivate() {
|
||
return true
|
||
}
|
||
|
||
tmp := strings.Split(sender.EmailAddress, "@")
|
||
if len(tmp) < 2 {
|
||
return false
|
||
}
|
||
|
||
res := spf.CheckHost(ip, tmp[1], senderString, "")
|
||
|
||
if res == spf.None || res == spf.Pass {
|
||
// spf校验通过
|
||
return true
|
||
}
|
||
return false
|
||
}
|