127 lines
2.9 KiB
Go
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()
|
|
}
|