tendermint/alert/email.go

179 lines
4.2 KiB
Go

// Forked from github.com/SlyMarbo/gmail
package alert
import (
"bytes"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net/smtp"
"path/filepath"
"regexp"
"strings"
"github.com/tendermint/tendermint/config"
)
// Convenience function
func SendEmail(subject, body string, tos []string) error {
email := Compose(subject, body)
email.From = config.App().GetString("SMTP.User")
email.ContentType = "text/html; charset=utf-8"
email.AddRecipients(tos...)
err := email.Send()
return err
}
// Email represents a single message, which may contain
// attachments.
type Email struct {
From string
To []string
Subject string
ContentType string
Body string
Attachments map[string][]byte
}
// Compose begins a new email, filling the subject and body,
// and allocating memory for the list of recipients and the
// attachments.
func Compose(Subject, Body string) *Email {
out := new(Email)
out.To = make([]string, 0, 1)
out.Subject = Subject
out.Body = Body
out.Attachments = make(map[string][]byte)
return out
}
// Attach takes a filename and adds this to the message.
// Note that since only the filename is stored (and not
// its path, for privacy reasons), multiple files in
// different directories but with the same filename and
// extension cannot be sent.
func (e *Email) Attach(Filename string) error {
b, err := ioutil.ReadFile(Filename)
if err != nil {
return err
}
_, fname := filepath.Split(Filename)
e.Attachments[fname] = b
return nil
}
// AddRecipient adds a single recipient.
func (e *Email) AddRecipient(Recipient string) {
e.To = append(e.To, Recipient)
}
// AddRecipients adds one or more recipients.
func (e *Email) AddRecipients(Recipients ...string) {
e.To = append(e.To, Recipients...)
}
// Send sends the email, returning any error encountered.
func (e *Email) Send() error {
if e.From == "" {
return errors.New("Error: No sender specified. Please set the Email.From field.")
}
if e.To == nil || len(e.To) == 0 {
return errors.New("Error: No recipient specified. Please set the Email.To field.")
}
auth := smtp.PlainAuth(
"",
config.App().GetString("SMTP.User"),
config.App().GetString("SMTP.Password"),
config.App().GetString("SMTP.Host"),
)
conn, err := smtp.Dial(fmt.Sprintf("%v:%v", config.App().GetString("SMTP.Host"), config.App().GetString("SMTP.Port")))
if err != nil {
return err
}
err = conn.StartTLS(&tls.Config{})
if err != nil {
return err
}
err = conn.Auth(auth)
if err != nil {
return err
}
err = conn.Mail(e.From)
if err != nil {
if strings.Contains(err.Error(), "530 5.5.1") {
return errors.New("Error: Authentication failure. Your username or password is incorrect.")
}
return err
}
for _, recipient := range e.To {
err = conn.Rcpt(recipient)
if err != nil {
return err
}
}
wc, err := conn.Data()
if err != nil {
return err
}
defer wc.Close()
_, err = wc.Write(e.Bytes())
if err != nil {
return err
}
return nil
}
func (e *Email) Bytes() []byte {
buf := bytes.NewBuffer(nil)
var subject = e.Subject
subject = regexp.MustCompile("\n+").ReplaceAllString(subject, " ")
subject = regexp.MustCompile(" +").ReplaceAllString(subject, " ")
buf.WriteString("Subject: " + subject + "\n")
buf.WriteString("To: <" + strings.Join(e.To, ">,<") + ">\n")
buf.WriteString("MIME-Version: 1.0\n")
// Boundary is used by MIME to separate files.
boundary := "f46d043c813270fc6b04c2d223da"
if len(e.Attachments) > 0 {
buf.WriteString("Content-Type: multipart/mixed; boundary=" + boundary + "\n")
buf.WriteString("--" + boundary + "\n")
}
if e.ContentType == "" {
e.ContentType = "text/plain; charset=utf-8"
}
buf.WriteString(fmt.Sprintf("Content-Type: %s\n\n", e.ContentType))
buf.WriteString(e.Body)
if len(e.Attachments) > 0 {
for k, v := range e.Attachments {
buf.WriteString("\n\n--" + boundary + "\n")
buf.WriteString("Content-Type: application/octet-stream\n")
buf.WriteString("Content-Transfer-Encoding: base64\n")
buf.WriteString("Content-Disposition: attachment; filename=\"" + k + "\"\n\n")
b := make([]byte, base64.StdEncoding.EncodedLen(len(v)))
base64.StdEncoding.Encode(b, v)
buf.Write(b)
buf.WriteString("\n--" + boundary)
}
buf.WriteString("--")
}
return buf.Bytes()
}