github.com/gophish/gophish@v0.12.2-0.20230915144530-8e7929441393/models/result.go (about) 1 package models 2 3 import ( 4 "crypto/rand" 5 "encoding/json" 6 "math/big" 7 "net" 8 "time" 9 10 log "github.com/gophish/gophish/logger" 11 "github.com/jinzhu/gorm" 12 "github.com/oschwald/maxminddb-golang" 13 ) 14 15 type mmCity struct { 16 GeoPoint mmGeoPoint `maxminddb:"location"` 17 } 18 19 type mmGeoPoint struct { 20 Latitude float64 `maxminddb:"latitude"` 21 Longitude float64 `maxminddb:"longitude"` 22 } 23 24 // Result contains the fields for a result object, 25 // which is a representation of a target in a campaign. 26 type Result struct { 27 Id int64 `json:"-"` 28 CampaignId int64 `json:"-"` 29 UserId int64 `json:"-"` 30 RId string `json:"id"` 31 Status string `json:"status" sql:"not null"` 32 IP string `json:"ip"` 33 Latitude float64 `json:"latitude"` 34 Longitude float64 `json:"longitude"` 35 SendDate time.Time `json:"send_date"` 36 Reported bool `json:"reported" sql:"not null"` 37 ModifiedDate time.Time `json:"modified_date"` 38 BaseRecipient 39 } 40 41 func (r *Result) createEvent(status string, details interface{}) (*Event, error) { 42 e := &Event{Email: r.Email, Message: status} 43 if details != nil { 44 dj, err := json.Marshal(details) 45 if err != nil { 46 return nil, err 47 } 48 e.Details = string(dj) 49 } 50 AddEvent(e, r.CampaignId) 51 return e, nil 52 } 53 54 // HandleEmailSent updates a Result to indicate that the email has been 55 // successfully sent to the remote SMTP server 56 func (r *Result) HandleEmailSent() error { 57 event, err := r.createEvent(EventSent, nil) 58 if err != nil { 59 return err 60 } 61 r.SendDate = event.Time 62 r.Status = EventSent 63 r.ModifiedDate = event.Time 64 return db.Save(r).Error 65 } 66 67 // HandleEmailError updates a Result to indicate that there was an error when 68 // attempting to send the email to the remote SMTP server. 69 func (r *Result) HandleEmailError(err error) error { 70 event, err := r.createEvent(EventSendingError, EventError{Error: err.Error()}) 71 if err != nil { 72 return err 73 } 74 r.Status = Error 75 r.ModifiedDate = event.Time 76 return db.Save(r).Error 77 } 78 79 // HandleEmailBackoff updates a Result to indicate that the email received a 80 // temporary error and needs to be retried 81 func (r *Result) HandleEmailBackoff(err error, sendDate time.Time) error { 82 event, err := r.createEvent(EventSendingError, EventError{Error: err.Error()}) 83 if err != nil { 84 return err 85 } 86 r.Status = StatusRetry 87 r.SendDate = sendDate 88 r.ModifiedDate = event.Time 89 return db.Save(r).Error 90 } 91 92 // HandleEmailOpened updates a Result in the case where the recipient opened the 93 // email. 94 func (r *Result) HandleEmailOpened(details EventDetails) error { 95 event, err := r.createEvent(EventOpened, details) 96 if err != nil { 97 return err 98 } 99 // Don't update the status if the user already clicked the link 100 // or submitted data to the campaign 101 if r.Status == EventClicked || r.Status == EventDataSubmit { 102 return nil 103 } 104 r.Status = EventOpened 105 r.ModifiedDate = event.Time 106 return db.Save(r).Error 107 } 108 109 // HandleClickedLink updates a Result in the case where the recipient clicked 110 // the link in an email. 111 func (r *Result) HandleClickedLink(details EventDetails) error { 112 event, err := r.createEvent(EventClicked, details) 113 if err != nil { 114 return err 115 } 116 // Don't update the status if the user has already submitted data via the 117 // landing page form. 118 if r.Status == EventDataSubmit { 119 return nil 120 } 121 r.Status = EventClicked 122 r.ModifiedDate = event.Time 123 return db.Save(r).Error 124 } 125 126 // HandleFormSubmit updates a Result in the case where the recipient submitted 127 // credentials to the form on a Landing Page. 128 func (r *Result) HandleFormSubmit(details EventDetails) error { 129 event, err := r.createEvent(EventDataSubmit, details) 130 if err != nil { 131 return err 132 } 133 r.Status = EventDataSubmit 134 r.ModifiedDate = event.Time 135 return db.Save(r).Error 136 } 137 138 // HandleEmailReport updates a Result in the case where they report a simulated 139 // phishing email using the HTTP handler. 140 func (r *Result) HandleEmailReport(details EventDetails) error { 141 event, err := r.createEvent(EventReported, details) 142 if err != nil { 143 return err 144 } 145 r.Reported = true 146 r.ModifiedDate = event.Time 147 return db.Save(r).Error 148 } 149 150 // UpdateGeo updates the latitude and longitude of the result in 151 // the database given an IP address 152 func (r *Result) UpdateGeo(addr string) error { 153 // Open a connection to the maxmind db 154 mmdb, err := maxminddb.Open("static/db/geolite2-city.mmdb") 155 if err != nil { 156 log.Fatal(err) 157 } 158 defer mmdb.Close() 159 ip := net.ParseIP(addr) 160 var city mmCity 161 // Get the record 162 err = mmdb.Lookup(ip, &city) 163 if err != nil { 164 return err 165 } 166 // Update the database with the record information 167 r.IP = addr 168 r.Latitude = city.GeoPoint.Latitude 169 r.Longitude = city.GeoPoint.Longitude 170 return db.Save(r).Error 171 } 172 173 func generateResultId() (string, error) { 174 const alphaNum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 175 k := make([]byte, 7) 176 for i := range k { 177 idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(alphaNum)))) 178 if err != nil { 179 return "", err 180 } 181 k[i] = alphaNum[idx.Int64()] 182 } 183 return string(k), nil 184 } 185 186 // GenerateId generates a unique key to represent the result 187 // in the database 188 func (r *Result) GenerateId(tx *gorm.DB) error { 189 // Keep trying until we generate a unique key (shouldn't take more than one or two iterations) 190 for { 191 rid, err := generateResultId() 192 if err != nil { 193 return err 194 } 195 r.RId = rid 196 err = tx.Table("results").Where("r_id=?", r.RId).First(&Result{}).Error 197 if err == gorm.ErrRecordNotFound { 198 break 199 } 200 } 201 return nil 202 } 203 204 // GetResult returns the Result object from the database 205 // given the ResultId 206 func GetResult(rid string) (Result, error) { 207 r := Result{} 208 err := db.Where("r_id=?", rid).First(&r).Error 209 return r, err 210 }