mirror of https://github.com/gophish/gophish
parent
dbadac3eca
commit
66c4be3d4f
|
@ -0,0 +1,12 @@
|
|||
|
||||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
CREATE TABLE IF NOT EXISTS headers(
|
||||
id integer primary key auto_increment,
|
||||
`key` varchar(255),
|
||||
`value` varchar(255),
|
||||
`smtp_id` bigint
|
||||
);
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
DROP TABLE headers;
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
CREATE TABLE IF NOT EXISTS headers(
|
||||
id integer primary key autoincrement,
|
||||
key varchar(255),
|
||||
value varchar(255),
|
||||
"smtp_id" bigint
|
||||
);
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
DROP TABLE headers;
|
|
@ -189,6 +189,11 @@ func (c *Campaign) getDetails() error {
|
|||
c.SMTP = SMTP{Name: "[Deleted]"}
|
||||
Logger.Printf("%s: sending profile not found for campaign\n", err)
|
||||
}
|
||||
err = db.Where("smtp_id=?", c.SMTP.Id).Find(&c.SMTP.Headers).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
Logger.Println(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -246,6 +246,24 @@ func (s *ModelsSuite) TestPostSMTPNoFrom(c *check.C) {
|
|||
c.Assert(err, check.Equals, ErrFromAddressNotSpecified)
|
||||
}
|
||||
|
||||
func (s *ModelsSuite) TestPostSMTPValidHeader(c *check.C) {
|
||||
smtp := SMTP{
|
||||
Name: "Test SMTP",
|
||||
Host: "1.1.1.1:25",
|
||||
FromAddress: "Foo Bar <foo@example.com>",
|
||||
UserId: 1,
|
||||
Headers: []Header{
|
||||
Header{Key: "Reply-To", Value: "test@example.com"},
|
||||
Header{Key: "X-Mailer", Value: "gophish"},
|
||||
},
|
||||
}
|
||||
err = PostSMTP(&smtp)
|
||||
c.Assert(err, check.Equals, nil)
|
||||
ss, err := GetSMTPs(1)
|
||||
c.Assert(err, check.Equals, nil)
|
||||
c.Assert(len(ss), check.Equals, 1)
|
||||
}
|
||||
|
||||
func (s *ModelsSuite) TestPostPage(c *check.C) {
|
||||
html := `<html>
|
||||
<head></head>
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
// SMTP contains the attributes needed to handle the sending of campaign emails
|
||||
|
@ -19,9 +21,19 @@ type SMTP struct {
|
|||
Password string `json:"password,omitempty"`
|
||||
FromAddress string `json:"from_address"`
|
||||
IgnoreCertErrors bool `json:"ignore_cert_errors"`
|
||||
Headers []Header `json:"headers"`
|
||||
ModifiedDate time.Time `json:"modified_date"`
|
||||
}
|
||||
|
||||
// Header contains the fields and methods for a sending profile to have
|
||||
// custom headers
|
||||
type Header struct {
|
||||
Id int64 `json:"-"`
|
||||
SMTPId int64 `json:"-"`
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// ErrFromAddressNotSpecified is thrown when there is no "From" address
|
||||
// specified in the SMTP configuration
|
||||
var ErrFromAddressNotSpecified = errors.New("No From Address specified")
|
||||
|
@ -70,9 +82,17 @@ func GetSMTPs(uid int64) ([]SMTP, error) {
|
|||
err := db.Where("user_id=?", uid).Find(&ss).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
return ss, err
|
||||
}
|
||||
for i, _ := range ss {
|
||||
err = db.Where("smtp_id=?", ss[i].Id).Find(&ss[i].Headers).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
Logger.Println(err)
|
||||
return ss, err
|
||||
}
|
||||
}
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
// GetSMTP returns the SMTP, if it exists, specified by the given id and user_id.
|
||||
func GetSMTP(id int64, uid int64) (SMTP, error) {
|
||||
|
@ -81,6 +101,11 @@ func GetSMTP(id int64, uid int64) (SMTP, error) {
|
|||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
err = db.Where("smtp_id=?", s.Id).Find(&s.Headers).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
Logger.Println(err)
|
||||
return s, err
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
|
||||
|
@ -90,6 +115,11 @@ func GetSMTPByName(n string, uid int64) (SMTP, error) {
|
|||
err := db.Where("user_id=? and name=?", uid, n).Find(&s).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
return s, err
|
||||
}
|
||||
err = db.Where("smtp_id=?", s.Id).Find(&s.Headers).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
Logger.Println(err)
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
|
@ -106,6 +136,15 @@ func PostSMTP(s *SMTP) error {
|
|||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
// Save custom headers
|
||||
for i, _ := range s.Headers {
|
||||
s.Headers[i].SMTPId = s.Id
|
||||
err := db.Save(&s.Headers[i]).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -121,12 +160,32 @@ func PutSMTP(s *SMTP) error {
|
|||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
// Delete all custom headers, and replace with new ones
|
||||
err = db.Where("smtp_id=?", s.Id).Delete(&Header{}).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
Logger.Println(err)
|
||||
return err
|
||||
}
|
||||
for i, _ := range s.Headers {
|
||||
s.Headers[i].SMTPId = s.Id
|
||||
err := db.Save(&s.Headers[i]).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteSMTP deletes an existing SMTP in the database.
|
||||
// An error is returned if a SMTP with the given user id and SMTP id is not found.
|
||||
func DeleteSMTP(id int64, uid int64) error {
|
||||
// Delete all custom headers
|
||||
err = db.Where("smtp_id=?", id).Delete(&Header{}).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
return err
|
||||
}
|
||||
err = db.Where("user_id=?", uid).Delete(SMTP{Id: id}).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
|
|
|
@ -64,6 +64,7 @@ func (t *Template) Validate() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err = template.New("text_template").Parse(t.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -81,6 +82,7 @@ func GetTemplates(uid int64) ([]Template, error) {
|
|||
return ts, err
|
||||
}
|
||||
for i, _ := range ts {
|
||||
// Get Attachments
|
||||
err = db.Where("template_id=?", ts[i].Id).Find(&ts[i].Attachments).Error
|
||||
if err == nil && len(ts[i].Attachments) == 0 {
|
||||
ts[i].Attachments = make([]Attachment, 0)
|
||||
|
@ -101,6 +103,8 @@ func GetTemplate(id int64, uid int64) (Template, error) {
|
|||
Logger.Println(err)
|
||||
return t, err
|
||||
}
|
||||
|
||||
// Get Attachments
|
||||
err = db.Where("template_id=?", t.Id).Find(&t.Attachments).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
Logger.Println(err)
|
||||
|
@ -120,6 +124,8 @@ func GetTemplateByName(n string, uid int64) (Template, error) {
|
|||
Logger.Println(err)
|
||||
return t, err
|
||||
}
|
||||
|
||||
// Get Attachments
|
||||
err = db.Where("template_id=?", t.Id).Find(&t.Attachments).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
Logger.Println(err)
|
||||
|
@ -142,6 +148,8 @@ func PostTemplate(t *Template) error {
|
|||
Logger.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Save every attachment
|
||||
for i, _ := range t.Attachments {
|
||||
Logger.Println(t.Attachments[i].Name)
|
||||
t.Attachments[i].TemplateId = t.Id
|
||||
|
@ -177,6 +185,8 @@ func PutTemplate(t *Template) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Save final template
|
||||
err = db.Where("id=?", t.Id).Save(t).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
|
@ -188,11 +198,14 @@ func PutTemplate(t *Template) error {
|
|||
// DeleteTemplate deletes an existing template in the database.
|
||||
// An error is returned if a template with the given user id and template id is not found.
|
||||
func DeleteTemplate(id int64, uid int64) error {
|
||||
// Delete attachments
|
||||
err := db.Where("template_id=?", id).Delete(&Attachment{}).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Finally, delete the template itself
|
||||
err = db.Where("user_id=?", uid).Delete(Template{Id: id}).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,6 +2,13 @@ var profiles = []
|
|||
|
||||
// Attempts to send a test email by POSTing to /campaigns/
|
||||
function sendTestEmail() {
|
||||
var headers = [];
|
||||
$.each($("#headersTable").DataTable().rows().data(), function(i, header) {
|
||||
headers.push({
|
||||
key: unescapeHtml(header[0]),
|
||||
value: unescapeHtml(header[1]),
|
||||
})
|
||||
})
|
||||
var test_email_request = {
|
||||
template: {},
|
||||
first_name: $("input[name=to_first_name]").val(),
|
||||
|
@ -14,7 +21,8 @@ function sendTestEmail() {
|
|||
host: $("#host").val(),
|
||||
username: $("#username").val(),
|
||||
password: $("#password").val(),
|
||||
ignore_cert_errors: $("#ignore_cert_errors").prop("checked")
|
||||
ignore_cert_errors: $("#ignore_cert_errors").prop("checked"),
|
||||
headers: headers,
|
||||
}
|
||||
}
|
||||
btnHtml = $("#sendTestModalSubmit").html()
|
||||
|
@ -35,7 +43,15 @@ function sendTestEmail() {
|
|||
|
||||
// Save attempts to POST to /smtp/
|
||||
function save(idx) {
|
||||
var profile = {}
|
||||
var profile = {
|
||||
headers: []
|
||||
}
|
||||
$.each($("#headersTable").DataTable().rows().data(), function(i, header) {
|
||||
profile.headers.push({
|
||||
key: unescapeHtml(header[0]),
|
||||
value: unescapeHtml(header[1]),
|
||||
})
|
||||
})
|
||||
profile.name = $("#name").val()
|
||||
profile.interface_type = $("#interface_type").val()
|
||||
profile.from_address = $("#from").val()
|
||||
|
@ -77,6 +93,7 @@ function dismiss() {
|
|||
$("#username").val("")
|
||||
$("#password").val("")
|
||||
$("#ignore_cert_errors").prop("checked", true)
|
||||
$("#headersTable").dataTable().DataTable().clear().draw()
|
||||
$("#modal").modal('hide')
|
||||
}
|
||||
|
||||
|
@ -91,6 +108,14 @@ function deleteProfile(idx) {
|
|||
}
|
||||
|
||||
function edit(idx) {
|
||||
headers = $("#headersTable").dataTable({
|
||||
destroy: true, // Destroy any other instantiated table - http://datatables.net/manual/tech-notes/3#destroy
|
||||
columnDefs: [{
|
||||
orderable: false,
|
||||
targets: "no-sort"
|
||||
}]
|
||||
})
|
||||
|
||||
$("#modalSubmit").unbind('click').click(function() {
|
||||
save(idx)
|
||||
})
|
||||
|
@ -104,6 +129,9 @@ function edit(idx) {
|
|||
$("#username").val(profile.username)
|
||||
$("#password").val(profile.password)
|
||||
$("#ignore_cert_errors").prop("checked", profile.ignore_cert_errors)
|
||||
$.each(profile.headers, function(i, record) {
|
||||
addCustomHeader(record.key, record.value)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,6 +195,34 @@ function load() {
|
|||
})
|
||||
}
|
||||
|
||||
function addCustomHeader(header, value) {
|
||||
// Create new data row.
|
||||
var newRow = [
|
||||
escapeHtml(header),
|
||||
escapeHtml(value),
|
||||
'<span style="cursor:pointer;"><i class="fa fa-trash-o"></i></span>'
|
||||
];
|
||||
|
||||
// Check table to see if header already exists.
|
||||
var headersTable = headers.DataTable();
|
||||
var existingRowIndex = headersTable
|
||||
.column(0) // Email column has index of 2
|
||||
.data()
|
||||
.indexOf(escapeHtml(header));
|
||||
|
||||
// Update or add new row as necessary.
|
||||
if (existingRowIndex >= 0) {
|
||||
headersTable
|
||||
.row(existingRowIndex, {
|
||||
order: "index"
|
||||
})
|
||||
.data(newRow);
|
||||
} else {
|
||||
headersTable.row.add(newRow);
|
||||
}
|
||||
headersTable.draw();
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
// Setup multiple modals
|
||||
// Code based on http://miles-by-motorcycle.com/static/bootstrap-modal/index.html
|
||||
|
@ -208,5 +264,26 @@ $(document).ready(function() {
|
|||
$('#modal').on('hidden.bs.modal', function(event) {
|
||||
dismiss()
|
||||
});
|
||||
// Code to deal with custom email headers
|
||||
$("#headersForm").on('submit', function() {
|
||||
headerKey = $("#headerKey").val();
|
||||
headerValue = $("#headerValue").val();
|
||||
|
||||
if (headerKey == "" || headerValue == "") {
|
||||
return false;
|
||||
}
|
||||
addCustomHeader(headerKey, headerValue);
|
||||
// Reset user input.
|
||||
$("#headersForm>div>input").val('');
|
||||
$("#headerKey").focus();
|
||||
return false;
|
||||
});
|
||||
// Handle Deletion
|
||||
$("#headersTable").on("click", "span>i.fa-trash-o", function() {
|
||||
headers.DataTable()
|
||||
.row($(this).parents('tr'))
|
||||
.remove()
|
||||
.draw();
|
||||
});
|
||||
load()
|
||||
})
|
||||
|
|
|
@ -42,6 +42,7 @@ function save(idx) {
|
|||
type: target[4],
|
||||
})
|
||||
})
|
||||
|
||||
if (idx != -1) {
|
||||
template.id = templates[idx].id
|
||||
api.templateId.put(template)
|
||||
|
@ -169,6 +170,7 @@ function edit(idx) {
|
|||
} else {
|
||||
$("#use_tracker_checkbox").prop("checked", false)
|
||||
}
|
||||
|
||||
}
|
||||
// Handle Deletion
|
||||
$("#attachmentsTable").unbind('click').on("click", "span>i.fa-trash-o", function() {
|
||||
|
@ -346,4 +348,5 @@ $(document).ready(function() {
|
|||
dismiss()
|
||||
});
|
||||
load()
|
||||
|
||||
})
|
||||
|
|
|
@ -86,8 +86,32 @@
|
|||
<input id="ignore_cert_errors" type="checkbox" checked>
|
||||
<label for="ignore_cert_errors">Ignore Certificate Errors <i class="fa fa-question-circle" data-toggle="tooltip" data-placement="right" title="Ignore common certificate errors such as self-signed certs (exposes you to MiTM attacks - use carefully!)"></i></label>
|
||||
</div>
|
||||
<label class="control-label" for="headersForm">Email Headers:</label>
|
||||
<form id="headersForm">
|
||||
<div class="col-md-4">
|
||||
<input type="text" class="form-control" name="headerKey" id="headerKey" placeholder="X-Custom-Header">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="text" class="form-control" name="headerValue" id="headerValue" placeholder="{{"{{"}}.URL{{"}}"}}-gophish">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-danger btn-headers" type="submit"><i class="fa fa-plus"></i> Add Custom Header</button>
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
<br />
|
||||
<table id="headersTable" class="table table-hover table-striped table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Header</th>
|
||||
<th>Value</th>
|
||||
<th class="no-sort"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="button" data-toggle="modal" data-target="#sendTestEmailModal" class="btn btn-primary"><i class="fa fa-envelope"></i> Send Test Email</button>
|
||||
<!-- disable sendTestEmail functionality on sending profile page until update handling of /util/send_test_email -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -136,7 +160,5 @@
|
|||
</div>
|
||||
{{end}}
|
||||
{{define "scripts"}}
|
||||
<script src="/js/ckeditor/ckeditor.js"></script>
|
||||
<script src="/js/ckeditor/adapters/jquery.js"></script>
|
||||
<script src="/js/dist/app/sending_profiles.min.js"></script>
|
||||
{{end}}
|
||||
|
|
|
@ -117,6 +117,7 @@
|
|||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" data-dismiss="modal" class="btn btn-default" onclick="dismiss()">Cancel</button>
|
||||
|
|
|
@ -127,7 +127,36 @@ func processCampaign(c *models.Campaign) {
|
|||
"<img alt='' style='display: none' src='" + c.URL + "/track?rid=" + t.RId + "'/>",
|
||||
fn,
|
||||
}
|
||||
// Parse the templates
|
||||
|
||||
// Parse the customHeader templates
|
||||
for _, header := range c.SMTP.Headers {
|
||||
parsedHeader := struct {
|
||||
Key bytes.Buffer
|
||||
Value bytes.Buffer
|
||||
}{}
|
||||
keytmpl, err := template.New("text_template").Parse(header.Key)
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
err = keytmpl.Execute(&parsedHeader.Key, td)
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
|
||||
valtmpl, err := template.New("text_template").Parse(header.Value)
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
err = valtmpl.Execute(&parsedHeader.Value, td)
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
|
||||
// Add our header immediately
|
||||
e.SetHeader(parsedHeader.Key.String(), parsedHeader.Value.String())
|
||||
}
|
||||
|
||||
// Parse remaining templates
|
||||
var subjBuff bytes.Buffer
|
||||
tmpl, err := template.New("text_template").Parse(c.Template.Subject)
|
||||
if err != nil {
|
||||
|
@ -243,10 +272,37 @@ func SendTestEmail(s *models.SendTestEmailRequest) error {
|
|||
Logger.Println(err)
|
||||
return err
|
||||
}
|
||||
Logger.Println("Creating email using template")
|
||||
e := gomail.NewMessage()
|
||||
// Parse the customHeader templates
|
||||
for _, header := range s.SMTP.Headers {
|
||||
parsedHeader := struct {
|
||||
Key bytes.Buffer
|
||||
Value bytes.Buffer
|
||||
}{}
|
||||
keytmpl, err := template.New("text_template").Parse(header.Key)
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
err = keytmpl.Execute(&parsedHeader.Key, s)
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
|
||||
valtmpl, err := template.New("text_template").Parse(header.Value)
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
err = valtmpl.Execute(&parsedHeader.Value, s)
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
|
||||
// Add our header immediately
|
||||
e.SetHeader(parsedHeader.Key.String(), parsedHeader.Value.String())
|
||||
}
|
||||
e.SetHeader("From", s.SMTP.FromAddress)
|
||||
e.SetHeader("To", s.Email)
|
||||
Logger.Println("Creating email using template")
|
||||
// Parse the templates
|
||||
var subjBuff bytes.Buffer
|
||||
tmpl, err := template.New("text_template").Parse(s.Template.Subject)
|
||||
|
|
Loading…
Reference in New Issue