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.

mailer.go 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2017 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package mailer
  5. import (
  6. "bytes"
  7. "context"
  8. "crypto/tls"
  9. "fmt"
  10. "hash/fnv"
  11. "io"
  12. "net"
  13. "net/smtp"
  14. "os"
  15. "os/exec"
  16. "strings"
  17. "time"
  18. "code.gitea.io/gitea/modules/base"
  19. "code.gitea.io/gitea/modules/graceful"
  20. "code.gitea.io/gitea/modules/log"
  21. "code.gitea.io/gitea/modules/process"
  22. "code.gitea.io/gitea/modules/queue"
  23. "code.gitea.io/gitea/modules/setting"
  24. "code.gitea.io/gitea/modules/templates"
  25. "github.com/jaytaylor/html2text"
  26. "gopkg.in/gomail.v2"
  27. )
  28. // Message mail body and log info
  29. type Message struct {
  30. Info string // Message information for log purpose.
  31. FromAddress string
  32. FromDisplayName string
  33. To []string
  34. ReplyTo string
  35. Subject string
  36. Date time.Time
  37. Body string
  38. Headers map[string][]string
  39. }
  40. // ToMessage converts a Message to gomail.Message
  41. func (m *Message) ToMessage() *gomail.Message {
  42. msg := gomail.NewMessage()
  43. msg.SetAddressHeader("From", m.FromAddress, m.FromDisplayName)
  44. msg.SetHeader("To", m.To...)
  45. if m.ReplyTo != "" {
  46. msg.SetHeader("Reply-To", m.ReplyTo)
  47. }
  48. for header := range m.Headers {
  49. msg.SetHeader(header, m.Headers[header]...)
  50. }
  51. if len(setting.MailService.SubjectPrefix) > 0 {
  52. msg.SetHeader("Subject", setting.MailService.SubjectPrefix+" "+m.Subject)
  53. } else {
  54. msg.SetHeader("Subject", m.Subject)
  55. }
  56. msg.SetDateHeader("Date", m.Date)
  57. msg.SetHeader("X-Auto-Response-Suppress", "All")
  58. plainBody, err := html2text.FromString(m.Body)
  59. if err != nil || setting.MailService.SendAsPlainText {
  60. if strings.Contains(base.TruncateString(m.Body, 100), "<html>") {
  61. log.Warn("Mail contains HTML but configured to send as plain text.")
  62. }
  63. msg.SetBody("text/plain", plainBody)
  64. } else {
  65. msg.SetBody("text/plain", plainBody)
  66. msg.AddAlternative("text/html", m.Body)
  67. }
  68. if len(msg.GetHeader("Message-ID")) == 0 {
  69. msg.SetHeader("Message-ID", m.generateAutoMessageID())
  70. }
  71. return msg
  72. }
  73. // SetHeader adds additional headers to a message
  74. func (m *Message) SetHeader(field string, value ...string) {
  75. m.Headers[field] = value
  76. }
  77. func (m *Message) generateAutoMessageID() string {
  78. dateMs := m.Date.UnixNano() / 1e6
  79. h := fnv.New64()
  80. if len(m.To) > 0 {
  81. _, _ = h.Write([]byte(m.To[0]))
  82. }
  83. _, _ = h.Write([]byte(m.Subject))
  84. _, _ = h.Write([]byte(m.Body))
  85. return fmt.Sprintf("<autogen-%d-%016x@%s>", dateMs, h.Sum64(), setting.Domain)
  86. }
  87. // NewMessageFrom creates new mail message object with custom From header.
  88. func NewMessageFrom(to []string, fromDisplayName, fromAddress, subject, body string) *Message {
  89. log.Trace("NewMessageFrom (body):\n%s", body)
  90. return &Message{
  91. FromAddress: fromAddress,
  92. FromDisplayName: fromDisplayName,
  93. To: to,
  94. Subject: subject,
  95. Date: time.Now(),
  96. Body: body,
  97. Headers: map[string][]string{},
  98. }
  99. }
  100. // NewMessage creates new mail message object with default From header.
  101. func NewMessage(to []string, subject, body string) *Message {
  102. return NewMessageFrom(to, setting.MailService.FromName, setting.MailService.FromEmail, subject, body)
  103. }
  104. type loginAuth struct {
  105. username, password string
  106. }
  107. // LoginAuth SMTP AUTH LOGIN Auth Handler
  108. func LoginAuth(username, password string) smtp.Auth {
  109. return &loginAuth{username, password}
  110. }
  111. // Start start SMTP login auth
  112. func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
  113. return "LOGIN", []byte{}, nil
  114. }
  115. // Next next step of SMTP login auth
  116. func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
  117. if more {
  118. switch string(fromServer) {
  119. case "Username:":
  120. return []byte(a.username), nil
  121. case "Password:":
  122. return []byte(a.password), nil
  123. default:
  124. return nil, fmt.Errorf("unknown fromServer: %s", string(fromServer))
  125. }
  126. }
  127. return nil, nil
  128. }
  129. // Sender SMTP mail sender
  130. type smtpSender struct{}
  131. // Send send email
  132. func (s *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
  133. opts := setting.MailService
  134. var network string
  135. var address string
  136. if opts.Protocol == "smtp+unix" {
  137. network = "unix"
  138. address = opts.SMTPAddr
  139. } else {
  140. network = "tcp"
  141. address = net.JoinHostPort(opts.SMTPAddr, opts.SMTPPort)
  142. }
  143. conn, err := net.Dial(network, address)
  144. if err != nil {
  145. return fmt.Errorf("failed to establish network connection to SMTP server: %w", err)
  146. }
  147. defer conn.Close()
  148. var tlsconfig *tls.Config
  149. if opts.Protocol == "smtps" || opts.Protocol == "smtp+starttls" {
  150. tlsconfig = &tls.Config{
  151. InsecureSkipVerify: opts.ForceTrustServerCert,
  152. ServerName: opts.SMTPAddr,
  153. }
  154. if opts.UseClientCert {
  155. cert, err := tls.LoadX509KeyPair(opts.ClientCertFile, opts.ClientKeyFile)
  156. if err != nil {
  157. return fmt.Errorf("could not load SMTP client certificate: %w", err)
  158. }
  159. tlsconfig.Certificates = []tls.Certificate{cert}
  160. }
  161. }
  162. if opts.Protocol == "smtps" {
  163. conn = tls.Client(conn, tlsconfig)
  164. }
  165. host := "localhost"
  166. if opts.Protocol == "smtp+unix" {
  167. host = opts.SMTPAddr
  168. }
  169. client, err := smtp.NewClient(conn, host)
  170. if err != nil {
  171. return fmt.Errorf("could not initiate SMTP session: %w", err)
  172. }
  173. if opts.EnableHelo {
  174. hostname := opts.HeloHostname
  175. if len(hostname) == 0 {
  176. hostname, err = os.Hostname()
  177. if err != nil {
  178. return fmt.Errorf("could not retrieve system hostname: %w", err)
  179. }
  180. }
  181. if err = client.Hello(hostname); err != nil {
  182. return fmt.Errorf("failed to issue HELO command: %w", err)
  183. }
  184. }
  185. if opts.Protocol == "smtp+starttls" {
  186. hasStartTLS, _ := client.Extension("STARTTLS")
  187. if hasStartTLS {
  188. if err = client.StartTLS(tlsconfig); err != nil {
  189. return fmt.Errorf("failed to start TLS connection: %w", err)
  190. }
  191. } else {
  192. log.Warn("StartTLS requested, but SMTP server does not support it; falling back to regular SMTP")
  193. }
  194. }
  195. canAuth, options := client.Extension("AUTH")
  196. if len(opts.User) > 0 {
  197. if !canAuth {
  198. return fmt.Errorf("SMTP server does not support AUTH, but credentials provided")
  199. }
  200. var auth smtp.Auth
  201. if strings.Contains(options, "CRAM-MD5") {
  202. auth = smtp.CRAMMD5Auth(opts.User, opts.Passwd)
  203. } else if strings.Contains(options, "PLAIN") {
  204. auth = smtp.PlainAuth("", opts.User, opts.Passwd, host)
  205. } else if strings.Contains(options, "LOGIN") {
  206. // Patch for AUTH LOGIN
  207. auth = LoginAuth(opts.User, opts.Passwd)
  208. }
  209. if auth != nil {
  210. if err = client.Auth(auth); err != nil {
  211. return fmt.Errorf("failed to authenticate SMTP: %w", err)
  212. }
  213. }
  214. }
  215. if opts.OverrideEnvelopeFrom {
  216. if err = client.Mail(opts.EnvelopeFrom); err != nil {
  217. return fmt.Errorf("failed to issue MAIL command: %w", err)
  218. }
  219. } else {
  220. if err = client.Mail(from); err != nil {
  221. return fmt.Errorf("failed to issue MAIL command: %w", err)
  222. }
  223. }
  224. for _, rec := range to {
  225. if err = client.Rcpt(rec); err != nil {
  226. return fmt.Errorf("failed to issue RCPT command: %w", err)
  227. }
  228. }
  229. w, err := client.Data()
  230. if err != nil {
  231. return fmt.Errorf("failed to issue DATA command: %w", err)
  232. } else if _, err = msg.WriteTo(w); err != nil {
  233. return fmt.Errorf("SMTP write failed: %w", err)
  234. } else if err = w.Close(); err != nil {
  235. return fmt.Errorf("SMTP close failed: %w", err)
  236. }
  237. return client.Quit()
  238. }
  239. // Sender sendmail mail sender
  240. type sendmailSender struct{}
  241. // Send send email
  242. func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error {
  243. var err error
  244. var closeError error
  245. var waitError error
  246. envelopeFrom := from
  247. if setting.MailService.OverrideEnvelopeFrom {
  248. envelopeFrom = setting.MailService.EnvelopeFrom
  249. }
  250. args := []string{"-f", envelopeFrom, "-i"}
  251. args = append(args, setting.MailService.SendmailArgs...)
  252. args = append(args, to...)
  253. log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args)
  254. desc := fmt.Sprintf("SendMail: %s %v", setting.MailService.SendmailPath, args)
  255. ctx, _, finished := process.GetManager().AddContextTimeout(graceful.GetManager().HammerContext(), setting.MailService.SendmailTimeout, desc)
  256. defer finished()
  257. cmd := exec.CommandContext(ctx, setting.MailService.SendmailPath, args...)
  258. pipe, err := cmd.StdinPipe()
  259. if err != nil {
  260. return err
  261. }
  262. process.SetSysProcAttribute(cmd)
  263. if err = cmd.Start(); err != nil {
  264. _ = pipe.Close()
  265. return err
  266. }
  267. if setting.MailService.SendmailConvertCRLF {
  268. buf := &strings.Builder{}
  269. _, err = msg.WriteTo(buf)
  270. if err == nil {
  271. _, err = strings.NewReplacer("\r\n", "\n").WriteString(pipe, buf.String())
  272. }
  273. } else {
  274. _, err = msg.WriteTo(pipe)
  275. }
  276. // we MUST close the pipe or sendmail will hang waiting for more of the message
  277. // Also we should wait on our sendmail command even if something fails
  278. closeError = pipe.Close()
  279. waitError = cmd.Wait()
  280. if err != nil {
  281. return err
  282. } else if closeError != nil {
  283. return closeError
  284. } else {
  285. return waitError
  286. }
  287. }
  288. // Sender sendmail mail sender
  289. type dummySender struct{}
  290. // Send send email
  291. func (s *dummySender) Send(from string, to []string, msg io.WriterTo) error {
  292. buf := bytes.Buffer{}
  293. if _, err := msg.WriteTo(&buf); err != nil {
  294. return err
  295. }
  296. log.Info("Mail From: %s To: %v Body: %s", from, to, buf.String())
  297. return nil
  298. }
  299. var mailQueue queue.Queue
  300. // Sender sender for sending mail synchronously
  301. var Sender gomail.Sender
  302. // NewContext start mail queue service
  303. func NewContext(ctx context.Context) {
  304. // Need to check if mailQueue is nil because in during reinstall (user had installed
  305. // before but switched install lock off), this function will be called again
  306. // while mail queue is already processing tasks, and produces a race condition.
  307. if setting.MailService == nil || mailQueue != nil {
  308. return
  309. }
  310. switch setting.MailService.Protocol {
  311. case "sendmail":
  312. Sender = &sendmailSender{}
  313. case "dummy":
  314. Sender = &dummySender{}
  315. default:
  316. Sender = &smtpSender{}
  317. }
  318. mailQueue = queue.CreateQueue("mail", func(data ...queue.Data) []queue.Data {
  319. for _, datum := range data {
  320. msg := datum.(*Message)
  321. gomailMsg := msg.ToMessage()
  322. log.Trace("New e-mail sending request %s: %s", gomailMsg.GetHeader("To"), msg.Info)
  323. if err := gomail.Send(Sender, gomailMsg); err != nil {
  324. log.Error("Failed to send emails %s: %s - %v", gomailMsg.GetHeader("To"), msg.Info, err)
  325. } else {
  326. log.Trace("E-mails sent %s: %s", gomailMsg.GetHeader("To"), msg.Info)
  327. }
  328. }
  329. return nil
  330. }, &Message{})
  331. go graceful.GetManager().RunWithShutdownFns(mailQueue.Run)
  332. subjectTemplates, bodyTemplates = templates.Mailer(ctx)
  333. }
  334. // SendAsync send mail asynchronously
  335. func SendAsync(msg *Message) {
  336. SendAsyncs([]*Message{msg})
  337. }
  338. // SendAsyncs send mails asynchronously
  339. func SendAsyncs(msgs []*Message) {
  340. if setting.MailService == nil {
  341. log.Error("Mailer: SendAsyncs is being invoked but mail service hasn't been initialized")
  342. return
  343. }
  344. go func() {
  345. for _, msg := range msgs {
  346. _ = mailQueue.Push(msg)
  347. }
  348. }()
  349. }