Files
RenewCMS/internal/infrastructure/mailer/transport.go
T

127 lines
2.9 KiB
Go

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()
}