github.com/Ne0nd0g/gophish@v0.7.1-0.20190220040016-11493024a07d/models/campaign.go (about)

     1  package models
     2  
     3  import (
     4  	"errors"
     5  	"net/url"
     6  	"time"
     7  
     8  	log "github.com/gophish/gophish/logger"
     9  	"github.com/jinzhu/gorm"
    10  	"github.com/sirupsen/logrus"
    11  )
    12  
    13  // Campaign is a struct representing a created campaign
    14  type Campaign struct {
    15  	Id            int64     `json:"id"`
    16  	UserId        int64     `json:"-"`
    17  	Name          string    `json:"name" sql:"not null"`
    18  	CreatedDate   time.Time `json:"created_date"`
    19  	LaunchDate    time.Time `json:"launch_date"`
    20  	SendByDate    time.Time `json:"send_by_date"`
    21  	CompletedDate time.Time `json:"completed_date"`
    22  	TemplateId    int64     `json:"-"`
    23  	Template      Template  `json:"template"`
    24  	PageId        int64     `json:"-"`
    25  	Page          Page      `json:"page"`
    26  	Status        string    `json:"status"`
    27  	Results       []Result  `json:"results,omitempty"`
    28  	Groups        []Group   `json:"groups,omitempty"`
    29  	Events        []Event   `json:"timeline,omitemtpy"`
    30  	SMTPId        int64     `json:"-"`
    31  	SMTP          SMTP      `json:"smtp"`
    32  	URL           string    `json:"url"`
    33  }
    34  
    35  // CampaignResults is a struct representing the results from a campaign
    36  type CampaignResults struct {
    37  	Id      int64    `json:"id"`
    38  	Name    string   `json:"name"`
    39  	Status  string   `json:"status"`
    40  	Results []Result `json:"results,omitempty"`
    41  	Events  []Event  `json:"timeline,omitempty"`
    42  }
    43  
    44  // CampaignSummaries is a struct representing the overview of campaigns
    45  type CampaignSummaries struct {
    46  	Total     int64             `json:"total"`
    47  	Campaigns []CampaignSummary `json:"campaigns"`
    48  }
    49  
    50  // CampaignSummary is a struct representing the overview of a single camaign
    51  type CampaignSummary struct {
    52  	Id            int64         `json:"id"`
    53  	CreatedDate   time.Time     `json:"created_date"`
    54  	LaunchDate    time.Time     `json:"launch_date"`
    55  	SendByDate    time.Time     `json:"send_by_date"`
    56  	CompletedDate time.Time     `json:"completed_date"`
    57  	Status        string        `json:"status"`
    58  	Name          string        `json:"name"`
    59  	Stats         CampaignStats `json:"stats"`
    60  }
    61  
    62  // CampaignStats is a struct representing the statistics for a single campaign
    63  type CampaignStats struct {
    64  	Total         int64 `json:"total"`
    65  	EmailsSent    int64 `json:"sent"`
    66  	OpenedEmail   int64 `json:"opened"`
    67  	ClickedLink   int64 `json:"clicked"`
    68  	SubmittedData int64 `json:"submitted_data"`
    69  	EmailReported int64 `json:"email_reported"`
    70  	Error         int64 `json:"error"`
    71  }
    72  
    73  // Event contains the fields for an event
    74  // that occurs during the campaign
    75  type Event struct {
    76  	Id         int64     `json:"-"`
    77  	CampaignId int64     `json:"-"`
    78  	Email      string    `json:"email"`
    79  	Time       time.Time `json:"time"`
    80  	Message    string    `json:"message"`
    81  	Details    string    `json:"details"`
    82  }
    83  
    84  // EventDetails is a struct that wraps common attributes we want to store
    85  // in an event
    86  type EventDetails struct {
    87  	Payload url.Values        `json:"payload"`
    88  	Browser map[string]string `json:"browser"`
    89  }
    90  
    91  // EventError is a struct that wraps an error that occurs when sending an
    92  // email to a recipient
    93  type EventError struct {
    94  	Error string `json:"error"`
    95  }
    96  
    97  // ErrCampaignNameNotSpecified indicates there was no template given by the user
    98  var ErrCampaignNameNotSpecified = errors.New("Campaign name not specified")
    99  
   100  // ErrGroupNotSpecified indicates there was no template given by the user
   101  var ErrGroupNotSpecified = errors.New("No groups specified")
   102  
   103  // ErrTemplateNotSpecified indicates there was no template given by the user
   104  var ErrTemplateNotSpecified = errors.New("No email template specified")
   105  
   106  // ErrPageNotSpecified indicates a landing page was not provided for the campaign
   107  var ErrPageNotSpecified = errors.New("No landing page specified")
   108  
   109  // ErrSMTPNotSpecified indicates a sending profile was not provided for the campaign
   110  var ErrSMTPNotSpecified = errors.New("No sending profile specified")
   111  
   112  // ErrTemplateNotFound indicates the template specified does not exist in the database
   113  var ErrTemplateNotFound = errors.New("Template not found")
   114  
   115  // ErrGroupNotFound indicates a group specified by the user does not exist in the database
   116  var ErrGroupNotFound = errors.New("Group not found")
   117  
   118  // ErrPageNotFound indicates a page specified by the user does not exist in the database
   119  var ErrPageNotFound = errors.New("Page not found")
   120  
   121  // ErrSMTPNotFound indicates a sending profile specified by the user does not exist in the database
   122  var ErrSMTPNotFound = errors.New("Sending profile not found")
   123  
   124  // ErrInvalidSendByDate indicates that the user specified a send by date that occurs before the
   125  // launch date
   126  var ErrInvalidSendByDate = errors.New("The launch date must be before the \"send emails by\" date")
   127  
   128  // RecipientParameter is the URL parameter that points to the result ID for a recipient.
   129  const RecipientParameter = "rid"
   130  
   131  // Validate checks to make sure there are no invalid fields in a submitted campaign
   132  func (c *Campaign) Validate() error {
   133  	switch {
   134  	case c.Name == "":
   135  		return ErrCampaignNameNotSpecified
   136  	case len(c.Groups) == 0:
   137  		return ErrGroupNotSpecified
   138  	case c.Template.Name == "":
   139  		return ErrTemplateNotSpecified
   140  	case c.Page.Name == "":
   141  		return ErrPageNotSpecified
   142  	case c.SMTP.Name == "":
   143  		return ErrSMTPNotSpecified
   144  	case !c.SendByDate.IsZero() && !c.LaunchDate.IsZero() && c.SendByDate.Before(c.LaunchDate):
   145  		return ErrInvalidSendByDate
   146  	}
   147  	return nil
   148  }
   149  
   150  // UpdateStatus changes the campaign status appropriately
   151  func (c *Campaign) UpdateStatus(s string) error {
   152  	// This could be made simpler, but I think there's a bug in gorm
   153  	return db.Table("campaigns").Where("id=?", c.Id).Update("status", s).Error
   154  }
   155  
   156  // AddEvent creates a new campaign event in the database
   157  func (c *Campaign) AddEvent(e *Event) error {
   158  	e.CampaignId = c.Id
   159  	e.Time = time.Now().UTC()
   160  	return db.Save(e).Error
   161  }
   162  
   163  // getDetails retrieves the related attributes of the campaign
   164  // from the database. If the Events and the Results are not available,
   165  // an error is returned. Otherwise, the attribute name is set to [Deleted],
   166  // indicating the user deleted the attribute (template, smtp, etc.)
   167  func (c *Campaign) getDetails() error {
   168  	err := db.Model(c).Related(&c.Results).Error
   169  	if err != nil {
   170  		log.Warnf("%s: results not found for campaign", err)
   171  		return err
   172  	}
   173  	err = db.Model(c).Related(&c.Events).Error
   174  	if err != nil {
   175  		log.Warnf("%s: events not found for campaign", err)
   176  		return err
   177  	}
   178  	err = db.Table("templates").Where("id=?", c.TemplateId).Find(&c.Template).Error
   179  	if err != nil {
   180  		if err != gorm.ErrRecordNotFound {
   181  			return err
   182  		}
   183  		c.Template = Template{Name: "[Deleted]"}
   184  		log.Warnf("%s: template not found for campaign", err)
   185  	}
   186  	err = db.Where("template_id=?", c.Template.Id).Find(&c.Template.Attachments).Error
   187  	if err != nil && err != gorm.ErrRecordNotFound {
   188  		log.Warn(err)
   189  		return err
   190  	}
   191  	err = db.Table("pages").Where("id=?", c.PageId).Find(&c.Page).Error
   192  	if err != nil {
   193  		if err != gorm.ErrRecordNotFound {
   194  			return err
   195  		}
   196  		c.Page = Page{Name: "[Deleted]"}
   197  		log.Warnf("%s: page not found for campaign", err)
   198  	}
   199  	err = db.Table("smtp").Where("id=?", c.SMTPId).Find(&c.SMTP).Error
   200  	if err != nil {
   201  		// Check if the SMTP was deleted
   202  		if err != gorm.ErrRecordNotFound {
   203  			return err
   204  		}
   205  		c.SMTP = SMTP{Name: "[Deleted]"}
   206  		log.Warnf("%s: sending profile not found for campaign", err)
   207  	}
   208  	err = db.Where("smtp_id=?", c.SMTP.Id).Find(&c.SMTP.Headers).Error
   209  	if err != nil && err != gorm.ErrRecordNotFound {
   210  		log.Warn(err)
   211  		return err
   212  	}
   213  	return nil
   214  }
   215  
   216  // getBaseURL returns the Campaign's configured URL.
   217  // This is used to implement the TemplateContext interface.
   218  func (c *Campaign) getBaseURL() string {
   219  	return c.URL
   220  }
   221  
   222  // getFromAddress returns the Campaign's configured SMTP "From" address.
   223  // This is used to implement the TemplateContext interface.
   224  func (c *Campaign) getFromAddress() string {
   225  	return c.SMTP.FromAddress
   226  }
   227  
   228  // generateSendDate creates a sendDate
   229  func (c *Campaign) generateSendDate(idx int, totalRecipients int) time.Time {
   230  	// If no send date is specified, just return the launch date
   231  	if c.SendByDate.IsZero() || c.SendByDate.Equal(c.LaunchDate) {
   232  		return c.LaunchDate
   233  	}
   234  	// Otherwise, we can calculate the range of minutes to send emails
   235  	// (since we only poll once per minute)
   236  	totalMinutes := c.SendByDate.Sub(c.LaunchDate).Minutes()
   237  
   238  	// Next, we can determine how many minutes should elapse between emails
   239  	minutesPerEmail := totalMinutes / float64(totalRecipients)
   240  
   241  	// Then, we can calculate the offset for this particular email
   242  	offset := int(minutesPerEmail * float64(idx))
   243  
   244  	// Finally, we can just add this offset to the launch date to determine
   245  	// when the email should be sent
   246  	return c.LaunchDate.Add(time.Duration(offset) * time.Minute)
   247  }
   248  
   249  // getCampaignStats returns a CampaignStats object for the campaign with the given campaign ID.
   250  // It also backfills numbers as appropriate with a running total, so that the values are aggregated.
   251  func getCampaignStats(cid int64) (CampaignStats, error) {
   252  	s := CampaignStats{}
   253  	query := db.Table("results").Where("campaign_id = ?", cid)
   254  	err := query.Count(&s.Total).Error
   255  	if err != nil {
   256  		return s, err
   257  	}
   258  	query.Where("status=?", EventDataSubmit).Count(&s.SubmittedData)
   259  	if err != nil {
   260  		return s, err
   261  	}
   262  	query.Where("status=?", EventClicked).Count(&s.ClickedLink)
   263  	if err != nil {
   264  		return s, err
   265  	}
   266  	query.Where("reported=?", true).Count(&s.EmailReported)
   267  	if err != nil {
   268  		return s, err
   269  	}
   270  	// Every submitted data event implies they clicked the link
   271  	s.ClickedLink += s.SubmittedData
   272  	err = query.Where("status=?", EventOpened).Count(&s.OpenedEmail).Error
   273  	if err != nil {
   274  		return s, err
   275  	}
   276  	// Every clicked link event implies they opened the email
   277  	s.OpenedEmail += s.ClickedLink
   278  	err = query.Where("status=?", EventSent).Count(&s.EmailsSent).Error
   279  	if err != nil {
   280  		return s, err
   281  	}
   282  	// Every opened email event implies the email was sent
   283  	s.EmailsSent += s.OpenedEmail
   284  	err = query.Where("status=?", Error).Count(&s.Error).Error
   285  	return s, err
   286  }
   287  
   288  // GetCampaigns returns the campaigns owned by the given user.
   289  func GetCampaigns(uid int64) ([]Campaign, error) {
   290  	cs := []Campaign{}
   291  	err := db.Model(&User{Id: uid}).Related(&cs).Error
   292  	if err != nil {
   293  		log.Error(err)
   294  	}
   295  	for i := range cs {
   296  		err = cs[i].getDetails()
   297  		if err != nil {
   298  			log.Error(err)
   299  		}
   300  	}
   301  	return cs, err
   302  }
   303  
   304  // GetCampaignSummaries gets the summary objects for all the campaigns
   305  // owned by the current user
   306  func GetCampaignSummaries(uid int64) (CampaignSummaries, error) {
   307  	overview := CampaignSummaries{}
   308  	cs := []CampaignSummary{}
   309  	// Get the basic campaign information
   310  	query := db.Table("campaigns").Where("user_id = ?", uid)
   311  	query = query.Select("id, name, created_date, launch_date, completed_date, status")
   312  	err := query.Scan(&cs).Error
   313  	if err != nil {
   314  		log.Error(err)
   315  		return overview, err
   316  	}
   317  	for i := range cs {
   318  		s, err := getCampaignStats(cs[i].Id)
   319  		if err != nil {
   320  			log.Error(err)
   321  			return overview, err
   322  		}
   323  		cs[i].Stats = s
   324  	}
   325  	overview.Total = int64(len(cs))
   326  	overview.Campaigns = cs
   327  	return overview, nil
   328  }
   329  
   330  // GetCampaignSummary gets the summary object for a campaign specified by the campaign ID
   331  func GetCampaignSummary(id int64, uid int64) (CampaignSummary, error) {
   332  	cs := CampaignSummary{}
   333  	query := db.Table("campaigns").Where("user_id = ? AND id = ?", uid, id)
   334  	query = query.Select("id, name, created_date, launch_date, completed_date, status")
   335  	err := query.Scan(&cs).Error
   336  	if err != nil {
   337  		log.Error(err)
   338  		return cs, err
   339  	}
   340  	s, err := getCampaignStats(cs.Id)
   341  	if err != nil {
   342  		log.Error(err)
   343  		return cs, err
   344  	}
   345  	cs.Stats = s
   346  	return cs, nil
   347  }
   348  
   349  // GetCampaign returns the campaign, if it exists, specified by the given id and user_id.
   350  func GetCampaign(id int64, uid int64) (Campaign, error) {
   351  	c := Campaign{}
   352  	err := db.Where("id = ?", id).Where("user_id = ?", uid).Find(&c).Error
   353  	if err != nil {
   354  		log.Errorf("%s: campaign not found", err)
   355  		return c, err
   356  	}
   357  	err = c.getDetails()
   358  	return c, err
   359  }
   360  
   361  // GetCampaignResults returns just the campaign results for the given campaign
   362  func GetCampaignResults(id int64, uid int64) (CampaignResults, error) {
   363  	cr := CampaignResults{}
   364  	err := db.Table("campaigns").Where("id=? and user_id=?", id, uid).Find(&cr).Error
   365  	if err != nil {
   366  		log.WithFields(logrus.Fields{
   367  			"campaign_id": id,
   368  			"error":       err,
   369  		}).Error(err)
   370  		return cr, err
   371  	}
   372  	err = db.Table("results").Where("campaign_id=? and user_id=?", cr.Id, uid).Find(&cr.Results).Error
   373  	if err != nil {
   374  		log.Errorf("%s: results not found for campaign", err)
   375  		return cr, err
   376  	}
   377  	err = db.Table("events").Where("campaign_id=?", cr.Id).Find(&cr.Events).Error
   378  	if err != nil {
   379  		log.Errorf("%s: events not found for campaign", err)
   380  		return cr, err
   381  	}
   382  	return cr, err
   383  }
   384  
   385  // GetQueuedCampaigns returns the campaigns that are queued up for this given minute
   386  func GetQueuedCampaigns(t time.Time) ([]Campaign, error) {
   387  	cs := []Campaign{}
   388  	err := db.Where("launch_date <= ?", t).
   389  		Where("status = ?", CampaignQueued).Find(&cs).Error
   390  	if err != nil {
   391  		log.Error(err)
   392  	}
   393  	log.Infof("Found %d Campaigns to run\n", len(cs))
   394  	for i := range cs {
   395  		err = cs[i].getDetails()
   396  		if err != nil {
   397  			log.Error(err)
   398  		}
   399  	}
   400  	return cs, err
   401  }
   402  
   403  // PostCampaign inserts a campaign and all associated records into the database.
   404  func PostCampaign(c *Campaign, uid int64) error {
   405  	err := c.Validate()
   406  	if err != nil {
   407  		return err
   408  	}
   409  	// Fill in the details
   410  	c.UserId = uid
   411  	c.CreatedDate = time.Now().UTC()
   412  	c.CompletedDate = time.Time{}
   413  	c.Status = CampaignQueued
   414  	if c.LaunchDate.IsZero() {
   415  		c.LaunchDate = c.CreatedDate
   416  	} else {
   417  		c.LaunchDate = c.LaunchDate.UTC()
   418  	}
   419  	if !c.SendByDate.IsZero() {
   420  		c.SendByDate = c.SendByDate.UTC()
   421  	}
   422  	if c.LaunchDate.Before(c.CreatedDate) || c.LaunchDate.Equal(c.CreatedDate) {
   423  		c.Status = CampaignInProgress
   424  	}
   425  	// Check to make sure all the groups already exist
   426  	// Also, later we'll need to know the total number of recipients (counting
   427  	// duplicates is ok for now), so we'll do that here to save a loop.
   428  	totalRecipients := 0
   429  	for i, g := range c.Groups {
   430  		c.Groups[i], err = GetGroupByName(g.Name, uid)
   431  		if err == gorm.ErrRecordNotFound {
   432  			log.WithFields(logrus.Fields{
   433  				"group": g.Name,
   434  			}).Error("Group does not exist")
   435  			return ErrGroupNotFound
   436  		} else if err != nil {
   437  			log.Error(err)
   438  			return err
   439  		}
   440  		totalRecipients += len(c.Groups[i].Targets)
   441  	}
   442  	// Check to make sure the template exists
   443  	t, err := GetTemplateByName(c.Template.Name, uid)
   444  	if err == gorm.ErrRecordNotFound {
   445  		log.WithFields(logrus.Fields{
   446  			"template": t.Name,
   447  		}).Error("Template does not exist")
   448  		return ErrTemplateNotFound
   449  	} else if err != nil {
   450  		log.Error(err)
   451  		return err
   452  	}
   453  	c.Template = t
   454  	c.TemplateId = t.Id
   455  	// Check to make sure the page exists
   456  	p, err := GetPageByName(c.Page.Name, uid)
   457  	if err == gorm.ErrRecordNotFound {
   458  		log.WithFields(logrus.Fields{
   459  			"page": p.Name,
   460  		}).Error("Page does not exist")
   461  		return ErrPageNotFound
   462  	} else if err != nil {
   463  		log.Error(err)
   464  		return err
   465  	}
   466  	c.Page = p
   467  	c.PageId = p.Id
   468  	// Check to make sure the sending profile exists
   469  	s, err := GetSMTPByName(c.SMTP.Name, uid)
   470  	if err == gorm.ErrRecordNotFound {
   471  		log.WithFields(logrus.Fields{
   472  			"smtp": s.Name,
   473  		}).Error("Sending profile does not exist")
   474  		return ErrSMTPNotFound
   475  	} else if err != nil {
   476  		log.Error(err)
   477  		return err
   478  	}
   479  	c.SMTP = s
   480  	c.SMTPId = s.Id
   481  	// Insert into the DB
   482  	err = db.Save(c).Error
   483  	if err != nil {
   484  		log.Error(err)
   485  		return err
   486  	}
   487  	err = c.AddEvent(&Event{Message: "Campaign Created"})
   488  	if err != nil {
   489  		log.Error(err)
   490  	}
   491  	// Insert all the results
   492  	resultMap := make(map[string]bool)
   493  	recipientIndex := 0
   494  	for _, g := range c.Groups {
   495  		// Insert a result for each target in the group
   496  		for _, t := range g.Targets {
   497  			// Remove duplicate results - we should only
   498  			// send emails to unique email addresses.
   499  			if _, ok := resultMap[t.Email]; ok {
   500  				continue
   501  			}
   502  			resultMap[t.Email] = true
   503  			sendDate := c.generateSendDate(recipientIndex, totalRecipients)
   504  			r := &Result{
   505  				BaseRecipient: BaseRecipient{
   506  					Email:     t.Email,
   507  					Position:  t.Position,
   508  					FirstName: t.FirstName,
   509  					LastName:  t.LastName,
   510  				},
   511  				Status:       StatusScheduled,
   512  				CampaignId:   c.Id,
   513  				UserId:       c.UserId,
   514  				SendDate:     sendDate,
   515  				Reported:     false,
   516  				ModifiedDate: c.CreatedDate,
   517  			}
   518  			err = r.GenerateId()
   519  			if err != nil {
   520  				log.Error(err)
   521  				continue
   522  			}
   523  			processing := false
   524  			if r.SendDate.Before(c.CreatedDate) || r.SendDate.Equal(c.CreatedDate) {
   525  				r.Status = StatusSending
   526  				processing = true
   527  			}
   528  			err = db.Save(r).Error
   529  			if err != nil {
   530  				log.WithFields(logrus.Fields{
   531  					"email": t.Email,
   532  				}).Error(err)
   533  			}
   534  			c.Results = append(c.Results, *r)
   535  			log.Infof("Creating maillog for %s to send at %s\n", r.Email, sendDate)
   536  			m := &MailLog{
   537  				UserId:     c.UserId,
   538  				CampaignId: c.Id,
   539  				RId:        r.RId,
   540  				SendDate:   sendDate,
   541  				Processing: processing,
   542  			}
   543  			err = db.Save(m).Error
   544  			if err != nil {
   545  				log.Error(err)
   546  				continue
   547  			}
   548  			recipientIndex++
   549  		}
   550  	}
   551  	err = db.Save(c).Error
   552  	return err
   553  }
   554  
   555  //DeleteCampaign deletes the specified campaign
   556  func DeleteCampaign(id int64) error {
   557  	log.WithFields(logrus.Fields{
   558  		"campaign_id": id,
   559  	}).Info("Deleting campaign")
   560  	// Delete all the campaign results
   561  	err := db.Where("campaign_id=?", id).Delete(&Result{}).Error
   562  	if err != nil {
   563  		log.Error(err)
   564  		return err
   565  	}
   566  	err = db.Where("campaign_id=?", id).Delete(&Event{}).Error
   567  	if err != nil {
   568  		log.Error(err)
   569  		return err
   570  	}
   571  	err = db.Where("campaign_id=?", id).Delete(&MailLog{}).Error
   572  	if err != nil {
   573  		log.Error(err)
   574  		return err
   575  	}
   576  	// Delete the campaign
   577  	err = db.Delete(&Campaign{Id: id}).Error
   578  	if err != nil {
   579  		log.Error(err)
   580  	}
   581  	return err
   582  }
   583  
   584  // CompleteCampaign effectively "ends" a campaign.
   585  // Any future emails clicked will return a simple "404" page.
   586  func CompleteCampaign(id int64, uid int64) error {
   587  	log.WithFields(logrus.Fields{
   588  		"campaign_id": id,
   589  	}).Info("Marking campaign as complete")
   590  	c, err := GetCampaign(id, uid)
   591  	if err != nil {
   592  		return err
   593  	}
   594  	// Delete any maillogs still set to be sent out, preventing future emails
   595  	err = db.Where("campaign_id=?", id).Delete(&MailLog{}).Error
   596  	if err != nil {
   597  		log.Error(err)
   598  		return err
   599  	}
   600  	// Don't overwrite original completed time
   601  	if c.Status == CampaignComplete {
   602  		return nil
   603  	}
   604  	// Mark the campaign as complete
   605  	c.CompletedDate = time.Now().UTC()
   606  	c.Status = CampaignComplete
   607  	err = db.Where("id=? and user_id=?", id, uid).Save(&c).Error
   608  	if err != nil {
   609  		log.Error(err)
   610  	}
   611  	return err
   612  }