jinnrry před 2 roky
rodič
revize
68de8f6b27

+ 5 - 0
server/config/config.go

@@ -13,6 +13,7 @@ var IsInit bool
 type Config struct {
 	LogLevel             string            `json:"logLevel"` // 日志级别
 	Domain               string            `json:"domain"`
+	Domains              []string          `json:"domains"` //多域名设置,把所有收信域名都填进去
 	WebDomain            string            `json:"webDomain"`
 	DkimPrivateKeyPath   string            `json:"dkimPrivateKeyPath"`
 	SSLType              string            `json:"sslType"` // 0表示自动生成证书,1表示用户上传证书
@@ -71,6 +72,10 @@ func Init() {
 		return
 	}
 
+	if len(Instance.Domains) == 0 && Instance.Domain != "" {
+		Instance.Domains = []string{Instance.Domain}
+	}
+
 	// 读取表设置
 	Instance.Tables = map[string]string{}
 	Instance.TablesInitData = map[string]string{}

+ 21 - 3
server/dto/parsemail/email.go

@@ -20,6 +20,15 @@ type User struct {
 	Name         string `json:"Name"`
 }
 
+func (u User) GetDomain() string {
+	infos := strings.Split(u.EmailAddress, "@")
+	if len(infos) > 2 {
+		return infos[1]
+	}
+
+	return ""
+}
+
 type Attachment struct {
 	Filename    string
 	ContentType string
@@ -47,15 +56,24 @@ type Email struct {
 	GroupId     int // 分组id
 }
 
-func NewEmailFromReader(r io.Reader) *Email {
+func NewEmailFromReader(from string, to []string, r io.Reader) *Email {
 	ret := &Email{}
 	m, err := message.Read(r)
 	if err != nil {
 		log.Errorf("email解析错误! Error %+v", err)
 	}
+	if from != "" {
+		ret.From = buildUser(from)
+	} else {
+		ret.From = buildUser(m.Header.Get("From"))
+	}
+
+	if len(to) > 0 {
+		ret.To = buildUsers(to)
+	} else {
+		ret.To = buildUsers(m.Header.Values("To"))
+	}
 
-	ret.From = buildUser(m.Header.Get("From"))
-	ret.To = buildUsers(m.Header.Values("To"))
 	ret.Cc = buildUsers(m.Header.Values("Cc"))
 	ret.ReplyTo = buildUsers(m.Header.Values("ReplyTo"))
 	ret.Sender = buildUser(m.Header.Get("Sender"))

+ 3 - 3
server/dto/parsemail/email_decode_test.go

@@ -13,7 +13,7 @@ func TestDecodeEmailContentFromTxt(t *testing.T) {
 
 	r := strings.NewReader(string(c))
 
-	email := NewEmailFromReader(r)
+	email := NewEmailFromReader(nil, r)
 
 	fmt.Println(email)
 }
@@ -24,7 +24,7 @@ func TestDecodeEmailContentFromTxt3(t *testing.T) {
 
 	r := strings.NewReader(string(c))
 
-	email := NewEmailFromReader(r)
+	email := NewEmailFromReader(nil, r)
 
 	fmt.Println(email)
 }
@@ -34,7 +34,7 @@ func TestDecodeEmailContentFromTxt2(t *testing.T) {
 
 	r := strings.NewReader(string(c))
 
-	email := NewEmailFromReader(r)
+	email := NewEmailFromReader(nil, r)
 
 	fmt.Println(email)
 

+ 11 - 0
server/pop3_server/action.go

@@ -25,6 +25,8 @@ func (a action) User(ctx *gopop.Data, username string) error {
 		ctx.Ctx = tc
 	}
 
+	log.WithContext(ctx.Ctx).Debugf("POP3 User %s", username)
+
 	ctx.User = username
 	return nil
 }
@@ -36,6 +38,8 @@ func (a action) Pass(ctx *gopop.Data, pwd string) error {
 		ctx.Ctx = tc
 	}
 
+	log.WithContext(ctx.Ctx).Debugf("POP3 PASS %s", pwd)
+
 	var user models.User
 
 	encodePwd := password.Encode(pwd)
@@ -65,6 +69,8 @@ func (a action) Apop(ctx *gopop.Data, username, digest string) error {
 		ctx.Ctx = tc
 	}
 
+	log.WithContext(ctx.Ctx).Debugf("POP3 APOP %s %s", username, digest)
+
 	var user models.User
 
 	err := db.Instance.Get(&user, db.WithContext(ctx.Ctx.(*context.Context), "select * from user where account =? "), username)
@@ -93,18 +99,23 @@ type statInfo struct {
 }
 
 func (a action) Stat(ctx *gopop.Data) (msgNum, msgSize int64, err error) {
+
 	var si statInfo
 	err = db.Instance.Get(&si, db.WithContext(ctx.Ctx.(*context.Context), "select count(1) as `num`, sum(length(text)+length(html)) as `size` from email"))
 	if err != nil && !errors.Is(err, sql.ErrNoRows) {
 		log.WithContext(ctx.Ctx.(*context.Context)).Errorf("%+v", err)
 		err = nil
+		log.WithContext(ctx.Ctx).Debugf("POP3 STAT RETURT :0,0")
 		return 0, 0, nil
 	}
+	log.WithContext(ctx.Ctx).Debugf("POP3 STAT RETURT : %d,%d", si.Num, si.Size)
 
 	return si.Num, si.Size, nil
 }
 
 func (a action) Uidl(ctx *gopop.Data, id int64) (string, error) {
+	log.WithContext(ctx.Ctx).Debugf("POP3 Uidl RETURT : %d", id)
+
 	return cast.ToString(id), nil
 }
 

+ 34 - 12
server/smtp_server/read_content.go

@@ -13,16 +13,18 @@ import (
 	"pmail/dto/parsemail"
 	"pmail/hooks"
 	"pmail/services/rule"
+	"pmail/utils/array"
 	"pmail/utils/async"
 	"pmail/utils/context"
-	"pmail/utils/id"
+	"pmail/utils/send"
 	"strings"
 	"time"
 )
 
 func (s *Session) Data(r io.Reader) error {
-	ctx := &context.Context{}
-	ctx.SetValue(context.LogID, id.GenLogID())
+
+	ctx := s.Ctx
+
 	log.WithContext(ctx).Debugf("收到邮件")
 
 	emailData, err := io.ReadAll(r)
@@ -44,19 +46,38 @@ func (s *Session) Data(r io.Reader) error {
 
 	log.WithContext(ctx).Infof("邮件原始内容: %s", emailData)
 
-	var dkimStatus, SPFStatus bool
+	email := parsemail.NewEmailFromReader(s.From, s.To, bytes.NewReader(emailData))
+	// 判断是收信还是转发
+	if array.InArray(email.From.GetDomain(), config.Instance.Domains) {
+		// 转发
+		err := saveEmail(ctx, email, 1, true, true)
+		if err != nil {
+			log.WithContext(ctx).Errorf("Email Save Error %v", err)
+		}
+
+		send.Send(ctx, email)
 
-	// DKIM校验
-	dkimStatus = parsemail.Check(bytes.NewReader(emailData))
+	} else {
+		// 收件
 
-	email := parsemail.NewEmailFromReader(bytes.NewReader(emailData))
+		var dkimStatus, SPFStatus bool
 
-	if err != nil {
-		log.WithContext(ctx).Errorf("邮件内容解析失败! Error : %v \n", err)
+		// DKIM校验
+		dkimStatus = parsemail.Check(bytes.NewReader(emailData))
+
+		if err != nil {
+			log.WithContext(ctx).Errorf("邮件内容解析失败! Error : %v \n", err)
+		}
+
+		SPFStatus = spfCheck(s.RemoteAddress.String(), email.Sender, email.Sender.EmailAddress)
+
+		saveEmail(ctx, email, 0, SPFStatus, dkimStatus)
 	}
 
-	SPFStatus = spfCheck(s.RemoteAddress.String(), email.Sender, email.Sender.EmailAddress)
+	return nil
+}
 
+func saveEmail(ctx *context.Context, email *parsemail.Email, emailType int, SPFStatus, dkimStatus bool) error {
 	var dkimV, spfV int8
 	if dkimStatus {
 		dkimV = 1
@@ -102,8 +123,9 @@ func (s *Session) Data(r io.Reader) error {
 		return nil
 	}
 
-	sql := "INSERT INTO email (send_date, subject, reply_to, from_name, from_address, `to`, bcc, cc, text, html, sender, attachments,spf_check, dkim_check, create_time,is_read,status,group_id) VALUES (?,?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
-	_, err = db.Instance.Exec(sql,
+	sql := "INSERT INTO email (type, send_date, subject, reply_to, from_name, from_address, `to`, bcc, cc, text, html, sender, attachments,spf_check, dkim_check, create_time,is_read,status,group_id) VALUES (?,?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
+	_, err := db.Instance.Exec(sql,
+		emailType,
 		email.Date,
 		email.Subject,
 		json2string(email.ReplyTo),

+ 47 - 2
server/smtp_server/smtp.go

@@ -2,10 +2,18 @@ package smtp_server
 
 import (
 	"crypto/tls"
+	"database/sql"
 	"github.com/emersion/go-smtp"
 	log "github.com/sirupsen/logrus"
 	"net"
 	"pmail/config"
+	"pmail/db"
+	"pmail/models"
+	"pmail/utils/context"
+	"pmail/utils/errors"
+	"pmail/utils/id"
+	"pmail/utils/password"
+	"strings"
 	"time"
 )
 
@@ -13,27 +21,63 @@ import (
 type Backend struct{}
 
 func (bkd *Backend) NewSession(conn *smtp.Conn) (smtp.Session, error) {
+
 	remoteAddress := conn.Conn().RemoteAddr()
+	ctx := &context.Context{}
+	ctx.SetValue(context.LogID, id.GenLogID())
+	log.WithContext(ctx).Debugf("新SMTP连接")
 
 	return &Session{
 		RemoteAddress: remoteAddress,
+		Ctx:           ctx,
 	}, nil
 }
 
 // A Session is returned after EHLO.
 type Session struct {
 	RemoteAddress net.Addr
+	User          string
+	From          string
+	To            []string
+	Ctx           *context.Context
 }
 
-func (s *Session) AuthPlain(username, password string) error {
-	return nil
+func (s *Session) AuthPlain(username, pwd string) error {
+	s.User = username
+
+	var user models.User
+
+	encodePwd := password.Encode(pwd)
+
+	infos := strings.Split(username, "@")
+	if len(infos) > 1 {
+		username = infos[0]
+	}
+
+	err := db.Instance.Get(&user, db.WithContext(s.Ctx, "select * from user where account =? and password =?"),
+		username, encodePwd)
+	if err != nil && err != sql.ErrNoRows {
+		log.Errorf("%+v", err)
+	}
+
+	if user.ID > 0 {
+		s.Ctx.UserAccount = user.Account
+		s.Ctx.UserID = user.ID
+		s.Ctx.UserName = user.Name
+		return nil
+	}
+
+	log.WithContext(s.Ctx).Debugf("登陆错误%s %s", username, pwd)
+	return errors.New("password error")
 }
 
 func (s *Session) Mail(from string, opts *smtp.MailOptions) error {
+	s.From = from
 	return nil
 }
 
 func (s *Session) Rcpt(to string) error {
+	s.To = append(s.To, to)
 	return nil
 }
 
@@ -53,6 +97,7 @@ func Start() {
 	instance.Addr = ":25"
 	instance.Domain = config.Instance.Domain
 	instance.ReadTimeout = 10 * time.Second
+	instance.AuthDisabled = false
 	instance.WriteTimeout = 10 * time.Second
 	instance.MaxMessageBytes = 1024 * 1024
 	instance.MaxRecipients = 50