mirror of
https://github.com/wiggin77/mailrelay.git
synced 2024-11-25 00:23:09 +01:00
143 lines
3.1 KiB
Go
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
|
|
}
|