Support for smtp LOGIN authentication (#1048)
* finished to implement new mail client * delete email_test.go
This commit is contained in:
		
							
								
								
									
										104
									
								
								report/email.go
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								report/email.go
									
									
									
									
									
								
							@@ -5,10 +5,11 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/mail"
 | 
			
		||||
	"net/smtp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	sasl "github.com/emersion/go-sasl"
 | 
			
		||||
	smtp "github.com/emersion/go-smtp"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
@@ -21,7 +22,6 @@ func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf
 | 
			
		||||
	var message string
 | 
			
		||||
	sender := NewEMailSender()
 | 
			
		||||
 | 
			
		||||
	m := map[string]int{}
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		if conf.FormatOneEMail {
 | 
			
		||||
@@ -85,37 +85,50 @@ type EMailSender interface {
 | 
			
		||||
 | 
			
		||||
type emailSender struct {
 | 
			
		||||
	conf config.SMTPConf
 | 
			
		||||
	send func(string, smtp.Auth, string, []string, []byte) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smtps(emailConf config.SMTPConf, message string) (err error) {
 | 
			
		||||
	auth := smtp.PlainAuth("",
 | 
			
		||||
		emailConf.User,
 | 
			
		||||
		emailConf.Password,
 | 
			
		||||
		emailConf.SMTPAddr,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
func (e *emailSender) sendMail(smtpServerAddr, message string) (err error) {
 | 
			
		||||
	var c *smtp.Client
 | 
			
		||||
	var auth sasl.Client
 | 
			
		||||
	emailConf := e.conf
 | 
			
		||||
	//TLS Config
 | 
			
		||||
	tlsConfig := &tls.Config{
 | 
			
		||||
		ServerName: emailConf.SMTPAddr,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	smtpServer := net.JoinHostPort(emailConf.SMTPAddr, emailConf.SMTPPort)
 | 
			
		||||
	//New TLS connection
 | 
			
		||||
	con, err := tls.Dial("tcp", smtpServer, tlsConfig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to create TLS connection: %w", err)
 | 
			
		||||
	switch emailConf.SMTPPort {
 | 
			
		||||
	case "465":
 | 
			
		||||
		//New TLS connection
 | 
			
		||||
		c, err = smtp.DialTLS(smtpServerAddr, tlsConfig)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return xerrors.Errorf("Failed to create TLS connection to SMTP server: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		c, err = smtp.Dial(smtpServerAddr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return xerrors.Errorf("Failed to create connection to SMTP server: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	defer con.Close()
 | 
			
		||||
	defer c.Close()
 | 
			
		||||
 | 
			
		||||
	c, err := smtp.NewClient(con, emailConf.SMTPAddr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to create new client: %w", err)
 | 
			
		||||
	if err = c.Hello("localhost"); err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to send Hello command: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ok, _ := c.Extension("STARTTLS"); ok {
 | 
			
		||||
		if err := c.StartTLS(tlsConfig); err != nil {
 | 
			
		||||
			return xerrors.Errorf("Failed to STARTTLS: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ok, param := c.Extension("AUTH"); ok {
 | 
			
		||||
		authList := strings.Split(param, " ")
 | 
			
		||||
		auth = e.newSaslClient(authList)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = c.Auth(auth); err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to authenticate: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err = c.Mail(emailConf.From); err != nil {
 | 
			
		||||
	if err = c.Mail(emailConf.From, nil); err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to send Mail command: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	for _, to := range emailConf.To {
 | 
			
		||||
@@ -169,38 +182,13 @@ func (e *emailSender) Send(subject, body string) (err error) {
 | 
			
		||||
	smtpServer := net.JoinHostPort(emailConf.SMTPAddr, emailConf.SMTPPort)
 | 
			
		||||
 | 
			
		||||
	if emailConf.User != "" && emailConf.Password != "" {
 | 
			
		||||
		switch emailConf.SMTPPort {
 | 
			
		||||
		case "465":
 | 
			
		||||
			err := smtps(emailConf, message)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return xerrors.Errorf("Failed to send emails: %w", err)
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			err = e.send(
 | 
			
		||||
				smtpServer,
 | 
			
		||||
				smtp.PlainAuth(
 | 
			
		||||
					"",
 | 
			
		||||
					emailConf.User,
 | 
			
		||||
					emailConf.Password,
 | 
			
		||||
					emailConf.SMTPAddr,
 | 
			
		||||
				),
 | 
			
		||||
				emailConf.From,
 | 
			
		||||
				mailAddresses,
 | 
			
		||||
				[]byte(message),
 | 
			
		||||
			)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return xerrors.Errorf("Failed to send emails: %w", err)
 | 
			
		||||
			}
 | 
			
		||||
		err = e.sendMail(smtpServer, message)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return xerrors.Errorf("Failed to send emails: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	err = e.send(
 | 
			
		||||
		smtpServer,
 | 
			
		||||
		nil,
 | 
			
		||||
		emailConf.From,
 | 
			
		||||
		mailAddresses,
 | 
			
		||||
		[]byte(message),
 | 
			
		||||
	)
 | 
			
		||||
	err = e.sendMail(smtpServer, message)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to send emails: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -209,5 +197,19 @@ func (e *emailSender) Send(subject, body string) (err error) {
 | 
			
		||||
 | 
			
		||||
// NewEMailSender creates emailSender
 | 
			
		||||
func NewEMailSender() EMailSender {
 | 
			
		||||
	return &emailSender{config.Conf.EMail, smtp.SendMail}
 | 
			
		||||
	return &emailSender{config.Conf.EMail}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *emailSender) newSaslClient(authList []string) sasl.Client {
 | 
			
		||||
	for _, v := range authList {
 | 
			
		||||
		switch v {
 | 
			
		||||
		case "PLAIN":
 | 
			
		||||
			auth := sasl.NewPlainClient("", e.conf.User, e.conf.Password)
 | 
			
		||||
			return auth
 | 
			
		||||
		case "LOGIN":
 | 
			
		||||
			auth := sasl.NewLoginClient(e.conf.User, e.conf.Password)
 | 
			
		||||
			return auth
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,115 +0,0 @@
 | 
			
		||||
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"},
 | 
			
		||||
			Cc:   []string{"cc@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", "cc@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"},
 | 
			
		||||
			Cc:   []string{"cc1@address.com", "cc2@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",
 | 
			
		||||
				"cc1@address.com", "cc2@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) && r.auth != nil {
 | 
			
		||||
			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
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user