Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 58 additions & 11 deletions agent/utils/email/smtp_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,48 @@ import (
"time"
)

// SMTPConfig holds SMTP connection info
type SMTPConfig struct {
Host string
Port int // 465, 587, 25
Port int
Username string
Password string
From string
Encryption string // "ssl", "starttls", "none"
Encryption string
Recipient string
}

// EmailMessage represents an email
type EmailMessage struct {
Subject string
Body string // HTML or plain text
Body string
IsHTML bool
}

type loginAuth struct {
username, password string
}

func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}

func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte{}, nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, fmt.Errorf("unknown server challenge: %s", fromServer)
}
}
return nil, nil
}

func SendMail(config SMTPConfig, message EmailMessage, transport *http.Transport) error {
if err := validateConfig(config); err != nil {
return err
Expand Down Expand Up @@ -147,7 +171,9 @@ func sendWithSSL(config SMTPConfig, addr string, toList []string, msg string, tr
return fmt.Errorf("failed to create SMTP client: %w", err)
}
defer client.Quit()

if err := tryAuth(client, config.Username, config.Password, config.Host); err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
return sendEmailWithClient(client, config, toList, msg)
}

Expand All @@ -173,7 +199,9 @@ func sendWithStartTLS(config SMTPConfig, addr string, toList []string, msg strin
if err = client.StartTLS(&tls.Config{ServerName: config.Host}); err != nil {
return fmt.Errorf("failed to start TLS: %w", err)
}

if err := tryAuth(client, config.Username, config.Password, config.Host); err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
return sendEmailWithClient(client, config, toList, msg)
}

Expand All @@ -199,10 +227,6 @@ func sendPlaintext(config SMTPConfig, addr string, toList []string, msg string,
}

func sendEmailWithClient(client *smtp.Client, config SMTPConfig, toList []string, msg string) error {
auth := smtp.PlainAuth("", config.Username, config.Password, config.Host)
if err := client.Auth(auth); err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
if err := client.Mail(config.Username); err != nil {
return fmt.Errorf("setting sender failed: %w", err)
}
Expand All @@ -223,3 +247,26 @@ func sendEmailWithClient(client *smtp.Client, config SMTPConfig, toList []string

return nil
}

func tryAuth(client *smtp.Client, username, password, host string) error {
ok, authCap := client.Extension("AUTH")
if !ok {
return fmt.Errorf("server does not support AUTH")
}
authCap = strings.ToUpper(authCap)
if strings.Contains(authCap, "PLAIN") {
auth := smtp.PlainAuth("", username, password, host)
if err := client.Auth(auth); err != nil {
return fmt.Errorf("plain auth failed: %w", err)
}
return nil
}
if strings.Contains(authCap, "LOGIN") {
if err := client.Auth(LoginAuth(username, password)); err != nil {
return fmt.Errorf("login auth failed: %w", err)
}
return nil
}

return fmt.Errorf("no supported auth mechanism, server supports: %s", authCap)
}
Loading