| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- 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/consts"
- "pmail/utils/context"
- "pmail/utils/smtp"
- "strings"
- "sync"
- )
- type mxDomain struct {
- domain string
- mxHost string
- }
- // Forward 转发邮件
- func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) error {
- _, fromDomain := e.From.GetDomainAccount()
- log.WithContext(ctx).Debugf("开始转发邮件")
- 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 {
- 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, 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 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)
- }
- }
- }
- }
- if err != nil {
- log.WithContext(ctx).Errorf("%v 邮件投递失败%+v", tos, err)
- for _, user := range tos {
- errEmailAddress = append(errEmailAddress, user.EmailAddress)
- }
- }
- }, 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) {
- _, fromDomain := e.From.GetDomainAccount()
- b := e.BuildBytes(ctx, true)
- log.WithContext(ctx).Debugf("Message Infos : %s", string(b))
- 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 {
- 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
- errMap := sync.Map{}
- 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, 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 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)
- }
- }
- }
- }
- if err != nil {
- log.WithContext(ctx).Errorf("%v 邮件投递失败%+v", tos, err)
- for _, user := range tos {
- errEmailAddress = append(errEmailAddress, user.EmailAddress)
- }
- }
- errMap.Store(domain.domain, err)
- }, nil)
- }
- as.Wait()
- orgMap := map[string]error{}
- errMap.Range(func(key, value any) bool {
- if value != nil {
- orgMap[key.(string)] = value.(error)
- } else {
- orgMap[key.(string)] = nil
- }
- return true
- })
- if len(errEmailAddress) > 0 {
- return errors.New("以下收件人投递失败:" + array.Join(errEmailAddress, ",")), orgMap
- }
- return nil, orgMap
- }
- 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 {
- if len(dnsNames) == 0 {
- return domain
- }
- 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], "*.", "")
- }
|