Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add -format-one-email option #331

Merged
merged 1 commit into from
Feb 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,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 @@ -881,6 +882,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 @@ -844,6 +844,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 @@ -890,6 +891,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
}
132 changes: 91 additions & 41 deletions report/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,54 +34,104 @@ 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 {
var subject string
if len(r.Errors) != 0 {
subject = fmt.Sprintf("%s%s An error occurred while scanning",
conf.EMail.SubjectPrefix, r.ServerInfo())
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())
var subject string
if len(r.Errors) != 0 {
subject = fmt.Sprintf("%s%s An error occurred while scanning",
conf.EMail.SubjectPrefix, r.ServerInfo())
} 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
}
}
}

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),
if conf.FormatOneEMail {
message = fmt.Sprintf(
`
One Line Summary
================
%s


%s`,
toOneLineSummary(rs...), message)

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

if err != nil {
return fmt.Errorf("Failed to send emails: %s", err)
}
// EMailSender is interface of sending e-mail
type EMailSender interface {
Send(subject, body string) error
}

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
}