package mailer import ( "RenewCMS/internal/infrastructure/config" "crypto/tls" "errors" "net" "net/smtp" "strconv" "time" ) const ( implicitTLSPort = 465 dialTimeout = 10 * time.Second sessionTimeout = 30 * time.Second ) type transport struct { settings config.SmtpSettings tlsConfig *tls.Config } func newTransport(settings config.SmtpSettings, tlsConfig *tls.Config) transport { if tlsConfig == nil { tlsConfig = &tls.Config{} } tlsConfig = tlsConfig.Clone() if tlsConfig.ServerName == "" { tlsConfig.ServerName = settings.Host } if tlsConfig.MinVersion == 0 { tlsConfig.MinVersion = tls.VersionTLS12 } return transport{settings: settings, tlsConfig: tlsConfig} } func (t transport) deliver(sender string, receiver string, payload []byte) error { client, err := t.connect() if err != nil { return err } defer func() { _ = client.Close() }() if err := t.negotiate(client); err != nil { return err } if err := client.Mail(sender); err != nil { return err } if err := client.Rcpt(receiver); err != nil { return err } if err := writeData(client, payload); err != nil { return err } return client.Quit() } func (t transport) connect() (*smtp.Client, error) { address := net.JoinHostPort(t.settings.Host, strconv.Itoa(t.settings.Port)) dialer := &net.Dialer{Timeout: dialTimeout} connection, err := dialer.Dial("tcp", address) if err != nil { return nil, err } if err := connection.SetDeadline(time.Now().Add(sessionTimeout)); err != nil { _ = connection.Close() return nil, err } if t.settings.Port == implicitTLSPort { connection = tls.Client(connection, t.tlsConfig) } client, err := smtp.NewClient(connection, t.settings.Host) if err != nil { _ = connection.Close() return nil, err } return client, nil } var errTLSUnavailable = errors.New("smtp server does not offer STARTTLS; set SMTP_ALLOW_INSECURE=true to send without encryption") func (t transport) negotiate(client *smtp.Client) error { if t.settings.Port != implicitTLSPort { if err := t.startTLS(client); err != nil { return err } } return t.authenticate(client) } func (t transport) startTLS(client *smtp.Client) error { if supported, _ := client.Extension("STARTTLS"); supported { return client.StartTLS(t.tlsConfig) } if t.settings.AllowInsecure || isLoopback(t.settings.Host) { return nil } return errTLSUnavailable } func (t transport) authenticate(client *smtp.Client) error { if t.settings.Login == "" { return nil } advertised, mechanisms := client.Extension("AUTH") if !advertised { return errors.New("smtp server does not support authentication") } return client.Auth(selectAuth(mechanisms, t.settings)) } func writeData(client *smtp.Client, payload []byte) error { writer, err := client.Data() if err != nil { return err } if _, err := writer.Write(payload); err != nil { _ = writer.Close() return err } return writer.Close() }