Skip to content

Commit

Permalink
Add -format-one-email option
Browse files Browse the repository at this point in the history
  • Loading branch information
knqyf263 committed Feb 13, 2017
1 parent 0066048 commit d9e5a6d
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 44 deletions.
3 changes: 3 additions & 0 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@ report:
[-to-azure-blob]
[-format-json]
[-format-xml]
[-format-one-email]
[-format-one-line-text]
[-format-short-text]
[-format-full-text]
Expand Down Expand Up @@ -877,6 +878,8 @@ report:
Detail report in plain text
-format-json
JSON format
-format-one-email
Send all the host report via only one EMail (Specify with -to-email)
-format-one-line-text
One line summary in plain text
-format-short-text
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,7 @@ report:
[-to-azure-blob]
[-format-json]
[-format-xml]
[-format-one-email]
[-format-one-line-text]
[-format-short-text]
[-format-full-text]
Expand Down Expand Up @@ -886,6 +887,8 @@ report:
Detail report in plain text
-format-json
JSON format
-format-one-email
Send all the host report via only one EMail (Specify with -to-email)
-format-one-line-text
One line summary in plain text
-format-short-text
Expand Down
8 changes: 8 additions & 0 deletions commands/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type ReportCmd struct {

formatJSON bool
formatXML bool
formatOneEMail bool
formatOneLineText bool
formatShortText bool
formatFullText bool
Expand Down Expand Up @@ -102,6 +103,7 @@ func (*ReportCmd) Usage() string {
[-to-azure-blob]
[-format-json]
[-format-xml]
[-format-one-email]
[-format-one-line-text]
[-format-short-text]
[-format-full-text]
Expand Down Expand Up @@ -191,6 +193,11 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
false,
fmt.Sprintf("XML format"))

f.BoolVar(&p.formatOneEMail,
"format-one-email",
false,
"Send all the host report via only one EMail (Specify with -to-email)")

f.BoolVar(&p.formatOneLineText,
"format-one-line-text",
false,
Expand Down Expand Up @@ -274,6 +281,7 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}

c.Conf.FormatXML = p.formatXML
c.Conf.FormatJSON = p.formatJSON
c.Conf.FormatOneEMail = p.formatOneEMail
c.Conf.FormatOneLineText = p.formatOneLineText
c.Conf.FormatShortText = p.formatShortText
c.Conf.FormatFullText = p.formatFullText
Expand Down
11 changes: 6 additions & 5 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type Config struct {
DebugSQL bool
Lang string

EMail smtpConf
EMail SMTPConf
Slack SlackConf
Default ServerInfo
Servers map[string]ServerInfo
Expand All @@ -60,6 +60,7 @@ type Config struct {

FormatXML bool
FormatJSON bool
FormatOneEMail bool
FormatOneLineText bool
FormatShortText bool
FormatFullText bool
Expand Down Expand Up @@ -216,8 +217,8 @@ func (c Config) ValidateOnTui() bool {
return len(errs) == 0
}

// smtpConf is smtp config
type smtpConf struct {
// SMTPConf is smtp config
type SMTPConf struct {
SMTPAddr string
SMTPPort string `valid:"port"`

Expand All @@ -244,7 +245,7 @@ func checkEmails(emails []string) (errs []error) {
}

// Validate SMTP configuration
func (c *smtpConf) Validate() (errs []error) {
func (c *SMTPConf) Validate() (errs []error) {

if !c.UseThisTime {
return
Expand Down Expand Up @@ -398,5 +399,5 @@ type Container struct {
ContainerID string
Name string
Type string
Image string
Image string
}
118 changes: 79 additions & 39 deletions report/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,51 +34,91 @@ type EMailWriter struct{}

func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
conf := config.Conf
to := strings.Join(conf.EMail.To[:], ", ")
cc := strings.Join(conf.EMail.Cc[:], ", ")
mailAddresses := append(conf.EMail.To, conf.EMail.Cc...)
if _, err := mail.ParseAddressList(strings.Join(mailAddresses[:], ", ")); err != nil {
return fmt.Errorf("Failed to parse email addresses: %s", err)
}
var message string
var totalResult models.ScanResult
sender := NewEMailSender()

for _, r := range rs {
subject := fmt.Sprintf("%s%s %s",
if conf.FormatOneEMail {
message += toFullPlainText(r) + "\r\n\r\n"
totalResult.KnownCves = append(totalResult.KnownCves, r.KnownCves...)
totalResult.UnknownCves = append(totalResult.UnknownCves, r.UnknownCves...)
} else {
subject := fmt.Sprintf("%s%s %s",
conf.EMail.SubjectPrefix,
r.ServerInfo(),
r.CveSummary(),
)
message = toFullPlainText(r)
if err := sender.Send(subject, message); err != nil {
return err
}
}
}

if conf.FormatOneEMail {
subject := fmt.Sprintf("%s %s",
conf.EMail.SubjectPrefix,
r.ServerInfo(),
r.CveSummary(),
totalResult.CveSummary(),
)
return sender.Send(subject, message)
}
return nil
}

headers := make(map[string]string)
headers["From"] = conf.EMail.From
headers["To"] = to
headers["Cc"] = cc
headers["Subject"] = subject
headers["Date"] = time.Now().Format(time.RFC1123Z)
headers["Content-Type"] = "text/plain; charset=utf-8"

var message string
for k, v := range headers {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + toFullPlainText(r)

smtpServer := net.JoinHostPort(conf.EMail.SMTPAddr, conf.EMail.SMTPPort)
err = smtp.SendMail(
smtpServer,
smtp.PlainAuth(
"",
conf.EMail.User,
conf.EMail.Password,
conf.EMail.SMTPAddr,
),
conf.EMail.From,
conf.EMail.To,
[]byte(message),
)
// EMailSender is interface of sending e-mail
type EMailSender interface {
Send(subject, body string) error
}

if err != nil {
return fmt.Errorf("Failed to send emails: %s", err)
}
type emailSender struct {
conf config.SMTPConf
send func(string, smtp.Auth, string, []string, []byte) error
}

func (e *emailSender) Send(subject, body string) (err error) {
emailConf := e.conf
to := strings.Join(emailConf.To[:], ", ")
cc := strings.Join(emailConf.Cc[:], ", ")
mailAddresses := append(emailConf.To, emailConf.Cc...)
if _, err := mail.ParseAddressList(strings.Join(mailAddresses[:], ", ")); err != nil {
return fmt.Errorf("Failed to parse email addresses: %s", err)
}

headers := make(map[string]string)
headers["From"] = emailConf.From
headers["To"] = to
headers["Cc"] = cc
headers["Subject"] = subject
headers["Date"] = time.Now().Format(time.RFC1123Z)
headers["Content-Type"] = "text/plain; charset=utf-8"

var header string
for k, v := range headers {
header += fmt.Sprintf("%s: %s\r\n", k, v)
}
message := fmt.Sprintf("%s\r\n%s", header, body)

smtpServer := net.JoinHostPort(emailConf.SMTPAddr, emailConf.SMTPPort)
err = e.send(
smtpServer,
smtp.PlainAuth(
"",
emailConf.User,
emailConf.Password,
emailConf.SMTPAddr,
),
emailConf.From,
emailConf.To,
[]byte(message),
)
if err != nil {
return fmt.Errorf("Failed to send emails: %s", err)
}
return nil
}

// NewEMailSender creates emailSender
func NewEMailSender() EMailSender {
return &emailSender{config.Conf.EMail, smtp.SendMail}
}
129 changes: 129 additions & 0 deletions report/email_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package report

import (
"net/smtp"
"reflect"
"strings"
"testing"

"github.com/future-architect/vuls/config"
)

type emailRecorder struct {
addr string
auth smtp.Auth
from string
to []string
body string
}

type mailTest struct {
in config.SMTPConf
out emailRecorder
}

var mailTests = []mailTest{
{
config.SMTPConf{
SMTPAddr: "127.0.0.1",
SMTPPort: "25",

From: "from@address.com",
To: []string{"to@address.com"},
},
emailRecorder{
addr: "127.0.0.1:25",
auth: smtp.PlainAuth("", "", "", "127.0.0.1"),
from: "from@address.com",
to: []string{"to@address.com"},
body: "body",
},
},
{
config.SMTPConf{
SMTPAddr: "127.0.0.1",
SMTPPort: "25",

User: "vuls",
Password: "password",

From: "from@address.com",
To: []string{"to1@address.com", "to2@address.com"},
},
emailRecorder{
addr: "127.0.0.1:25",
auth: smtp.PlainAuth(
"",
"vuls",
"password",
"127.0.0.1",
),
from: "from@address.com",
to: []string{"to1@address.com", "to2@address.com"},
body: "body",
},
},
}

func TestSend(t *testing.T) {
for i, test := range mailTests {
f, r := mockSend(nil)
sender := &emailSender{conf: test.in, send: f}

subject := "subject"
body := "body"
if err := sender.Send(subject, body); err != nil {
t.Errorf("unexpected error: %s", err)
}

if r.addr != test.out.addr {
t.Errorf("#%d: wrong 'addr' field.\r\nexpected: %s\n got: %s", i, test.out.addr, r.addr)
}

if !reflect.DeepEqual(r.auth, test.out.auth) {
t.Errorf("#%d: wrong 'auth' field.\r\nexpected: %v\n got: %v", i, test.out.auth, r.auth)
}

if r.from != test.out.from {
t.Errorf("#%d: wrong 'from' field.\r\nexpected: %v\n got: %v", i, test.out.from, r.from)
}

if !reflect.DeepEqual(r.to, test.out.to) {
t.Errorf("#%d: wrong 'to' field.\r\nexpected: %v\n got: %v", i, test.out.to, r.to)
}

if r.body != test.out.body {
t.Errorf("#%d: wrong 'body' field.\r\nexpected: %v\n got: %v", i, test.out.body, r.body)
}

}

}

func mockSend(errToReturn error) (func(string, smtp.Auth, string, []string, []byte) error, *emailRecorder) {
r := new(emailRecorder)
return func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
// Split into header and body
messages := strings.Split(string(msg), "\r\n\r\n")
body := messages[1]
*r = emailRecorder{addr, a, from, to, body}
return errToReturn
}, r
}

0 comments on commit d9e5a6d

Please sign in to comment.