|
@@ -4,7 +4,6 @@ import (
|
|
|
"crypto/tls"
|
|
"crypto/tls"
|
|
|
"crypto/x509"
|
|
"crypto/x509"
|
|
|
"errors"
|
|
"errors"
|
|
|
- "fmt"
|
|
|
|
|
"github.com/Jinnrry/pmail/config"
|
|
"github.com/Jinnrry/pmail/config"
|
|
|
"github.com/Jinnrry/pmail/dto/parsemail"
|
|
"github.com/Jinnrry/pmail/dto/parsemail"
|
|
|
"github.com/Jinnrry/pmail/models"
|
|
"github.com/Jinnrry/pmail/models"
|
|
@@ -28,7 +27,6 @@ type mxDomain struct {
|
|
|
func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string, user *models.User) error {
|
|
func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string, user *models.User) error {
|
|
|
|
|
|
|
|
log.WithContext(ctx).Debugf("开始转发邮件")
|
|
log.WithContext(ctx).Debugf("开始转发邮件")
|
|
|
- sender := fmt.Sprintf("%s@%s", user.Account, config.Instance.Domains[0])
|
|
|
|
|
|
|
|
|
|
b := e.ForwardBuildBytes(ctx, user)
|
|
b := e.ForwardBuildBytes(ctx, user)
|
|
|
|
|
|
|
@@ -39,91 +37,9 @@ func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string, us
|
|
|
{EmailAddress: forwardAddress},
|
|
{EmailAddress: forwardAddress},
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 按域名整理
|
|
|
|
|
- toByDomain := map[mxDomain][]*parsemail.User{}
|
|
|
|
|
- for _, s := range to {
|
|
|
|
|
- args := strings.Split(s.EmailAddress, "@")
|
|
|
|
|
- if len(args) == 2 {
|
|
|
|
|
- if args[1] == consts.TEST_DOMAIN {
|
|
|
|
|
- // 测试使用
|
|
|
|
|
- address := mxDomain{
|
|
|
|
|
- domain: "localhost",
|
|
|
|
|
- mxHost: "127.0.0.1",
|
|
|
|
|
- }
|
|
|
|
|
- toByDomain[address] = append(toByDomain[address], s)
|
|
|
|
|
- } else {
|
|
|
|
|
- //查询dns mx记录
|
|
|
|
|
- mxInfo, err := net.LookupMX(args[1])
|
|
|
|
|
- address := mxDomain{
|
|
|
|
|
- domain: "smtp." + args[1],
|
|
|
|
|
- mxHost: "smtp." + args[1],
|
|
|
|
|
- }
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- log.WithContext(ctx).Errorf(s.EmailAddress, "域名mx记录查询失败")
|
|
|
|
|
- }
|
|
|
|
|
- if len(mxInfo) > 0 {
|
|
|
|
|
- address = mxDomain{
|
|
|
|
|
- domain: args[1],
|
|
|
|
|
- mxHost: mxInfo[0].Host,
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- toByDomain[address] = append(toByDomain[address], s)
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- log.WithContext(ctx).Errorf("邮箱地址解析错误! %s", s)
|
|
|
|
|
- continue
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- var errEmailAddress []string
|
|
|
|
|
-
|
|
|
|
|
- as := async.New(ctx)
|
|
|
|
|
- for domain, tos := range toByDomain {
|
|
|
|
|
- domain := domain
|
|
|
|
|
- tos := tos
|
|
|
|
|
- as.WaitProcess(func(p any) {
|
|
|
|
|
- err := smtp.SendMail("", domain.mxHost+":25", nil, sender, config.Instance.Domains[0], buildAddress(tos), b)
|
|
|
|
|
-
|
|
|
|
|
- // 使用其他方式发送
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- // EOF 表示未知错误,此时降级为非tls连接发送(目前仅139邮箱有这个问题)
|
|
|
|
|
- if errors.Is(err, smtp.NoSupportSTARTTLSError) || err.Error() == "EOF" {
|
|
|
|
|
- err = smtp.SendMailWithTls("", domain.mxHost+":465", nil, sender, config.Instance.Domains[0], buildAddress(tos), b)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- log.WithContext(ctx).Warnf("Unsafe! %s Server Not Support SMTPS & STARTTLS", domain.domain)
|
|
|
|
|
- err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, sender, config.Instance.Domains[0], buildAddress(tos), b)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 证书错误,从新选取证书发送
|
|
|
|
|
- if certificateErr, ok := err.(*tls.CertificateVerificationError); ok {
|
|
|
|
|
- // 单测使用
|
|
|
|
|
- if domain.domain == "localhost" {
|
|
|
|
|
- err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, sender, config.Instance.Domains[0], buildAddress(tos), b)
|
|
|
|
|
- } else if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is {
|
|
|
|
|
- if hostnameErr.Certificate != nil {
|
|
|
|
|
- certificateHostName := hostnameErr.Certificate.DNSNames
|
|
|
|
|
- // 重新选取证书发送
|
|
|
|
|
- err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, sender, config.Instance.Domains[0], buildAddress(tos), b)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- log.WithContext(ctx).Errorf("%v 邮件投递失败%+v", tos, err)
|
|
|
|
|
- for _, user := range tos {
|
|
|
|
|
- errEmailAddress = append(errEmailAddress, user.EmailAddress)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }, nil)
|
|
|
|
|
- }
|
|
|
|
|
- as.Wait()
|
|
|
|
|
|
|
+ err, _ := doSend(ctx, config.Instance.Domains[0], b, to, e.From.EmailAddress)
|
|
|
|
|
|
|
|
- if len(errEmailAddress) > 0 {
|
|
|
|
|
- return errors.New("以下收件人投递失败:" + array.Join(errEmailAddress, ","))
|
|
|
|
|
- }
|
|
|
|
|
- return nil
|
|
|
|
|
|
|
+ return err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
|
|
func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
|
|
@@ -135,6 +51,12 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
|
|
|
var to []*parsemail.User
|
|
var to []*parsemail.User
|
|
|
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
|
|
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
|
|
|
|
|
|
|
|
|
|
+ return doSend(ctx, fromDomain, b, to, e.From.EmailAddress)
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func doSend(ctx *context.Context, fromDomain string, data []byte, to []*parsemail.User, from string) (error, map[string]error) {
|
|
|
|
|
+
|
|
|
// 按域名整理
|
|
// 按域名整理
|
|
|
toByDomain := map[mxDomain][]*parsemail.User{}
|
|
toByDomain := map[mxDomain][]*parsemail.User{}
|
|
|
for _, s := range to {
|
|
for _, s := range to {
|
|
@@ -181,33 +103,57 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
|
|
|
tos := tos
|
|
tos := tos
|
|
|
as.WaitProcess(func(p any) {
|
|
as.WaitProcess(func(p any) {
|
|
|
|
|
|
|
|
- err := smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
|
|
|
|
|
-
|
|
|
|
|
- // 使用其他方式发送
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- // EOF 表示未知错误,此时降级为非tls连接发送(目前仅139邮箱有这个问题)
|
|
|
|
|
- if errors.Is(err, smtp.NoSupportSTARTTLSError) || err.Error() == "EOF" {
|
|
|
|
|
- err = smtp.SendMailWithTls("", domain.mxHost+":465", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- log.WithContext(ctx).Warnf("Unsafe! %s Server Not Support SMTPS & STARTTLS", domain.domain)
|
|
|
|
|
- err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if domain.domain == "localhost" {
|
|
|
|
|
+ err := smtp.SendMailUnsafe("", domain.mxHost+":25", nil, from, fromDomain, buildAddress(tos), data)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ log.WithContext(ctx).Errorf("send error %s", err.Error())
|
|
|
}
|
|
}
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 证书错误,从新选取证书发送
|
|
|
|
|
- if certificateErr, ok := err.(*tls.CertificateVerificationError); ok {
|
|
|
|
|
- // 单测使用
|
|
|
|
|
- if domain.domain == "localhost" {
|
|
|
|
|
- err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
|
|
|
|
|
- } else if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is {
|
|
|
|
|
- if hostnameErr.Certificate != nil {
|
|
|
|
|
- certificateHostName := hostnameErr.Certificate.DNSNames
|
|
|
|
|
- // 重新选取证书发送
|
|
|
|
|
- err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 优先尝试25端口,starttls方式投递
|
|
|
|
|
+ err := smtp.SendMail("", domain.mxHost+":25", nil, from, fromDomain, buildAddress(tos), data)
|
|
|
|
|
+ if err == nil {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 证书错误,从新选取证书发送
|
|
|
|
|
+ var certificateErr *tls.CertificateVerificationError
|
|
|
|
|
+ if errors.As(err, &certificateErr) {
|
|
|
|
|
+ // 单测使用
|
|
|
|
|
+ var hostnameErr x509.HostnameError
|
|
|
|
|
+ if errors.As(certificateErr.Err, &hostnameErr) {
|
|
|
|
|
+ if hostnameErr.Certificate != nil {
|
|
|
|
|
+ certificateHostName := hostnameErr.Certificate.DNSNames
|
|
|
|
|
+ // 重新选取证书发送
|
|
|
|
|
+ err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, from, fromDomain, buildAddress(tos), data)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ if err == nil {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ log.WithContext(ctx).Infof("SMTP STARTTLS on 25 Send Error. %s", err.Error())
|
|
|
|
|
+
|
|
|
|
|
+ // 再试用587投递
|
|
|
|
|
+ err = smtp.SendMailWithTls("", domain.mxHost+":587", nil, from, fromDomain, buildAddress(tos), data)
|
|
|
|
|
+ if err == nil {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ log.WithContext(ctx).Infof("SMTPS on 587 Send Error. %s", err.Error())
|
|
|
|
|
+
|
|
|
|
|
+ // 再次尝试465投递
|
|
|
|
|
+ err = smtp.SendMailWithTls("", domain.mxHost+":465", nil, from, fromDomain, buildAddress(tos), data)
|
|
|
|
|
+ if err == nil {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ log.WithContext(ctx).Infof("SMTPS on 465 Send Error. %s", err.Error())
|
|
|
|
|
+
|
|
|
|
|
+ // 最后尝试非安全方式投递
|
|
|
|
|
+ err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, from, fromDomain, buildAddress(tos), data)
|
|
|
|
|
+ if err == nil {
|
|
|
|
|
+ log.WithContext(ctx).Warnf("Send By Unsafe SMTP")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
log.WithContext(ctx).Errorf("%v 邮件投递失败%+v", tos, err)
|
|
log.WithContext(ctx).Errorf("%v 邮件投递失败%+v", tos, err)
|
|
@@ -235,7 +181,6 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
|
|
|
return errors.New("以下收件人投递失败:" + array.Join(errEmailAddress, ",")), orgMap
|
|
return errors.New("以下收件人投递失败:" + array.Join(errEmailAddress, ",")), orgMap
|
|
|
}
|
|
}
|
|
|
return nil, orgMap
|
|
return nil, orgMap
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func buildAddress(u []*parsemail.User) []string {
|
|
func buildAddress(u []*parsemail.User) []string {
|