PMail/server/smtp_server/read_content.go
Jinnrry e6a56b199d
v2.6.2 (#177)
1、修复邮件插入失败
2、插件支持设置页面
3、修复pop3邮箱拉取权限判断
4、修复非管理员账户的附件权限判断

Co-authored-by: jinnrry <i@jinnrry.com>
2024-07-27 20:47:42 +08:00

321 lines
8.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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("开始执行插件SendBeforeEnd")
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("开始执行插件ReceiveParseAfterEnd")
// 垃圾过滤
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("开始执行插件ReceiveSaveAfterEnd")
}
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
}