Skip to content

Commit

Permalink
smtp协议完事
Browse files Browse the repository at this point in the history
  • Loading branch information
Jinnrry committed Nov 10, 2023
1 parent 13f6570 commit 68de8f6
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 20 deletions.
5 changes: 5 additions & 0 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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表示用户上传证书
Expand Down Expand Up @@ -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{}
Expand Down
24 changes: 21 additions & 3 deletions server/dto/parsemail/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"))
Expand Down
6 changes: 3 additions & 3 deletions server/dto/parsemail/email_decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestDecodeEmailContentFromTxt(t *testing.T) {

r := strings.NewReader(string(c))

email := NewEmailFromReader(r)
email := NewEmailFromReader(nil, r)

fmt.Println(email)
}
Expand All @@ -24,7 +24,7 @@ func TestDecodeEmailContentFromTxt3(t *testing.T) {

r := strings.NewReader(string(c))

email := NewEmailFromReader(r)
email := NewEmailFromReader(nil, r)

fmt.Println(email)
}
Expand All @@ -34,7 +34,7 @@ func TestDecodeEmailContentFromTxt2(t *testing.T) {

r := strings.NewReader(string(c))

email := NewEmailFromReader(r)
email := NewEmailFromReader(nil, r)

fmt.Println(email)

Expand Down
11 changes: 11 additions & 0 deletions server/pop3_server/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down
46 changes: 34 additions & 12 deletions server/smtp_server/read_content.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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),
Expand Down
49 changes: 47 additions & 2 deletions server/smtp_server/smtp.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,82 @@ 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"
)

// The Backend implements SMTP server methods.
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
}

Expand All @@ -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
Expand Down

0 comments on commit 68de8f6

Please sign in to comment.