github.com/merlinepedra/gopphish-attack@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  }