github.com/infraboard/keyauth@v0.8.1/apps/system/notify/mail/mail.go (about)

     1  package mail
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"net/mail"
     8  	"net/smtp"
     9  	"strings"
    10  
    11  	"github.com/infraboard/mcube/logger"
    12  	"github.com/infraboard/mcube/logger/zap"
    13  
    14  	"github.com/infraboard/keyauth/apps/system/notify"
    15  )
    16  
    17  // NewSender todo
    18  func NewSender(conf *Config) (notify.MailSender, error) {
    19  	if err := conf.Validate(); err != nil {
    20  		return nil, err
    21  	}
    22  
    23  	return &sender{
    24  		Config: conf,
    25  		log:    zap.L().Named("Mail Sender"),
    26  	}, nil
    27  }
    28  
    29  type sender struct {
    30  	*Config
    31  
    32  	log logger.Logger
    33  }
    34  
    35  // Send 发送邮件
    36  func (s *sender) Send(req *notify.SendMailRequest) error {
    37  	if err := req.Validate(); err != nil {
    38  		return fmt.Errorf("validate send mail request error, %s", err)
    39  	}
    40  
    41  	c, err := s.client()
    42  	if err != nil {
    43  		s.log.Errorf("new smtp client error, %s", err)
    44  		return err
    45  	}
    46  
    47  	// 设置发信人
    48  	from, err := mail.ParseAddress(s.From)
    49  	if err != nil {
    50  		return fmt.Errorf("parsing from addresses: %s", err)
    51  	}
    52  	if err := c.Mail(from.Address); err != nil {
    53  		return fmt.Errorf("sending mail from: %s", err)
    54  	}
    55  
    56  	// 设置收信人
    57  	toAddrs, err := mail.ParseAddressList(req.To)
    58  	if err != nil {
    59  		return fmt.Errorf("parsing to addresses: %s", err)
    60  	}
    61  	for _, addr := range toAddrs {
    62  		if err := c.Rcpt(addr.Address); err != nil {
    63  			return fmt.Errorf("sending rcpt to: %s", err)
    64  		}
    65  	}
    66  
    67  	msg, err := req.PrepareBody(s.From)
    68  	if err != nil {
    69  		return fmt.Errorf("parpare body error")
    70  	}
    71  
    72  	// 设置内容
    73  	s.log.Debugf("start issues a DATA command to the server ...")
    74  	w, err := c.Data()
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	s.log.Debugf("start send message to the server ...")
    80  	_, err = w.Write(msg)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	err = w.Close()
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	return c.Quit()
    91  }
    92  
    93  func (s *sender) auth(mechs string) (smtp.Auth, error) {
    94  	var (
    95  		errStr []string
    96  	)
    97  
    98  	for _, mech := range strings.Split(mechs, " ") {
    99  		switch mech {
   100  		case "CRAM-MD5":
   101  			if s.AuthSecret == "" {
   102  				errStr = append(errStr, "missing secret for CRAM-MD5 auth mechanism")
   103  				continue
   104  			}
   105  			return smtp.CRAMMD5Auth(s.AuthUserName, s.AuthSecret), nil
   106  		case "PLAIN":
   107  			if s.AuthPassword == "" {
   108  				errStr = append(errStr, "missing password for PLAIN auth mechanism")
   109  				continue
   110  			}
   111  			identity := s.AuthIdentity
   112  
   113  			// We need to know the hostname for both auth and TLS.
   114  			host, _, err := net.SplitHostPort(s.Host)
   115  			if err != nil {
   116  				return nil, fmt.Errorf("invalid address: %s", err)
   117  			}
   118  			return smtp.PlainAuth(identity, s.AuthUserName, s.AuthPassword, host), nil
   119  		case "LOGIN":
   120  			if s.AuthPassword == "" {
   121  				errStr = append(errStr, "missing password for LOGIN auth mechanism")
   122  				continue
   123  			}
   124  			return newLoginAuth(s.AuthUserName, s.AuthPassword), nil
   125  		case "NTLM":
   126  			errStr = append(errStr, "NTLM auth not impliment")
   127  		}
   128  
   129  	}
   130  
   131  	if len(errStr) == 0 {
   132  		errStr = append(errStr, "unknown auth mechanism: "+mechs)
   133  	}
   134  
   135  	return nil, errors.New(strings.Join(errStr, ","))
   136  }
   137  
   138  func (s *sender) client() (*smtp.Client, error) {
   139  	s.log.Debug("start new smtp client ...")
   140  
   141  	// We need to know the hostname for both auth and TLS.
   142  	var c *smtp.Client
   143  
   144  	host, port, err := net.SplitHostPort(s.Host)
   145  	if err != nil {
   146  		return nil, fmt.Errorf("invalid address: %s", err)
   147  	}
   148  
   149  	if port == "465" {
   150  		tlsConfig, err := s.TLSConfig.NewTLSConfig()
   151  		if err != nil {
   152  			return nil, err
   153  		}
   154  		if tlsConfig.ServerName == "" {
   155  			tlsConfig.ServerName = host
   156  		}
   157  
   158  		conn, err := s.TLSConfig.Connect(s.Host)
   159  		if err != nil {
   160  			return nil, err
   161  		}
   162  		c, err = smtp.NewClient(conn, host)
   163  		if err != nil {
   164  			return nil, err
   165  		}
   166  		s.log.Debug("new tls smtp client ok")
   167  	} else {
   168  		// Connect to the SMTP smarthost.
   169  		c, err = smtp.Dial(s.Host)
   170  		if err != nil {
   171  			return nil, err
   172  		}
   173  		s.log.Debug("new smtp client ok")
   174  	}
   175  
   176  	// 发送自定义Hello数据
   177  	if s.Hello != "" {
   178  		err := c.Hello(s.Hello)
   179  		if err != nil {
   180  			return nil, err
   181  		}
   182  	}
   183  
   184  	// Global Config guarantees RequireTLS is not nil.
   185  	if s.RequireTLS {
   186  		s.log.Debug("start STARTTLS ...")
   187  		if ok, _ := c.Extension("STARTTLS"); !ok {
   188  			return nil, fmt.Errorf("require_tls: true (default), but %q does not advertise the STARTTLS extension", s.Host)
   189  		}
   190  
   191  		tlsConf, err := s.TLSConfig.NewTLSConfig()
   192  		if err != nil {
   193  			return nil, err
   194  		}
   195  
   196  		if tlsConf.ServerName == "" {
   197  			tlsConf.ServerName = host
   198  		}
   199  
   200  		if err := c.StartTLS(tlsConf); err != nil {
   201  			return nil, fmt.Errorf("starttls failed: %s", err)
   202  		}
   203  		s.log.Debug("start STARTTLS ok")
   204  	}
   205  
   206  	// 根据服务器推荐的方式选择认证方式
   207  	if !s.SkipAuth {
   208  		s.log.Debug("start auth ...")
   209  		if ok, mech := c.Extension("AUTH"); ok {
   210  			auth, err := s.auth(mech)
   211  			if err != nil {
   212  				return nil, err
   213  			}
   214  
   215  			if auth == nil {
   216  				return nil, fmt.Errorf("auth mechanism is nil")
   217  			}
   218  
   219  			if err := c.Auth(auth); err != nil {
   220  				return nil, fmt.Errorf("%T failed: %s", auth, err)
   221  			}
   222  			s.log.Debug("auth ok")
   223  		}
   224  	}
   225  
   226  	s.log.Debug("new smtp client ok")
   227  	return c, nil
   228  }