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  }