github.com/amar224/phishing-tool@v0.9.0/models/maillog.go (about) 1 package models 2 3 import ( 4 "crypto/rand" 5 "encoding/base64" 6 "errors" 7 "fmt" 8 "io" 9 "math" 10 "math/big" 11 "net/mail" 12 "os" 13 "strings" 14 "time" 15 16 "github.com/gophish/gomail" 17 "github.com/gophish/gophish/config" 18 log "github.com/gophish/gophish/logger" 19 "github.com/gophish/gophish/mailer" 20 ) 21 22 // MaxSendAttempts set to 8 since we exponentially backoff after each failed send 23 // attempt. This will give us a maximum send delay of 256 minutes, or about 4.2 hours. 24 var MaxSendAttempts = 8 25 26 // ErrMaxSendAttempts is thrown when the maximum number of sending attempts for a given 27 // MailLog is exceeded. 28 var ErrMaxSendAttempts = errors.New("max send attempts exceeded") 29 30 // MailLog is a struct that holds information about an email that is to be 31 // sent out. 32 type MailLog struct { 33 Id int64 `json:"-"` 34 UserId int64 `json:"-"` 35 CampaignId int64 `json:"campaign_id"` 36 RId string `json:"id"` 37 SendDate time.Time `json:"send_date"` 38 SendAttempt int `json:"send_attempt"` 39 Processing bool `json:"-"` 40 } 41 42 // GenerateMailLog creates a new maillog for the given campaign and 43 // result. It sets the initial send date to match the campaign's launch date. 44 func GenerateMailLog(c *Campaign, r *Result, sendDate time.Time) error { 45 m := &MailLog{ 46 UserId: c.UserId, 47 CampaignId: c.Id, 48 RId: r.RId, 49 SendDate: sendDate, 50 } 51 return db.Save(m).Error 52 } 53 54 // Backoff sets the MailLog SendDate to be the next entry in an exponential 55 // backoff. ErrMaxRetriesExceeded is thrown if this maillog has been retried 56 // too many times. Backoff also unlocks the maillog so that it can be processed 57 // again in the future. 58 func (m *MailLog) Backoff(reason error) error { 59 r, err := GetResult(m.RId) 60 if err != nil { 61 return err 62 } 63 if m.SendAttempt == MaxSendAttempts { 64 r.HandleEmailError(ErrMaxSendAttempts) 65 return ErrMaxSendAttempts 66 } 67 // Add an error, since we had to backoff because of a 68 // temporary error of some sort during the SMTP transaction 69 m.SendAttempt++ 70 backoffDuration := math.Pow(2, float64(m.SendAttempt)) 71 m.SendDate = m.SendDate.Add(time.Minute * time.Duration(backoffDuration)) 72 err = db.Save(m).Error 73 if err != nil { 74 return err 75 } 76 err = r.HandleEmailBackoff(reason, m.SendDate) 77 if err != nil { 78 return err 79 } 80 err = m.Unlock() 81 return err 82 } 83 84 // Unlock removes the processing flag so the maillog can be processed again 85 func (m *MailLog) Unlock() error { 86 m.Processing = false 87 return db.Save(&m).Error 88 } 89 90 // Lock sets the processing flag so that other processes cannot modify the maillog 91 func (m *MailLog) Lock() error { 92 m.Processing = true 93 return db.Save(&m).Error 94 } 95 96 // Error sets the error status on the models.Result that the 97 // maillog refers to. Since MailLog errors are permanent, 98 // this action also deletes the maillog. 99 func (m *MailLog) Error(e error) error { 100 r, err := GetResult(m.RId) 101 if err != nil { 102 log.Warn(err) 103 return err 104 } 105 err = r.HandleEmailError(e) 106 if err != nil { 107 log.Warn(err) 108 return err 109 } 110 err = db.Delete(m).Error 111 return err 112 } 113 114 // Success deletes the maillog from the database and updates the underlying 115 // campaign result. 116 func (m *MailLog) Success() error { 117 r, err := GetResult(m.RId) 118 if err != nil { 119 return err 120 } 121 err = r.HandleEmailSent() 122 if err != nil { 123 return err 124 } 125 err = db.Delete(m).Error 126 return nil 127 } 128 129 // GetDialer returns a dialer based on the maillog campaign's SMTP configuration 130 func (m *MailLog) GetDialer() (mailer.Dialer, error) { 131 c, err := GetCampaign(m.CampaignId, m.UserId) 132 if err != nil { 133 return nil, err 134 } 135 return c.SMTP.GetDialer() 136 } 137 138 // Generate fills in the details of a gomail.Message instance with 139 // the correct headers and body from the campaign and recipient listed in 140 // the maillog. We accept the gomail.Message as an argument so that the caller 141 // can choose to re-use the message across recipients. 142 func (m *MailLog) Generate(msg *gomail.Message) error { 143 r, err := GetResult(m.RId) 144 if err != nil { 145 return err 146 } 147 c, err := GetCampaign(m.CampaignId, m.UserId) 148 if err != nil { 149 return err 150 } 151 152 f, err := mail.ParseAddress(c.SMTP.FromAddress) 153 if err != nil { 154 return err 155 } 156 msg.SetAddressHeader("From", f.Address, f.Name) 157 158 ptx, err := NewPhishingTemplateContext(&c, r.BaseRecipient, r.RId) 159 if err != nil { 160 return err 161 } 162 163 // Add the transparency headers 164 msg.SetHeader("X-Mailer", config.ServerName) 165 if conf.ContactAddress != "" { 166 msg.SetHeader("X-Gophish-Contact", conf.ContactAddress) 167 } 168 169 // Add Message-Id header as described in RFC 2822. 170 messageID, err := m.generateMessageID() 171 if err != nil { 172 return err 173 } 174 msg.SetHeader("Message-Id", messageID) 175 176 // Parse the customHeader templates 177 for _, header := range c.SMTP.Headers { 178 key, err := ExecuteTemplate(header.Key, ptx) 179 if err != nil { 180 log.Warn(err) 181 } 182 183 value, err := ExecuteTemplate(header.Value, ptx) 184 if err != nil { 185 log.Warn(err) 186 } 187 188 // Add our header immediately 189 msg.SetHeader(key, value) 190 } 191 192 // Parse remaining templates 193 subject, err := ExecuteTemplate(c.Template.Subject, ptx) 194 if err != nil { 195 log.Warn(err) 196 } 197 // don't set Subject header if the subject is empty 198 if len(subject) != 0 { 199 msg.SetHeader("Subject", subject) 200 } 201 202 msg.SetHeader("To", r.FormatAddress()) 203 if c.Template.Text != "" { 204 text, err := ExecuteTemplate(c.Template.Text, ptx) 205 if err != nil { 206 log.Warn(err) 207 } 208 msg.SetBody("text/plain", text) 209 } 210 if c.Template.HTML != "" { 211 html, err := ExecuteTemplate(c.Template.HTML, ptx) 212 if err != nil { 213 log.Warn(err) 214 } 215 if c.Template.Text == "" { 216 msg.SetBody("text/html", html) 217 } else { 218 msg.AddAlternative("text/html", html) 219 } 220 } 221 // Attach the files 222 for _, a := range c.Template.Attachments { 223 msg.Attach(func(a Attachment) (string, gomail.FileSetting, gomail.FileSetting) { 224 h := map[string][]string{"Content-ID": {fmt.Sprintf("<%s>", a.Name)}} 225 return a.Name, gomail.SetCopyFunc(func(w io.Writer) error { 226 decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(a.Content)) 227 _, err = io.Copy(w, decoder) 228 return err 229 }), gomail.SetHeader(h) 230 }(a)) 231 } 232 233 return nil 234 } 235 236 // GetQueuedMailLogs returns the mail logs that are queued up for the given minute. 237 func GetQueuedMailLogs(t time.Time) ([]*MailLog, error) { 238 ms := []*MailLog{} 239 err := db.Where("send_date <= ? AND processing = ?", t, false). 240 Find(&ms).Error 241 if err != nil { 242 log.Warn(err) 243 } 244 return ms, err 245 } 246 247 // GetMailLogsByCampaign returns all of the mail logs for a given campaign. 248 func GetMailLogsByCampaign(cid int64) ([]*MailLog, error) { 249 ms := []*MailLog{} 250 err := db.Where("campaign_id = ?", cid).Find(&ms).Error 251 return ms, err 252 } 253 254 // LockMailLogs locks or unlocks a slice of maillogs for processing. 255 func LockMailLogs(ms []*MailLog, lock bool) error { 256 tx := db.Begin() 257 for i := range ms { 258 ms[i].Processing = lock 259 err := tx.Save(ms[i]).Error 260 if err != nil { 261 tx.Rollback() 262 return err 263 } 264 } 265 tx.Commit() 266 return nil 267 } 268 269 // UnlockAllMailLogs removes the processing lock for all maillogs 270 // in the database. This is intended to be called when Gophish is started 271 // so that any previously locked maillogs can resume processing. 272 func UnlockAllMailLogs() error { 273 return db.Model(&MailLog{}).Update("processing", false).Error 274 } 275 276 var maxBigInt = big.NewInt(math.MaxInt64) 277 278 // generateMessageID generates and returns a string suitable for an RFC 2822 279 // compliant Message-ID, e.g.: 280 // <1444789264909237300.3464.1819418242800517193@DESKTOP01> 281 // 282 // The following parameters are used to generate a Message-ID: 283 // - The nanoseconds since Epoch 284 // - The calling PID 285 // - A cryptographically random int64 286 // - The sending hostname 287 func (m *MailLog) generateMessageID() (string, error) { 288 t := time.Now().UnixNano() 289 pid := os.Getpid() 290 rint, err := rand.Int(rand.Reader, maxBigInt) 291 if err != nil { 292 return "", err 293 } 294 h, err := os.Hostname() 295 // If we can't get the hostname, we'll use localhost 296 if err != nil { 297 h = "localhost.localdomain" 298 } 299 msgid := fmt.Sprintf("<%d.%d.%d@%s>", t, pid, rint, h) 300 return msgid, nil 301 }