You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

178 lines
4.2 KiB

  1. // Forked from github.com/SlyMarbo/gmail
  2. package alert
  3. import (
  4. "bytes"
  5. "crypto/tls"
  6. "encoding/base64"
  7. "errors"
  8. "fmt"
  9. "io/ioutil"
  10. "net/smtp"
  11. "path/filepath"
  12. "regexp"
  13. "strings"
  14. "github.com/tendermint/tendermint/config"
  15. )
  16. // Convenience function
  17. func SendEmail(subject, body string, tos []string) error {
  18. email := Compose(subject, body)
  19. email.From = config.App().GetString("smtp_user")
  20. email.ContentType = "text/html; charset=utf-8"
  21. email.AddRecipients(tos...)
  22. err := email.Send()
  23. return err
  24. }
  25. // Email represents a single message, which may contain
  26. // attachments.
  27. type Email struct {
  28. From string
  29. To []string
  30. Subject string
  31. ContentType string
  32. Body string
  33. Attachments map[string][]byte
  34. }
  35. // Compose begins a new email, filling the subject and body,
  36. // and allocating memory for the list of recipients and the
  37. // attachments.
  38. func Compose(Subject, Body string) *Email {
  39. out := new(Email)
  40. out.To = make([]string, 0, 1)
  41. out.Subject = Subject
  42. out.Body = Body
  43. out.Attachments = make(map[string][]byte)
  44. return out
  45. }
  46. // Attach takes a filename and adds this to the message.
  47. // Note that since only the filename is stored (and not
  48. // its path, for privacy reasons), multiple files in
  49. // different directories but with the same filename and
  50. // extension cannot be sent.
  51. func (e *Email) Attach(Filename string) error {
  52. b, err := ioutil.ReadFile(Filename)
  53. if err != nil {
  54. return err
  55. }
  56. _, fname := filepath.Split(Filename)
  57. e.Attachments[fname] = b
  58. return nil
  59. }
  60. // AddRecipient adds a single recipient.
  61. func (e *Email) AddRecipient(Recipient string) {
  62. e.To = append(e.To, Recipient)
  63. }
  64. // AddRecipients adds one or more recipients.
  65. func (e *Email) AddRecipients(Recipients ...string) {
  66. e.To = append(e.To, Recipients...)
  67. }
  68. // Send sends the email, returning any error encountered.
  69. func (e *Email) Send() error {
  70. if e.From == "" {
  71. return errors.New("Error: No sender specified. Please set the Email.From field.")
  72. }
  73. if e.To == nil || len(e.To) == 0 {
  74. return errors.New("Error: No recipient specified. Please set the Email.To field.")
  75. }
  76. auth := smtp.PlainAuth(
  77. "",
  78. config.App().GetString("smtp_user"),
  79. config.App().GetString("smtp_password"),
  80. config.App().GetString("smtp_host"),
  81. )
  82. conn, err := smtp.Dial(fmt.Sprintf("%v:%v", config.App().GetString("smtp_host"), config.App().GetString("smtp_port")))
  83. if err != nil {
  84. return err
  85. }
  86. err = conn.StartTLS(&tls.Config{})
  87. if err != nil {
  88. return err
  89. }
  90. err = conn.Auth(auth)
  91. if err != nil {
  92. return err
  93. }
  94. err = conn.Mail(e.From)
  95. if err != nil {
  96. if strings.Contains(err.Error(), "530 5.5.1") {
  97. return errors.New("Error: Authentication failure. Your username or password is incorrect.")
  98. }
  99. return err
  100. }
  101. for _, recipient := range e.To {
  102. err = conn.Rcpt(recipient)
  103. if err != nil {
  104. return err
  105. }
  106. }
  107. wc, err := conn.Data()
  108. if err != nil {
  109. return err
  110. }
  111. defer wc.Close()
  112. _, err = wc.Write(e.Bytes())
  113. if err != nil {
  114. return err
  115. }
  116. return nil
  117. }
  118. func (e *Email) Bytes() []byte {
  119. buf := bytes.NewBuffer(nil)
  120. var subject = e.Subject
  121. subject = regexp.MustCompile("\n+").ReplaceAllString(subject, " ")
  122. subject = regexp.MustCompile(" +").ReplaceAllString(subject, " ")
  123. buf.WriteString("Subject: " + subject + "\n")
  124. buf.WriteString("To: <" + strings.Join(e.To, ">,<") + ">\n")
  125. buf.WriteString("MIME-Version: 1.0\n")
  126. // Boundary is used by MIME to separate files.
  127. boundary := "f46d043c813270fc6b04c2d223da"
  128. if len(e.Attachments) > 0 {
  129. buf.WriteString("Content-Type: multipart/mixed; boundary=" + boundary + "\n")
  130. buf.WriteString("--" + boundary + "\n")
  131. }
  132. if e.ContentType == "" {
  133. e.ContentType = "text/plain; charset=utf-8"
  134. }
  135. buf.WriteString(fmt.Sprintf("Content-Type: %s\n\n", e.ContentType))
  136. buf.WriteString(e.Body)
  137. if len(e.Attachments) > 0 {
  138. for k, v := range e.Attachments {
  139. buf.WriteString("\n\n--" + boundary + "\n")
  140. buf.WriteString("Content-Type: application/octet-stream\n")
  141. buf.WriteString("Content-Transfer-Encoding: base64\n")
  142. buf.WriteString("Content-Disposition: attachment; filename=\"" + k + "\"\n\n")
  143. b := make([]byte, base64.StdEncoding.EncodedLen(len(v)))
  144. base64.StdEncoding.Encode(b, v)
  145. buf.Write(b)
  146. buf.WriteString("\n--" + boundary)
  147. }
  148. buf.WriteString("--")
  149. }
  150. return buf.Bytes()
  151. }