eostre_2.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package eostre
  2. import (
  3. "bytes"
  4. "encoding/base64"
  5. "fmt"
  6. "io"
  7. "log"
  8. "os"
  9. "os/exec"
  10. "path"
  11. "strings"
  12. "time"
  13. "apiote.xyz/p/asgard/jotunheim"
  14. "filippo.io/age"
  15. "github.com/emersion/go-imap"
  16. "github.com/emersion/go-imap/client"
  17. "github.com/emersion/go-message"
  18. _ "github.com/emersion/go-message/charset"
  19. "github.com/emersion/go-sasl"
  20. "github.com/emersion/go-smtp"
  21. )
  22. func DownloadDiary(config jotunheim.Config) error {
  23. c, err := client.DialTLS(config.Eostre.DiaryImapAddress, nil)
  24. if err != nil {
  25. log.Fatalln(err)
  26. }
  27. defer c.Close()
  28. log.Println("Connected")
  29. defer c.Logout()
  30. if err := c.Login(config.Eostre.DiaryImapUsername, config.Eostre.DiaryImapPassword); err != nil {
  31. log.Fatalln(err)
  32. }
  33. log.Println("Logged in")
  34. mbox, err := c.Select("INBOX", false)
  35. if err != nil {
  36. log.Fatalln(err)
  37. }
  38. from := uint32(1)
  39. to := mbox.Messages
  40. seqset := new(imap.SeqSet)
  41. seqset.AddRange(from, to)
  42. messages := make(chan *imap.Message, 10)
  43. done := make(chan error, 1)
  44. section := &imap.BodySectionName{}
  45. go func() {
  46. done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope, section.FetchItem(), imap.FetchFlags, imap.FetchUid, imap.FetchInternalDate}, messages)
  47. }()
  48. var latestMessage message.Entity
  49. var latestDate time.Time
  50. for msg := range messages {
  51. subject := msg.Envelope.Subject
  52. if subject != config.Eostre.DiarySubject {
  53. log.Printf("ignoring subject %s\n", subject)
  54. continue
  55. }
  56. sender := msg.Envelope.From[0]
  57. if sender.Address() != config.Eostre.DiarySender {
  58. log.Printf("ignoring from %s as not authorised\n", sender)
  59. continue
  60. }
  61. bodyReader := msg.GetBody(section)
  62. if bodyReader == nil {
  63. log.Printf("body for %d is nil\n", msg.Uid)
  64. continue
  65. }
  66. m, err := message.Read(bodyReader)
  67. if err != nil {
  68. log.Println(err)
  69. continue
  70. }
  71. t, _, err := m.Header.ContentType()
  72. if err != nil {
  73. log.Println(err)
  74. continue
  75. }
  76. if t != "application/age" {
  77. log.Printf("%d is not age\n", msg.Uid)
  78. continue
  79. }
  80. log.Printf("msg %v is after %v?\n", msg.InternalDate, latestDate)
  81. if msg.InternalDate.After(latestDate) {
  82. log.Printf("yes\n")
  83. latestDate = msg.InternalDate
  84. latestMessage = *m
  85. }
  86. }
  87. if latestMessage.Header.Len() == 0 {
  88. return fmt.Errorf("No messages with diary")
  89. }
  90. identity, err := age.ParseX25519Identity(config.Eostre.DiaryPrivateKey)
  91. if err != nil {
  92. log.Fatalf("Failed to parse private key: %v", err)
  93. }
  94. r, err := age.Decrypt(latestMessage.Body, identity)
  95. if err != nil {
  96. log.Fatalf("Failed to open encrypted file: %v", err)
  97. }
  98. out, err := os.Create("diary.epub")
  99. if err != nil {
  100. log.Fatalf("Failed to create diary file: %v", err)
  101. }
  102. if _, err := io.Copy(out, r); err != nil {
  103. log.Fatalf("Failed to read encrypted file: %v", err)
  104. }
  105. out.Close()
  106. return nil
  107. }
  108. func UpdateDiary(config jotunheim.Config) error {
  109. home, err := os.UserHomeDir()
  110. if err != nil {
  111. return err
  112. }
  113. // TODO find also in /usr/share/asgard
  114. _, err = exec.Command(path.Join(home, ".local/share/asgard/eostre.sh")).Output()
  115. return err
  116. }
  117. func SendDiary(config jotunheim.Config) error {
  118. recipient, err := age.ParseX25519Recipient(config.Eostre.DiaryPublicKey)
  119. if err != nil {
  120. log.Fatalf("Failed to parse public key: %v", err)
  121. }
  122. b := &bytes.Buffer{}
  123. b64 := base64.NewEncoder(base64.StdEncoding, b)
  124. in, err := os.Open("diary.epub")
  125. if err != nil {
  126. log.Fatalf("Failed to open decrypted file: %v", err)
  127. }
  128. w, err := age.Encrypt(b64, recipient)
  129. if err != nil {
  130. log.Fatalf("Failed to encrypt file: %v", err)
  131. }
  132. if _, err := io.Copy(w, in); err != nil {
  133. log.Fatalf("Failed to read decrypted file: %v", err)
  134. }
  135. in.Close()
  136. w.Close()
  137. b64.Close()
  138. os.Remove("diary.epub")
  139. now := time.Now().Format("20060102T150405Z0700")
  140. msg := strings.NewReader("To: " + config.Eostre.DiaryRecipient + "\r\n" +
  141. "From: " + config.Eostre.DiarySender + "\r\n" +
  142. "Date: " + now + "\r\n" +
  143. "Message-ID: " + now + "_eostre@apiote.xyz\r\n" +
  144. "MIME-Version: 1.0\r\n" +
  145. "Subject: Diary\r\n" +
  146. "Content-Type: application/age; name=diary.epub.age\r\n" +
  147. "Content-Transfer-Encoding: base64\r\n" +
  148. "\r\n" +
  149. string(b.Bytes()),
  150. )
  151. c, err := smtp.DialTLS(config.Eostre.DiarySmtpAddress, nil)
  152. if err != nil {
  153. log.Fatalf("Failed smtp dial: %v", err)
  154. }
  155. auth := sasl.NewPlainClient("", config.Eostre.DiarySmtpUsername, config.Eostre.DiarySmtpPassword)
  156. err = c.Auth(auth)
  157. if err != nil {
  158. log.Fatalf("Failed smtp auth: %v", err)
  159. }
  160. err = c.SendMail(config.Eostre.DiarySender, []string{config.Eostre.DiaryRecipient}, msg)
  161. if err != nil {
  162. return err
  163. }
  164. return nil
  165. }