mirror of https://github.com/gophish/gophish
Implemented the ability to preview landing pages when sending a test email.
parent
a04f6d031b
commit
ebb6cd61b2
|
@ -652,8 +652,9 @@ func API_Import_Site(w http.ResponseWriter, r *http.Request) {
|
||||||
// API_Send_Test_Email sends a test email using the template name
|
// API_Send_Test_Email sends a test email using the template name
|
||||||
// and Target given.
|
// and Target given.
|
||||||
func API_Send_Test_Email(w http.ResponseWriter, r *http.Request) {
|
func API_Send_Test_Email(w http.ResponseWriter, r *http.Request) {
|
||||||
s := &models.SendTestEmailRequest{
|
s := &models.EmailRequest{
|
||||||
ErrorChan: make(chan error),
|
ErrorChan: make(chan error),
|
||||||
|
UserId: ctx.Get(r, "user_id").(int64),
|
||||||
}
|
}
|
||||||
if r.Method != "POST" {
|
if r.Method != "POST" {
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Method not allowed"}, http.StatusBadRequest)
|
JSONResponse(w, models.Response{Success: false, Message: "Method not allowed"}, http.StatusBadRequest)
|
||||||
|
@ -664,11 +665,8 @@ func API_Send_Test_Email(w http.ResponseWriter, r *http.Request) {
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error decoding JSON Request"}, http.StatusBadRequest)
|
JSONResponse(w, models.Response{Success: false, Message: "Error decoding JSON Request"}, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Validate the given request
|
|
||||||
if err = s.Validate(); err != nil {
|
storeRequest := false
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a Template is not specified use a default
|
// If a Template is not specified use a default
|
||||||
if s.Template.Name == "" {
|
if s.Template.Name == "" {
|
||||||
|
@ -678,17 +676,15 @@ func API_Send_Test_Email(w http.ResponseWriter, r *http.Request) {
|
||||||
"{{if .FirstName}} First Name: {{.FirstName}}\n{{end}}" +
|
"{{if .FirstName}} First Name: {{.FirstName}}\n{{end}}" +
|
||||||
"{{if .LastName}} Last Name: {{.LastName}}\n{{end}}" +
|
"{{if .LastName}} Last Name: {{.LastName}}\n{{end}}" +
|
||||||
"{{if .Position}} Position: {{.Position}}\n{{end}}" +
|
"{{if .Position}} Position: {{.Position}}\n{{end}}" +
|
||||||
"{{if .TrackingURL}} Tracking URL: {{.TrackingURL}}\n{{end}}" +
|
|
||||||
"\nNow go send some phish!"
|
"\nNow go send some phish!"
|
||||||
t := models.Template{
|
t := models.Template{
|
||||||
Subject: "Default Email from Gophish",
|
Subject: "Default Email from Gophish",
|
||||||
Text: text,
|
Text: text,
|
||||||
}
|
}
|
||||||
s.Template = t
|
s.Template = t
|
||||||
// Try to lookup the Template by name
|
|
||||||
} else {
|
} else {
|
||||||
// Get the Template requested by name
|
// Get the Template requested by name
|
||||||
s.Template, err = models.GetTemplateByName(s.Template.Name, ctx.Get(r, "user_id").(int64))
|
s.Template, err = models.GetTemplateByName(s.Template.Name, s.UserId)
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
log.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"template": s.Template.Name,
|
"template": s.Template.Name,
|
||||||
|
@ -700,12 +696,32 @@ func API_Send_Test_Email(w http.ResponseWriter, r *http.Request) {
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
s.TemplateId = s.Template.Id
|
||||||
|
// We'll only save the test request to the database if there is a
|
||||||
|
// user-specified template to use.
|
||||||
|
storeRequest = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Page.Name != "" {
|
||||||
|
s.Page, err = models.GetPageByName(s.Page.Name, s.UserId)
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"page": s.Page.Name,
|
||||||
|
}).Error("Page does not exist")
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: models.ErrPageNotFound.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.PageId = s.Page.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a complete sending profile is provided use it
|
// If a complete sending profile is provided use it
|
||||||
if err := s.SMTP.Validate(); err != nil {
|
if err := s.SMTP.Validate(); err != nil {
|
||||||
// Otherwise get the SMTP requested by name
|
// Otherwise get the SMTP requested by name
|
||||||
smtp, lookupErr := models.GetSMTPByName(s.SMTP.Name, ctx.Get(r, "user_id").(int64))
|
smtp, lookupErr := models.GetSMTPByName(s.SMTP.Name, s.UserId)
|
||||||
// If the Sending Profile doesn't exist, let's err on the side
|
// If the Sending Profile doesn't exist, let's err on the side
|
||||||
// of caution and assume that the validation failure was more important.
|
// of caution and assume that the validation failure was more important.
|
||||||
if lookupErr != nil {
|
if lookupErr != nil {
|
||||||
|
@ -716,9 +732,25 @@ func API_Send_Test_Email(w http.ResponseWriter, r *http.Request) {
|
||||||
s.SMTP = smtp
|
s.SMTP = smtp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the given request
|
||||||
|
if err = s.Validate(); err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the request if this wasn't the default template
|
||||||
|
if storeRequest {
|
||||||
|
err = models.PostEmailRequest(s)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
// Send the test email
|
// Send the test email
|
||||||
err = Worker.SendTestEmail(s)
|
err = Worker.SendTestEmail(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,8 +63,8 @@ func (s *ControllersSuite) SetupTest() {
|
||||||
// Add a group
|
// Add a group
|
||||||
group := models.Group{Name: "Test Group"}
|
group := models.Group{Name: "Test Group"}
|
||||||
group.Targets = []models.Target{
|
group.Targets = []models.Target{
|
||||||
models.Target{Email: "test1@example.com", FirstName: "First", LastName: "Example"},
|
models.Target{BaseRecipient: models.BaseRecipient{Email: "test1@example.com", FirstName: "First", LastName: "Example"}},
|
||||||
models.Target{Email: "test2@example.com", FirstName: "Second", LastName: "Example"},
|
models.Target{BaseRecipient: models.BaseRecipient{Email: "test2@example.com", FirstName: "Second", LastName: "Example"}},
|
||||||
}
|
}
|
||||||
group.UserId = 1
|
group.UserId = 1
|
||||||
models.PostGroup(&group)
|
models.PostGroup(&group)
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/mail"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
ctx "github.com/gophish/gophish/context"
|
ctx "github.com/gophish/gophish/context"
|
||||||
|
@ -50,6 +46,11 @@ func PhishTracker(w http.ResponseWriter, r *http.Request) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Check for a preview
|
||||||
|
if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
|
||||||
|
http.ServeFile(w, r, "static/images/pixel.png")
|
||||||
|
return
|
||||||
|
}
|
||||||
rs := ctx.Get(r, "result").(models.Result)
|
rs := ctx.Get(r, "result").(models.Result)
|
||||||
d := ctx.Get(r, "details").(models.EventDetails)
|
d := ctx.Get(r, "details").(models.EventDetails)
|
||||||
err = rs.HandleEmailOpened(d)
|
err = rs.HandleEmailOpened(d)
|
||||||
|
@ -70,6 +71,11 @@ func PhishReporter(w http.ResponseWriter, r *http.Request) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Check for a preview
|
||||||
|
if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
rs := ctx.Get(r, "result").(models.Result)
|
rs := ctx.Get(r, "result").(models.Result)
|
||||||
d := ctx.Get(r, "details").(models.EventDetails)
|
d := ctx.Get(r, "details").(models.EventDetails)
|
||||||
|
|
||||||
|
@ -92,6 +98,24 @@ func PhishHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var ptx models.PhishingTemplateContext
|
||||||
|
// Check for a preview
|
||||||
|
if preview, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
|
||||||
|
ptx, err = models.NewPhishingTemplateContext(&preview, preview.BaseRecipient, preview.RId)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p, err := models.GetPage(preview.PageId, preview.UserId)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
renderPhishResponse(w, r, ptx, p)
|
||||||
|
return
|
||||||
|
}
|
||||||
rs := ctx.Get(r, "result").(models.Result)
|
rs := ctx.Get(r, "result").(models.Result)
|
||||||
c := ctx.Get(r, "campaign").(models.Campaign)
|
c := ctx.Get(r, "campaign").(models.Campaign)
|
||||||
d := ctx.Get(r, "details").(models.EventDetails)
|
d := ctx.Get(r, "details").(models.EventDetails)
|
||||||
|
@ -112,49 +136,35 @@ func PhishHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
// Redirect to the desired page
|
}
|
||||||
|
ptx, err = models.NewPhishingTemplateContext(&c, rs.BaseRecipient, rs.RId)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
renderPhishResponse(w, r, ptx, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderPhishResponse handles rendering the correct response to the phishing
|
||||||
|
// connection. This usually involves writing out the page HTML or redirecting
|
||||||
|
// the user to the correct URL.
|
||||||
|
func renderPhishResponse(w http.ResponseWriter, r *http.Request, ptx models.PhishingTemplateContext, p models.Page) {
|
||||||
|
// If the request was a form submit and a redirect URL was specified, we
|
||||||
|
// should send the user to that URL
|
||||||
|
if r.Method == "POST" {
|
||||||
if p.RedirectURL != "" {
|
if p.RedirectURL != "" {
|
||||||
http.Redirect(w, r, p.RedirectURL, 302)
|
http.Redirect(w, r, p.RedirectURL, 302)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var htmlBuff bytes.Buffer
|
// Otherwise, we just need to write out the templated HTML
|
||||||
tmpl, err := template.New("html_template").Parse(p.HTML)
|
html, err := models.ExecuteTemplate(p.HTML, ptx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f, err := mail.ParseAddress(c.SMTP.FromAddress)
|
w.Write([]byte(html))
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
fn := f.Name
|
|
||||||
if fn == "" {
|
|
||||||
fn = f.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
phishURL, _ := url.Parse(c.URL)
|
|
||||||
q := phishURL.Query()
|
|
||||||
q.Set(models.RecipientParameter, rs.RId)
|
|
||||||
phishURL.RawQuery = q.Encode()
|
|
||||||
|
|
||||||
rsf := struct {
|
|
||||||
models.Result
|
|
||||||
URL string
|
|
||||||
From string
|
|
||||||
}{
|
|
||||||
rs,
|
|
||||||
phishURL.String(),
|
|
||||||
fn,
|
|
||||||
}
|
|
||||||
err = tmpl.Execute(&htmlBuff, rsf)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Write(htmlBuff.Bytes())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RobotsHandler prevents search engines, etc. from indexing phishing materials
|
// RobotsHandler prevents search engines, etc. from indexing phishing materials
|
||||||
|
@ -173,6 +183,15 @@ func setupContext(r *http.Request) (error, *http.Request) {
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return ErrInvalidRequest, r
|
return ErrInvalidRequest, r
|
||||||
}
|
}
|
||||||
|
// Check to see if this is a preview or a real result
|
||||||
|
if strings.HasPrefix(id, models.PreviewPrefix) {
|
||||||
|
rs, err := models.GetEmailRequestByResultId(id)
|
||||||
|
if err != nil {
|
||||||
|
return err, r
|
||||||
|
}
|
||||||
|
r = ctx.Set(r, "result", rs)
|
||||||
|
return nil, r
|
||||||
|
}
|
||||||
rs, err := models.GetResult(id)
|
rs, err := models.GetResult(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, r
|
return err, r
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gophish/gophish/models"
|
"github.com/gophish/gophish/models"
|
||||||
|
@ -15,6 +16,23 @@ func (s *ControllersSuite) getFirstCampaign() models.Campaign {
|
||||||
return campaigns[0]
|
return campaigns[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ControllersSuite) getFirstEmailRequest() models.EmailRequest {
|
||||||
|
campaign := s.getFirstCampaign()
|
||||||
|
req := models.EmailRequest{
|
||||||
|
TemplateId: campaign.TemplateId,
|
||||||
|
Template: campaign.Template,
|
||||||
|
PageId: campaign.PageId,
|
||||||
|
Page: campaign.Page,
|
||||||
|
URL: "http://localhost.localdomain",
|
||||||
|
UserId: 1,
|
||||||
|
BaseRecipient: campaign.Results[0].BaseRecipient,
|
||||||
|
SMTP: campaign.SMTP,
|
||||||
|
}
|
||||||
|
err := models.PostEmailRequest(&req)
|
||||||
|
s.Nil(err)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ControllersSuite) openEmail(rid string) {
|
func (s *ControllersSuite) openEmail(rid string) {
|
||||||
resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ps.URL, models.RecipientParameter, rid))
|
resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ps.URL, models.RecipientParameter, rid))
|
||||||
s.Nil(err)
|
s.Nil(err)
|
||||||
|
@ -40,13 +58,14 @@ func (s *ControllersSuite) openEmail404(rid string) {
|
||||||
s.Equal(resp.StatusCode, http.StatusNotFound)
|
s.Equal(resp.StatusCode, http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ControllersSuite) clickLink(rid string, campaign models.Campaign) {
|
func (s *ControllersSuite) clickLink(rid string, expectedHTML string) {
|
||||||
resp, err := http.Get(fmt.Sprintf("%s/?%s=%s", ps.URL, models.RecipientParameter, rid))
|
resp, err := http.Get(fmt.Sprintf("%s/?%s=%s", ps.URL, models.RecipientParameter, rid))
|
||||||
s.Nil(err)
|
s.Nil(err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
s.Nil(err)
|
s.Nil(err)
|
||||||
s.Equal(bytes.Compare(body, []byte(campaign.Page.HTML)), 0)
|
log.Printf("%s\n\n\n", body)
|
||||||
|
s.Equal(bytes.Compare(body, []byte(expectedHTML)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ControllersSuite) clickLink404(rid string) {
|
func (s *ControllersSuite) clickLink404(rid string) {
|
||||||
|
@ -93,7 +112,7 @@ func (s *ControllersSuite) TestClickedPhishingLinkAfterOpen() {
|
||||||
s.Equal(result.Status, models.STATUS_SENDING)
|
s.Equal(result.Status, models.STATUS_SENDING)
|
||||||
|
|
||||||
s.openEmail(result.RId)
|
s.openEmail(result.RId)
|
||||||
s.clickLink(result.RId, campaign)
|
s.clickLink(result.RId, campaign.Page.HTML)
|
||||||
|
|
||||||
campaign = s.getFirstCampaign()
|
campaign = s.getFirstCampaign()
|
||||||
result = campaign.Results[0]
|
result = campaign.Results[0]
|
||||||
|
@ -153,3 +172,19 @@ func (s *ControllersSuite) TestRobotsHandler() {
|
||||||
s.Nil(err)
|
s.Nil(err)
|
||||||
s.Equal(bytes.Compare(body, expected), 0)
|
s.Equal(bytes.Compare(body, expected), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ControllersSuite) TestInvalidPreviewID() {
|
||||||
|
bogusRId := fmt.Sprintf("%sbogus", models.PreviewPrefix)
|
||||||
|
s.openEmail404(bogusRId)
|
||||||
|
s.clickLink404(bogusRId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ControllersSuite) TestPreviewTrack() {
|
||||||
|
req := s.getFirstEmailRequest()
|
||||||
|
s.openEmail(req.RId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ControllersSuite) TestPreviewClick() {
|
||||||
|
req := s.getFirstEmailRequest()
|
||||||
|
s.clickLink(req.RId, req.Page.HTML)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
-- +goose Up
|
||||||
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
CREATE TABLE IF NOT EXISTS email_requests (
|
||||||
|
`id` integer primary key auto_increment,
|
||||||
|
`user_id` integer,
|
||||||
|
`template_id` integer,
|
||||||
|
`page_id` integer,
|
||||||
|
`first_name` varchar(255),
|
||||||
|
`last_name` varchar(255),
|
||||||
|
`email` varchar(255),
|
||||||
|
`position` varchar(255),
|
||||||
|
`url` varchar(255),
|
||||||
|
`r_id` varchar(255),
|
||||||
|
`from_address` varchar(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
DROP TABLE email_requests
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
-- +goose Up
|
||||||
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
CREATE TABLE IF NOT EXISTS "email_requests" (
|
||||||
|
"id" integer primary key autoincrement,
|
||||||
|
"user_id" integer,
|
||||||
|
"template_id" integer,
|
||||||
|
"page_id" integer,
|
||||||
|
"first_name" varchar(255),
|
||||||
|
"last_name" varchar(255),
|
||||||
|
"email" varchar(255),
|
||||||
|
"position" varchar(255),
|
||||||
|
"url" varchar(255),
|
||||||
|
"r_id" varchar(255),
|
||||||
|
"from_address" varchar(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
|
|
@ -206,6 +206,18 @@ func (c *Campaign) getDetails() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getBaseURL returns the Campaign's configured URL.
|
||||||
|
// This is used to implement the TemplateContext interface.
|
||||||
|
func (c *Campaign) getBaseURL() string {
|
||||||
|
return c.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFromAddress returns the Campaign's configured SMTP "From" address.
|
||||||
|
// This is used to implement the TemplateContext interface.
|
||||||
|
func (c *Campaign) getFromAddress() string {
|
||||||
|
return c.SMTP.FromAddress
|
||||||
|
}
|
||||||
|
|
||||||
// getCampaignStats returns a CampaignStats object for the campaign with the given campaign ID.
|
// getCampaignStats returns a CampaignStats object for the campaign with the given campaign ID.
|
||||||
// It also backfills numbers as appropriate with a running total, so that the values are aggregated.
|
// It also backfills numbers as appropriate with a running total, so that the values are aggregated.
|
||||||
func getCampaignStats(cid int64) (CampaignStats, error) {
|
func getCampaignStats(cid int64) (CampaignStats, error) {
|
||||||
|
@ -452,13 +464,15 @@ func PostCampaign(c *Campaign, uid int64) error {
|
||||||
}
|
}
|
||||||
resultMap[t.Email] = true
|
resultMap[t.Email] = true
|
||||||
r := &Result{
|
r := &Result{
|
||||||
Email: t.Email,
|
BaseRecipient: BaseRecipient{
|
||||||
Position: t.Position,
|
Email: t.Email,
|
||||||
|
Position: t.Position,
|
||||||
|
FirstName: t.FirstName,
|
||||||
|
LastName: t.LastName,
|
||||||
|
},
|
||||||
Status: STATUS_SCHEDULED,
|
Status: STATUS_SCHEDULED,
|
||||||
CampaignId: c.Id,
|
CampaignId: c.Id,
|
||||||
UserId: c.UserId,
|
UserId: c.UserId,
|
||||||
FirstName: t.FirstName,
|
|
||||||
LastName: t.LastName,
|
|
||||||
SendDate: c.LaunchDate,
|
SendDate: c.LaunchDate,
|
||||||
Reported: false,
|
Reported: false,
|
||||||
ModifiedDate: c.CreatedDate,
|
ModifiedDate: c.CreatedDate,
|
||||||
|
|
|
@ -12,55 +12,94 @@ import (
|
||||||
"github.com/gophish/gophish/mailer"
|
"github.com/gophish/gophish/mailer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SendTestEmailRequest is the structure of a request
|
// PreviewPrefix is the standard prefix added to the rid parameter when sending
|
||||||
|
// test emails.
|
||||||
|
const PreviewPrefix = "preview-"
|
||||||
|
|
||||||
|
// EmailRequest is the structure of a request
|
||||||
// to send a test email to test an SMTP connection.
|
// to send a test email to test an SMTP connection.
|
||||||
// This type implements the mailer.Mail interface.
|
// This type implements the mailer.Mail interface.
|
||||||
type SendTestEmailRequest struct {
|
type EmailRequest struct {
|
||||||
Template Template `json:"template"`
|
Id int64 `json:"-"`
|
||||||
Page Page `json:"page"`
|
Template Template `json:"template"`
|
||||||
SMTP SMTP `json:"smtp"`
|
TemplateId int64 `json:"-"`
|
||||||
URL string `json:"url"`
|
Page Page `json:"page"`
|
||||||
Tracker string `json:"tracker"`
|
PageId int64 `json:"-"`
|
||||||
TrackingURL string `json:"tracking_url"`
|
SMTP SMTP `json:"smtp"`
|
||||||
From string `json:"from"`
|
URL string `json:"url"`
|
||||||
Target
|
Tracker string `json:"tracker" gorm:"-"`
|
||||||
ErrorChan chan (error) `json:"-"`
|
TrackingURL string `json:"tracking_url" gorm:"-"`
|
||||||
|
UserId int64 `json:"-"`
|
||||||
|
ErrorChan chan (error) `json:"-" gorm:"-"`
|
||||||
|
RId string `json:"id"`
|
||||||
|
FromAddress string `json:"-"`
|
||||||
|
BaseRecipient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EmailRequest) getBaseURL() string {
|
||||||
|
return s.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EmailRequest) getFromAddress() string {
|
||||||
|
return s.FromAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate ensures the SendTestEmailRequest structure
|
// Validate ensures the SendTestEmailRequest structure
|
||||||
// is valid.
|
// is valid.
|
||||||
func (s *SendTestEmailRequest) Validate() error {
|
func (s *EmailRequest) Validate() error {
|
||||||
switch {
|
switch {
|
||||||
case s.Email == "":
|
case s.Email == "":
|
||||||
return ErrEmailNotSpecified
|
return ErrEmailNotSpecified
|
||||||
|
case s.FromAddress == "" && s.SMTP.FromAddress == "":
|
||||||
|
return ErrFromAddressNotSpecified
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backoff treats temporary errors as permanent since this is expected to be a
|
// Backoff treats temporary errors as permanent since this is expected to be a
|
||||||
// synchronous operation. It returns any errors given back to the ErrorChan
|
// synchronous operation. It returns any errors given back to the ErrorChan
|
||||||
func (s *SendTestEmailRequest) Backoff(reason error) error {
|
func (s *EmailRequest) Backoff(reason error) error {
|
||||||
s.ErrorChan <- reason
|
s.ErrorChan <- reason
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error returns an error on the ErrorChan.
|
// Error returns an error on the ErrorChan.
|
||||||
func (s *SendTestEmailRequest) Error(err error) error {
|
func (s *EmailRequest) Error(err error) error {
|
||||||
s.ErrorChan <- err
|
s.ErrorChan <- err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success returns nil on the ErrorChan to indicate that the email was sent
|
// Success returns nil on the ErrorChan to indicate that the email was sent
|
||||||
// successfully.
|
// successfully.
|
||||||
func (s *SendTestEmailRequest) Success() error {
|
func (s *EmailRequest) Success() error {
|
||||||
s.ErrorChan <- nil
|
s.ErrorChan <- nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostEmailRequest stores a SendTestEmailRequest in the database.
|
||||||
|
func PostEmailRequest(s *EmailRequest) error {
|
||||||
|
// Generate an ID to be used in the underlying Result object
|
||||||
|
rid, err := generateResultId()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.RId = fmt.Sprintf("%s%s", PreviewPrefix, rid)
|
||||||
|
s.FromAddress = s.SMTP.FromAddress
|
||||||
|
return db.Save(&s).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailRequestByResultId retrieves the EmailRequest by the underlying rid
|
||||||
|
// parameter.
|
||||||
|
func GetEmailRequestByResultId(id string) (EmailRequest, error) {
|
||||||
|
s := EmailRequest{}
|
||||||
|
err := db.Table("email_requests").Where("r_id=?", id).First(&s).Error
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
// Generate fills in the details of a gomail.Message with the contents
|
// Generate fills in the details of a gomail.Message with the contents
|
||||||
// from the SendTestEmailRequest.
|
// from the SendTestEmailRequest.
|
||||||
func (s *SendTestEmailRequest) Generate(msg *gomail.Message) error {
|
func (s *EmailRequest) Generate(msg *gomail.Message) error {
|
||||||
f, err := mail.ParseAddress(s.SMTP.FromAddress)
|
f, err := mail.ParseAddress(s.FromAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -70,7 +109,12 @@ func (s *SendTestEmailRequest) Generate(msg *gomail.Message) error {
|
||||||
}
|
}
|
||||||
msg.SetAddressHeader("From", f.Address, f.Name)
|
msg.SetAddressHeader("From", f.Address, f.Name)
|
||||||
|
|
||||||
url, err := buildTemplate(s.URL, s)
|
ptx, err := NewPhishingTemplateContext(s, s.BaseRecipient, s.RId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := ExecuteTemplate(s.URL, ptx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -78,12 +122,12 @@ func (s *SendTestEmailRequest) Generate(msg *gomail.Message) error {
|
||||||
|
|
||||||
// Parse the customHeader templates
|
// Parse the customHeader templates
|
||||||
for _, header := range s.SMTP.Headers {
|
for _, header := range s.SMTP.Headers {
|
||||||
key, err := buildTemplate(header.Key, s)
|
key, err := ExecuteTemplate(header.Key, ptx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := buildTemplate(header.Value, s)
|
value, err := ExecuteTemplate(header.Value, ptx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -93,7 +137,7 @@ func (s *SendTestEmailRequest) Generate(msg *gomail.Message) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse remaining templates
|
// Parse remaining templates
|
||||||
subject, err := buildTemplate(s.Template.Subject, s)
|
subject, err := ExecuteTemplate(s.Template.Subject, ptx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -104,14 +148,14 @@ func (s *SendTestEmailRequest) Generate(msg *gomail.Message) error {
|
||||||
|
|
||||||
msg.SetHeader("To", s.FormatAddress())
|
msg.SetHeader("To", s.FormatAddress())
|
||||||
if s.Template.Text != "" {
|
if s.Template.Text != "" {
|
||||||
text, err := buildTemplate(s.Template.Text, s)
|
text, err := ExecuteTemplate(s.Template.Text, ptx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
msg.SetBody("text/plain", text)
|
msg.SetBody("text/plain", text)
|
||||||
}
|
}
|
||||||
if s.Template.HTML != "" {
|
if s.Template.HTML != "" {
|
||||||
html, err := buildTemplate(s.Template.HTML, s)
|
html, err := ExecuteTemplate(s.Template.HTML, ptx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -137,6 +181,6 @@ func (s *SendTestEmailRequest) Generate(msg *gomail.Message) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDialer returns the mailer.Dialer for the underlying SMTP object
|
// GetDialer returns the mailer.Dialer for the underlying SMTP object
|
||||||
func (s *SendTestEmailRequest) GetDialer() (mailer.Dialer, error) {
|
func (s *EmailRequest) GetDialer() (mailer.Dialer, error) {
|
||||||
return s.SMTP.GetDialer()
|
return s.SMTP.GetDialer()
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,14 +11,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *ModelsSuite) TestEmailNotPresent(ch *check.C) {
|
func (s *ModelsSuite) TestEmailNotPresent(ch *check.C) {
|
||||||
req := &SendTestEmailRequest{}
|
req := &EmailRequest{}
|
||||||
ch.Assert(req.Validate(), check.Equals, ErrEmailNotSpecified)
|
ch.Assert(req.Validate(), check.Equals, ErrEmailNotSpecified)
|
||||||
req.Email = "test@example.com"
|
req.Email = "test@example.com"
|
||||||
|
ch.Assert(req.Validate(), check.Equals, ErrFromAddressNotSpecified)
|
||||||
|
req.FromAddress = "from@example.com"
|
||||||
ch.Assert(req.Validate(), check.Equals, nil)
|
ch.Assert(req.Validate(), check.Equals, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ModelsSuite) TestEmailRequestBackoff(ch *check.C) {
|
func (s *ModelsSuite) TestEmailRequestBackoff(ch *check.C) {
|
||||||
req := &SendTestEmailRequest{
|
req := &EmailRequest{
|
||||||
ErrorChan: make(chan error),
|
ErrorChan: make(chan error),
|
||||||
}
|
}
|
||||||
expected := errors.New("Temporary Error")
|
expected := errors.New("Temporary Error")
|
||||||
|
@ -30,7 +32,7 @@ func (s *ModelsSuite) TestEmailRequestBackoff(ch *check.C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ModelsSuite) TestEmailRequestError(ch *check.C) {
|
func (s *ModelsSuite) TestEmailRequestError(ch *check.C) {
|
||||||
req := &SendTestEmailRequest{
|
req := &EmailRequest{
|
||||||
ErrorChan: make(chan error),
|
ErrorChan: make(chan error),
|
||||||
}
|
}
|
||||||
expected := errors.New("Temporary Error")
|
expected := errors.New("Temporary Error")
|
||||||
|
@ -42,7 +44,7 @@ func (s *ModelsSuite) TestEmailRequestError(ch *check.C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ModelsSuite) TestEmailRequestSuccess(ch *check.C) {
|
func (s *ModelsSuite) TestEmailRequestSuccess(ch *check.C) {
|
||||||
req := &SendTestEmailRequest{
|
req := &EmailRequest{
|
||||||
ErrorChan: make(chan error),
|
ErrorChan: make(chan error),
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -62,15 +64,15 @@ func (s *ModelsSuite) TestEmailRequestGenerate(ch *check.C) {
|
||||||
Text: "{{.Email}} - Text",
|
Text: "{{.Email}} - Text",
|
||||||
HTML: "{{.Email}} - HTML",
|
HTML: "{{.Email}} - HTML",
|
||||||
}
|
}
|
||||||
target := Target{
|
req := &EmailRequest{
|
||||||
FirstName: "First",
|
|
||||||
LastName: "Last",
|
|
||||||
Email: "firstlast@example.com",
|
|
||||||
}
|
|
||||||
req := &SendTestEmailRequest{
|
|
||||||
SMTP: smtp,
|
SMTP: smtp,
|
||||||
Template: template,
|
Template: template,
|
||||||
Target: target,
|
BaseRecipient: BaseRecipient{
|
||||||
|
FirstName: "First",
|
||||||
|
LastName: "Last",
|
||||||
|
Email: "firstlast@example.com",
|
||||||
|
},
|
||||||
|
FromAddress: smtp.FromAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := gomail.NewMessage()
|
msg := gomail.NewMessage()
|
||||||
|
@ -104,23 +106,24 @@ func (s *ModelsSuite) TestEmailRequestURLTemplating(ch *check.C) {
|
||||||
Text: "{{.URL}}",
|
Text: "{{.URL}}",
|
||||||
HTML: "{{.URL}}",
|
HTML: "{{.URL}}",
|
||||||
}
|
}
|
||||||
target := Target{
|
req := &EmailRequest{
|
||||||
FirstName: "First",
|
|
||||||
LastName: "Last",
|
|
||||||
Email: "firstlast@example.com",
|
|
||||||
}
|
|
||||||
req := &SendTestEmailRequest{
|
|
||||||
SMTP: smtp,
|
SMTP: smtp,
|
||||||
Template: template,
|
Template: template,
|
||||||
Target: target,
|
URL: "http://127.0.0.1/{{.Email}}",
|
||||||
URL: "http://127.0.0.1/{{.Email}}",
|
BaseRecipient: BaseRecipient{
|
||||||
|
FirstName: "First",
|
||||||
|
LastName: "Last",
|
||||||
|
Email: "firstlast@example.com",
|
||||||
|
},
|
||||||
|
FromAddress: smtp.FromAddress,
|
||||||
|
RId: fmt.Sprintf("%s-foobar", PreviewPrefix),
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := gomail.NewMessage()
|
msg := gomail.NewMessage()
|
||||||
err = req.Generate(msg)
|
err = req.Generate(msg)
|
||||||
ch.Assert(err, check.Equals, nil)
|
ch.Assert(err, check.Equals, nil)
|
||||||
|
|
||||||
expectedURL := fmt.Sprintf("http://127.0.0.1/%s", target.Email)
|
expectedURL := fmt.Sprintf("http://127.0.0.1/%s?%s=%s", req.Email, RecipientParameter, req.RId)
|
||||||
|
|
||||||
msgBuff := &bytes.Buffer{}
|
msgBuff := &bytes.Buffer{}
|
||||||
_, err = msg.WriteTo(msgBuff)
|
_, err = msg.WriteTo(msgBuff)
|
||||||
|
@ -142,15 +145,15 @@ func (s *ModelsSuite) TestEmailRequestGenerateEmptySubject(ch *check.C) {
|
||||||
Text: "{{.Email}} - Text",
|
Text: "{{.Email}} - Text",
|
||||||
HTML: "{{.Email}} - HTML",
|
HTML: "{{.Email}} - HTML",
|
||||||
}
|
}
|
||||||
target := Target{
|
req := &EmailRequest{
|
||||||
FirstName: "First",
|
|
||||||
LastName: "Last",
|
|
||||||
Email: "firstlast@example.com",
|
|
||||||
}
|
|
||||||
req := &SendTestEmailRequest{
|
|
||||||
SMTP: smtp,
|
SMTP: smtp,
|
||||||
Template: template,
|
Template: template,
|
||||||
Target: target,
|
BaseRecipient: BaseRecipient{
|
||||||
|
FirstName: "First",
|
||||||
|
LastName: "Last",
|
||||||
|
Email: "firstlast@example.com",
|
||||||
|
},
|
||||||
|
FromAddress: smtp.FromAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := gomail.NewMessage()
|
msg := gomail.NewMessage()
|
||||||
|
@ -171,3 +174,44 @@ func (s *ModelsSuite) TestEmailRequestGenerateEmptySubject(ch *check.C) {
|
||||||
ch.Assert(err, check.Equals, nil)
|
ch.Assert(err, check.Equals, nil)
|
||||||
ch.Assert(got.Subject, check.Equals, expected.Subject)
|
ch.Assert(got.Subject, check.Equals, expected.Subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ModelsSuite) TestPostSendTestEmailRequest(ch *check.C) {
|
||||||
|
smtp := SMTP{
|
||||||
|
FromAddress: "from@example.com",
|
||||||
|
}
|
||||||
|
template := Template{
|
||||||
|
Name: "Test Template",
|
||||||
|
Subject: "",
|
||||||
|
Text: "{{.Email}} - Text",
|
||||||
|
HTML: "{{.Email}} - HTML",
|
||||||
|
UserId: 1,
|
||||||
|
}
|
||||||
|
err := PostTemplate(&template)
|
||||||
|
ch.Assert(err, check.Equals, nil)
|
||||||
|
|
||||||
|
page := Page{
|
||||||
|
Name: "Test Page",
|
||||||
|
HTML: "test",
|
||||||
|
UserId: 1,
|
||||||
|
}
|
||||||
|
err = PostPage(&page)
|
||||||
|
ch.Assert(err, check.Equals, nil)
|
||||||
|
|
||||||
|
req := &EmailRequest{
|
||||||
|
SMTP: smtp,
|
||||||
|
TemplateId: template.Id,
|
||||||
|
PageId: page.Id,
|
||||||
|
BaseRecipient: BaseRecipient{
|
||||||
|
FirstName: "First",
|
||||||
|
LastName: "Last",
|
||||||
|
Email: "firstlast@example.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = PostEmailRequest(req)
|
||||||
|
ch.Assert(err, check.Equals, nil)
|
||||||
|
|
||||||
|
got, err := GetEmailRequestByResultId(req.RId)
|
||||||
|
ch.Assert(err, check.Equals, nil)
|
||||||
|
ch.Assert(got.RId, check.Equals, req.RId)
|
||||||
|
ch.Assert(got.Email, check.Equals, req.Email)
|
||||||
|
}
|
||||||
|
|
|
@ -46,14 +46,33 @@ type GroupTarget struct {
|
||||||
// Target contains the fields needed for individual targets specified by the user
|
// Target contains the fields needed for individual targets specified by the user
|
||||||
// Groups contain 1..* Targets, but 1 Target may belong to 1..* Groups
|
// Groups contain 1..* Targets, but 1 Target may belong to 1..* Groups
|
||||||
type Target struct {
|
type Target struct {
|
||||||
Id int64 `json:"-"`
|
Id int64 `json:"-"`
|
||||||
|
BaseRecipient
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseRecipient contains the fields for a single recipient. This is the base
|
||||||
|
// struct used in members of groups and campaign results.
|
||||||
|
type BaseRecipient struct {
|
||||||
|
Email string `json:"email"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
Email string `json:"email"`
|
|
||||||
Position string `json:"position"`
|
Position string `json:"position"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the email address to use in the "To" header of the email
|
// FormatAddress returns the email address to use in the "To" header of the email
|
||||||
|
func (r *BaseRecipient) FormatAddress() string {
|
||||||
|
addr := r.Email
|
||||||
|
if r.FirstName != "" && r.LastName != "" {
|
||||||
|
a := &mail.Address{
|
||||||
|
Name: fmt.Sprintf("%s %s", r.FirstName, r.LastName),
|
||||||
|
Address: r.Email,
|
||||||
|
}
|
||||||
|
addr = a.String()
|
||||||
|
}
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatAddress returns the email address to use in the "To" header of the email
|
||||||
func (t *Target) FormatAddress() string {
|
func (t *Target) FormatAddress() string {
|
||||||
addr := t.Email
|
addr := t.Email
|
||||||
if t.FirstName != "" && t.LastName != "" {
|
if t.FirstName != "" && t.LastName != "" {
|
||||||
|
@ -66,7 +85,7 @@ func (t *Target) FormatAddress() string {
|
||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrNoEmailSpecified is thrown when no email is specified for the Target
|
// ErrEmailNotSpecified is thrown when no email is specified for the Target
|
||||||
var ErrEmailNotSpecified = errors.New("No email address specified")
|
var ErrEmailNotSpecified = errors.New("No email address specified")
|
||||||
|
|
||||||
// ErrGroupNameNotSpecified is thrown when a group name is not specified
|
// ErrGroupNameNotSpecified is thrown when a group name is not specified
|
||||||
|
@ -274,11 +293,12 @@ func insertTargetIntoGroup(t Target, gid int64) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
trans := db.Begin()
|
trans := db.Begin()
|
||||||
trans.Where(t).FirstOrCreate(&t)
|
err = trans.Where(t).FirstOrCreate(&t).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"email": t.Email,
|
"email": t.Email,
|
||||||
}).Error("Error adding target")
|
}).Error(err)
|
||||||
|
trans.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = trans.Where("group_id=? and target_id=?", gid, t.Id).Find(&GroupTarget{}).Error
|
err = trans.Where("group_id=? and target_id=?", gid, t.Id).Find(&GroupTarget{}).Error
|
||||||
|
@ -286,6 +306,7 @@ func insertTargetIntoGroup(t Target, gid int64) error {
|
||||||
err = trans.Save(&GroupTarget{GroupId: gid, TargetId: t.Id}).Error
|
err = trans.Save(&GroupTarget{GroupId: gid, TargetId: t.Id}).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
trans.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -293,10 +314,12 @@ func insertTargetIntoGroup(t Target, gid int64) error {
|
||||||
log.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"email": t.Email,
|
"email": t.Email,
|
||||||
}).Error("Error adding many-many mapping")
|
}).Error("Error adding many-many mapping")
|
||||||
|
trans.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = trans.Commit().Error
|
err = trans.Commit().Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
trans.Rollback()
|
||||||
log.Error("Error committing db changes")
|
log.Error("Error committing db changes")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
func (s *ModelsSuite) TestPostGroup(c *check.C) {
|
func (s *ModelsSuite) TestPostGroup(c *check.C) {
|
||||||
g := Group{Name: "Test Group"}
|
g := Group{Name: "Test Group"}
|
||||||
g.Targets = []Target{Target{Email: "test@example.com"}}
|
g.Targets = []Target{Target{BaseRecipient: BaseRecipient{Email: "test@example.com"}}}
|
||||||
g.UserId = 1
|
g.UserId = 1
|
||||||
err := PostGroup(&g)
|
err := PostGroup(&g)
|
||||||
c.Assert(err, check.Equals, nil)
|
c.Assert(err, check.Equals, nil)
|
||||||
|
@ -17,7 +17,7 @@ func (s *ModelsSuite) TestPostGroup(c *check.C) {
|
||||||
|
|
||||||
func (s *ModelsSuite) TestPostGroupNoName(c *check.C) {
|
func (s *ModelsSuite) TestPostGroupNoName(c *check.C) {
|
||||||
g := Group{Name: ""}
|
g := Group{Name: ""}
|
||||||
g.Targets = []Target{Target{Email: "test@example.com"}}
|
g.Targets = []Target{Target{BaseRecipient: BaseRecipient{Email: "test@example.com"}}}
|
||||||
g.UserId = 1
|
g.UserId = 1
|
||||||
err := PostGroup(&g)
|
err := PostGroup(&g)
|
||||||
c.Assert(err, check.Equals, ErrGroupNameNotSpecified)
|
c.Assert(err, check.Equals, ErrGroupNameNotSpecified)
|
||||||
|
@ -34,14 +34,22 @@ func (s *ModelsSuite) TestPostGroupNoTargets(c *check.C) {
|
||||||
func (s *ModelsSuite) TestGetGroups(c *check.C) {
|
func (s *ModelsSuite) TestGetGroups(c *check.C) {
|
||||||
// Add groups.
|
// Add groups.
|
||||||
PostGroup(&Group{
|
PostGroup(&Group{
|
||||||
Name: "Test Group 1",
|
Name: "Test Group 1",
|
||||||
Targets: []Target{Target{Email: "test1@example.com"}},
|
Targets: []Target{
|
||||||
UserId: 1,
|
Target{
|
||||||
|
BaseRecipient: BaseRecipient{Email: "test1@example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UserId: 1,
|
||||||
})
|
})
|
||||||
PostGroup(&Group{
|
PostGroup(&Group{
|
||||||
Name: "Test Group 2",
|
Name: "Test Group 2",
|
||||||
Targets: []Target{Target{Email: "test2@example.com"}},
|
Targets: []Target{
|
||||||
UserId: 1,
|
Target{
|
||||||
|
BaseRecipient: BaseRecipient{Email: "test2@example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UserId: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get groups and test result.
|
// Get groups and test result.
|
||||||
|
@ -65,9 +73,13 @@ func (s *ModelsSuite) TestGetGroupsNoGroups(c *check.C) {
|
||||||
func (s *ModelsSuite) TestGetGroup(c *check.C) {
|
func (s *ModelsSuite) TestGetGroup(c *check.C) {
|
||||||
// Add group.
|
// Add group.
|
||||||
originalGroup := &Group{
|
originalGroup := &Group{
|
||||||
Name: "Test Group",
|
Name: "Test Group",
|
||||||
Targets: []Target{Target{Email: "test@example.com"}},
|
Targets: []Target{
|
||||||
UserId: 1,
|
Target{
|
||||||
|
BaseRecipient: BaseRecipient{Email: "test@example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UserId: 1,
|
||||||
}
|
}
|
||||||
c.Assert(PostGroup(originalGroup), check.Equals, nil)
|
c.Assert(PostGroup(originalGroup), check.Equals, nil)
|
||||||
|
|
||||||
|
@ -87,9 +99,13 @@ func (s *ModelsSuite) TestGetGroupNoGroups(c *check.C) {
|
||||||
func (s *ModelsSuite) TestGetGroupByName(c *check.C) {
|
func (s *ModelsSuite) TestGetGroupByName(c *check.C) {
|
||||||
// Add group.
|
// Add group.
|
||||||
PostGroup(&Group{
|
PostGroup(&Group{
|
||||||
Name: "Test Group",
|
Name: "Test Group",
|
||||||
Targets: []Target{Target{Email: "test@example.com"}},
|
Targets: []Target{
|
||||||
UserId: 1,
|
Target{
|
||||||
|
BaseRecipient: BaseRecipient{Email: "test@example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UserId: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get group and test result.
|
// Get group and test result.
|
||||||
|
@ -109,8 +125,8 @@ func (s *ModelsSuite) TestPutGroup(c *check.C) {
|
||||||
// Add test group.
|
// Add test group.
|
||||||
group := Group{Name: "Test Group"}
|
group := Group{Name: "Test Group"}
|
||||||
group.Targets = []Target{
|
group.Targets = []Target{
|
||||||
Target{Email: "test1@example.com", FirstName: "First", LastName: "Example"},
|
Target{BaseRecipient: BaseRecipient{Email: "test1@example.com", FirstName: "First", LastName: "Example"}},
|
||||||
Target{Email: "test2@example.com", FirstName: "Second", LastName: "Example"},
|
Target{BaseRecipient: BaseRecipient{Email: "test2@example.com", FirstName: "Second", LastName: "Example"}},
|
||||||
}
|
}
|
||||||
group.UserId = 1
|
group.UserId = 1
|
||||||
PostGroup(&group)
|
PostGroup(&group)
|
||||||
|
@ -134,8 +150,8 @@ func (s *ModelsSuite) TestPutGroupEmptyAttribute(c *check.C) {
|
||||||
// Add test group.
|
// Add test group.
|
||||||
group := Group{Name: "Test Group"}
|
group := Group{Name: "Test Group"}
|
||||||
group.Targets = []Target{
|
group.Targets = []Target{
|
||||||
Target{Email: "test1@example.com", FirstName: "First", LastName: "Example"},
|
Target{BaseRecipient: BaseRecipient{Email: "test1@example.com", FirstName: "First", LastName: "Example"}},
|
||||||
Target{Email: "test2@example.com", FirstName: "Second", LastName: "Example"},
|
Target{BaseRecipient: BaseRecipient{Email: "test2@example.com", FirstName: "Second", LastName: "Example"}},
|
||||||
}
|
}
|
||||||
group.UserId = 1
|
group.UserId = 1
|
||||||
PostGroup(&group)
|
PostGroup(&group)
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gophish/gomail"
|
"github.com/gophish/gomail"
|
||||||
|
@ -136,18 +132,6 @@ func (m *MailLog) GetDialer() (mailer.Dialer, error) {
|
||||||
return c.SMTP.GetDialer()
|
return c.SMTP.GetDialer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildTemplate creates a templated string based on the provided
|
|
||||||
// template body and data.
|
|
||||||
func buildTemplate(text string, data interface{}) (string, error) {
|
|
||||||
buff := bytes.Buffer{}
|
|
||||||
tmpl, err := template.New("template").Parse(text)
|
|
||||||
if err != nil {
|
|
||||||
return buff.String(), err
|
|
||||||
}
|
|
||||||
err = tmpl.Execute(&buff, data)
|
|
||||||
return buff.String(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate fills in the details of a gomail.Message instance with
|
// Generate fills in the details of a gomail.Message instance with
|
||||||
// the correct headers and body from the campaign and recipient listed in
|
// the correct headers and body from the campaign and recipient listed in
|
||||||
// the maillog. We accept the gomail.Message as an argument so that the caller
|
// the maillog. We accept the gomail.Message as an argument so that the caller
|
||||||
|
@ -161,51 +145,26 @@ func (m *MailLog) Generate(msg *gomail.Message) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := mail.ParseAddress(c.SMTP.FromAddress)
|
f, err := mail.ParseAddress(c.SMTP.FromAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fn := f.Name
|
|
||||||
if fn == "" {
|
|
||||||
fn = f.Address
|
|
||||||
}
|
|
||||||
msg.SetAddressHeader("From", f.Address, f.Name)
|
msg.SetAddressHeader("From", f.Address, f.Name)
|
||||||
campaignURL, err := buildTemplate(c.URL, r)
|
|
||||||
|
ptx, err := NewPhishingTemplateContext(&c, r.BaseRecipient, r.RId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
phishURL, _ := url.Parse(campaignURL)
|
|
||||||
q := phishURL.Query()
|
|
||||||
q.Set("rid", r.RId)
|
|
||||||
phishURL.RawQuery = q.Encode()
|
|
||||||
|
|
||||||
trackingURL, _ := url.Parse(campaignURL)
|
|
||||||
trackingURL.Path = path.Join(trackingURL.Path, "/track")
|
|
||||||
trackingURL.RawQuery = q.Encode()
|
|
||||||
|
|
||||||
td := struct {
|
|
||||||
Result
|
|
||||||
URL string
|
|
||||||
TrackingURL string
|
|
||||||
Tracker string
|
|
||||||
From string
|
|
||||||
}{
|
|
||||||
r,
|
|
||||||
phishURL.String(),
|
|
||||||
trackingURL.String(),
|
|
||||||
"<img alt='' style='display: none' src='" + trackingURL.String() + "'/>",
|
|
||||||
fn,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the customHeader templates
|
// Parse the customHeader templates
|
||||||
for _, header := range c.SMTP.Headers {
|
for _, header := range c.SMTP.Headers {
|
||||||
key, err := buildTemplate(header.Key, td)
|
key, err := ExecuteTemplate(header.Key, ptx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
log.Warn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := buildTemplate(header.Value, td)
|
value, err := ExecuteTemplate(header.Value, ptx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
log.Warn(err)
|
||||||
}
|
}
|
||||||
|
@ -215,7 +174,7 @@ func (m *MailLog) Generate(msg *gomail.Message) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse remaining templates
|
// Parse remaining templates
|
||||||
subject, err := buildTemplate(c.Template.Subject, td)
|
subject, err := ExecuteTemplate(c.Template.Subject, ptx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
log.Warn(err)
|
||||||
}
|
}
|
||||||
|
@ -226,14 +185,14 @@ func (m *MailLog) Generate(msg *gomail.Message) error {
|
||||||
|
|
||||||
msg.SetHeader("To", r.FormatAddress())
|
msg.SetHeader("To", r.FormatAddress())
|
||||||
if c.Template.Text != "" {
|
if c.Template.Text != "" {
|
||||||
text, err := buildTemplate(c.Template.Text, td)
|
text, err := ExecuteTemplate(c.Template.Text, ptx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
log.Warn(err)
|
||||||
}
|
}
|
||||||
msg.SetBody("text/plain", text)
|
msg.SetBody("text/plain", text)
|
||||||
}
|
}
|
||||||
if c.Template.HTML != "" {
|
if c.Template.HTML != "" {
|
||||||
html, err := buildTemplate(c.Template.HTML, td)
|
html, err := ExecuteTemplate(c.Template.HTML, ptx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
log.Warn(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,8 @@ func (s *ModelsSuite) createCampaignDependencies(ch *check.C, optional ...string
|
||||||
// we use the optional parameter to pass an alternative subject
|
// we use the optional parameter to pass an alternative subject
|
||||||
group := Group{Name: "Test Group"}
|
group := Group{Name: "Test Group"}
|
||||||
group.Targets = []Target{
|
group.Targets = []Target{
|
||||||
Target{Email: "test1@example.com", FirstName: "First", LastName: "Example"},
|
Target{BaseRecipient: BaseRecipient{Email: "test1@example.com", FirstName: "First", LastName: "Example"}},
|
||||||
Target{Email: "test2@example.com", FirstName: "Second", LastName: "Example"},
|
Target{BaseRecipient: BaseRecipient{Email: "test2@example.com", FirstName: "Second", LastName: "Example"}},
|
||||||
}
|
}
|
||||||
group.UserId = 1
|
group.UserId = 1
|
||||||
ch.Assert(PostGroup(&group), check.Equals, nil)
|
ch.Assert(PostGroup(&group), check.Equals, nil)
|
||||||
|
|
|
@ -3,10 +3,8 @@ package models
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/mail"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/gophish/gophish/logger"
|
log "github.com/gophish/gophish/logger"
|
||||||
|
@ -30,10 +28,6 @@ type Result struct {
|
||||||
CampaignId int64 `json:"-"`
|
CampaignId int64 `json:"-"`
|
||||||
UserId int64 `json:"-"`
|
UserId int64 `json:"-"`
|
||||||
RId string `json:"id"`
|
RId string `json:"id"`
|
||||||
Email string `json:"email"`
|
|
||||||
FirstName string `json:"first_name"`
|
|
||||||
LastName string `json:"last_name"`
|
|
||||||
Position string `json:"position"`
|
|
||||||
Status string `json:"status" sql:"not null"`
|
Status string `json:"status" sql:"not null"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Latitude float64 `json:"latitude"`
|
Latitude float64 `json:"latitude"`
|
||||||
|
@ -41,6 +35,7 @@ type Result struct {
|
||||||
SendDate time.Time `json:"send_date"`
|
SendDate time.Time `json:"send_date"`
|
||||||
Reported bool `json:"reported" sql:"not null"`
|
Reported bool `json:"reported" sql:"not null"`
|
||||||
ModifiedDate time.Time `json:"modified_date"`
|
ModifiedDate time.Time `json:"modified_date"`
|
||||||
|
BaseRecipient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Result) createEvent(status string, details interface{}) (*Event, error) {
|
func (r *Result) createEvent(status string, details interface{}) (*Event, error) {
|
||||||
|
@ -178,22 +173,30 @@ func (r *Result) UpdateGeo(addr string) error {
|
||||||
return db.Save(r).Error
|
return db.Save(r).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateResultId() (string, error) {
|
||||||
|
const alphaNum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
k := make([]byte, 7)
|
||||||
|
for i := range k {
|
||||||
|
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(alphaNum))))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
k[i] = alphaNum[idx.Int64()]
|
||||||
|
}
|
||||||
|
return string(k), nil
|
||||||
|
}
|
||||||
|
|
||||||
// GenerateId generates a unique key to represent the result
|
// GenerateId generates a unique key to represent the result
|
||||||
// in the database
|
// in the database
|
||||||
func (r *Result) GenerateId() error {
|
func (r *Result) GenerateId() error {
|
||||||
// Keep trying until we generate a unique key (shouldn't take more than one or two iterations)
|
// Keep trying until we generate a unique key (shouldn't take more than one or two iterations)
|
||||||
const alphaNum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
||||||
k := make([]byte, 7)
|
|
||||||
for {
|
for {
|
||||||
for i := range k {
|
rid, err := generateResultId()
|
||||||
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(alphaNum))))
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
k[i] = alphaNum[idx.Int64()]
|
|
||||||
}
|
}
|
||||||
r.RId = string(k)
|
r.RId = rid
|
||||||
err := db.Table("results").Where("r_id=?", r.RId).First(&Result{}).Error
|
err = db.Table("results").Where("r_id=?", r.RId).First(&Result{}).Error
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -201,19 +204,6 @@ func (r *Result) GenerateId() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatAddress returns the email address to use in the "To" header of the email
|
|
||||||
func (r *Result) FormatAddress() string {
|
|
||||||
addr := r.Email
|
|
||||||
if r.FirstName != "" && r.LastName != "" {
|
|
||||||
a := &mail.Address{
|
|
||||||
Name: fmt.Sprintf("%s %s", r.FirstName, r.LastName),
|
|
||||||
Address: r.Email,
|
|
||||||
}
|
|
||||||
addr = a.String()
|
|
||||||
}
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResult returns the Result object from the database
|
// GetResult returns the Result object from the database
|
||||||
// given the ResultId
|
// given the ResultId
|
||||||
func GetResult(rid string) (Result, error) {
|
func GetResult(rid string) (Result, error) {
|
||||||
|
|
|
@ -18,9 +18,11 @@ func (s *ModelsSuite) TestGenerateResultId(c *check.C) {
|
||||||
|
|
||||||
func (s *ModelsSuite) TestFormatAddress(c *check.C) {
|
func (s *ModelsSuite) TestFormatAddress(c *check.C) {
|
||||||
r := Result{
|
r := Result{
|
||||||
FirstName: "John",
|
BaseRecipient: BaseRecipient{
|
||||||
LastName: "Doe",
|
FirstName: "John",
|
||||||
Email: "johndoe@example.com",
|
LastName: "Doe",
|
||||||
|
Email: "johndoe@example.com",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
expected := &mail.Address{
|
expected := &mail.Address{
|
||||||
Name: "John Doe",
|
Name: "John Doe",
|
||||||
|
@ -29,7 +31,7 @@ func (s *ModelsSuite) TestFormatAddress(c *check.C) {
|
||||||
c.Assert(r.FormatAddress(), check.Equals, expected.String())
|
c.Assert(r.FormatAddress(), check.Equals, expected.String())
|
||||||
|
|
||||||
r = Result{
|
r = Result{
|
||||||
Email: "johndoe@example.com",
|
BaseRecipient: BaseRecipient{Email: "johndoe@example.com"},
|
||||||
}
|
}
|
||||||
c.Assert(r.FormatAddress(), check.Equals, r.Email)
|
c.Assert(r.FormatAddress(), check.Equals, r.Email)
|
||||||
}
|
}
|
||||||
|
@ -59,9 +61,9 @@ func (s *ModelsSuite) TestResultScheduledStatus(ch *check.C) {
|
||||||
func (s *ModelsSuite) TestDuplicateResults(ch *check.C) {
|
func (s *ModelsSuite) TestDuplicateResults(ch *check.C) {
|
||||||
group := Group{Name: "Test Group"}
|
group := Group{Name: "Test Group"}
|
||||||
group.Targets = []Target{
|
group.Targets = []Target{
|
||||||
Target{Email: "test1@example.com", FirstName: "First", LastName: "Example"},
|
Target{BaseRecipient: BaseRecipient{Email: "test1@example.com", FirstName: "First", LastName: "Example"}},
|
||||||
Target{Email: "test1@example.com", FirstName: "Duplicate", LastName: "Duplicate"},
|
Target{BaseRecipient: BaseRecipient{Email: "test1@example.com", FirstName: "Duplicate", LastName: "Duplicate"}},
|
||||||
Target{Email: "test2@example.com", FirstName: "Second", LastName: "Example"},
|
Target{BaseRecipient: BaseRecipient{Email: "test2@example.com", FirstName: "Second", LastName: "Example"}},
|
||||||
}
|
}
|
||||||
group.UserId = 1
|
group.UserId = 1
|
||||||
ch.Assert(PostGroup(&group), check.Equals, nil)
|
ch.Assert(PostGroup(&group), check.Equals, nil)
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"html/template"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/gophish/gophish/logger"
|
log "github.com/gophish/gophish/logger"
|
||||||
|
@ -36,7 +34,6 @@ func (t *Template) Validate() error {
|
||||||
case t.Text == "" && t.HTML == "":
|
case t.Text == "" && t.HTML == "":
|
||||||
return ErrTemplateMissingParameter
|
return ErrTemplateMissingParameter
|
||||||
}
|
}
|
||||||
var buff bytes.Buffer
|
|
||||||
// Test that the variables used in the template
|
// Test that the variables used in the template
|
||||||
// validate with no issues
|
// validate with no issues
|
||||||
td := struct {
|
td := struct {
|
||||||
|
@ -47,31 +44,27 @@ func (t *Template) Validate() error {
|
||||||
From string
|
From string
|
||||||
}{
|
}{
|
||||||
Result{
|
Result{
|
||||||
Email: "foo@bar.com",
|
BaseRecipient: BaseRecipient{
|
||||||
FirstName: "Foo",
|
Email: "foo@bar.com",
|
||||||
LastName: "Bar",
|
FirstName: "Foo",
|
||||||
Position: "Test",
|
LastName: "Bar",
|
||||||
|
Position: "Test",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"http://foo.bar",
|
"http://foo.bar",
|
||||||
"http://foo.bar/track",
|
"http://foo.bar/track",
|
||||||
"<img src='http://foo.bar/track",
|
"<img src='http://foo.bar/track",
|
||||||
"John Doe <foo@bar.com>",
|
"John Doe <foo@bar.com>",
|
||||||
}
|
}
|
||||||
tmpl, err := template.New("html_template").Parse(t.HTML)
|
_, err := ExecuteTemplate(t.HTML, td)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = tmpl.Execute(&buff, td)
|
_, err = ExecuteTemplate(t.Text, td)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
tmpl, err = template.New("text_template").Parse(t.Text)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = tmpl.Execute(&buff, td)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTemplates returns the templates owned by the given user.
|
// GetTemplates returns the templates owned by the given user.
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/mail"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TemplateContext is an interface that allows both campaigns and email
|
||||||
|
// requests to have a PhishingTemplateContext generated for them.
|
||||||
|
type TemplateContext interface {
|
||||||
|
getFromAddress() string
|
||||||
|
getBaseURL() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PhishingTemplateContext is the context that is sent to any template, such
|
||||||
|
// as the email or landing page content.
|
||||||
|
type PhishingTemplateContext struct {
|
||||||
|
From string
|
||||||
|
URL string
|
||||||
|
Tracker string
|
||||||
|
TrackingURL string
|
||||||
|
RId string
|
||||||
|
BaseRecipient
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPhishingTemplateContext returns a populated PhishingTemplateContext,
|
||||||
|
// parsing the correct fields from the provided TemplateContext and recipient.
|
||||||
|
func NewPhishingTemplateContext(ctx TemplateContext, r BaseRecipient, rid string) (PhishingTemplateContext, error) {
|
||||||
|
f, err := mail.ParseAddress(ctx.getFromAddress())
|
||||||
|
if err != nil {
|
||||||
|
return PhishingTemplateContext{}, err
|
||||||
|
}
|
||||||
|
fn := f.Name
|
||||||
|
if fn == "" {
|
||||||
|
fn = f.Address
|
||||||
|
}
|
||||||
|
baseURL, err := ExecuteTemplate(ctx.getBaseURL(), r)
|
||||||
|
if err != nil {
|
||||||
|
return PhishingTemplateContext{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
phishURL, _ := url.Parse(baseURL)
|
||||||
|
q := phishURL.Query()
|
||||||
|
q.Set(RecipientParameter, rid)
|
||||||
|
phishURL.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
trackingURL, _ := url.Parse(baseURL)
|
||||||
|
trackingURL.Path = path.Join(trackingURL.Path, "/track")
|
||||||
|
trackingURL.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
return PhishingTemplateContext{
|
||||||
|
BaseRecipient: r,
|
||||||
|
URL: phishURL.String(),
|
||||||
|
TrackingURL: trackingURL.String(),
|
||||||
|
Tracker: "<img alt='' style='display: none' src='" + trackingURL.String() + "'/>",
|
||||||
|
From: fn,
|
||||||
|
RId: rid,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteTemplate creates a templated string based on the provided
|
||||||
|
// template body and data.
|
||||||
|
func ExecuteTemplate(text string, data interface{}) (string, error) {
|
||||||
|
buff := bytes.Buffer{}
|
||||||
|
tmpl, err := template.New("template").Parse(text)
|
||||||
|
if err != nil {
|
||||||
|
return buff.String(), err
|
||||||
|
}
|
||||||
|
err = tmpl.Execute(&buff, data)
|
||||||
|
return buff.String(), err
|
||||||
|
}
|
10
util/util.go
10
util/util.go
|
@ -100,10 +100,12 @@ func ParseCSV(r *http.Request) ([]models.Target, error) {
|
||||||
ps = record[pi]
|
ps = record[pi]
|
||||||
}
|
}
|
||||||
t := models.Target{
|
t := models.Target{
|
||||||
FirstName: fn,
|
BaseRecipient: models.BaseRecipient{
|
||||||
LastName: ln,
|
FirstName: fn,
|
||||||
Email: ea,
|
LastName: ln,
|
||||||
Position: ps,
|
Email: ea,
|
||||||
|
Position: ps,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
ts = append(ts, t)
|
ts = append(ts, t)
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,9 +52,11 @@ func buildCSVRequest(csvPayload string) (*http.Request, error) {
|
||||||
|
|
||||||
func (s *UtilSuite) TestParseCSVEmail() {
|
func (s *UtilSuite) TestParseCSVEmail() {
|
||||||
expected := models.Target{
|
expected := models.Target{
|
||||||
FirstName: "John",
|
BaseRecipient: models.BaseRecipient{
|
||||||
LastName: "Doe",
|
FirstName: "John",
|
||||||
Email: "johndoe@example.com",
|
LastName: "Doe",
|
||||||
|
Email: "johndoe@example.com",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
csvPayload := fmt.Sprintf("%s,%s,<%s>", expected.FirstName, expected.LastName, expected.Email)
|
csvPayload := fmt.Sprintf("%s,%s,<%s>", expected.FirstName, expected.LastName, expected.Email)
|
||||||
|
|
|
@ -86,7 +86,7 @@ func (w *Worker) LaunchCampaign(c models.Campaign) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTestEmail sends a test email
|
// SendTestEmail sends a test email
|
||||||
func (w *Worker) SendTestEmail(s *models.SendTestEmailRequest) error {
|
func (w *Worker) SendTestEmail(s *models.EmailRequest) error {
|
||||||
go func() {
|
go func() {
|
||||||
mailer.Mailer.Queue <- []mailer.Mail{s}
|
mailer.Mailer.Queue <- []mailer.Mail{s}
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -35,8 +35,8 @@ func (s *WorkerSuite) SetupTest() {
|
||||||
// Add a group
|
// Add a group
|
||||||
group := models.Group{Name: "Test Group"}
|
group := models.Group{Name: "Test Group"}
|
||||||
group.Targets = []models.Target{
|
group.Targets = []models.Target{
|
||||||
models.Target{Email: "test1@example.com", FirstName: "First", LastName: "Example"},
|
models.Target{BaseRecipient: models.BaseRecipient{Email: "test1@example.com", FirstName: "First", LastName: "Example"}},
|
||||||
models.Target{Email: "test2@example.com", FirstName: "Second", LastName: "Example"},
|
models.Target{BaseRecipient: models.BaseRecipient{Email: "test2@example.com", FirstName: "Second", LastName: "Example"}},
|
||||||
}
|
}
|
||||||
group.UserId = 1
|
group.UserId = 1
|
||||||
models.PostGroup(&group)
|
models.PostGroup(&group)
|
||||||
|
|
Loading…
Reference in New Issue