| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- package smtp
- import (
- "crypto/tls"
- "fmt"
- "log/slog"
- "net"
- "net/smtp"
- "net/url"
- "strconv"
- "strings"
- "time"
- "github.com/ncarlier/webhookd/pkg/helper"
- "github.com/ncarlier/webhookd/pkg/notification"
- )
- // smtpNotifier is able to send notification to a email destination.
- type smtpNotifier struct {
- Host string
- Username string
- Password string
- Conn string
- From string
- To string
- Subject string
- PrefixFilter string
- }
- func newSMTPNotifier(uri *url.URL) (notification.Notifier, error) {
- slog.Info("using SMTP notification system", "uri", uri.Redacted())
- q := uri.Query()
- return &smtpNotifier{
- Host: helper.GetValueOrAlt(q, "smtp", "localhost:25"),
- Username: helper.GetValueOrAlt(q, "username", ""),
- Password: helper.GetValueOrAlt(q, "password", ""),
- Conn: helper.GetValueOrAlt(q, "conn", "plain"),
- From: helper.GetValueOrAlt(q, "from", "noreply@nunux.org"),
- To: uri.Opaque,
- Subject: helper.GetValueOrAlt(uri.Query(), "subject", "[whd-notification] {name}#{id} {status}"),
- PrefixFilter: helper.GetValueOrAlt(uri.Query(), "prefix", "notify:"),
- }, nil
- }
- func (n *smtpNotifier) buildEmailPayload(result notification.HookResult) string {
- // Get email body
- body := result.Logs(n.PrefixFilter)
- if strings.TrimSpace(body) == "" {
- return ""
- }
- // Build email subject
- subject := buildSubject(n.Subject, result)
- // Build email headers
- headers := make(map[string]string)
- headers["From"] = n.From
- headers["To"] = n.To
- headers["Subject"] = subject
- // Build email payload
- payload := ""
- for k, v := range headers {
- payload += fmt.Sprintf("%s: %s\r\n", k, v)
- }
- payload += "\r\n" + body
- return payload
- }
- // Notify send a notification to a email destination.
- func (n *smtpNotifier) Notify(result notification.HookResult) error {
- hostname, _, _ := net.SplitHostPort(n.Host)
- payload := n.buildEmailPayload(result)
- if payload == "" {
- // Nothing to notify, abort
- return nil
- }
- // Dial connection
- conn, err := net.DialTimeout("tcp", n.Host, 5*time.Second)
- if err != nil {
- return err
- }
- // Connect to SMTP server
- client, err := smtp.NewClient(conn, hostname)
- if err != nil {
- return err
- }
- if n.Conn == "tls" || n.Conn == "tls-insecure" {
- // TLS config
- tlsConfig := &tls.Config{
- InsecureSkipVerify: n.Conn == "tls-insecure",
- ServerName: hostname,
- }
- if err := client.StartTLS(tlsConfig); err != nil {
- return err
- }
- }
- // Set auth if needed
- if n.Username != "" {
- if err := client.Auth(smtp.PlainAuth("", n.Username, n.Password, hostname)); err != nil {
- return err
- }
- }
- // Set the sender and recipient first
- if err := client.Mail(n.From); err != nil {
- return err
- }
- if err := client.Rcpt(n.To); err != nil {
- return err
- }
- // Send the email body.
- wc, err := client.Data()
- if err != nil {
- return err
- }
- _, err = wc.Write([]byte(payload))
- if err != nil {
- return err
- }
- err = wc.Close()
- if err != nil {
- return err
- }
- slog.Info("notification sent", "hook", result.Name(), "id", result.ID(), "to", n.To)
- // Send the QUIT command and close the connection.
- return client.Quit()
- }
- func buildSubject(template string, result notification.HookResult) string {
- subject := strings.ReplaceAll(template, "{name}", result.Name())
- subject = strings.ReplaceAll(subject, "{id}", strconv.FormatUint(uint64(result.ID()), 10))
- subject = strings.ReplaceAll(subject, "{status}", result.StatusLabel())
- return subject
- }
- func init() {
- notification.Register("mailto", newSMTPNotifier)
- }
|