// Monitor is a worker that monitors IMAP servers for reported campaign emails
typeMonitorstruct{
cancelfunc()
}
// 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(ctxcontext.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.
iferr!=nil{
log.Error(err)
break
}
for_,dbuser:=rangedbusers{
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
gomonitor(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).
funcmonitor(uidint64,ctxcontext.Context){
for{
select{
case<-ctx.Done():
return
default:
// 1. Check if user exists, if not, return.
_,err:=models.GetUser(uid)
iferr!=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)
iferr!=nil{
log.Error(err)
break
}
iflen(imapSettings)>0{
im:=imapSettings[0]
// 3. Check if IMAP is enabled
ifim.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
funcNewMonitor()*Monitor{
im:=&Monitor{}
returnim
}
// 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
goim.start(ctx)
returnnil
}
// Shutdown attempts to gracefully shutdown the IMAP monitor.
func(im*Monitor)Shutdown()error{
log.Info("Shutting down IMAP monitor manager")
im.cancel()
returnnil
}
// checkForNewEmails logs into an IMAP account and checks unread emails
// for the rid campaign identifier.
funccheckForNewEmails(immodels.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)
iferr!=nil{
log.Error(err)
return
}
// Update last_succesful_login here via im.Host
err=models.SuccessfulLogin(&im)
iflen(msgs)>0{
varreportingFailed[]uint32// UIDs of emails that were unable to be reported to phishing server, mark as unread
varcampaignEmails[]uint32// UIDs of campaign emails. If DeleteReportedCampaignEmail is true, we will delete these
for_,m:=rangemsgs{
// Check if sender is from company's domain, if enabled. TODO: Make this an IMAP filter
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)
ifrid!=""{
rid=rid[5:]
log.Infof("User '%s' reported email with rid %s",m.Email.From,rid)
result,err:=models.GetResult(rid)
iferr!=nil{
log.Error("Error reporting GoPhish email with rid ",rid,": ",err.Error())