Smail/server/utils/send/send.go
2023-09-08 21:18:38 +08:00

276 lines
7.1 KiB
Go
Raw 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 send
import (
"crypto/tls"
"crypto/x509"
"errors"
log "github.com/sirupsen/logrus"
"net"
"pmail/dto/parsemail"
"pmail/utils/array"
"pmail/utils/async"
"pmail/utils/context"
"pmail/utils/smtp"
"strings"
)
type mxDomain struct {
domain string
mxHost string
}
// Forward 转发邮件
func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) error {
b := e.ForwardBuildBytes(ctx, forwardAddress)
var to []*parsemail.User
to = []*parsemail.User{
{EmailAddress: forwardAddress},
}
// 按域名整理
toByDomain := map[mxDomain][]*parsemail.User{}
for _, s := range to {
args := strings.Split(s.EmailAddress, "@")
if len(args) == 2 {
//查询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
errMap := map[string]error{}
as := async.New(ctx)
for domain, tos := range toByDomain {
domain := domain
tos := tos
as.WaitProcess(func(p any) {
// 先使用smtps协议尝试
err := smtp.SendMailWithTls("", domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTPS Send Error! Error:%+v", err)
// smtps发送失败,尝试smtp
err = smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTP Send Error! Error:%+v", err)
}
}
// 重新选取证书域名
if err != nil {
if certificateErr, ok := err.(*tls.CertificateVerificationError); ok {
if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is {
if hostnameErr.Certificate != nil {
certificateHostName := hostnameErr.Certificate.DNSNames
// 先使用smtps协议尝试
err = smtp.SendMailWithTls(domainMatch(domain.domain, certificateHostName), domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTPS Send Error! Error:%+v", err)
// smtps发送失败,尝试smtp
err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTP Send Error! Error:%+v", err)
}
}
}
}
}
}
if err != nil {
log.WithContext(ctx).Errorf("%v 邮件投递失败%+v", tos, err)
for _, user := range tos {
errEmailAddress = append(errEmailAddress, user.EmailAddress)
}
}
errMap[domain.domain] = err
}, nil)
}
as.Wait()
if len(errEmailAddress) > 0 {
return errors.New("以下收件人投递失败:" + array.Join(errEmailAddress, ","))
}
return nil
}
func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
b := e.BuildBytes(ctx)
var to []*parsemail.User
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
// 按域名整理
toByDomain := map[mxDomain][]*parsemail.User{}
for _, s := range to {
args := strings.Split(s.EmailAddress, "@")
if len(args) == 2 {
//查询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
errMap := map[string]error{}
as := async.New(ctx)
for domain, tos := range toByDomain {
domain := domain
tos := tos
as.WaitProcess(func(p any) {
// 先使用smtps协议尝试
err := smtp.SendMailWithTls("", domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTPS Send Error! Error:%+v", err)
// smtps发送失败,尝试smtp
err = smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTP Send Error! Error:%+v", err)
}
}
// 重新选取证书域名
if err != nil {
if certificateErr, ok := err.(*tls.CertificateVerificationError); ok {
if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is {
if hostnameErr.Certificate != nil {
certificateHostName := hostnameErr.Certificate.DNSNames
// 先使用smtps协议尝试
err = smtp.SendMailWithTls(domainMatch(domain.domain, certificateHostName), domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTPS Send Error! Error:%+v", err)
// smtps发送失败,尝试smtp
err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Errorf("SMTP Send Error! Error:%+v", err)
}
}
}
}
}
}
if err != nil {
log.WithContext(ctx).Errorf("%v 邮件投递失败%+v", tos, err)
for _, user := range tos {
errEmailAddress = append(errEmailAddress, user.EmailAddress)
}
}
errMap[domain.domain] = err
}, nil)
}
as.Wait()
if len(errEmailAddress) > 0 {
return errors.New("以下收件人投递失败:" + array.Join(errEmailAddress, ",")), errMap
}
return nil, errMap
}
func buildAddress(u []*parsemail.User) []string {
var ret []string
for _, user := range u {
ret = append(ret, user.EmailAddress)
}
return ret
}
func domainMatch(domain string, dnsNames []string) string {
secondMatch := ""
for _, name := range dnsNames {
if strings.Contains(name, "smtp") {
secondMatch = name
}
if name == domain {
return name
}
if strings.Contains(name, "*") {
nameArg := strings.Split(name, ".")
domainArg := strings.Split(domain, ".")
match := true
for i := 0; i < len(nameArg); i++ {
if nameArg[len(nameArg)-1-i] == "*" {
continue
}
if len(domainArg) > i {
if nameArg[len(nameArg)-1-i] == domainArg[len(domainArg)-1-i] {
continue
}
}
match = false
break
}
for i := 0; i < len(domainArg); i++ {
if len(nameArg) > i && nameArg[len(nameArg)-1-i] == domainArg[len(domainArg)-1-i] {
continue
}
if len(nameArg) > i && nameArg[len(nameArg)-1-i] == "*" {
continue
}
match = false
break
}
if match {
return domain
}
}
}
if secondMatch != "" {
return strings.ReplaceAll(secondMatch, "*.", "")
}
return strings.ReplaceAll(dnsNames[0], "*.", "")
}