fix(server): SMTP dispatch dial timeout + implicit/STARTTLS handling
Server Deploy / deploy (push) Successful in 2m19s
Server Deploy / deploy (push) Successful in 2m19s
This commit is contained in:
@@ -1,15 +1,25 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mrhid6/vantage/server/internal/models"
|
||||
)
|
||||
|
||||
const smtpTimeout = 15 * time.Second
|
||||
|
||||
// dispatchSMTP sends the alert as a plain-text email. Config keys: host, port,
|
||||
// username, password, from, to. Auth is skipped when username is empty.
|
||||
// username, password, from, to. Auth is skipped when username is empty. Port 465
|
||||
// uses implicit TLS; other ports use STARTTLS when the server advertises it.
|
||||
//
|
||||
// It dials with a timeout and sets a connection deadline so an unreachable or
|
||||
// misconfigured SMTP host fails fast instead of hanging the request until the OS
|
||||
// TCP timeout (which resets the upstream proxy connection).
|
||||
func dispatchSMTP(ch models.NotificationChannel, ev Event) error {
|
||||
host := ch.Config["host"]
|
||||
port := ch.Config["port"]
|
||||
@@ -19,6 +29,56 @@ func dispatchSMTP(ch models.NotificationChannel, ev Event) error {
|
||||
return fmt.Errorf("smtp: missing host/port/from/to")
|
||||
}
|
||||
|
||||
addr := net.JoinHostPort(host, port)
|
||||
conn, err := net.DialTimeout("tcp", addr, smtpTimeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("smtp: dial %s: %w", addr, err)
|
||||
}
|
||||
_ = conn.SetDeadline(time.Now().Add(smtpTimeout))
|
||||
|
||||
// Implicit TLS on 465; otherwise start plain and upgrade via STARTTLS.
|
||||
if port == "465" {
|
||||
conn = tls.Client(conn, &tls.Config{ServerName: host})
|
||||
}
|
||||
|
||||
c, err := smtp.NewClient(conn, host)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return fmt.Errorf("smtp: client: %w", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
if port != "465" {
|
||||
if ok, _ := c.Extension("STARTTLS"); ok {
|
||||
if err := c.StartTLS(&tls.Config{ServerName: host}); err != nil {
|
||||
return fmt.Errorf("smtp: starttls: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if user := ch.Config["username"]; user != "" {
|
||||
if err := c.Auth(smtp.PlainAuth("", user, ch.Config["password"], host)); err != nil {
|
||||
return fmt.Errorf("smtp: auth: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
recipients := strings.Split(to, ",")
|
||||
for i := range recipients {
|
||||
recipients[i] = strings.TrimSpace(recipients[i])
|
||||
}
|
||||
|
||||
if err := c.Mail(from); err != nil {
|
||||
return fmt.Errorf("smtp: mail from: %w", err)
|
||||
}
|
||||
for _, rcpt := range recipients {
|
||||
if rcpt == "" {
|
||||
continue
|
||||
}
|
||||
if err := c.Rcpt(rcpt); err != nil {
|
||||
return fmt.Errorf("smtp: rcpt %s: %w", rcpt, err)
|
||||
}
|
||||
}
|
||||
|
||||
title := ev.title()
|
||||
msg := strings.Join([]string{
|
||||
"From: " + from,
|
||||
@@ -32,14 +92,15 @@ func dispatchSMTP(ch models.NotificationChannel, ev Event) error {
|
||||
"Time: " + ev.Time.String(),
|
||||
}, "\r\n")
|
||||
|
||||
var auth smtp.Auth
|
||||
if user := ch.Config["username"]; user != "" {
|
||||
auth = smtp.PlainAuth("", user, ch.Config["password"], host)
|
||||
w, err := c.Data()
|
||||
if err != nil {
|
||||
return fmt.Errorf("smtp: data: %w", err)
|
||||
}
|
||||
addr := host + ":" + port
|
||||
recipients := strings.Split(to, ",")
|
||||
for i := range recipients {
|
||||
recipients[i] = strings.TrimSpace(recipients[i])
|
||||
if _, err := w.Write([]byte(msg)); err != nil {
|
||||
return fmt.Errorf("smtp: write: %w", err)
|
||||
}
|
||||
return smtp.SendMail(addr, auth, from, recipients, []byte(msg))
|
||||
if err := w.Close(); err != nil {
|
||||
return fmt.Errorf("smtp: close data: %w", err)
|
||||
}
|
||||
return c.Quit()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user