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