🎣 Open-Source Phishing Toolkit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

194 lines
6.5 KiB

  1. package imap
  2. /* TODO:
  3. * - Have a counter per config for number of consecutive login errors and backoff (e.g if supplied creds are incorrect)
  4. * - Have a DB field "last_login_error" if last login failed
  5. * - DB counter for non-campaign emails that the admin should investigate
  6. * - Add field to User for numner of non-campaign emails reported
  7. */
  8. import (
  9. "context"
  10. "regexp"
  11. "strconv"
  12. "strings"
  13. "time"
  14. log "github.com/gophish/gophish/logger"
  15. "github.com/gophish/gophish/models"
  16. )
  17. // Pattern for GoPhish emails e.g ?rid=AbC123
  18. var goPhishRegex = regexp.MustCompile("(\\?rid=[A-Za-z0-9]{7})")
  19. // Monitor is a worker that monitors IMAP servers for reported campaign emails
  20. type Monitor struct {
  21. cancel func()
  22. }
  23. // Monitor.start() checks for campaign emails
  24. // As each account can have its own polling frequency set we need to run one Go routine for
  25. // each, as well as keeping an eye on newly created user accounts.
  26. func (im *Monitor) start(ctx context.Context) {
  27. usermap := make(map[int64]int) // Keep track of running go routines, one per user. We assume incrementing non-repeating UIDs (for the case where users are deleted and re-added).
  28. for {
  29. select {
  30. case <-ctx.Done():
  31. return
  32. default:
  33. dbusers, err := models.GetUsers() //Slice of all user ids. Each user gets their own IMAP monitor routine.
  34. if err != nil {
  35. log.Error(err)
  36. break
  37. }
  38. for _, dbuser := range dbusers {
  39. if _, ok := usermap[dbuser.Id]; !ok { // If we don't currently have a running Go routine for this user, start one.
  40. log.Info("Starting new IMAP monitor for user ", dbuser.Username)
  41. usermap[dbuser.Id] = 1
  42. go monitor(dbuser.Id, ctx)
  43. }
  44. }
  45. time.Sleep(10 * time.Second) // Every ten seconds we check if a new user has been created
  46. }
  47. }
  48. }
  49. // monitor will continuously login to the IMAP settings associated to the supplied user id (if the user account has IMAP settings, and they're enabled.)
  50. // It also verifies the user account exists, and returns if not (for the case of a user being deleted).
  51. func monitor(uid int64, ctx context.Context) {
  52. for {
  53. select {
  54. case <-ctx.Done():
  55. return
  56. default:
  57. // 1. Check if user exists, if not, return.
  58. _, err := models.GetUser(uid)
  59. if err != nil { // Not sure if there's a better way to determine user existence via id.
  60. log.Info("User ", uid, " seems to have been deleted. Stopping IMAP monitor for this user.")
  61. return
  62. }
  63. // 2. Check if user has IMAP settings.
  64. imapSettings, err := models.GetIMAP(uid)
  65. if err != nil {
  66. log.Error(err)
  67. break
  68. }
  69. if len(imapSettings) > 0 {
  70. im := imapSettings[0]
  71. // 3. Check if IMAP is enabled
  72. if im.Enabled {
  73. log.Debug("Checking IMAP for user ", uid, ": ", im.Username, "@", im.Host)
  74. checkForNewEmails(im)
  75. time.Sleep((time.Duration(im.IMAPFreq) - 10) * time.Second) // Subtract 10 to compensate for the default sleep of 10 at the bottom
  76. }
  77. }
  78. }
  79. time.Sleep(10 * time.Second)
  80. }
  81. }
  82. // NewMonitor returns a new instance of imap.Monitor
  83. func NewMonitor() *Monitor {
  84. im := &Monitor{}
  85. return im
  86. }
  87. // Start launches the IMAP campaign monitor
  88. func (im *Monitor) Start() error {
  89. log.Info("Starting IMAP monitor manager")
  90. ctx, cancel := context.WithCancel(context.Background()) // ctx is the derivedContext
  91. im.cancel = cancel
  92. go im.start(ctx)
  93. return nil
  94. }
  95. // Shutdown attempts to gracefully shutdown the IMAP monitor.
  96. func (im *Monitor) Shutdown() error {
  97. log.Info("Shutting down IMAP monitor manager")
  98. im.cancel()
  99. return nil
  100. }
  101. // checkForNewEmails logs into an IMAP account and checks unread emails
  102. // for the rid campaign identifier.
  103. func checkForNewEmails(im models.IMAP) {
  104. im.Host = im.Host + ":" + strconv.Itoa(int(im.Port)) // Append port
  105. mailServer := Mailbox{
  106. Host: im.Host,
  107. TLS: im.TLS,
  108. User: im.Username,
  109. Pwd: im.Password,
  110. Folder: im.Folder}
  111. msgs, err := mailServer.GetUnread(true, false)
  112. if err != nil {
  113. log.Error(err)
  114. return
  115. }
  116. // Update last_succesful_login here via im.Host
  117. err = models.SuccessfulLogin(&im)
  118. if len(msgs) > 0 {
  119. var reportingFailed []uint32 // UIDs of emails that were unable to be reported to phishing server, mark as unread
  120. var campaignEmails []uint32 // UIDs of campaign emails. If DeleteReportedCampaignEmail is true, we will delete these
  121. for _, m := range msgs {
  122. // Check if sender is from company's domain, if enabled. TODO: Make this an IMAP filter
  123. if im.RestrictDomain != "" { // e.g domainResitct = widgets.com
  124. splitEmail := strings.Split(m.Email.From, "@")
  125. senderDomain := splitEmail[len(splitEmail)-1]
  126. if senderDomain != im.RestrictDomain {
  127. log.Debug("Ignoring email as not from company domain: ", senderDomain)
  128. continue
  129. }
  130. }
  131. body := string(append(m.Email.Text, m.Email.HTML...)) // Not sure if we need to check the Text as well as the HTML. Perhaps sometimes Text only emails won't have an HTML component?
  132. rid := goPhishRegex.FindString(body)
  133. if rid != "" {
  134. rid = rid[5:]
  135. log.Infof("User '%s' reported email with rid %s", m.Email.From, rid)
  136. result, err := models.GetResult(rid)
  137. if err != nil {
  138. log.Error("Error reporting GoPhish email with rid ", rid, ": ", err.Error())
  139. reportingFailed = append(reportingFailed, m.UID)
  140. } else {
  141. err = result.HandleEmailReport(models.EventDetails{})
  142. if err != nil {
  143. log.Error("Error updating GoPhish email with rid ", rid, ": ", err.Error())
  144. } else {
  145. if im.DeleteReportedCampaignEmail == true {
  146. campaignEmails = append(campaignEmails, m.UID)
  147. }
  148. }
  149. }
  150. } else {
  151. // In the future this should be an alert in Gophish
  152. log.Debugf("User '%s' reported email with subject '%s'. This is not a GoPhish campaign; you should investigate it.\n", m.Email.From, m.Email.Subject)
  153. }
  154. // Check if any emails were unable to be reported, so we can mark them as unread
  155. if len(reportingFailed) > 0 {
  156. log.Debugf("Marking %d emails as unread as failed to report\n", len(reportingFailed))
  157. err := mailServer.MarkAsUnread(reportingFailed) // Set emails as unread that we failed to report to GoPhish
  158. if err != nil {
  159. log.Error("Unable to mark emails as unread: ", err.Error())
  160. }
  161. }
  162. // If the DeleteReportedCampaignEmail flag is set, delete reported Gophish campaign emails
  163. if im.DeleteReportedCampaignEmail == true && len(campaignEmails) > 0 {
  164. log.Debugf("Deleting %d campaign emails\n", len(campaignEmails))
  165. err := mailServer.DeleteEmails(campaignEmails) // Delete GoPhish campaign emails.
  166. if err != nil {
  167. log.Error("Failed to delete emails: ", err.Error())
  168. }
  169. }
  170. }
  171. } else {
  172. log.Debug("No new emails for ", im.Username)
  173. }
  174. }