Migrating to use github.com/gomail/gomail to provide:

Better auth compatibility
(support for upcoming) embedded images
Much faster email sending (sending multiple emails per connection)
pull/284/head
Jordan Wright 2016-05-30 14:55:45 -05:00
parent 1933eb7ff1
commit a8aac75f99
1 changed files with 123 additions and 77 deletions

View File

@ -6,15 +6,16 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"io"
"log" "log"
"net/mail" "net/mail"
"net/smtp"
"os" "os"
"strconv"
"strings" "strings"
"text/template" "text/template"
"github.com/gophish/gophish/models" "github.com/gophish/gophish/models"
"github.com/jordan-wright/email" "gopkg.in/gomail.v2"
) )
// Logger is the logger for the worker // Logger is the logger for the worker
@ -47,27 +48,59 @@ func processCampaign(c *models.Campaign) {
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
} }
e := email.Email{
Subject: c.Template.Subject,
From: c.SMTP.FromAddress,
}
var auth smtp.Auth
if c.SMTP.Username != "" && c.SMTP.Password != "" {
auth = smtp.PlainAuth("", c.SMTP.Username, c.SMTP.Password, strings.Split(c.SMTP.Host, ":")[0])
}
tc := &tls.Config{
ServerName: c.SMTP.Host,
InsecureSkipVerify: c.SMTP.IgnoreCertErrors,
}
f, err := mail.ParseAddress(c.SMTP.FromAddress) f, err := mail.ParseAddress(c.SMTP.FromAddress)
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
} }
ft := f.Name fn := f.Name
if ft == "" { if fn == "" {
ft = f.Address fn = f.Address
} }
// Setup the message and dial
hp := strings.Split(c.SMTP.Host, ":")
if len(hp) < 2 {
hp = append(hp, "25")
}
// Any issues should have been caught in validation, so we just log
port, err := strconv.Atoi(hp[1])
if err != nil {
Logger.Println(err)
}
d := gomail.NewDialer(hp[0], port, c.SMTP.Username, c.SMTP.Password)
d.TLSConfig = &tls.Config{
ServerName: c.SMTP.Host,
InsecureSkipVerify: c.SMTP.IgnoreCertErrors,
}
s, err := d.Dial()
// Short circuit if we have an err
// However, we still need to update each target
if err != nil {
Logger.Println(err)
for _, t := range c.Results {
es := struct {
Error string `json:"error"`
}{
Error: err.Error(),
}
ej, err := json.Marshal(es)
if err != nil {
Logger.Println(err)
}
err = t.UpdateStatus(models.ERROR)
if err != nil {
Logger.Println(err)
}
err = c.AddEvent(models.Event{Email: t.Email, Message: models.EVENT_SENDING_ERROR, Details: string(ej)})
if err != nil {
Logger.Println(err)
}
}
return
}
// Send each email
e := gomail.NewMessage()
for _, t := range c.Results { for _, t := range c.Results {
e.SetHeader("From", c.SMTP.FromAddress)
td := struct { td := struct {
models.Result models.Result
URL string URL string
@ -79,21 +112,23 @@ func processCampaign(c *models.Campaign) {
c.URL + "?rid=" + t.RId, c.URL + "?rid=" + t.RId,
c.URL + "/track?rid=" + t.RId, c.URL + "/track?rid=" + t.RId,
"<img style='display: none' src='" + c.URL + "/track?rid=" + t.RId + "'/>", "<img style='display: none' src='" + c.URL + "/track?rid=" + t.RId + "'/>",
ft, fn,
} }
// Parse the templates // Parse the templates
var subjBuff bytes.Buffer var subjBuff bytes.Buffer
var htmlBuff bytes.Buffer var htmlBuff bytes.Buffer
var textBuff bytes.Buffer var textBuff bytes.Buffer
tmpl, err := template.New("html_template").Parse(c.Template.HTML) tmpl, err := template.New("text_template").Parse(c.Template.Subject)
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
} }
err = tmpl.Execute(&htmlBuff, td) err = tmpl.Execute(&subjBuff, td)
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
} }
e.HTML = htmlBuff.Bytes() e.SetHeader("Subject", subjBuff.String())
Logger.Println("Creating email using template")
e.SetHeader("To", t.Email)
tmpl, err = template.New("text_template").Parse(c.Template.Text) tmpl, err = template.New("text_template").Parse(c.Template.Text)
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
@ -102,29 +137,28 @@ func processCampaign(c *models.Campaign) {
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
} }
e.Text = textBuff.Bytes() e.SetBody("text/plain", textBuff.String())
tmpl, err = template.New("text_template").Parse(c.Template.Subject) tmpl, err = template.New("html_template").Parse(c.Template.HTML)
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
} }
err = tmpl.Execute(&subjBuff, td) err = tmpl.Execute(&htmlBuff, td)
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
} }
e.Subject = string(subjBuff.Bytes()) e.AddAlternative("text/html", htmlBuff.String())
Logger.Println("Creating email using template")
e.To = []string{t.Email}
e.Attachments = []*email.Attachment{}
// Attach the files // Attach the files
for _, a := range c.Template.Attachments { for _, a := range c.Template.Attachments {
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(a.Content)) e.Attach(func(a models.Attachment) (string, gomail.FileSetting) {
_, err = e.Attach(decoder, a.Name, a.Type) return a.Name, gomail.SetCopyFunc(func(w io.Writer) error {
if err != nil { decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(a.Content))
Logger.Println(err) _, err = io.Copy(w, decoder)
} return err
})
}(a))
} }
Logger.Printf("Sending Email to %s\n", t.Email) Logger.Printf("Sending Email to %s\n", t.Email)
err = e.SendWithTLS(c.SMTP.Host, auth, tc) err = gomail.Send(s, e)
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
es := struct { es := struct {
@ -154,6 +188,7 @@ func processCampaign(c *models.Campaign) {
Logger.Println(err) Logger.Println(err)
} }
} }
e.Reset()
} }
err = c.UpdateStatus(models.CAMPAIGN_EMAILS_SENT) err = c.UpdateStatus(models.CAMPAIGN_EMAILS_SENT)
if err != nil { if err != nil {
@ -162,51 +197,32 @@ func processCampaign(c *models.Campaign) {
} }
func SendTestEmail(s *models.SendTestEmailRequest) error { func SendTestEmail(s *models.SendTestEmailRequest) error {
e := email.Email{ hp := strings.Split(s.SMTP.Host, ":")
Subject: s.Template.Subject, if len(hp) < 2 {
From: s.SMTP.FromAddress, hp = append(hp, "25")
} }
var auth smtp.Auth port, err := strconv.Atoi(hp[1])
if s.SMTP.Username != "" && s.SMTP.Password != "" {
auth = smtp.PlainAuth("", s.SMTP.Username, s.SMTP.Password, strings.Split(s.SMTP.Host, ":")[0])
}
t := &tls.Config{
ServerName: s.SMTP.Host,
InsecureSkipVerify: s.SMTP.IgnoreCertErrors,
}
f, err := mail.ParseAddress(s.SMTP.FromAddress)
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
return err return err
} }
s.From = f.Name d := gomail.NewDialer(hp[0], port, s.SMTP.Username, s.SMTP.Password)
if s.From == "" { d.TLSConfig = &tls.Config{
s.From = f.Address ServerName: s.SMTP.Host,
InsecureSkipVerify: s.SMTP.IgnoreCertErrors,
} }
dc, err := d.Dial()
if err != nil {
Logger.Println(err)
return err
}
e := gomail.NewMessage()
e.SetHeader("From", s.SMTP.FromAddress)
e.SetHeader("To", s.Email)
Logger.Println("Creating email using template") Logger.Println("Creating email using template")
// Parse the templates // Parse the templates
var subjBuff bytes.Buffer var subjBuff bytes.Buffer
var htmlBuff bytes.Buffer tmpl, err := template.New("text_template").Parse(s.Template.Subject)
var textBuff bytes.Buffer
tmpl, err := template.New("html_template").Parse(s.Template.HTML)
if err != nil {
Logger.Println(err)
}
err = tmpl.Execute(&htmlBuff, s)
if err != nil {
Logger.Println(err)
}
e.HTML = htmlBuff.Bytes()
tmpl, err = template.New("text_template").Parse(s.Template.Text)
if err != nil {
Logger.Println(err)
}
err = tmpl.Execute(&textBuff, s)
if err != nil {
Logger.Println(err)
}
e.Text = textBuff.Bytes()
tmpl, err = template.New("text_template").Parse(s.Template.Subject)
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
} }
@ -214,18 +230,48 @@ func SendTestEmail(s *models.SendTestEmailRequest) error {
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
} }
e.Subject = string(subjBuff.Bytes()) e.SetHeader("Subject", subjBuff.String())
e.To = []string{s.Email} if s.Template.Text != "" {
// Attach the files var textBuff bytes.Buffer
for _, a := range s.Template.Attachments { tmpl, err = template.New("text_template").Parse(s.Template.Text)
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(a.Content))
_, err = e.Attach(decoder, a.Name, a.Type)
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
} }
err = tmpl.Execute(&textBuff, s)
if err != nil {
Logger.Println(err)
}
e.SetBody("text/plain", textBuff.String())
}
if s.Template.HTML != "" {
var htmlBuff bytes.Buffer
tmpl, err = template.New("html_template").Parse(s.Template.HTML)
if err != nil {
Logger.Println(err)
}
err = tmpl.Execute(&htmlBuff, s)
if err != nil {
Logger.Println(err)
}
// If we don't have a text part, make the html the root part
if s.Template.Text == "" {
e.SetBody("text/html", htmlBuff.String())
} else {
e.AddAlternative("text/html", htmlBuff.String())
}
}
// Attach the files
for _, a := range s.Template.Attachments {
e.Attach(func(a models.Attachment) (string, gomail.FileSetting) {
return a.Name, gomail.SetCopyFunc(func(w io.Writer) error {
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(a.Content))
_, err = io.Copy(w, decoder)
return err
})
}(a))
} }
Logger.Printf("Sending Email to %s\n", s.Email) Logger.Printf("Sending Email to %s\n", s.Email)
err = e.SendWithTLS(s.SMTP.Host, auth, t) err = gomail.Send(dc, e)
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
// For now, let's split the error and return // For now, let's split the error and return