hermodr.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. package hermodr
  2. import (
  3. "io"
  4. "net/mail"
  5. "strings"
  6. "apiote.xyz/p/asgard/idavollr"
  7. "apiote.xyz/p/asgard/jotunheim"
  8. "apiote.xyz/p/gott/v2"
  9. "github.com/ProtonMail/gopenpgp/v2/helper"
  10. "github.com/emersion/go-imap"
  11. "github.com/emersion/go-smtp"
  12. )
  13. type EmptyMessageError struct{}
  14. func (EmptyMessageError) Error() string {
  15. return "Server didn't return message body"
  16. }
  17. type HermodrMailbox struct {
  18. idavollr.Mailbox
  19. }
  20. type HermodrImapMessage struct {
  21. idavollr.ImapMessage
  22. config jotunheim.Config
  23. literal imap.Literal
  24. mailMsg *mail.Message
  25. body string
  26. plain string
  27. armour string
  28. }
  29. func redirectMessages(am idavollr.AbstractMailbox) error {
  30. m := am.(*HermodrMailbox)
  31. for msg := range m.Messages() {
  32. imapMessage := idavollr.ImapMessage{
  33. Msg: msg,
  34. Sect: m.Section(),
  35. }
  36. imapMessage.SetClient(am.Client())
  37. r := gott.R[idavollr.AbstractImapMessage]{
  38. S: &HermodrImapMessage{
  39. ImapMessage: imapMessage,
  40. config: m.Conf,
  41. },
  42. }.
  43. Bind(getBodySection).
  44. Bind(readLiteralMessage).
  45. Bind(readLiteralBody).
  46. Map(composePlaintextBody).
  47. Bind(encrypt).
  48. Tee(send).
  49. Tee(markRead).
  50. Tee(moveMessage)
  51. if r.E != nil {
  52. return r.E
  53. }
  54. }
  55. return nil
  56. }
  57. func getBodySection(m idavollr.AbstractImapMessage) (idavollr.AbstractImapMessage, error) {
  58. hm := m.(*HermodrImapMessage)
  59. r := hm.Message().GetBody(m.Section())
  60. if r == nil {
  61. return hm, EmptyMessageError{}
  62. }
  63. hm.literal = r
  64. return hm, nil
  65. }
  66. func readLiteralMessage(m idavollr.AbstractImapMessage) (idavollr.AbstractImapMessage, error) {
  67. hm := m.(*HermodrImapMessage)
  68. msg, err := mail.ReadMessage(hm.literal)
  69. hm.mailMsg = msg
  70. return hm, err
  71. }
  72. func readLiteralBody(m idavollr.AbstractImapMessage) (idavollr.AbstractImapMessage, error) {
  73. hm := m.(*HermodrImapMessage)
  74. body, err := io.ReadAll(hm.mailMsg.Body)
  75. hm.body = string(body)
  76. return hm, err
  77. }
  78. func composePlaintextBody(m idavollr.AbstractImapMessage) idavollr.AbstractImapMessage {
  79. hm := m.(*HermodrImapMessage)
  80. header := hm.mailMsg.Header
  81. plainText := "Content-Type: " + header.Get("Content-Type") + "; protected-headers=\"v1\"\r\n"
  82. plainText += "From: " + header.Get("From") + "\r\n"
  83. plainText += "Message-ID: " + header.Get("Message-ID") + "\r\n"
  84. plainText += "Subject: " + header.Get("Subject") + "\r\n"
  85. plainText += "\r\n"
  86. plainText += string(hm.body)
  87. hm.plain = plainText
  88. return hm
  89. }
  90. func encrypt(m idavollr.AbstractImapMessage) (idavollr.AbstractImapMessage, error) {
  91. hm := m.(*HermodrImapMessage)
  92. armour, err := helper.EncryptMessageArmored(hm.config.Hermodr.PublicKey, hm.plain)
  93. hm.armour = armour
  94. return hm, err
  95. }
  96. func send(m idavollr.AbstractImapMessage) error {
  97. hm := m.(*HermodrImapMessage)
  98. from := hm.mailMsg.Header.Get("From")
  99. date := hm.mailMsg.Header.Get("Date")
  100. messageID := hm.mailMsg.Header.Get("Message-ID")
  101. to := []string{hm.config.Hermodr.Recipient}
  102. msg := strings.NewReader("To: " + hm.config.Hermodr.Recipient + "\r\n" +
  103. "From: " + from + "\r\n" +
  104. "Date: " + date + "\r\n" +
  105. "Message-ID: " + messageID + "\r\n" +
  106. "MIME-Version: 1.0\r\n" +
  107. "Subject: ...\r\n" +
  108. "Content-Type: multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"---------------------997d365ae018229dc62ea2ff6b617cac\"; charset=utf-8\r\n" +
  109. "\r\n" +
  110. "This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)\r\n" +
  111. "-----------------------997d365ae018229dc62ea2ff6b617cac\r\n" +
  112. "Content-Type: application/pgp-encrypted\r\n" +
  113. "Content-Description: PGP/MIME version identification\r\n" +
  114. "\r\n" +
  115. "Version: 1\r\n" +
  116. "\r\n" +
  117. "-----------------------997d365ae018229dc62ea2ff6b617cac\r\n" +
  118. "Content-Type: application/octet-stream; name=\"encrypted.asc\"\r\n" +
  119. "Content-Description: OpenPGP encrypted message\r\n" +
  120. "Content-Disposition: inline; filename=\"encrypted.asc\"\r\n" +
  121. "\r\n" +
  122. hm.armour +
  123. "\r\n" +
  124. "\r\n" +
  125. "-----------------------997d365ae018229dc62ea2ff6b617cac--\r\n")
  126. err := smtp.SendMail(hm.config.Hermodr.SmtpServer, nil, hm.config.Hermodr.SmtpUsername, to, msg) // TODO send with login
  127. return err
  128. }
  129. func markRead(m idavollr.AbstractImapMessage) error {
  130. seqset := new(imap.SeqSet)
  131. seqset.AddNum(1) // TODO collect seqset
  132. item := imap.FormatFlagsOp(imap.AddFlags, true)
  133. flags := []interface{}{imap.SeenFlag}
  134. err := m.Client().Store(seqset, item, flags, nil) // TODO store outside of loop
  135. return err
  136. }
  137. func moveMessage(m idavollr.AbstractImapMessage) error { // TODO collect messages out of loop and move all
  138. hm := m.(*HermodrImapMessage)
  139. return idavollr.MoveMsg(m.Client(), hm.Msg, hm.config.Hermodr.ImapFolderRedirected)
  140. }
  141. func Hermodr(config jotunheim.Config) error {
  142. mailbox := &HermodrMailbox{
  143. Mailbox: idavollr.Mailbox{
  144. MboxName: config.Hermodr.ImapFolderInbox,
  145. ImapAdr: config.Hermodr.ImapAddress,
  146. ImapUser: config.Hermodr.ImapUsername,
  147. ImapPass: config.Hermodr.ImapPassword,
  148. Conf: config,
  149. },
  150. }
  151. mailbox.SetupChannels()
  152. r := gott.R[idavollr.AbstractMailbox]{
  153. S: mailbox,
  154. }.
  155. Bind(idavollr.ConnectInsecure).
  156. Tee(idavollr.Login).
  157. Bind(idavollr.SelectInbox).
  158. Tee(idavollr.CheckEmptyBox).
  159. Map(idavollr.FetchMessages).
  160. Tee(redirectMessages).
  161. Recover(idavollr.IgnoreEmptyBox).
  162. Recover(idavollr.Disconnect)
  163. return r.E
  164. }