215 lines
6.5 KiB
Go
215 lines
6.5 KiB
Go
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||
|
//
|
||
|
// All rights reserved.
|
||
|
//
|
||
|
// Redistribution and use in source and binary forms, with or without
|
||
|
// modification, are permitted provided that the following conditions are met:
|
||
|
//
|
||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||
|
// list of conditions and the following disclaimer.
|
||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||
|
// this list of conditions and the following disclaimer in the documentation
|
||
|
// and/or other materials provided with the distribution.
|
||
|
//
|
||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
||
|
package seelog
|
||
|
|
||
|
import (
|
||
|
"crypto/tls"
|
||
|
"crypto/x509"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"net/smtp"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// Default subject phrase for sending emails.
|
||
|
DefaultSubjectPhrase = "Diagnostic message from server: "
|
||
|
|
||
|
// Message subject pattern composed according to RFC 5321.
|
||
|
rfc5321SubjectPattern = "From: %s <%s>\nSubject: %s\n\n"
|
||
|
)
|
||
|
|
||
|
// smtpWriter is used to send emails via given SMTP-server.
|
||
|
type smtpWriter struct {
|
||
|
auth smtp.Auth
|
||
|
hostName string
|
||
|
hostPort string
|
||
|
hostNameWithPort string
|
||
|
senderAddress string
|
||
|
senderName string
|
||
|
recipientAddresses []string
|
||
|
caCertDirPaths []string
|
||
|
mailHeaders []string
|
||
|
subject string
|
||
|
}
|
||
|
|
||
|
// NewSMTPWriter returns a new SMTP-writer.
|
||
|
func NewSMTPWriter(sa, sn string, ras []string, hn, hp, un, pwd string, cacdps []string, subj string, headers []string) *smtpWriter {
|
||
|
return &smtpWriter{
|
||
|
auth: smtp.PlainAuth("", un, pwd, hn),
|
||
|
hostName: hn,
|
||
|
hostPort: hp,
|
||
|
hostNameWithPort: fmt.Sprintf("%s:%s", hn, hp),
|
||
|
senderAddress: sa,
|
||
|
senderName: sn,
|
||
|
recipientAddresses: ras,
|
||
|
caCertDirPaths: cacdps,
|
||
|
subject: subj,
|
||
|
mailHeaders: headers,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func prepareMessage(senderAddr, senderName, subject string, body []byte, headers []string) []byte {
|
||
|
headerLines := fmt.Sprintf(rfc5321SubjectPattern, senderName, senderAddr, subject)
|
||
|
// Build header lines if configured.
|
||
|
if headers != nil && len(headers) > 0 {
|
||
|
headerLines += strings.Join(headers, "\n")
|
||
|
headerLines += "\n"
|
||
|
}
|
||
|
return append([]byte(headerLines), body...)
|
||
|
}
|
||
|
|
||
|
// getTLSConfig gets paths of PEM files with certificates,
|
||
|
// host server name and tries to create an appropriate TLS.Config.
|
||
|
func getTLSConfig(pemFileDirPaths []string, hostName string) (config *tls.Config, err error) {
|
||
|
if pemFileDirPaths == nil || len(pemFileDirPaths) == 0 {
|
||
|
err = errors.New("invalid PEM file paths")
|
||
|
return
|
||
|
}
|
||
|
pemEncodedContent := []byte{}
|
||
|
var (
|
||
|
e error
|
||
|
bytes []byte
|
||
|
)
|
||
|
// Create a file-filter-by-extension, set aside non-pem files.
|
||
|
pemFilePathFilter := func(fp string) bool {
|
||
|
if filepath.Ext(fp) == ".pem" {
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
for _, pemFileDirPath := range pemFileDirPaths {
|
||
|
pemFilePaths, err := getDirFilePaths(pemFileDirPath, pemFilePathFilter, false)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Put together all the PEM files to decode them as a whole byte slice.
|
||
|
for _, pfp := range pemFilePaths {
|
||
|
if bytes, e = ioutil.ReadFile(pfp); e == nil {
|
||
|
pemEncodedContent = append(pemEncodedContent, bytes...)
|
||
|
} else {
|
||
|
return nil, fmt.Errorf("cannot read file: %s: %s", pfp, e.Error())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
config = &tls.Config{RootCAs: x509.NewCertPool(), ServerName: hostName}
|
||
|
isAppended := config.RootCAs.AppendCertsFromPEM(pemEncodedContent)
|
||
|
if !isAppended {
|
||
|
// Extract this into a separate error.
|
||
|
err = errors.New("invalid PEM content")
|
||
|
return
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// SendMail accepts TLS configuration, connects to the server at addr,
|
||
|
// switches to TLS if possible, authenticates with mechanism a if possible,
|
||
|
// and then sends an email from address from, to addresses to, with message msg.
|
||
|
func sendMailWithTLSConfig(config *tls.Config, addr string, a smtp.Auth, from string, to []string, msg []byte) error {
|
||
|
c, err := smtp.Dial(addr)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// Check if the server supports STARTTLS extension.
|
||
|
if ok, _ := c.Extension("STARTTLS"); ok {
|
||
|
if err = c.StartTLS(config); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
// Check if the server supports AUTH extension and use given smtp.Auth.
|
||
|
if a != nil {
|
||
|
if isSupported, _ := c.Extension("AUTH"); isSupported {
|
||
|
if err = c.Auth(a); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Portion of code from the official smtp.SendMail function,
|
||
|
// see http://golang.org/src/pkg/net/smtp/smtp.go.
|
||
|
if err = c.Mail(from); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
for _, addr := range to {
|
||
|
if err = c.Rcpt(addr); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
w, err := c.Data()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
_, err = w.Write(msg)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
err = w.Close()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return c.Quit()
|
||
|
}
|
||
|
|
||
|
// Write pushes a text message properly composed according to RFC 5321
|
||
|
// to a post server, which sends it to the recipients.
|
||
|
func (smtpw *smtpWriter) Write(data []byte) (int, error) {
|
||
|
var err error
|
||
|
|
||
|
if smtpw.caCertDirPaths == nil {
|
||
|
err = smtp.SendMail(
|
||
|
smtpw.hostNameWithPort,
|
||
|
smtpw.auth,
|
||
|
smtpw.senderAddress,
|
||
|
smtpw.recipientAddresses,
|
||
|
prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders),
|
||
|
)
|
||
|
} else {
|
||
|
config, e := getTLSConfig(smtpw.caCertDirPaths, smtpw.hostName)
|
||
|
if e != nil {
|
||
|
return 0, e
|
||
|
}
|
||
|
err = sendMailWithTLSConfig(
|
||
|
config,
|
||
|
smtpw.hostNameWithPort,
|
||
|
smtpw.auth,
|
||
|
smtpw.senderAddress,
|
||
|
smtpw.recipientAddresses,
|
||
|
prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders),
|
||
|
)
|
||
|
}
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
return len(data), nil
|
||
|
}
|
||
|
|
||
|
// Close closes down SMTP-connection.
|
||
|
func (smtpw *smtpWriter) Close() error {
|
||
|
// Do nothing as Write method opens and closes connection automatically.
|
||
|
return nil
|
||
|
}
|