mímir.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. package main
  2. import (
  3. "bytes"
  4. "database/sql"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "log"
  9. "net/http"
  10. "regexp"
  11. "strings"
  12. "github.com/emersion/go-imap"
  13. "github.com/emersion/go-imap/client"
  14. "github.com/emersion/go-message"
  15. _ "github.com/emersion/go-message/charset"
  16. "github.com/emersion/go-msgauth/dkim"
  17. )
  18. func checkRecipientTemplate(recipients []*imap.Address, config Config) (string, error) {
  19. categories := config.Mímir.Categories
  20. if len(categories) == 0 {
  21. return "", nil
  22. }
  23. parts := strings.SplitN(config.Mímir.RecipientTemplate, "[:]", 2) // todo if [:] not found
  24. r, err := regexp.Compile(parts[0] + "(.*)" + parts[1])
  25. if err != nil {
  26. return "", err
  27. }
  28. for _, recipient := range recipients {
  29. matches := r.FindStringSubmatch(recipient.Address())
  30. if len(matches) != 2 {
  31. return "", errors.New("No category in recipient")
  32. }
  33. for _, category := range categories {
  34. if matches[1] == category {
  35. return category, nil
  36. }
  37. }
  38. }
  39. return "", errors.New("Unknown category")
  40. }
  41. func getPlainTextPart(r io.Reader) (io.Reader, error) {
  42. m, err := message.Read(r)
  43. if err != nil {
  44. log.Fatal(err)
  45. }
  46. if mr := m.MultipartReader(); mr != nil {
  47. for {
  48. p, err := mr.NextPart()
  49. if err == io.EOF {
  50. break
  51. } else if err != nil {
  52. log.Fatal(err)
  53. }
  54. t, _, _ := p.Header.ContentType()
  55. if t == "text/plain" {
  56. return p.Body, nil
  57. }
  58. }
  59. } else {
  60. t, _, _ := m.Header.ContentType()
  61. if t == "text/plain" {
  62. return m.Body, nil
  63. }
  64. }
  65. return nil, errors.New("text/plain no found")
  66. }
  67. func archiveInbox(db *sql.DB, mboxName string, c *client.Client, config Config) error {
  68. mbox, err := c.Select(mboxName, true)
  69. if err != nil {
  70. return err
  71. }
  72. fmt.Printf("reading %d messages from %s\n", mbox.Messages, mboxName)
  73. if mbox.Messages == 0 {
  74. return nil
  75. }
  76. from := uint32(1)
  77. to := mbox.Messages
  78. seqset := new(imap.SeqSet)
  79. seqset.AddRange(from, to)
  80. section := &imap.BodySectionName{}
  81. items := []imap.FetchItem{imap.FetchEnvelope, section.FetchItem()}
  82. messages := make(chan *imap.Message, 10)
  83. done := make(chan error, 1)
  84. go func() {
  85. done <- c.Fetch(seqset, items, messages)
  86. }()
  87. for msg := range messages {
  88. recipients := append(msg.Envelope.To, msg.Envelope.Cc...)
  89. category, err := checkRecipientTemplate(recipients, config)
  90. if err != nil {
  91. return err
  92. }
  93. sender := msg.Envelope.From[0]
  94. messageID := msg.Envelope.MessageId
  95. subject := msg.Envelope.Subject
  96. date := msg.Envelope.Date.UTC()
  97. inReplyTo := msg.Envelope.InReplyTo
  98. dkimStatus := false
  99. r := msg.GetBody(section)
  100. if r == nil {
  101. return errors.New("No body in message")
  102. }
  103. messageBytes, err := io.ReadAll(r)
  104. if err != nil {
  105. panic(err)
  106. }
  107. r = bytes.NewReader(messageBytes)
  108. verifications, err := dkim.Verify(r)
  109. if err != nil {
  110. fmt.Println("there")
  111. return err
  112. }
  113. for _, v := range verifications {
  114. if v.Err == nil && sender.HostName == v.Domain {
  115. dkimStatus = true
  116. }
  117. }
  118. r = bytes.NewReader(messageBytes)
  119. bodyReader, err := getPlainTextPart(r)
  120. if err != nil {
  121. return err
  122. }
  123. body, err := io.ReadAll(bodyReader)
  124. if err != nil {
  125. return err
  126. }
  127. addArchiveEntry(db, messageID, category, subject, body, date, inReplyTo, dkimStatus, sender, recipients)
  128. if sender.Address() != config.Mímir.PersonalAddress { // todo if forwarding is on
  129. forwardMessage(config, category, []string{sender.Address(), strings.Replace(config.Mímir.RecipientTemplate, "[:]", category, 1)}, messageID, inReplyTo, subject, body)
  130. }
  131. }
  132. if err := <-done; err != nil {
  133. return err
  134. }
  135. return nil
  136. }
  137. func mimir(db *sql.DB, config Config) {
  138. c, err := client.DialTLS(config.Mímir.ImapAddress, nil)
  139. if err != nil {
  140. log.Fatalln(err)
  141. }
  142. log.Println("Connected")
  143. defer c.Logout()
  144. if err := c.Login(config.Mímir.ImapUsername, config.Mímir.ImapPassword); err != nil {
  145. log.Fatalln(err)
  146. }
  147. log.Println("Logged in")
  148. err = archiveInbox(db, config.Mímir.ImapInbox, c, config)
  149. if err != nil {
  150. log.Fatalln(err)
  151. }
  152. }
  153. func mimir_serve(w http.ResponseWriter, r *http.Request) {
  154. // todo
  155. }