gitea源码

incoming.go 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package incoming
  4. import (
  5. "context"
  6. "crypto/tls"
  7. "fmt"
  8. net_mail "net/mail"
  9. "regexp"
  10. "strings"
  11. "time"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/process"
  14. "code.gitea.io/gitea/modules/setting"
  15. "code.gitea.io/gitea/services/mailer/token"
  16. "github.com/dimiro1/reply"
  17. "github.com/emersion/go-imap"
  18. "github.com/emersion/go-imap/client"
  19. "github.com/jhillyerd/enmime"
  20. )
  21. var (
  22. addressTokenRegex *regexp.Regexp
  23. referenceTokenRegex *regexp.Regexp
  24. )
  25. func Init(ctx context.Context) error {
  26. if !setting.IncomingEmail.Enabled {
  27. return nil
  28. }
  29. var err error
  30. addressTokenRegex, err = regexp.Compile(
  31. fmt.Sprintf(
  32. `\A%s\z`,
  33. strings.Replace(regexp.QuoteMeta(setting.IncomingEmail.ReplyToAddress), regexp.QuoteMeta(setting.IncomingEmail.TokenPlaceholder), "(.+)", 1),
  34. ),
  35. )
  36. if err != nil {
  37. return err
  38. }
  39. referenceTokenRegex, err = regexp.Compile(fmt.Sprintf(`\Areply-(.+)@%s\z`, regexp.QuoteMeta(setting.Domain)))
  40. if err != nil {
  41. return err
  42. }
  43. go func() {
  44. ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Incoming Email", process.SystemProcessType, true)
  45. defer finished()
  46. // This background job processes incoming emails. It uses the IMAP IDLE command to get notified about incoming emails.
  47. // The following loop restarts the processing logic after errors until ctx indicates to stop.
  48. for {
  49. select {
  50. case <-ctx.Done():
  51. return
  52. default:
  53. if err := processIncomingEmails(ctx); err != nil {
  54. log.Error("Error while processing incoming emails: %v", err)
  55. }
  56. select {
  57. case <-ctx.Done():
  58. return
  59. case <-time.NewTimer(10 * time.Second).C:
  60. }
  61. }
  62. }
  63. }()
  64. return nil
  65. }
  66. // processIncomingEmails is the "main" method with the wait/process loop
  67. func processIncomingEmails(ctx context.Context) error {
  68. server := fmt.Sprintf("%s:%d", setting.IncomingEmail.Host, setting.IncomingEmail.Port)
  69. var c *client.Client
  70. var err error
  71. if setting.IncomingEmail.UseTLS {
  72. c, err = client.DialTLS(server, &tls.Config{InsecureSkipVerify: setting.IncomingEmail.SkipTLSVerify})
  73. } else {
  74. c, err = client.Dial(server)
  75. }
  76. if err != nil {
  77. return fmt.Errorf("could not connect to server '%s': %w", server, err)
  78. }
  79. if err := c.Login(setting.IncomingEmail.Username, setting.IncomingEmail.Password); err != nil {
  80. return fmt.Errorf("could not login: %w", err)
  81. }
  82. defer func() {
  83. if err := c.Logout(); err != nil {
  84. log.Error("Logout from incoming email server failed: %v", err)
  85. }
  86. }()
  87. if _, err := c.Select(setting.IncomingEmail.Mailbox, false); err != nil {
  88. return fmt.Errorf("selecting box '%s' failed: %w", setting.IncomingEmail.Mailbox, err)
  89. }
  90. // The following loop processes messages. If there are no messages available, IMAP IDLE is used to wait for new messages.
  91. // This process is repeated until an IMAP error occurs or ctx indicates to stop.
  92. for {
  93. select {
  94. case <-ctx.Done():
  95. return nil
  96. default:
  97. if err := processMessages(ctx, c); err != nil {
  98. return fmt.Errorf("could not process messages: %w", err)
  99. }
  100. if err := waitForUpdates(ctx, c); err != nil {
  101. return fmt.Errorf("wait for updates failed: %w", err)
  102. }
  103. select {
  104. case <-ctx.Done():
  105. return nil
  106. case <-time.NewTimer(time.Second).C:
  107. }
  108. }
  109. }
  110. }
  111. // waitForUpdates uses IMAP IDLE to wait for new emails
  112. func waitForUpdates(ctx context.Context, c *client.Client) error {
  113. updates := make(chan client.Update, 1)
  114. c.Updates = updates
  115. defer func() {
  116. c.Updates = nil
  117. }()
  118. errs := make(chan error, 1)
  119. stop := make(chan struct{})
  120. go func() {
  121. errs <- c.Idle(stop, nil)
  122. }()
  123. stopped := false
  124. for {
  125. select {
  126. case update := <-updates:
  127. switch update.(type) {
  128. case *client.MailboxUpdate:
  129. if !stopped {
  130. close(stop)
  131. stopped = true
  132. }
  133. default:
  134. }
  135. case err := <-errs:
  136. if err != nil {
  137. return fmt.Errorf("imap idle failed: %w", err)
  138. }
  139. return nil
  140. case <-ctx.Done():
  141. return nil
  142. }
  143. }
  144. }
  145. // processMessages searches unread mails and processes them.
  146. func processMessages(ctx context.Context, c *client.Client) error {
  147. criteria := imap.NewSearchCriteria()
  148. criteria.WithoutFlags = []string{imap.SeenFlag}
  149. criteria.Smaller = setting.IncomingEmail.MaximumMessageSize
  150. ids, err := c.Search(criteria)
  151. if err != nil {
  152. return fmt.Errorf("imap search failed: %w", err)
  153. }
  154. if len(ids) == 0 {
  155. return nil
  156. }
  157. seqset := new(imap.SeqSet)
  158. seqset.AddNum(ids...)
  159. messages := make(chan *imap.Message, 10)
  160. section := &imap.BodySectionName{}
  161. errs := make(chan error, 1)
  162. go func() {
  163. errs <- c.Fetch(
  164. seqset,
  165. []imap.FetchItem{section.FetchItem()},
  166. messages,
  167. )
  168. }()
  169. handledSet := new(imap.SeqSet)
  170. loop:
  171. for {
  172. select {
  173. case <-ctx.Done():
  174. break loop
  175. case msg, ok := <-messages:
  176. if !ok {
  177. if setting.IncomingEmail.DeleteHandledMessage && !handledSet.Empty() {
  178. if err := c.Store(
  179. handledSet,
  180. imap.FormatFlagsOp(imap.AddFlags, true),
  181. []any{imap.DeletedFlag},
  182. nil,
  183. ); err != nil {
  184. return fmt.Errorf("imap store failed: %w", err)
  185. }
  186. if err := c.Expunge(nil); err != nil {
  187. return fmt.Errorf("imap expunge failed: %w", err)
  188. }
  189. }
  190. return nil
  191. }
  192. err := func() error {
  193. r := msg.GetBody(section)
  194. if r == nil {
  195. return fmt.Errorf("could not get body from message: %w", err)
  196. }
  197. env, err := enmime.ReadEnvelope(r)
  198. if err != nil {
  199. return fmt.Errorf("could not read envelope: %w", err)
  200. }
  201. if isAutomaticReply(env) {
  202. log.Debug("Skipping automatic email reply")
  203. return nil
  204. }
  205. t := searchTokenInHeaders(env)
  206. if t == "" {
  207. log.Debug("Incoming email token not found in headers")
  208. return nil
  209. }
  210. handlerType, user, payload, err := token.ExtractToken(ctx, t)
  211. if err != nil {
  212. if _, ok := err.(*token.ErrToken); ok {
  213. log.Info("Invalid incoming email token: %v", err)
  214. return nil
  215. }
  216. return err
  217. }
  218. handler, ok := handlers[handlerType]
  219. if !ok {
  220. return fmt.Errorf("unexpected handler type: %v", handlerType)
  221. }
  222. content := getContentFromMailReader(env)
  223. if err := handler.Handle(ctx, content, user, payload); err != nil {
  224. return fmt.Errorf("could not handle message: %w", err)
  225. }
  226. handledSet.AddNum(msg.SeqNum)
  227. return nil
  228. }()
  229. if err != nil {
  230. log.Error("Error while processing incoming email[%v]: %v", msg.Uid, err)
  231. }
  232. }
  233. }
  234. if err := <-errs; err != nil {
  235. return fmt.Errorf("imap fetch failed: %w", err)
  236. }
  237. return nil
  238. }
  239. // isAutomaticReply tests if the headers indicate an automatic reply
  240. func isAutomaticReply(env *enmime.Envelope) bool {
  241. autoSubmitted := env.GetHeader("Auto-Submitted")
  242. if autoSubmitted != "" && autoSubmitted != "no" {
  243. return true
  244. }
  245. autoReply := env.GetHeader("X-Autoreply")
  246. if autoReply == "yes" {
  247. return true
  248. }
  249. autoRespond := env.GetHeader("X-Autorespond")
  250. return autoRespond != ""
  251. }
  252. // searchTokenInHeaders looks for the token in To, Delivered-To and References
  253. func searchTokenInHeaders(env *enmime.Envelope) string {
  254. if addressTokenRegex != nil {
  255. to, _ := env.AddressList("To")
  256. token := searchTokenInAddresses(to)
  257. if token != "" {
  258. return token
  259. }
  260. deliveredTo, _ := env.AddressList("Delivered-To")
  261. token = searchTokenInAddresses(deliveredTo)
  262. if token != "" {
  263. return token
  264. }
  265. }
  266. references := env.GetHeader("References")
  267. for {
  268. begin := strings.IndexByte(references, '<')
  269. if begin == -1 {
  270. break
  271. }
  272. begin++
  273. end := strings.IndexByte(references, '>')
  274. if end == -1 || begin > end {
  275. break
  276. }
  277. match := referenceTokenRegex.FindStringSubmatch(references[begin:end])
  278. if len(match) == 2 {
  279. return match[1]
  280. }
  281. references = references[end+1:]
  282. }
  283. return ""
  284. }
  285. // searchTokenInAddresses looks for the token in an address
  286. func searchTokenInAddresses(addresses []*net_mail.Address) string {
  287. for _, address := range addresses {
  288. match := addressTokenRegex.FindStringSubmatch(address.Address)
  289. if len(match) != 2 {
  290. continue
  291. }
  292. return match[1]
  293. }
  294. return ""
  295. }
  296. type MailContent struct {
  297. Content string
  298. Attachments []*Attachment
  299. }
  300. type Attachment struct {
  301. Name string
  302. Content []byte
  303. }
  304. // getContentFromMailReader grabs the plain content and the attachments from the mail.
  305. // A potential reply/signature gets stripped from the content.
  306. func getContentFromMailReader(env *enmime.Envelope) *MailContent {
  307. attachments := make([]*Attachment, 0, len(env.Attachments))
  308. for _, attachment := range env.Attachments {
  309. attachments = append(attachments, &Attachment{
  310. Name: attachment.FileName,
  311. Content: attachment.Content,
  312. })
  313. }
  314. return &MailContent{
  315. Content: reply.FromText(env.Text),
  316. Attachments: attachments,
  317. }
  318. }