hermóðr.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "net/mail"
  6. "os"
  7. "strings"
  8. "github.com/ProtonMail/gopenpgp/v2/helper"
  9. "github.com/emersion/go-imap"
  10. "github.com/emersion/go-imap/client"
  11. "github.com/emersion/go-smtp"
  12. "notabug.org/apiote/gott"
  13. )
  14. type EmptyMessageError struct{}
  15. func (EmptyMessageError) Error() string {
  16. return "Server didn't return message body"
  17. }
  18. type Result struct {
  19. Config Config
  20. Client *client.Client
  21. Mailbox *imap.MailboxStatus
  22. Literal imap.Literal
  23. Message *mail.Message
  24. Body string
  25. PlainText string
  26. Armour string
  27. }
  28. func connect(args ...interface{}) (interface{}, error) {
  29. result := args[0].(Result)
  30. c, err := client.Dial(result.Config.Hermóðr.ImapAddress)
  31. result.Client = c
  32. return result, err
  33. }
  34. func login(args ...interface{}) error {
  35. result := args[0].(Result)
  36. err := result.Client.Login(result.Config.Hermóðr.ImapUsername, result.Config.Hermóðr.ImapPassword)
  37. return err
  38. }
  39. func selectInbox(args ...interface{}) (interface{}, error) {
  40. result := args[0].(Result)
  41. mbox, err := result.Client.Select(result.Config.Hermóðr.ImapFolderInbox, false)
  42. result.Mailbox = mbox
  43. return result, err
  44. }
  45. func redirectMessages(args ...interface{}) (interface{}, error) {
  46. result := args[0].(Result)
  47. for result.Mailbox.Messages > 0 {
  48. r, err := gott.NewResult(result).
  49. Bind(getMessage).
  50. Bind(readMessage).
  51. Bind(readBody).
  52. Map(composePlaintextBody).
  53. Bind(encrypt).
  54. Tee(send).
  55. Tee(markRead).
  56. Tee(moveMessage).
  57. Bind(selectInbox).
  58. Finish()
  59. if err != nil {
  60. return r, err
  61. }
  62. result = r.(Result)
  63. }
  64. return result, nil
  65. }
  66. func getMessage(args ...interface{}) (interface{}, error) {
  67. result := args[0].(Result)
  68. seqset := new(imap.SeqSet)
  69. seqset.AddNum(1)
  70. section := &imap.BodySectionName{}
  71. items := []imap.FetchItem{section.FetchItem()}
  72. messages := make(chan *imap.Message, 1)
  73. done := make(chan error, 1)
  74. go func() {
  75. done <- result.Client.Fetch(seqset, items, messages)
  76. }()
  77. msg := <-messages
  78. r := msg.GetBody(section)
  79. if r == nil {
  80. return result, EmptyMessageError{}
  81. }
  82. result.Literal = r
  83. err := <-done
  84. return result, err
  85. }
  86. func readMessage(args ...interface{}) (interface{}, error) {
  87. result := args[0].(Result)
  88. m, err := mail.ReadMessage(result.Literal)
  89. result.Message = m
  90. return result, err
  91. }
  92. func readBody(args ...interface{}) (interface{}, error) {
  93. result := args[0].(Result)
  94. body, err := io.ReadAll(result.Message.Body)
  95. result.Body = string(body)
  96. return result, err
  97. }
  98. func composePlaintextBody(args ...interface{}) interface{} {
  99. result := args[0].(Result)
  100. header := result.Message.Header
  101. plainText := "Content-Type: " + header.Get("Content-Type") + "; protected-headers=\"v1\"\r\n"
  102. plainText += "From: " + header.Get("From") + "\r\n"
  103. plainText += "Message-ID: " + header.Get("Message-ID") + "\r\n"
  104. plainText += "Subject: " + header.Get("Subject") + "\r\n"
  105. plainText += "\r\n"
  106. plainText += string(result.Body)
  107. result.PlainText = plainText
  108. return result
  109. }
  110. func encrypt(args ...interface{}) (interface{}, error) {
  111. result := args[0].(Result)
  112. armour, err := helper.EncryptMessageArmored(result.Config.Hermóðr.PublicKey, result.PlainText)
  113. result.Armour = armour
  114. return result, err
  115. }
  116. func send(args ...interface{}) error {
  117. result := args[0].(Result)
  118. from := result.Message.Header.Get("From")
  119. date := result.Message.Header.Get("Date")
  120. messageID := result.Message.Header.Get("Message-ID")
  121. to := []string{result.Config.Hermóðr.Recipient}
  122. msg := strings.NewReader("To: " + result.Config.Hermóðr.Recipient + "\r\n" +
  123. "From: " + from + "\r\n" +
  124. "Date: " + date + "\r\n" +
  125. "Message-ID: " + messageID + "\r\n" +
  126. "MIME-Version: 1.0\r\n" +
  127. "Subject: ...\r\n" +
  128. "Content-Type: multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"---------------------997d365ae018229dc62ea2ff6b617cac\"; charset=utf-8\r\n" +
  129. "\r\n" +
  130. "This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)\r\n" +
  131. "-----------------------997d365ae018229dc62ea2ff6b617cac\r\n" +
  132. "Content-Type: application/pgp-encrypted\r\n" +
  133. "Content-Description: PGP/MIME version identification\r\n" +
  134. "\r\n" +
  135. "Version: 1\r\n" +
  136. "\r\n" +
  137. "-----------------------997d365ae018229dc62ea2ff6b617cac\r\n" +
  138. "Content-Type: application/octet-stream; name=\"encrypted.asc\"\r\n" +
  139. "Content-Description: OpenPGP encrypted message\r\n" +
  140. "Content-Disposition: inline; filename=\"encrypted.asc\"\r\n" +
  141. "\r\n" +
  142. result.Armour +
  143. "\r\n" +
  144. "\r\n" +
  145. "-----------------------997d365ae018229dc62ea2ff6b617cac--\r\n")
  146. err := smtp.SendMail(result.Config.Hermóðr.SmtpServer, nil, result.Config.Hermóðr.SmtpUsername, to, msg)
  147. return err
  148. }
  149. func markRead(args ...interface{}) error {
  150. result := args[0].(Result)
  151. seqset := new(imap.SeqSet)
  152. seqset.AddNum(1)
  153. item := imap.FormatFlagsOp(imap.AddFlags, true)
  154. flags := []interface{}{imap.SeenFlag}
  155. err := result.Client.Store(seqset, item, flags, nil)
  156. return err
  157. }
  158. func moveMessage(args ...interface{}) error {
  159. result := args[0].(Result)
  160. seqset := new(imap.SeqSet)
  161. seqset.AddNum(1)
  162. err := result.Client.Copy(seqset, result.Config.Hermóðr.ImapFolderRedirected)
  163. return err
  164. }
  165. func hermodr(config Config) {
  166. r, err := gott.NewResult(Result{Config: config}).
  167. SetLevelLog(gott.Debug).
  168. Bind(connect).
  169. Tee(login).
  170. Bind(selectInbox).
  171. Bind(redirectMessages).
  172. Finish()
  173. if err != nil {
  174. fmt.Fprintf(os.Stderr, "Error: %v", err)
  175. return
  176. }
  177. // Don't forget to logout
  178. if r.(Result).Client != nil {
  179. r.(Result).Client.Logout()
  180. }
  181. }