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