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.

282 lines
6.5 KiB

  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package mailer
  5. import (
  6. "crypto/tls"
  7. "fmt"
  8. "io"
  9. "net"
  10. "net/smtp"
  11. "os"
  12. "os/exec"
  13. "strings"
  14. "time"
  15. "github.com/jaytaylor/html2text"
  16. "gopkg.in/gomail.v2"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/setting"
  19. )
  20. // Message mail body and log info
  21. type Message struct {
  22. Info string // Message information for log purpose.
  23. *gomail.Message
  24. }
  25. // NewMessageFrom creates new mail message object with custom From header.
  26. func NewMessageFrom(to []string, from, subject, htmlBody string) *Message {
  27. log.Trace("NewMessageFrom (htmlBody):\n%s", htmlBody)
  28. msg := gomail.NewMessage()
  29. msg.SetHeader("From", from)
  30. msg.SetHeader("To", to...)
  31. msg.SetHeader("Subject", subject)
  32. msg.SetDateHeader("Date", time.Now())
  33. body, err := html2text.FromString(htmlBody)
  34. if err != nil {
  35. log.Error(4, "html2text.FromString: %v", err)
  36. msg.SetBody("text/html", htmlBody)
  37. } else {
  38. msg.SetBody("text/plain", body)
  39. if setting.MailService.EnableHTMLAlternative {
  40. msg.AddAlternative("text/html", htmlBody)
  41. }
  42. }
  43. return &Message{
  44. Message: msg,
  45. }
  46. }
  47. // NewMessage creates new mail message object with default From header.
  48. func NewMessage(to []string, subject, body string) *Message {
  49. return NewMessageFrom(to, setting.MailService.From, subject, body)
  50. }
  51. type loginAuth struct {
  52. username, password string
  53. }
  54. // LoginAuth SMTP AUTH LOGIN Auth Handler
  55. func LoginAuth(username, password string) smtp.Auth {
  56. return &loginAuth{username, password}
  57. }
  58. // Start start SMTP login auth
  59. func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
  60. return "LOGIN", []byte{}, nil
  61. }
  62. // Next next step of SMTP login auth
  63. func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
  64. if more {
  65. switch string(fromServer) {
  66. case "Username:":
  67. return []byte(a.username), nil
  68. case "Password:":
  69. return []byte(a.password), nil
  70. default:
  71. return nil, fmt.Errorf("unknwon fromServer: %s", string(fromServer))
  72. }
  73. }
  74. return nil, nil
  75. }
  76. // Sender SMTP mail sender
  77. type smtpSender struct {
  78. }
  79. // Send send email
  80. func (s *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
  81. opts := setting.MailService
  82. host, port, err := net.SplitHostPort(opts.Host)
  83. if err != nil {
  84. return err
  85. }
  86. tlsconfig := &tls.Config{
  87. InsecureSkipVerify: opts.SkipVerify,
  88. ServerName: host,
  89. }
  90. if opts.UseCertificate {
  91. cert, err := tls.LoadX509KeyPair(opts.CertFile, opts.KeyFile)
  92. if err != nil {
  93. return err
  94. }
  95. tlsconfig.Certificates = []tls.Certificate{cert}
  96. }
  97. conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
  98. if err != nil {
  99. return err
  100. }
  101. defer conn.Close()
  102. isSecureConn := false
  103. // Start TLS directly if the port ends with 465 (SMTPS protocol)
  104. if strings.HasSuffix(port, "465") {
  105. conn = tls.Client(conn, tlsconfig)
  106. isSecureConn = true
  107. }
  108. client, err := smtp.NewClient(conn, host)
  109. if err != nil {
  110. return fmt.Errorf("NewClient: %v", err)
  111. }
  112. if !opts.DisableHelo {
  113. hostname := opts.HeloHostname
  114. if len(hostname) == 0 {
  115. hostname, err = os.Hostname()
  116. if err != nil {
  117. return err
  118. }
  119. }
  120. if err = client.Hello(hostname); err != nil {
  121. return fmt.Errorf("Hello: %v", err)
  122. }
  123. }
  124. // If not using SMTPS, alway use STARTTLS if available
  125. hasStartTLS, _ := client.Extension("STARTTLS")
  126. if !isSecureConn && hasStartTLS {
  127. if err = client.StartTLS(tlsconfig); err != nil {
  128. return fmt.Errorf("StartTLS: %v", err)
  129. }
  130. }
  131. canAuth, options := client.Extension("AUTH")
  132. if canAuth && len(opts.User) > 0 {
  133. var auth smtp.Auth
  134. if strings.Contains(options, "CRAM-MD5") {
  135. auth = smtp.CRAMMD5Auth(opts.User, opts.Passwd)
  136. } else if strings.Contains(options, "PLAIN") {
  137. auth = smtp.PlainAuth("", opts.User, opts.Passwd, host)
  138. } else if strings.Contains(options, "LOGIN") {
  139. // Patch for AUTH LOGIN
  140. auth = LoginAuth(opts.User, opts.Passwd)
  141. }
  142. if auth != nil {
  143. if err = client.Auth(auth); err != nil {
  144. return fmt.Errorf("Auth: %v", err)
  145. }
  146. }
  147. }
  148. if err = client.Mail(from); err != nil {
  149. return fmt.Errorf("Mail: %v", err)
  150. }
  151. for _, rec := range to {
  152. if err = client.Rcpt(rec); err != nil {
  153. return fmt.Errorf("Rcpt: %v", err)
  154. }
  155. }
  156. w, err := client.Data()
  157. if err != nil {
  158. return fmt.Errorf("Data: %v", err)
  159. } else if _, err = msg.WriteTo(w); err != nil {
  160. return fmt.Errorf("WriteTo: %v", err)
  161. } else if err = w.Close(); err != nil {
  162. return fmt.Errorf("Close: %v", err)
  163. }
  164. return client.Quit()
  165. }
  166. // Sender sendmail mail sender
  167. type sendmailSender struct {
  168. }
  169. // Send send email
  170. func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error {
  171. var err error
  172. var closeError error
  173. var waitError error
  174. args := []string{"-F", from, "-i"}
  175. args = append(args, to...)
  176. log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args)
  177. cmd := exec.Command(setting.MailService.SendmailPath, args...)
  178. pipe, err := cmd.StdinPipe()
  179. if err != nil {
  180. return err
  181. }
  182. if err = cmd.Start(); err != nil {
  183. return err
  184. }
  185. _,err = msg.WriteTo(pipe)
  186. // we MUST close the pipe or sendmail will hang waiting for more of the message
  187. // Also we should wait on our sendmail command even if something fails
  188. closeError = pipe.Close()
  189. waitError = cmd.Wait()
  190. if err != nil {
  191. return err
  192. } else if closeError != nil {
  193. return closeError
  194. } else {
  195. return waitError
  196. }
  197. }
  198. func processMailQueue() {
  199. for {
  200. select {
  201. case msg := <-mailQueue:
  202. log.Trace("New e-mail sending request %s: %s", msg.GetHeader("To"), msg.Info)
  203. if err := gomail.Send(Sender, msg.Message); err != nil {
  204. log.Error(3, "Fail to send emails %s: %s - %v", msg.GetHeader("To"), msg.Info, err)
  205. } else {
  206. log.Trace("E-mails sent %s: %s", msg.GetHeader("To"), msg.Info)
  207. }
  208. }
  209. }
  210. }
  211. var mailQueue chan *Message
  212. // Sender sender for sending mail synchronously
  213. var Sender gomail.Sender
  214. // NewContext start mail queue service
  215. func NewContext() {
  216. // Need to check if mailQueue is nil because in during reinstall (user had installed
  217. // before but swithed install lock off), this function will be called again
  218. // while mail queue is already processing tasks, and produces a race condition.
  219. if setting.MailService == nil || mailQueue != nil {
  220. return
  221. }
  222. if setting.MailService.UseSendmail {
  223. Sender = &sendmailSender{}
  224. } else {
  225. Sender = &smtpSender{}
  226. }
  227. mailQueue = make(chan *Message, setting.MailService.QueueLength)
  228. go processMailQueue()
  229. }
  230. // SendAsync send mail asynchronous
  231. func SendAsync(msg *Message) {
  232. go func() {
  233. mailQueue <- msg
  234. }()
  235. }