Ability to specify different Envelope-Sender and SMTP-From address (#986)

Adds the ability to specify an envelope sender in templates.
986-custom-envelope-sender
ChessSpider 2018-03-27 04:28:59 +02:00 committed by Jordan Wright
parent 535fbf487b
commit 94794939f6
13 changed files with 139 additions and 14 deletions

View File

@ -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

View File

@ -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

View File

@ -51,6 +51,7 @@ type Mail interface {
Success() error Success() error
Generate(msg *gomail.Message) error Generate(msg *gomail.Message) error
GetDialer() (Dialer, error) GetDialer() (Dialer, error)
GetSmtpFrom() (string, error)
} }
// Mailer is a global instance of the mailer that can // Mailer is a global instance of the mailer that can
@ -161,7 +162,13 @@ func sendMail(ctx context.Context, dialer Dialer, ms []Mail) {
continue 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 err != nil {
if te, ok := err.(*textproto.Error); ok { if te, ok := err.(*textproto.Error); ok {
switch { switch {

View File

@ -170,6 +170,10 @@ func (mm *mockMessage) Generate(message *gomail.Message) error {
return nil return nil
} }
func (mm *mockMessage) GetSmtpFrom() (string, error) {
return mm.from, nil
}
func (mm *mockMessage) Success() error { func (mm *mockMessage) Success() error {
mm.finished = true mm.finished = true
return nil return nil

View File

@ -56,9 +56,14 @@ func (s *SendTestEmailRequest) Success() error {
return nil return nil
} }
func (s *SendTestEmailRequest) GetSmtpFrom() (string, error) {
return s.SMTP.FromAddress, nil
}
// 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 *SendTestEmailRequest) Generate(msg *gomail.Message) error {
// Naively use the SMTP-from as the Envelope-from for this test message
f, err := mail.ParseAddress(s.SMTP.FromAddress) f, err := mail.ParseAddress(s.SMTP.FromAddress)
if err != nil { if err != nil {
return err return err

View File

@ -94,6 +94,35 @@ func (s *ModelsSuite) TestEmailRequestGenerate(ch *check.C) {
ch.Assert(string(got.HTML), check.Equals, string(expected.HTML)) ch.Assert(string(got.HTML), check.Equals, string(expected.HTML))
} }
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",
}
target := Target{
FirstName: "First",
LastName: "Last",
Email: "firstlast@example.com",
}
req := &SendTestEmailRequest{
SMTP: smtp,
Template: template,
Target: target,
}
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) { func (s *ModelsSuite) TestEmailRequestURLTemplating(ch *check.C) {
smtp := SMTP{ smtp := SMTP{
FromAddress: "from@example.com", FromAddress: "from@example.com",
@ -113,7 +142,7 @@ func (s *ModelsSuite) TestEmailRequestURLTemplating(ch *check.C) {
SMTP: smtp, SMTP: smtp,
Template: template, Template: template,
Target: target, Target: target,
URL: "http://127.0.0.1/{{.Email}}", URL: "http://127.0.0.1/{{.Email}}",
} }
msg := gomail.NewMessage() msg := gomail.NewMessage()

View File

@ -193,6 +193,16 @@ func buildTemplate(text string, data interface{}) (string, error) {
return buff.String(), err return buff.String(), err
} }
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 // 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
@ -206,9 +216,13 @@ 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.Template.EnvelopeSender)
if err != nil { if err != nil {
return err f, err = mail.ParseAddress(c.SMTP.FromAddress)
if err != nil {
return err
}
} }
fn := f.Name fn := f.Name
if fn == "" { if fn == "" {

View File

@ -188,6 +188,40 @@ func (s *ModelsSuite) TestGenerateMailLog(ch *check.C) {
ch.Assert(m.Processing, check.Equals, false) 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) { func (s *ModelsSuite) TestMailLogGenerate(ch *check.C) {
campaign := s.createCampaign(ch) campaign := s.createCampaign(ch)
result := campaign.Results[0] result := campaign.Results[0]
@ -201,6 +235,7 @@ func (s *ModelsSuite) TestMailLogGenerate(ch *check.C) {
ch.Assert(err, check.Equals, nil) ch.Assert(err, check.Equals, nil)
expected := &email.Email{ expected := &email.Email{
From: "test@test.com", // Default smtp.FromAddress
Subject: fmt.Sprintf("%s - Subject", result.RId), Subject: fmt.Sprintf("%s - Subject", result.RId),
Text: []byte(fmt.Sprintf("%s - Text", result.RId)), Text: []byte(fmt.Sprintf("%s - Text", result.RId)),
HTML: []byte(fmt.Sprintf("%s - HTML", result.RId)), HTML: []byte(fmt.Sprintf("%s - HTML", result.RId)),
@ -212,6 +247,7 @@ func (s *ModelsSuite) TestMailLogGenerate(ch *check.C) {
got, err := email.NewEmailFromReader(msgBuff) got, err := email.NewEmailFromReader(msgBuff)
ch.Assert(err, check.Equals, nil) ch.Assert(err, check.Equals, nil)
ch.Assert(got.From, check.Equals, expected.From)
ch.Assert(got.Subject, check.Equals, expected.Subject) ch.Assert(got.Subject, check.Equals, expected.Subject)
ch.Assert(string(got.Text), check.Equals, string(expected.Text)) ch.Assert(string(got.Text), check.Equals, string(expected.Text))
ch.Assert(string(got.HTML), check.Equals, string(expected.HTML)) ch.Assert(string(got.HTML), check.Equals, string(expected.HTML))

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"errors" "errors"
"html/template" "html/template"
"net/mail"
"time" "time"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
@ -11,14 +12,15 @@ import (
// Template models hold the attributes for an email template to be sent to targets // Template models hold the attributes for an email template to be sent to targets
type Template struct { type Template struct {
Id int64 `json:"id" gorm:"column:id; primary_key:yes"` Id int64 `json:"id" gorm:"column:id; primary_key:yes"`
UserId int64 `json:"-" gorm:"column:user_id"` UserId int64 `json:"-" gorm:"column:user_id"`
Name string `json:"name"` Name string `json:"name"`
Subject string `json:"subject"` EnvelopeSender string `json:"envelope_sender"`
Text string `json:"text"` Subject string `json:"subject"`
HTML string `json:"html" gorm:"column:html"` Text string `json:"text"`
ModifiedDate time.Time `json:"modified_date"` HTML string `json:"html" gorm:"column:html"`
Attachments []Attachment `json:"attachments"` ModifiedDate time.Time `json:"modified_date"`
Attachments []Attachment `json:"attachments"`
} }
// ErrTemplateNameNotSpecified is thrown when a template name is not specified // ErrTemplateNameNotSpecified is thrown when a template name is not specified
@ -34,6 +36,11 @@ func (t *Template) Validate() error {
return ErrTemplateNameNotSpecified return ErrTemplateNameNotSpecified
case t.Text == "" && t.HTML == "": case t.Text == "" && t.HTML == "":
return ErrTemplateMissingParameter return ErrTemplateMissingParameter
case t.EnvelopeSender != "":
_, err := mail.ParseAddress(t.EnvelopeSender)
if err != nil {
return err
}
} }
var buff bytes.Buffer var buff bytes.Buffer
// Test that the variables used in the template // Test that the variables used in the template

View File

@ -20,6 +20,7 @@ function save(idx) {
} }
template.name = $("#name").val() template.name = $("#name").val()
template.subject = $("#subject").val() template.subject = $("#subject").val()
template.envelope_sender = $("#envelope-sender").val()
template.html = CKEDITOR.instances["html_editor"].getData(); template.html = CKEDITOR.instances["html_editor"].getData();
// Fix the URL Scheme added by CKEditor (until we can remove it from the plugin) // Fix the URL Scheme added by CKEditor (until we can remove it from the plugin)
template.html = template.html.replace(/https?:\/\/{{\.URL}}/gi, "{{.URL}}") template.html = template.html.replace(/https?:\/\/{{\.URL}}/gi, "{{.URL}}")
@ -152,6 +153,7 @@ function edit(idx) {
template = templates[idx] template = templates[idx]
$("#name").val(template.name) $("#name").val(template.name)
$("#subject").val(template.subject) $("#subject").val(template.subject)
$("#envelope-sender").val(template.envelope_sender)
$("#html_editor").val(template.html) $("#html_editor").val(template.html)
$("#text_editor").val(template.text) $("#text_editor").val(template.text)
$.each(template.attachments, function (i, file) { $.each(template.attachments, function (i, file) {
@ -208,6 +210,7 @@ function copy(idx) {
template = templates[idx] template = templates[idx]
$("#name").val("Copy of " + template.name) $("#name").val("Copy of " + template.name)
$("#subject").val(template.subject) $("#subject").val(template.subject)
$("#envelope-sender").val(template.envelope_sender)
$("#html_editor").val(template.html) $("#html_editor").val(template.html)
$("#text_editor").val(template.text) $("#text_editor").val(template.text)
$.each(template.attachments, function (i, file) { $.each(template.attachments, function (i, file) {

View File

@ -74,7 +74,7 @@
<input type="text" class="form-control" placeholder="Profile name" id="name" autofocus/> <input type="text" class="form-control" placeholder="Profile name" id="name" autofocus/>
<label class="control-label" for="interface_type">Interface Type:</label> <label class="control-label" for="interface_type">Interface Type:</label>
<input type="text" class="form-control" value="SMTP" id="interface_type" disabled/> <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 Template. The Envelope-sender is shown to the victim."></i></label>
<input type="text" class="form-control" placeholder="First Last <test@example.com>" id="from" required/> <input type="text" class="form-control" placeholder="First Last <test@example.com>" id="from" required/>
<label class="control-label" for="host">Host:</label> <label class="control-label" for="host">Host:</label>
<input type="text" class="form-control" placeholder="smtp.example.com:25" id="host" required/> <input type="text" class="form-control" placeholder="smtp.example.com:25" id="host" required/>

View File

@ -77,6 +77,10 @@
<div class="form-group"> <div class="form-group">
<button class="btn btn-danger" data-toggle="modal" data-target="#importEmailModal"><i class="fa fa-envelope"></i> Import Email</button> <button class="btn btn-danger" data-toggle="modal" data-target="#importEmailModal"><i class="fa fa-envelope"></i> Import Email</button>
</div> </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 (all?) mailclients. Defaults to the SMTP-From as defined by Sender profile."></i></label>
<div class="form-group">
<input type="text" class="form-control" placeholder="John Doe <john.doe@victim.com>" id="envelope-sender" />
</div>
<label class="control-label" for="subject">Subject:</label> <label class="control-label" for="subject">Subject:</label>
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" placeholder="Email Subject" id="subject" /> <input type="text" class="form-control" placeholder="Email Subject" id="subject" />