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>
This commit is contained in:
ptitdoc 2022-03-25 16:24:49 +01:00 committed by GitHub
parent e0acb99734
commit bb516ef7ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 151 additions and 9 deletions

View file

@ -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
}
s.FromAddress = s.SMTP.FromAddress
_, 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 {

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

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

View file

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

View file

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

View file

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

View file

@ -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,9 +177,12 @@ 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 {
return err
f, err = mail.ParseAddress(c.SMTP.FromAddress)
if err != nil {
return err
}
}
msg.SetAddressHeader("From", f.Address, f.Name)

View file

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

View file

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

View file

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

View 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>
@ -140,4 +141,4 @@
</div>
{{end}} {{define "scripts"}}
<script src="/js/dist/app/sending_profiles.min.js"></script>
{{end}}
{{end}}

View file

@ -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" />
@ -137,4 +141,4 @@
<script src="/js/src/vendor/ckeditor/adapters/jquery.js"></script>
<script src="/js/dist/app/autocomplete.min.js"></script>
<script src="/js/dist/app/templates.min.js"></script>
{{end}}
{{end}}