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 }