mirror of https://github.com/gophish/gophish
Adding the ability to schedule campaigns. Fixes #21
parent
a8aac75f99
commit
082023aae0
|
@ -83,7 +83,6 @@ func API_Campaigns(w http.ResponseWriter, r *http.Request) {
|
|||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
Worker.Queue <- &c
|
||||
JSONResponse(w, c, http.StatusCreated)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
ALTER TABLE "campaigns" ADD COLUMN "launch_date" DATETIME;
|
||||
|
||||
UPDATE "campaigns" SET "launch_date" = "created_date";
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
|
@ -2,7 +2,6 @@ package models
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
|
@ -14,6 +13,7 @@ type Campaign struct {
|
|||
UserId int64 `json:"-"`
|
||||
Name string `json:"name" sql:"not null"`
|
||||
CreatedDate time.Time `json:"created_date"`
|
||||
LaunchDate time.Time `json:"launch_date"`
|
||||
CompletedDate time.Time `json:"completed_date"`
|
||||
TemplateId int64 `json:"-"`
|
||||
Template Template `json:"template"`
|
||||
|
@ -139,7 +139,7 @@ func (c *Campaign) getDetails() error {
|
|||
c.Page = Page{Name: "[Deleted]"}
|
||||
Logger.Printf("%s: page not found for campaign\n", err)
|
||||
}
|
||||
err = db.Table("SMTP").Where("id=?", c.SMTPId).Find(&c.SMTP).Error
|
||||
err = db.Table("smtp").Where("id=?", c.SMTPId).Find(&c.SMTP).Error
|
||||
if err != nil {
|
||||
// Check if the SMTP was deleted
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
|
@ -167,7 +167,7 @@ func GetCampaigns(uid int64) ([]Campaign, error) {
|
|||
cs := []Campaign{}
|
||||
err := db.Model(&User{Id: uid}).Related(&cs).Error
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
Logger.Println(err)
|
||||
}
|
||||
for i, _ := range cs {
|
||||
err = cs[i].getDetails()
|
||||
|
@ -190,6 +190,24 @@ func GetCampaign(id int64, uid int64) (Campaign, error) {
|
|||
return c, err
|
||||
}
|
||||
|
||||
// GetQueuedCampaigns returns the campaigns that are queued up for this given minute
|
||||
func GetQueuedCampaigns(t time.Time) ([]Campaign, error) {
|
||||
cs := []Campaign{}
|
||||
err := db.Where("launch_date <= ?", t).
|
||||
Where("status = ?", CAMPAIGN_QUEUED).Find(&cs).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
Logger.Printf("Found %d Campaigns to run\n", len(cs))
|
||||
for i, _ := range cs {
|
||||
err = cs[i].getDetails()
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
}
|
||||
return cs, err
|
||||
}
|
||||
|
||||
// PostCampaign inserts a campaign and all associated records into the database.
|
||||
func PostCampaign(c *Campaign, uid int64) error {
|
||||
if err := c.Validate(); err != nil {
|
||||
|
@ -200,6 +218,9 @@ func PostCampaign(c *Campaign, uid int64) error {
|
|||
c.CreatedDate = time.Now()
|
||||
c.CompletedDate = time.Time{}
|
||||
c.Status = CAMPAIGN_QUEUED
|
||||
if c.LaunchDate.IsZero() {
|
||||
c.LaunchDate = time.Now()
|
||||
}
|
||||
// Check to make sure all the groups already exist
|
||||
for i, g := range c.Groups {
|
||||
c.Groups[i], err = GetGroupByName(g.Name, uid)
|
||||
|
|
|
@ -0,0 +1,373 @@
|
|||
/*!
|
||||
* Datetimepicker for Bootstrap 3
|
||||
* version : 4.15.35
|
||||
* https://github.com/Eonasdan/bootstrap-datetimepicker/
|
||||
*/
|
||||
.bootstrap-datetimepicker-widget {
|
||||
list-style: none;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu {
|
||||
margin: 2px 0;
|
||||
padding: 4px;
|
||||
width: 19em;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
|
||||
width: 38em;
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
|
||||
width: 38em;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
|
||||
width: 38em;
|
||||
}
|
||||
}
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu:before,
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before {
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-bottom: 7px solid #cccccc;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
||||
top: -7px;
|
||||
left: 7px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after {
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid white;
|
||||
top: -6px;
|
||||
left: 8px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.top:before {
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-top: 7px solid #cccccc;
|
||||
border-top-color: rgba(0, 0, 0, 0.2);
|
||||
bottom: -7px;
|
||||
left: 6px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.top:after {
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 6px solid white;
|
||||
bottom: -6px;
|
||||
left: 7px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before {
|
||||
left: auto;
|
||||
right: 6px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after {
|
||||
left: auto;
|
||||
right: 7px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .list-unstyled {
|
||||
margin: 0;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget a[data-action] {
|
||||
padding: 6px 0;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget a[data-action]:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .timepicker-hour,
|
||||
.bootstrap-datetimepicker-widget .timepicker-minute,
|
||||
.bootstrap-datetimepicker-widget .timepicker-second {
|
||||
width: 54px;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
margin: 0;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget button[data-action] {
|
||||
padding: 6px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
content: "Increment Hours";
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
content: "Increment Minutes";
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
content: "Decrement Hours";
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
content: "Decrement Minutes";
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
content: "Show Hours";
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
content: "Show Minutes";
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
content: "Toggle AM/PM";
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
content: "Clear the picker";
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="today"]::after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
content: "Set the date to today";
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .picker-switch {
|
||||
text-align: center;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .picker-switch::after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
content: "Toggle Date and Time Screens";
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .picker-switch td {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: auto;
|
||||
width: auto;
|
||||
line-height: inherit;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .picker-switch td span {
|
||||
line-height: 2.5;
|
||||
height: 2.5em;
|
||||
width: 100%;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td,
|
||||
.bootstrap-datetimepicker-widget table th {
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table th {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table th.picker-switch {
|
||||
width: 145px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table th.disabled,
|
||||
.bootstrap-datetimepicker-widget table th.disabled:hover {
|
||||
background: none;
|
||||
color: #777777;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table th.prev::after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
content: "Previous Month";
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table th.next::after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
content: "Next Month";
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table thead tr:first-child th {
|
||||
cursor: pointer;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table thead tr:first-child th:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td {
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
width: 54px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td.cw {
|
||||
font-size: .8em;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
color: #777777;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td.day {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td.day:hover,
|
||||
.bootstrap-datetimepicker-widget table td.hour:hover,
|
||||
.bootstrap-datetimepicker-widget table td.minute:hover,
|
||||
.bootstrap-datetimepicker-widget table td.second:hover {
|
||||
background: #eeeeee;
|
||||
cursor: pointer;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td.old,
|
||||
.bootstrap-datetimepicker-widget table td.new {
|
||||
color: #777777;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td.today {
|
||||
position: relative;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td.today:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border: solid transparent;
|
||||
border-width: 0 0 7px 7px;
|
||||
border-bottom-color: #337ab7;
|
||||
border-top-color: rgba(0, 0, 0, 0.2);
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td.active,
|
||||
.bootstrap-datetimepicker-widget table td.active:hover {
|
||||
background-color: #337ab7;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td.active.today:before {
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td.disabled,
|
||||
.bootstrap-datetimepicker-widget table td.disabled:hover {
|
||||
background: none;
|
||||
color: #777777;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td span {
|
||||
display: inline-block;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
margin: 2px 1.5px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td span:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td span.active {
|
||||
background-color: #337ab7;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td span.old {
|
||||
color: #777777;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget table td span.disabled,
|
||||
.bootstrap-datetimepicker-widget table td span.disabled:hover {
|
||||
background: none;
|
||||
color: #777777;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget.usetwentyfour td.hour {
|
||||
height: 27px;
|
||||
line-height: 27px;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget.wider {
|
||||
width: 21em;
|
||||
}
|
||||
.bootstrap-datetimepicker-widget .datepicker-decades .decade {
|
||||
line-height: 1.8em !important;
|
||||
}
|
||||
.input-group.date .input-group-addon {
|
||||
cursor: pointer;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
}
|
|
@ -33,6 +33,7 @@ function launch() {
|
|||
smtp: {
|
||||
name: $("#profile").val()
|
||||
},
|
||||
launch_date: moment($("#launch_date").val(), "MM/DD/YYYY HH:mm").format(),
|
||||
groups: groups
|
||||
}
|
||||
launchHtml = $("launchButton").html()
|
||||
|
@ -215,6 +216,13 @@ function copy(idx) {
|
|||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#launch_date").datetimepicker({
|
||||
"widgetPositioning": {
|
||||
"vertical": "bottom"
|
||||
},
|
||||
"showTodayButton": true,
|
||||
"defaultDate": moment()
|
||||
})
|
||||
// Setup multiple modals
|
||||
// Code based on http://miles-by-motorcycle.com/static/bootstrap-modal/index.html
|
||||
$('.modal').on('hidden.bs.modal', function(event) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,6 +21,7 @@
|
|||
<link href="/css/dataTables.bootstrap.css" rel="stylesheet">
|
||||
<link href="/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="/css/chartist.min.css" rel="stylesheet">
|
||||
<link href="/css/bootstrap-datetime.css" rel='stylesheet' type='text/css'>
|
||||
<link href='https://fonts.googleapis.com/css?family=Roboto:700,500' rel='stylesheet' type='text/css'>
|
||||
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,300,600,700' rel='stylesheet' type='text/css'>
|
||||
<link href="/css/checkbox.css" rel="stylesheet">
|
||||
|
|
|
@ -81,6 +81,8 @@
|
|||
<br>
|
||||
<label class="control-label" for="url">URL: <i class="fa fa-question-circle" data-toggle="tooltip" data-placement="right" title="Location of gophish listener (must be reachable by targets!)"></i></label>
|
||||
<input type="text" class="form-control" placeholder="http://192.168.1.1" id="url"/>
|
||||
<label class="control-label" for="url">Schedule: </label>
|
||||
<input type="text" class="form-control" id="launch_date"/>
|
||||
<label class="control-label" for="profile">Sending Profile:</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="typeahead form-control" placeholder="Sending Profile" id="profile"/>
|
||||
|
@ -153,6 +155,7 @@
|
|||
</div>
|
||||
{{end}}
|
||||
{{define "scripts"}}
|
||||
<script src="/js/bootstrap-datetime.js"></script>
|
||||
<script src="/js/hogan.js"></script>
|
||||
<script src="/js/typeahead.min.js"></script>
|
||||
<script src="/js/app/campaigns.js"></script>
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/gophish/gophish/models"
|
||||
"gopkg.in/gomail.v2"
|
||||
|
@ -22,23 +23,29 @@ import (
|
|||
var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||
|
||||
// Worker is the background worker that handles watching for new campaigns and sending emails appropriately.
|
||||
type Worker struct {
|
||||
Queue chan *models.Campaign
|
||||
}
|
||||
type Worker struct{}
|
||||
|
||||
// New creates a new worker object to handle the creation of campaigns
|
||||
func New() *Worker {
|
||||
return &Worker{
|
||||
Queue: make(chan *models.Campaign),
|
||||
}
|
||||
return &Worker{}
|
||||
}
|
||||
|
||||
// Start launches the worker to monitor the database for any jobs.
|
||||
// Start launches the worker to poll the database every minute for any jobs.
|
||||
// If a job is found, it launches the job
|
||||
func (w *Worker) Start() {
|
||||
Logger.Println("Background Worker Started Successfully - Waiting for Campaigns")
|
||||
for {
|
||||
processCampaign(<-w.Queue)
|
||||
for t := range time.Tick(1 * time.Minute) {
|
||||
cs, err := models.GetQueuedCampaigns(t)
|
||||
// Not really sure of a clean way to catch errors per campaign...
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
continue
|
||||
}
|
||||
for _, c := range cs {
|
||||
go func(c models.Campaign) {
|
||||
processCampaign(&c)
|
||||
}(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue