github.com/gophish/gophish@v0.12.2-0.20230915144530-8e7929441393/models/maillog_test.go (about) 1 package models 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "math" 8 "net/textproto" 9 "testing" 10 "time" 11 12 "github.com/gophish/gophish/config" 13 14 "github.com/gophish/gomail" 15 "github.com/jordan-wright/email" 16 "gopkg.in/check.v1" 17 ) 18 19 func (s *ModelsSuite) emailFromFirstMailLog(campaign Campaign, ch *check.C) *email.Email { 20 result := campaign.Results[0] 21 m := &MailLog{} 22 err := db.Where("r_id=? AND campaign_id=?", result.RId, campaign.Id). 23 Find(m).Error 24 ch.Assert(err, check.Equals, nil) 25 26 msg := gomail.NewMessage() 27 err = m.Generate(msg) 28 ch.Assert(err, check.Equals, nil) 29 30 msgBuff := &bytes.Buffer{} 31 _, err = msg.WriteTo(msgBuff) 32 ch.Assert(err, check.Equals, nil) 33 34 got, err := email.NewEmailFromReader(msgBuff) 35 ch.Assert(err, check.Equals, nil) 36 return got 37 } 38 39 func (s *ModelsSuite) TestGetQueuedMailLogs(ch *check.C) { 40 campaign := s.createCampaign(ch) 41 // By default, for campaigns with no launch date, the maillogs are set as 42 // being processed. We need to unlock them first. 43 ms, err := GetMailLogsByCampaign(campaign.Id) 44 ch.Assert(err, check.Equals, nil) 45 err = LockMailLogs(ms, false) 46 ch.Assert(err, check.Equals, nil) 47 ms, err = GetQueuedMailLogs(campaign.LaunchDate) 48 ch.Assert(err, check.Equals, nil) 49 got := make(map[string]*MailLog) 50 for _, m := range ms { 51 got[m.RId] = m 52 } 53 for _, r := range campaign.Results { 54 if m, ok := got[r.RId]; ok { 55 ch.Assert(m.RId, check.Equals, r.RId) 56 ch.Assert(m.CampaignId, check.Equals, campaign.Id) 57 ch.Assert(m.SendDate, check.Equals, campaign.LaunchDate) 58 ch.Assert(m.UserId, check.Equals, campaign.UserId) 59 ch.Assert(m.SendAttempt, check.Equals, 0) 60 } else { 61 ch.Fatalf("Result not found in maillogs: %s", r.RId) 62 } 63 } 64 } 65 66 func (s *ModelsSuite) TestMailLogBackoff(ch *check.C) { 67 campaign := s.createCampaign(ch) 68 result := campaign.Results[0] 69 m := &MailLog{} 70 err := db.Where("r_id=? AND campaign_id=?", result.RId, campaign.Id). 71 Find(m).Error 72 ch.Assert(err, check.Equals, nil) 73 ch.Assert(m.SendAttempt, check.Equals, 0) 74 ch.Assert(m.SendDate, check.Equals, campaign.LaunchDate) 75 76 expectedError := &textproto.Error{ 77 Code: 500, 78 Msg: "Recipient not found", 79 } 80 for i := m.SendAttempt; i < MaxSendAttempts; i++ { 81 err = m.Lock() 82 ch.Assert(err, check.Equals, nil) 83 ch.Assert(m.Processing, check.Equals, true) 84 85 expectedDuration := math.Pow(2, float64(m.SendAttempt+1)) 86 expectedSendDate := m.SendDate.Add(time.Minute * time.Duration(expectedDuration)) 87 err = m.Backoff(expectedError) 88 ch.Assert(err, check.Equals, nil) 89 ch.Assert(m.SendDate, check.Equals, expectedSendDate) 90 ch.Assert(m.Processing, check.Equals, false) 91 result, err := GetResult(m.RId) 92 ch.Assert(err, check.Equals, nil) 93 ch.Assert(result.SendDate, check.Equals, expectedSendDate) 94 ch.Assert(result.Status, check.Equals, StatusRetry) 95 } 96 // Get our updated campaign and check for the added event 97 campaign, err = GetCampaign(campaign.Id, int64(1)) 98 ch.Assert(err, check.Equals, nil) 99 100 // We expect MaxSendAttempts + the initial campaign created event 101 ch.Assert(len(campaign.Events), check.Equals, MaxSendAttempts+1) 102 103 // Check that we receive our error after meeting the maximum send attempts 104 err = m.Backoff(expectedError) 105 ch.Assert(err, check.Equals, ErrMaxSendAttempts) 106 } 107 108 func (s *ModelsSuite) TestMailLogError(ch *check.C) { 109 campaign := s.createCampaign(ch) 110 result := campaign.Results[0] 111 m := &MailLog{} 112 err := db.Where("r_id=? AND campaign_id=?", result.RId, campaign.Id). 113 Find(m).Error 114 ch.Assert(err, check.Equals, nil) 115 ch.Assert(m.RId, check.Equals, result.RId) 116 117 expectedError := &textproto.Error{ 118 Code: 500, 119 Msg: "Recipient not found", 120 } 121 err = m.Error(expectedError) 122 ch.Assert(err, check.Equals, nil) 123 124 // Get our result and make sure the status is set correctly 125 result, err = GetResult(result.RId) 126 ch.Assert(err, check.Equals, nil) 127 ch.Assert(result.Status, check.Equals, Error) 128 129 // Get our updated campaign and check for the added event 130 campaign, err = GetCampaign(campaign.Id, int64(1)) 131 ch.Assert(err, check.Equals, nil) 132 133 expectedEventLength := 2 134 ch.Assert(len(campaign.Events), check.Equals, expectedEventLength) 135 136 gotEvent := campaign.Events[1] 137 es := EventError{Error: expectedError.Error()} 138 ej, _ := json.Marshal(es) 139 expectedEvent := Event{ 140 Id: gotEvent.Id, 141 Email: result.Email, 142 Message: EventSendingError, 143 CampaignId: campaign.Id, 144 Details: string(ej), 145 Time: gotEvent.Time, 146 } 147 ch.Assert(gotEvent, check.DeepEquals, expectedEvent) 148 149 ms, err := GetMailLogsByCampaign(campaign.Id) 150 ch.Assert(err, check.Equals, nil) 151 ch.Assert(len(ms), check.Equals, len(campaign.Results)-1) 152 } 153 154 func (s *ModelsSuite) TestMailLogSuccess(ch *check.C) { 155 campaign := s.createCampaign(ch) 156 result := campaign.Results[0] 157 m := &MailLog{} 158 err := db.Where("r_id=? AND campaign_id=?", result.RId, campaign.Id). 159 Find(m).Error 160 ch.Assert(err, check.Equals, nil) 161 ch.Assert(m.RId, check.Equals, result.RId) 162 163 err = m.Success() 164 ch.Assert(err, check.Equals, nil) 165 166 // Get our result and make sure the status is set correctly 167 result, err = GetResult(result.RId) 168 ch.Assert(err, check.Equals, nil) 169 ch.Assert(result.Status, check.Equals, EventSent) 170 171 // Get our updated campaign and check for the added event 172 campaign, err = GetCampaign(campaign.Id, int64(1)) 173 ch.Assert(err, check.Equals, nil) 174 175 expectedEventLength := 2 176 ch.Assert(len(campaign.Events), check.Equals, expectedEventLength) 177 178 gotEvent := campaign.Events[1] 179 expectedEvent := Event{ 180 Id: gotEvent.Id, 181 Email: result.Email, 182 Message: EventSent, 183 CampaignId: campaign.Id, 184 Time: gotEvent.Time, 185 } 186 ch.Assert(gotEvent, check.DeepEquals, expectedEvent) 187 ch.Assert(result.SendDate, check.Equals, gotEvent.Time) 188 189 ms, err := GetMailLogsByCampaign(campaign.Id) 190 ch.Assert(err, check.Equals, nil) 191 ch.Assert(len(ms), check.Equals, len(campaign.Results)-1) 192 } 193 194 func (s *ModelsSuite) TestGenerateMailLog(ch *check.C) { 195 campaign := Campaign{ 196 Id: 1, 197 UserId: 1, 198 } 199 result := Result{ 200 RId: "abc1234", 201 } 202 err := GenerateMailLog(&campaign, &result, campaign.LaunchDate) 203 ch.Assert(err, check.Equals, nil) 204 205 m := MailLog{} 206 err = db.Where("r_id=?", result.RId).Find(&m).Error 207 ch.Assert(err, check.Equals, nil) 208 ch.Assert(m.RId, check.Equals, result.RId) 209 ch.Assert(m.CampaignId, check.Equals, campaign.Id) 210 ch.Assert(m.SendDate, check.Equals, campaign.LaunchDate) 211 ch.Assert(m.UserId, check.Equals, campaign.UserId) 212 ch.Assert(m.SendAttempt, check.Equals, 0) 213 ch.Assert(m.Processing, check.Equals, false) 214 } 215 216 func (s *ModelsSuite) TestMailLogGetSmtpFrom(ch *check.C) { 217 template := Template{ 218 Name: "OverrideSmtpFrom", 219 UserId: 1, 220 Text: "dummytext", 221 HTML: "Dummyhtml", 222 Subject: "Dummysubject", 223 EnvelopeSender: "spoofing@example.com", 224 } 225 ch.Assert(PostTemplate(&template), check.Equals, nil) 226 campaign := s.createCampaignDependencies(ch) 227 campaign.Template = template 228 229 ch.Assert(PostCampaign(&campaign, campaign.UserId), check.Equals, nil) 230 result := campaign.Results[0] 231 232 m := &MailLog{} 233 err := db.Where("r_id=? AND campaign_id=?", result.RId, campaign.Id). 234 Find(m).Error 235 ch.Assert(err, check.Equals, nil) 236 237 msg := gomail.NewMessage() 238 err = m.Generate(msg) 239 ch.Assert(err, check.Equals, nil) 240 241 msgBuff := &bytes.Buffer{} 242 _, err = msg.WriteTo(msgBuff) 243 ch.Assert(err, check.Equals, nil) 244 245 got, err := email.NewEmailFromReader(msgBuff) 246 ch.Assert(err, check.Equals, nil) 247 ch.Assert(got.From, check.Equals, "spoofing@example.com") 248 } 249 250 func (s *ModelsSuite) TestMailLogGenerate(ch *check.C) { 251 campaign := s.createCampaign(ch) 252 result := campaign.Results[0] 253 expected := &email.Email{ 254 From: "test@test.com", // Default smtp.FromAddress 255 Subject: fmt.Sprintf("%s - Subject", result.RId), 256 Text: []byte(fmt.Sprintf("%s - Text", result.RId)), 257 HTML: []byte(fmt.Sprintf("%s - HTML", result.RId)), 258 } 259 got := s.emailFromFirstMailLog(campaign, ch) 260 ch.Assert(got.From, check.Equals, expected.From) 261 ch.Assert(got.Subject, check.Equals, expected.Subject) 262 ch.Assert(string(got.Text), check.Equals, string(expected.Text)) 263 ch.Assert(string(got.HTML), check.Equals, string(expected.HTML)) 264 } 265 266 func (s *ModelsSuite) TestMailLogGenerateTransparencyHeaders(ch *check.C) { 267 s.config.ContactAddress = "test@test.com" 268 expectedHeaders := map[string]string{ 269 "X-Mailer": config.ServerName, 270 "X-Gophish-Contact": s.config.ContactAddress, 271 } 272 campaign := s.createCampaign(ch) 273 got := s.emailFromFirstMailLog(campaign, ch) 274 for k, v := range expectedHeaders { 275 ch.Assert(got.Headers.Get(k), check.Equals, v) 276 } 277 } 278 279 func (s *ModelsSuite) TestMailLogGenerateOverrideTransparencyHeaders(ch *check.C) { 280 expectedHeaders := map[string]string{ 281 "X-Mailer": "", 282 "X-Gophish-Contact": "", 283 } 284 smtp := SMTP{ 285 Name: "Test SMTP", 286 Host: "1.1.1.1:25", 287 FromAddress: "foo@example.com", 288 UserId: 1, 289 Headers: []Header{ 290 Header{Key: "X-Gophish-Contact", Value: ""}, 291 Header{Key: "X-Mailer", Value: ""}, 292 }, 293 } 294 ch.Assert(PostSMTP(&smtp), check.Equals, nil) 295 campaign := s.createCampaignDependencies(ch) 296 campaign.SMTP = smtp 297 298 ch.Assert(PostCampaign(&campaign, campaign.UserId), check.Equals, nil) 299 got := s.emailFromFirstMailLog(campaign, ch) 300 for k, v := range expectedHeaders { 301 ch.Assert(got.Headers.Get(k), check.Equals, v) 302 } 303 } 304 305 func (s *ModelsSuite) TestUnlockAllMailLogs(ch *check.C) { 306 campaign := s.createCampaign(ch) 307 ms, err := GetMailLogsByCampaign(campaign.Id) 308 ch.Assert(err, check.Equals, nil) 309 for _, m := range ms { 310 ch.Assert(m.Processing, check.Equals, true) 311 } 312 err = UnlockAllMailLogs() 313 ch.Assert(err, check.Equals, nil) 314 ms, err = GetMailLogsByCampaign(campaign.Id) 315 ch.Assert(err, check.Equals, nil) 316 for _, m := range ms { 317 ch.Assert(m.Processing, check.Equals, false) 318 } 319 } 320 321 func (s *ModelsSuite) TestURLTemplateRendering(ch *check.C) { 322 template := Template{ 323 Name: "URLTemplate", 324 UserId: 1, 325 Text: "{{.URL}}", 326 HTML: "{{.URL}}", 327 Subject: "{{.URL}}", 328 } 329 ch.Assert(PostTemplate(&template), check.Equals, nil) 330 campaign := s.createCampaignDependencies(ch) 331 campaign.URL = "http://127.0.0.1/{{.Email}}/" 332 campaign.Template = template 333 334 ch.Assert(PostCampaign(&campaign, campaign.UserId), check.Equals, nil) 335 result := campaign.Results[0] 336 expectedURL := fmt.Sprintf("http://127.0.0.1/%s/?%s=%s", result.Email, RecipientParameter, result.RId) 337 338 got := s.emailFromFirstMailLog(campaign, ch) 339 ch.Assert(got.Subject, check.Equals, expectedURL) 340 ch.Assert(string(got.Text), check.Equals, expectedURL) 341 ch.Assert(string(got.HTML), check.Equals, expectedURL) 342 } 343 344 func (s *ModelsSuite) TestMailLogGenerateEmptySubject(ch *check.C) { 345 346 // in place of using createCampaign, we replicate its small code body 347 // here internally as we want to specify an empty subject to createCampaignDependencies 348 // campaign := s.createCampaign(ch) 349 campaign := s.createCampaignDependencies(ch, "") // specify empty subject 350 // Setup and "launch" our campaign 351 ch.Assert(PostCampaign(&campaign, campaign.UserId), check.Equals, nil) 352 result := campaign.Results[0] 353 354 expected := &email.Email{ 355 Subject: "", 356 Text: []byte(fmt.Sprintf("%s - Text", result.RId)), 357 HTML: []byte(fmt.Sprintf("%s - HTML", result.RId)), 358 } 359 got := s.emailFromFirstMailLog(campaign, ch) 360 ch.Assert(got.Subject, check.Equals, expected.Subject) 361 } 362 363 func (s *ModelsSuite) TestShouldEmbedAttachment(ch *check.C) { 364 365 // Supported file extensions 366 ch.Assert(shouldEmbedAttachment(".png"), check.Equals, true) 367 ch.Assert(shouldEmbedAttachment(".jpg"), check.Equals, true) 368 ch.Assert(shouldEmbedAttachment(".jpeg"), check.Equals, true) 369 ch.Assert(shouldEmbedAttachment(".gif"), check.Equals, true) 370 371 // Some other file extensions 372 ch.Assert(shouldEmbedAttachment(".docx"), check.Equals, false) 373 ch.Assert(shouldEmbedAttachment(".txt"), check.Equals, false) 374 ch.Assert(shouldEmbedAttachment(".jar"), check.Equals, false) 375 ch.Assert(shouldEmbedAttachment(".exe"), check.Equals, false) 376 377 // Invalid input 378 ch.Assert(shouldEmbedAttachment(""), check.Equals, false) 379 ch.Assert(shouldEmbedAttachment("png"), check.Equals, false) 380 } 381 382 func (s *ModelsSuite) TestEmbedAttachment(ch *check.C) { 383 campaign := s.createCampaignDependencies(ch) 384 campaign.Template.Attachments = []Attachment{ 385 { 386 Name: "test.png", 387 Type: "image/png", 388 Content: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=", 389 }, 390 { 391 Name: "test.txt", 392 Type: "text/plain", 393 Content: "VGVzdCB0ZXh0IGZpbGU=", 394 }, 395 } 396 PutTemplate(&campaign.Template) 397 ch.Assert(PostCampaign(&campaign, campaign.UserId), check.Equals, nil) 398 got := s.emailFromFirstMailLog(campaign, ch) 399 400 // The email package simply ignores attachments where the Content-Disposition header is set 401 // to inline, so the best we can do without replacing the whole thing is to check that only 402 // the text file was added as an attachment. 403 ch.Assert(got.Attachments, check.HasLen, 1) 404 ch.Assert(got.Attachments[0].Filename, check.Equals, "test.txt") 405 } 406 407 func BenchmarkMailLogGenerate100(b *testing.B) { 408 setupBenchmark(b) 409 campaign := setupCampaign(b, 100) 410 ms, err := GetMailLogsByCampaign(campaign.Id) 411 if err != nil { 412 b.Fatalf("error getting maillogs for campaign: %v", err) 413 } 414 ms[0].CacheCampaign(&campaign) 415 b.ResetTimer() 416 for i := 0; i < b.N; i++ { 417 msg := gomail.NewMessage() 418 ms[0].Generate(msg) 419 } 420 tearDownBenchmark(b) 421 } 422 423 func BenchmarkMailLogGenerate1000(b *testing.B) { 424 setupBenchmark(b) 425 campaign := setupCampaign(b, 1000) 426 ms, err := GetMailLogsByCampaign(campaign.Id) 427 if err != nil { 428 b.Fatalf("error getting maillogs for campaign: %v", err) 429 } 430 ms[0].CacheCampaign(&campaign) 431 b.ResetTimer() 432 for i := 0; i < b.N; i++ { 433 msg := gomail.NewMessage() 434 ms[0].Generate(msg) 435 } 436 tearDownBenchmark(b) 437 } 438 439 func BenchmarkMailLogGenerate5000(b *testing.B) { 440 setupBenchmark(b) 441 campaign := setupCampaign(b, 5000) 442 ms, err := GetMailLogsByCampaign(campaign.Id) 443 if err != nil { 444 b.Fatalf("error getting maillogs for campaign: %v", err) 445 } 446 ms[0].CacheCampaign(&campaign) 447 b.ResetTimer() 448 for i := 0; i < b.N; i++ { 449 msg := gomail.NewMessage() 450 ms[0].Generate(msg) 451 } 452 tearDownBenchmark(b) 453 } 454 455 func BenchmarkMailLogGenerate10000(b *testing.B) { 456 setupBenchmark(b) 457 campaign := setupCampaign(b, 10000) 458 ms, err := GetMailLogsByCampaign(campaign.Id) 459 if err != nil { 460 b.Fatalf("error getting maillogs for campaign: %v", err) 461 } 462 ms[0].CacheCampaign(&campaign) 463 b.ResetTimer() 464 for i := 0; i < b.N; i++ { 465 msg := gomail.NewMessage() 466 ms[0].Generate(msg) 467 } 468 tearDownBenchmark(b) 469 }