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  }