mirror of https://github.com/gophish/gophish
986 custom envelope sender remerge (#2334)
* Adds the ability to specify an envelope sender in templates (#986) Authored-by: ChessSpider <ChessSpider@users.noreply.github.com> Authored-by: Olivier MEDOC <o_medoc@yahoo.fr> Authored-by: ptitdoc <ptitdoc@free.fr>pull/2318/head^2
parent
e0acb99734
commit
bb516ef7ab
|
@ -3,6 +3,7 @@ package api
|
|||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
|
||||
ctx "github.com/gophish/gophish/context"
|
||||
log "github.com/gophish/gophish/logger"
|
||||
|
@ -93,7 +94,19 @@ func (as *Server) SendTestEmail(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
s.SMTP = smtp
|
||||
}
|
||||
|
||||
_, err = mail.ParseAddress(s.Template.EnvelopeSender)
|
||||
if err != nil {
|
||||
_, err = mail.ParseAddress(s.SMTP.FromAddress)
|
||||
if err != nil {
|
||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||
return
|
||||
} else {
|
||||
s.FromAddress = s.SMTP.FromAddress
|
||||
}
|
||||
} else {
|
||||
s.FromAddress = s.Template.EnvelopeSender
|
||||
}
|
||||
|
||||
// Validate the given request
|
||||
if err = s.Validate(); err != nil {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
ALTER TABLE templates ADD COLUMN envelope_sender varchar(255);
|
||||
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
ALTER TABLE templates ADD COLUMN envelope_sender varchar(255);
|
||||
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
|
@ -55,6 +55,7 @@ type Mail interface {
|
|||
Success() error
|
||||
Generate(msg *gomail.Message) error
|
||||
GetDialer() (Dialer, error)
|
||||
GetSmtpFrom() (string, error)
|
||||
}
|
||||
|
||||
// MailWorker is the worker that receives slices of emails
|
||||
|
@ -160,7 +161,14 @@ func sendMail(ctx context.Context, dialer Dialer, ms []Mail) {
|
|||
m.Error(err)
|
||||
continue
|
||||
}
|
||||
err = gomail.Send(sender, message)
|
||||
|
||||
smtp_from, err := m.GetSmtpFrom()
|
||||
if err != nil {
|
||||
m.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = gomail.SendCustomFrom(sender, smtp_from, message)
|
||||
if err != nil {
|
||||
if te, ok := err.(*textproto.Error); ok {
|
||||
switch {
|
||||
|
@ -215,6 +223,8 @@ func sendMail(ctx context.Context, dialer Dialer, ms []Mail) {
|
|||
}
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"smtp_from": smtp_from,
|
||||
"envelope_from": message.GetHeader("From")[0],
|
||||
"email": message.GetHeader("To")[0],
|
||||
}).Info("Email sent")
|
||||
m.Success()
|
||||
|
|
|
@ -162,6 +162,10 @@ func (mm *mockMessage) Generate(message *gomail.Message) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (mm *mockMessage) GetSmtpFrom() (string, error) {
|
||||
return mm.from, nil
|
||||
}
|
||||
|
||||
func (mm *mockMessage) Success() error {
|
||||
mm.finished = true
|
||||
return nil
|
||||
|
|
|
@ -77,6 +77,10 @@ func (s *EmailRequest) Success() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *EmailRequest) GetSmtpFrom() (string, error) {
|
||||
return s.SMTP.FromAddress, nil
|
||||
}
|
||||
|
||||
// PostEmailRequest stores a SendTestEmailRequest in the database.
|
||||
func PostEmailRequest(s *EmailRequest) error {
|
||||
// Generate an ID to be used in the underlying Result object
|
||||
|
@ -99,7 +103,7 @@ func GetEmailRequestByResultId(id string) (EmailRequest, error) {
|
|||
// Generate fills in the details of a gomail.Message with the contents
|
||||
// from the SendTestEmailRequest.
|
||||
func (s *EmailRequest) Generate(msg *gomail.Message) error {
|
||||
f, err := mail.ParseAddress(s.FromAddress)
|
||||
f, err := mail.ParseAddress(s.getFromAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -106,6 +106,37 @@ func (s *ModelsSuite) TestEmailRequestGenerate(ch *check.C) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *ModelsSuite) TestGetSmtpFrom(ch *check.C) {
|
||||
smtp := SMTP{
|
||||
FromAddress: "from@example.com",
|
||||
}
|
||||
template := Template{
|
||||
Name: "Test Template",
|
||||
Subject: "{{.FirstName}} - Subject",
|
||||
Text: "{{.Email}} - Text",
|
||||
HTML: "{{.Email}} - HTML",
|
||||
}
|
||||
req := &EmailRequest{
|
||||
SMTP: smtp,
|
||||
Template: template,
|
||||
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()
|
||||
err := req.Generate(msg)
|
||||
smtp_from, err := req.GetSmtpFrom()
|
||||
|
||||
ch.Assert(err, check.Equals, nil)
|
||||
ch.Assert(smtp_from, check.Equals, "from@example.com")
|
||||
}
|
||||
|
||||
func (s *ModelsSuite) TestEmailRequestURLTemplating(ch *check.C) {
|
||||
smtp := SMTP{
|
||||
FromAddress: "from@example.com",
|
||||
|
|
|
@ -149,6 +149,16 @@ func (m *MailLog) CacheCampaign(campaign *Campaign) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *MailLog) GetSmtpFrom() (string, error) {
|
||||
c, err := GetCampaign(m.CampaignId, m.UserId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
f, err := mail.ParseAddress(c.SMTP.FromAddress)
|
||||
return f.Address, err
|
||||
}
|
||||
|
||||
// Generate fills in the details of a gomail.Message instance with
|
||||
// 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
|
||||
|
@ -167,10 +177,13 @@ func (m *MailLog) Generate(msg *gomail.Message) error {
|
|||
c = &campaign
|
||||
}
|
||||
|
||||
f, err := mail.ParseAddress(c.SMTP.FromAddress)
|
||||
f, err := mail.ParseAddress(c.Template.EnvelopeSender)
|
||||
if err != nil {
|
||||
f, err = mail.ParseAddress(c.SMTP.FromAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
msg.SetAddressHeader("From", f.Address, f.Name)
|
||||
|
||||
ptx, err := NewPhishingTemplateContext(c, r.BaseRecipient, r.RId)
|
||||
|
|
|
@ -213,15 +213,51 @@ func (s *ModelsSuite) TestGenerateMailLog(ch *check.C) {
|
|||
ch.Assert(m.Processing, check.Equals, false)
|
||||
}
|
||||
|
||||
func (s *ModelsSuite) TestMailLogGetSmtpFrom(ch *check.C) {
|
||||
template := Template{
|
||||
Name: "OverrideSmtpFrom",
|
||||
UserId: 1,
|
||||
Text: "dummytext",
|
||||
HTML: "Dummyhtml",
|
||||
Subject: "Dummysubject",
|
||||
EnvelopeSender: "spoofing@example.com",
|
||||
}
|
||||
ch.Assert(PostTemplate(&template), check.Equals, nil)
|
||||
campaign := s.createCampaignDependencies(ch)
|
||||
campaign.Template = template
|
||||
|
||||
ch.Assert(PostCampaign(&campaign, campaign.UserId), check.Equals, nil)
|
||||
result := campaign.Results[0]
|
||||
|
||||
m := &MailLog{}
|
||||
err := db.Where("r_id=? AND campaign_id=?", result.RId, campaign.Id).
|
||||
Find(m).Error
|
||||
ch.Assert(err, check.Equals, nil)
|
||||
|
||||
msg := gomail.NewMessage()
|
||||
err = m.Generate(msg)
|
||||
ch.Assert(err, check.Equals, nil)
|
||||
|
||||
msgBuff := &bytes.Buffer{}
|
||||
_, err = msg.WriteTo(msgBuff)
|
||||
ch.Assert(err, check.Equals, nil)
|
||||
|
||||
got, err := email.NewEmailFromReader(msgBuff)
|
||||
ch.Assert(err, check.Equals, nil)
|
||||
ch.Assert(got.From, check.Equals, "spoofing@example.com")
|
||||
}
|
||||
|
||||
func (s *ModelsSuite) TestMailLogGenerate(ch *check.C) {
|
||||
campaign := s.createCampaign(ch)
|
||||
result := campaign.Results[0]
|
||||
expected := &email.Email{
|
||||
From: "test@test.com", // Default smtp.FromAddress
|
||||
Subject: fmt.Sprintf("%s - Subject", result.RId),
|
||||
Text: []byte(fmt.Sprintf("%s - Text", result.RId)),
|
||||
HTML: []byte(fmt.Sprintf("%s - HTML", result.RId)),
|
||||
}
|
||||
got := s.emailFromFirstMailLog(campaign, ch)
|
||||
ch.Assert(got.From, check.Equals, expected.From)
|
||||
ch.Assert(got.Subject, check.Equals, expected.Subject)
|
||||
ch.Assert(string(got.Text), check.Equals, string(expected.Text))
|
||||
ch.Assert(string(got.HTML), check.Equals, string(expected.HTML))
|
||||
|
|
|
@ -2,6 +2,7 @@ package models
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"net/mail"
|
||||
"time"
|
||||
|
||||
log "github.com/gophish/gophish/logger"
|
||||
|
@ -13,6 +14,7 @@ type Template struct {
|
|||
Id int64 `json:"id" gorm:"column:id; primary_key:yes"`
|
||||
UserId int64 `json:"-" gorm:"column:user_id"`
|
||||
Name string `json:"name"`
|
||||
EnvelopeSender string `json:"envelope_sender"`
|
||||
Subject string `json:"subject"`
|
||||
Text string `json:"text"`
|
||||
HTML string `json:"html" gorm:"column:html"`
|
||||
|
@ -33,6 +35,11 @@ func (t *Template) Validate() error {
|
|||
return ErrTemplateNameNotSpecified
|
||||
case t.Text == "" && t.HTML == "":
|
||||
return ErrTemplateMissingParameter
|
||||
case t.EnvelopeSender != "":
|
||||
_, err := mail.ParseAddress(t.EnvelopeSender)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := ValidateTemplate(t.HTML); err != nil {
|
||||
return err
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -20,6 +20,7 @@ function save(idx) {
|
|||
}
|
||||
template.name = $("#name").val()
|
||||
template.subject = $("#subject").val()
|
||||
template.envelope_sender = $("#envelope-sender").val()
|
||||
template.html = CKEDITOR.instances["html_editor"].getData();
|
||||
// Fix the URL Scheme added by CKEditor (until we can remove it from the plugin)
|
||||
template.html = template.html.replace(/https?:\/\/{{\.URL}}/gi, "{{.URL}}")
|
||||
|
@ -189,6 +190,7 @@ function edit(idx) {
|
|||
template = templates[idx]
|
||||
$("#name").val(template.name)
|
||||
$("#subject").val(template.subject)
|
||||
$("#envelope-sender").val(template.envelope_sender)
|
||||
$("#html_editor").val(template.html)
|
||||
$("#text_editor").val(template.text)
|
||||
attachmentRows = []
|
||||
|
@ -247,6 +249,7 @@ function copy(idx) {
|
|||
template = templates[idx]
|
||||
$("#name").val("Copy of " + template.name)
|
||||
$("#subject").val(template.subject)
|
||||
$("#envelope-sender").val(template.envelope_sender)
|
||||
$("#html_editor").val(template.html)
|
||||
$("#text_editor").val(template.text)
|
||||
$.each(template.attachments, function (i, file) {
|
||||
|
|
|
@ -50,7 +50,8 @@
|
|||
<input type="text" class="form-control" placeholder="Profile name" id="name" autofocus />
|
||||
<label class="control-label" for="interface_type">Interface Type:</label>
|
||||
<input type="text" class="form-control" value="SMTP" id="interface_type" disabled />
|
||||
<label class="control-label" for="from">From:</label>
|
||||
<label class="control-label" for="from">SMTP From: <i class="fa fa-question-circle"
|
||||
data-toggle="tooltip" data-placement="right" title="Set this to an email address from your sending domain to bypass SPF-checks. You can set the Envelope Sender in Email Templates. The Envelope Sender is shown to the user."></i></label>
|
||||
<input type="text" class="form-control" placeholder="First Last <test@example.com>" id="from"
|
||||
required />
|
||||
<label class="control-label" for="host">Host:</label>
|
||||
|
|
|
@ -55,6 +55,10 @@
|
|||
class="fa fa-envelope"></i>
|
||||
Import Email</button>
|
||||
</div>
|
||||
<label class="control-label" for="envelope-sender">Envelope Sender: <i class="fa fa-question-circle" data-toggle="tooltip" data-placement="right" title="This sender is shown to the user by most email clients. Defaults to the SMTP From as defined in the Sending Profile."></i></label>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" placeholder="First Last <test@example.com>" id="envelope-sender" />
|
||||
</div>
|
||||
<label class="control-label" for="subject">Subject:</label>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" placeholder="Email Subject" id="subject" />
|
||||
|
|
Loading…
Reference in New Issue