mailrelay/client.go
2020-05-17 00:15:49 -04:00

143 lines
3.1 KiB
Go

package main
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"net"
"net/smtp"
"net/textproto"
"github.com/flashmob/go-guerrilla/mail"
"github.com/pkg/errors"
)
type closeable interface {
Close() error
}
// sendMail sends the contents of the envelope to a SMTP server.
func sendMail(e *mail.Envelope, config *relayConfig) error {
server := fmt.Sprintf("%s:%d", config.Server, config.Port)
to := getTo(e)
var msg bytes.Buffer
msg.Write(e.Data.Bytes())
msg.WriteString("\r\n")
Logger.Infof("starting email send -- from:%s, starttls:%t", e.MailFrom.String(), config.STARTTLS)
var err error
var conn net.Conn
var client *smtp.Client
var writer io.WriteCloser
tlsconfig := &tls.Config{
InsecureSkipVerify: config.SkipVerify, //nolint:gosec
ServerName: config.Server,
}
if config.STARTTLS {
if conn, err = net.Dial("tcp", server); err != nil {
return errors.Wrap(err, "dial error")
}
} else {
if conn, err = tls.Dial("tcp", server, tlsconfig); err != nil {
return errors.Wrap(err, "TLS dial error")
}
}
if client, err = smtp.NewClient(conn, config.Server); err != nil {
close(conn, "conn")
return errors.Wrap(err, "newclient error")
}
shouldCloseClient := true
defer func(shouldClose *bool) {
if *shouldClose {
close(client, "client")
}
}(&shouldCloseClient)
if err = handshake(client, config, tlsconfig); err != nil {
return err
}
if err = client.Mail(e.MailFrom.String()); err != nil {
return errors.Wrap(err, "mail error")
}
for _, addy := range to {
if err = client.Rcpt(addy); err != nil {
return errors.Wrap(err, "rcpt error")
}
}
if writer, err = client.Data(); err != nil {
return errors.Wrap(err, "data error")
}
_, err = writer.Write(msg.Bytes())
close(writer, "writer")
if err != nil {
return errors.Wrap(err, "write error")
}
if err = client.Quit(); isQuitError(err) {
return errors.Wrap(err, "quit error")
}
// We only need to close client if some other error prevented us
// from getting to `client.Quit`
shouldCloseClient = false
Logger.Info("email sent with no errors.")
return nil
}
func handshake(client *smtp.Client, config *relayConfig, tlsConfig *tls.Config) error {
if config.STARTTLS {
if err := client.StartTLS(tlsConfig); err != nil {
return errors.Wrap(err, "starttls error")
}
}
var auth smtp.Auth
if config.LoginAuthType {
auth = LoginAuth(config.Username, config.Password)
} else {
auth = smtp.PlainAuth("", config.Username, config.Password, config.Server)
}
if err := client.Auth(auth); err != nil {
return errors.Wrap(err, "auth error")
}
return nil
}
func close(c closeable, what string) {
err := c.Close()
if err != nil {
fmt.Printf("Error closing %s: %v\n", what, err)
}
}
func isQuitError(err error) bool {
if err == nil {
return false
}
e, ok := err.(*textproto.Error)
if ok {
// SMTP codes 221 or 250 are acceptable here
if e.Code == 221 || e.Code == 250 {
return false
}
}
return true
}
// getTo returns the array of email addresses in the envelope.
func getTo(e *mail.Envelope) []string {
var ret []string
for i := range e.RcptTo {
ret = append(ret, e.RcptTo[i].String())
}
return ret
}