gitea源码

smtp.go 3.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package sender
  4. import (
  5. "crypto/tls"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net"
  10. "os"
  11. "strings"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/setting"
  14. "github.com/wneessen/go-mail/smtp"
  15. )
  16. // SMTPSender Sender SMTP mail sender
  17. type SMTPSender struct{}
  18. var _ Sender = &SMTPSender{}
  19. // Send send email
  20. func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error {
  21. opts := setting.MailService
  22. var network string
  23. var address string
  24. if opts.Protocol == "smtp+unix" {
  25. network = "unix"
  26. address = opts.SMTPAddr
  27. } else {
  28. network = "tcp"
  29. address = net.JoinHostPort(opts.SMTPAddr, opts.SMTPPort)
  30. }
  31. conn, err := net.Dial(network, address)
  32. if err != nil {
  33. return fmt.Errorf("failed to establish network connection to SMTP server: %w", err)
  34. }
  35. defer conn.Close()
  36. var tlsconfig *tls.Config
  37. if opts.Protocol == "smtps" || opts.Protocol == "smtp+starttls" {
  38. tlsconfig = &tls.Config{
  39. InsecureSkipVerify: opts.ForceTrustServerCert,
  40. ServerName: opts.SMTPAddr,
  41. }
  42. if opts.UseClientCert {
  43. cert, err := tls.LoadX509KeyPair(opts.ClientCertFile, opts.ClientKeyFile)
  44. if err != nil {
  45. return fmt.Errorf("could not load SMTP client certificate: %w", err)
  46. }
  47. tlsconfig.Certificates = []tls.Certificate{cert}
  48. }
  49. }
  50. if opts.Protocol == "smtps" {
  51. conn = tls.Client(conn, tlsconfig)
  52. }
  53. host := "localhost"
  54. if opts.Protocol == "smtp+unix" {
  55. host = opts.SMTPAddr
  56. }
  57. client, err := smtp.NewClient(conn, host)
  58. if err != nil {
  59. return fmt.Errorf("could not initiate SMTP session: %w", err)
  60. }
  61. if opts.EnableHelo {
  62. hostname := opts.HeloHostname
  63. if len(hostname) == 0 {
  64. hostname, err = os.Hostname()
  65. if err != nil {
  66. return fmt.Errorf("could not retrieve system hostname: %w", err)
  67. }
  68. }
  69. if err = client.Hello(hostname); err != nil {
  70. return fmt.Errorf("failed to issue HELO command: %w", err)
  71. }
  72. }
  73. if opts.Protocol == "smtp+starttls" {
  74. hasStartTLS, _ := client.Extension("STARTTLS")
  75. if hasStartTLS {
  76. if err = client.StartTLS(tlsconfig); err != nil {
  77. return fmt.Errorf("failed to start TLS connection: %w", err)
  78. }
  79. } else {
  80. log.Warn("StartTLS requested, but SMTP server does not support it; falling back to regular SMTP")
  81. }
  82. }
  83. canAuth, options := client.Extension("AUTH")
  84. if len(opts.User) > 0 {
  85. if !canAuth {
  86. return errors.New("SMTP server does not support AUTH, but credentials provided")
  87. }
  88. var auth smtp.Auth
  89. if strings.Contains(options, "CRAM-MD5") {
  90. auth = smtp.CRAMMD5Auth(opts.User, opts.Passwd)
  91. } else if strings.Contains(options, "PLAIN") {
  92. auth = smtp.PlainAuth("", opts.User, opts.Passwd, host, false)
  93. } else if strings.Contains(options, "LOGIN") {
  94. // Patch for AUTH LOGIN
  95. auth = LoginAuth(opts.User, opts.Passwd)
  96. } else if strings.Contains(options, "NTLM") {
  97. auth = NtlmAuth(opts.User, opts.Passwd)
  98. }
  99. if auth != nil {
  100. if err = client.Auth(auth); err != nil {
  101. return fmt.Errorf("failed to authenticate SMTP: %w", err)
  102. }
  103. }
  104. }
  105. if opts.OverrideEnvelopeFrom {
  106. if err = client.Mail(opts.EnvelopeFrom); err != nil {
  107. return fmt.Errorf("failed to issue MAIL command: %w", err)
  108. }
  109. } else {
  110. if err = client.Mail(from); err != nil {
  111. return fmt.Errorf("failed to issue MAIL command: %w", err)
  112. }
  113. }
  114. for _, rec := range to {
  115. if err = client.Rcpt(rec); err != nil {
  116. return fmt.Errorf("failed to issue RCPT command: %w", err)
  117. }
  118. }
  119. w, err := client.Data()
  120. if err != nil {
  121. return fmt.Errorf("failed to issue DATA command: %w", err)
  122. } else if _, err = msg.WriteTo(w); err != nil {
  123. return fmt.Errorf("SMTP write failed: %w", err)
  124. } else if err = w.Close(); err != nil {
  125. return fmt.Errorf("SMTP close failed: %w", err)
  126. }
  127. err = client.Quit()
  128. if err != nil {
  129. log.Error("Quit client failed: %v", err)
  130. }
  131. return nil
  132. }