From a8aac75f99612eb42be2c94700b55bad8fcee50a Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Mon, 30 May 2016 14:55:45 -0500 Subject: [PATCH] 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) --- worker/worker.go | 200 +++++++++++++++++++++++++++++------------------ 1 file changed, 123 insertions(+), 77 deletions(-) diff --git a/worker/worker.go b/worker/worker.go index 981740db..9d93f646 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -6,15 +6,16 @@ import ( "encoding/base64" "encoding/json" "errors" + "io" "log" "net/mail" - "net/smtp" "os" + "strconv" "strings" "text/template" "github.com/gophish/gophish/models" - "github.com/jordan-wright/email" + "gopkg.in/gomail.v2" ) // Logger is the logger for the worker @@ -47,27 +48,59 @@ func processCampaign(c *models.Campaign) { if err != nil { 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) if err != nil { Logger.Println(err) } - ft := f.Name - if ft == "" { - ft = f.Address + fn := f.Name + if fn == "" { + 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 { + e.SetHeader("From", c.SMTP.FromAddress) td := struct { models.Result URL string @@ -79,21 +112,23 @@ func processCampaign(c *models.Campaign) { c.URL + "?rid=" + t.RId, c.URL + "/track?rid=" + t.RId, "", - ft, + fn, } // Parse the templates var subjBuff bytes.Buffer var htmlBuff 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 { Logger.Println(err) } - err = tmpl.Execute(&htmlBuff, td) + err = tmpl.Execute(&subjBuff, td) if err != nil { 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) if err != nil { Logger.Println(err) @@ -102,29 +137,28 @@ func processCampaign(c *models.Campaign) { if err != nil { Logger.Println(err) } - e.Text = textBuff.Bytes() - tmpl, err = template.New("text_template").Parse(c.Template.Subject) + e.SetBody("text/plain", textBuff.String()) + tmpl, err = template.New("html_template").Parse(c.Template.HTML) if err != nil { Logger.Println(err) } - err = tmpl.Execute(&subjBuff, td) + err = tmpl.Execute(&htmlBuff, td) if err != nil { Logger.Println(err) } - e.Subject = string(subjBuff.Bytes()) - Logger.Println("Creating email using template") - e.To = []string{t.Email} - e.Attachments = []*email.Attachment{} + e.AddAlternative("text/html", htmlBuff.String()) // Attach the files for _, a := range c.Template.Attachments { - decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(a.Content)) - _, err = e.Attach(decoder, a.Name, a.Type) - if err != nil { - Logger.Println(err) - } + 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", t.Email) - err = e.SendWithTLS(c.SMTP.Host, auth, tc) + err = gomail.Send(s, e) if err != nil { Logger.Println(err) es := struct { @@ -154,6 +188,7 @@ func processCampaign(c *models.Campaign) { Logger.Println(err) } } + e.Reset() } err = c.UpdateStatus(models.CAMPAIGN_EMAILS_SENT) if err != nil { @@ -162,51 +197,32 @@ func processCampaign(c *models.Campaign) { } func SendTestEmail(s *models.SendTestEmailRequest) error { - e := email.Email{ - Subject: s.Template.Subject, - From: s.SMTP.FromAddress, + hp := strings.Split(s.SMTP.Host, ":") + if len(hp) < 2 { + hp = append(hp, "25") } - var auth smtp.Auth - 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) + port, err := strconv.Atoi(hp[1]) if err != nil { Logger.Println(err) return err } - s.From = f.Name - if s.From == "" { - s.From = f.Address + d := gomail.NewDialer(hp[0], port, s.SMTP.Username, s.SMTP.Password) + d.TLSConfig = &tls.Config{ + 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") // Parse the templates var subjBuff bytes.Buffer - var htmlBuff bytes.Buffer - 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) + tmpl, err := template.New("text_template").Parse(s.Template.Subject) if err != nil { Logger.Println(err) } @@ -214,18 +230,48 @@ func SendTestEmail(s *models.SendTestEmailRequest) error { if err != nil { Logger.Println(err) } - e.Subject = string(subjBuff.Bytes()) - e.To = []string{s.Email} - // Attach the files - for _, a := range s.Template.Attachments { - decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(a.Content)) - _, err = e.Attach(decoder, a.Name, a.Type) + e.SetHeader("Subject", subjBuff.String()) + if s.Template.Text != "" { + var textBuff bytes.Buffer + 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.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) - err = e.SendWithTLS(s.SMTP.Host, auth, t) + err = gomail.Send(dc, e) if err != nil { Logger.Println(err) // For now, let's split the error and return