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