github.com/glennzw/gophish@v0.8.1-0.20190824020715-24fe998a3aa0/worker/worker.go (about)

     1  package worker
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	log "github.com/gophish/gophish/logger"
     8  	"github.com/gophish/gophish/mailer"
     9  	"github.com/gophish/gophish/models"
    10  	"github.com/sirupsen/logrus"
    11  )
    12  
    13  // Worker is an interface that defines the operations needed for a background worker
    14  type Worker interface {
    15  	Start()
    16  	LaunchCampaign(c models.Campaign)
    17  	SendTestEmail(s *models.EmailRequest) error
    18  }
    19  
    20  // DefaultWorker is the background worker that handles watching for new campaigns and sending emails appropriately.
    21  type DefaultWorker struct {
    22  	mailer mailer.Mailer
    23  }
    24  
    25  // New creates a new worker object to handle the creation of campaigns
    26  func New(options ...func(Worker) error) (Worker, error) {
    27  	defaultMailer := mailer.NewMailWorker()
    28  	w := &DefaultWorker{
    29  		mailer: defaultMailer,
    30  	}
    31  	for _, opt := range options {
    32  		if err := opt(w); err != nil {
    33  			return nil, err
    34  		}
    35  	}
    36  	return w, nil
    37  }
    38  
    39  // WithMailer sets the mailer for a given worker.
    40  // By default, workers use a standard, default mailworker.
    41  func WithMailer(m mailer.Mailer) func(*DefaultWorker) error {
    42  	return func(w *DefaultWorker) error {
    43  		w.mailer = m
    44  		return nil
    45  	}
    46  }
    47  
    48  // Start launches the worker to poll the database every minute for any pending maillogs
    49  // that need to be processed.
    50  func (w *DefaultWorker) Start() {
    51  	log.Info("Background Worker Started Successfully - Waiting for Campaigns")
    52  	go w.mailer.Start(context.Background())
    53  	for t := range time.Tick(1 * time.Minute) {
    54  		ms, err := models.GetQueuedMailLogs(t.UTC())
    55  		if err != nil {
    56  			log.Error(err)
    57  			continue
    58  		}
    59  		// Lock the MailLogs (they will be unlocked after processing)
    60  		err = models.LockMailLogs(ms, true)
    61  		if err != nil {
    62  			log.Error(err)
    63  			continue
    64  		}
    65  		// We'll group the maillogs by campaign ID to (sort of) group
    66  		// them by sending profile. This lets the mailer re-use the Sender
    67  		// instead of having to re-connect to the SMTP server for every
    68  		// email.
    69  		msg := make(map[int64][]mailer.Mail)
    70  		for _, m := range ms {
    71  			msg[m.CampaignId] = append(msg[m.CampaignId], m)
    72  		}
    73  
    74  		// Next, we process each group of maillogs in parallel
    75  		for cid, msc := range msg {
    76  			go func(cid int64, msc []mailer.Mail) {
    77  				uid := msc[0].(*models.MailLog).UserId
    78  				c, err := models.GetCampaign(cid, uid)
    79  				if err != nil {
    80  					log.Error(err)
    81  					errorMail(err, msc)
    82  					return
    83  				}
    84  				if c.Status == models.CampaignQueued {
    85  					err := c.UpdateStatus(models.CampaignInProgress)
    86  					if err != nil {
    87  						log.Error(err)
    88  						return
    89  					}
    90  				}
    91  				log.WithFields(logrus.Fields{
    92  					"num_emails": len(msc),
    93  				}).Info("Sending emails to mailer for processing")
    94  				w.mailer.Queue(msc)
    95  			}(cid, msc)
    96  		}
    97  	}
    98  }
    99  
   100  // LaunchCampaign starts a campaign
   101  func (w *DefaultWorker) LaunchCampaign(c models.Campaign) {
   102  	ms, err := models.GetMailLogsByCampaign(c.Id)
   103  	if err != nil {
   104  		log.Error(err)
   105  		return
   106  	}
   107  	models.LockMailLogs(ms, true)
   108  	// This is required since you cannot pass a slice of values
   109  	// that implements an interface as a slice of that interface.
   110  	mailEntries := []mailer.Mail{}
   111  	currentTime := time.Now().UTC()
   112  	for _, m := range ms {
   113  		// Only send the emails scheduled to be sent for the past minute to
   114  		// respect the campaign scheduling options
   115  		if m.SendDate.After(currentTime) {
   116  			m.Unlock()
   117  			continue
   118  		}
   119  		mailEntries = append(mailEntries, m)
   120  	}
   121  	w.mailer.Queue(mailEntries)
   122  }
   123  
   124  // SendTestEmail sends a test email
   125  func (w *DefaultWorker) SendTestEmail(s *models.EmailRequest) error {
   126  	go func() {
   127  		ms := []mailer.Mail{s}
   128  		w.mailer.Queue(ms)
   129  	}()
   130  	return <-s.ErrorChan
   131  }
   132  
   133  // errorMail is a helper to handle erroring out a slice of Mail instances
   134  // in the case that an unrecoverable error occurs.
   135  func errorMail(err error, ms []mailer.Mail) {
   136  	for _, m := range ms {
   137  		m.Error(err)
   138  	}
   139  }