diff --git a/README.md b/README.md index f1e3cb2..a52f710 100644 --- a/README.md +++ b/README.md @@ -263,7 +263,10 @@ Options (using query parameters): - `prefix`: Prefix to filter output log - `smtp`: SMTP host to use (by default: `localhost:25`) -- `from`: Sender email (by default: `webhookd `) +- `username`: SMTP username (not set by default) +- `password`: SMTP password (not set by default) +- `conn`: SMTP connection type (`tls`, `tls-insecure` or by default: `plain`) +- `from`: Sender email (by default: `noreply@nunux.org`) ### Authentication diff --git a/pkg/notification/smtp_notifier.go b/pkg/notification/smtp_notifier.go index ebd6124..1a4da5f 100644 --- a/pkg/notification/smtp_notifier.go +++ b/pkg/notification/smtp_notifier.go @@ -1,73 +1,124 @@ package notification import ( + "crypto/tls" "fmt" - "log" + "net" "net/smtp" "net/url" "strings" + "time" "github.com/ncarlier/webhookd/pkg/logger" "github.com/ncarlier/webhookd/pkg/model" ) -// SMTPNotifier is able to send notifcation to a email destination. +// 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 PrefixFilter string } func newSMTPNotifier(uri *url.URL) *SMTPNotifier { - logger.Info.Println("using SMTP notification system: ", uri.Opaque) + logger.Info.Println("using SMTP notification system:", uri.Opaque) + q := uri.Query() return &SMTPNotifier{ - Host: getValueOrAlt(uri.Query(), "smtp", "localhost:25"), - From: getValueOrAlt(uri.Query(), "from", "noreply@nunux.org"), + Host: getValueOrAlt(q, "smtp", "localhost:25"), + Username: getValueOrAlt(q, "username", ""), + Password: getValueOrAlt(q, "password", ""), + Conn: getValueOrAlt(q, "conn", "plain"), + From: getValueOrAlt(q, "from", "noreply@nunux.org"), To: uri.Opaque, - PrefixFilter: getValueOrAlt(uri.Query(), "prefix", "notify:"), + PrefixFilter: getValueOrAlt(q, "prefix", "notify:"), } } -// Notify send a notification to a email destination. -func (n *SMTPNotifier) Notify(work *model.WorkRequest) error { +func (n *SMTPNotifier) buildEmailPayload(work *model.WorkRequest) string { // Get email body - payload := work.GetLogContent(n.PrefixFilter) - if strings.TrimSpace(payload) == "" { - // Nothing to notify, abort - return nil + body := work.GetLogContent(n.PrefixFilter) + if strings.TrimSpace(body) == "" { + return "" } - - // Buidl subject + // Get email subject var subject string if work.Status == model.Success { subject = fmt.Sprintf("Webhook %s#%d SUCCESS.", work.Name, work.ID) } else { subject = fmt.Sprintf("Webhook %s#%d FAILED.", work.Name, work.ID) } + // Build email headers + headers := make(map[string]string) + headers["From"] = n.From + headers["To"] = n.To + headers["Subject"] = subject - // Connect to the remote SMTP server. - c, err := smtp.Dial(n.Host) + // 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(work *model.WorkRequest) error { + hostname, _, _ := net.SplitHostPort(n.Host) + payload := n.buildEmailPayload(work) + 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 := c.Mail(n.From); err != nil { + if err := client.Mail(n.From); err != nil { return err } - if err := c.Rcpt(n.To); err != nil { - log.Println(err) + if err := client.Rcpt(n.To); err != nil { return err } // Send the email body. - wc, err := c.Data() + wc, err := client.Data() if err != nil { return err } - _, err = fmt.Fprintf(wc, "Subject: %s\r\n\r\n%s\r\n\r\n", subject, payload) + _, err = wc.Write([]byte(payload)) if err != nil { return err } @@ -79,5 +130,5 @@ func (n *SMTPNotifier) Notify(work *model.WorkRequest) error { logger.Info.Printf("job %s#%d notification sent to %s\n", work.Name, work.ID, n.To) // Send the QUIT command and close the connection. - return c.Quit() + return client.Quit() } diff --git a/pkg/worker/worker.go b/pkg/worker/worker.go index 59bf32e..ad4a1ee 100644 --- a/pkg/worker/worker.go +++ b/pkg/worker/worker.go @@ -50,7 +50,7 @@ func (w Worker) Start() { work.MessageChan <- []byte(fmt.Sprintf("error: %s", err.Error())) } // Send notification - notification.Notify(&work) + go notification.Notify(&work) close(work.MessageChan) case <-w.QuitChan: