2020-09-20 13:01:24 +02:00
|
|
|
package email
|
2018-10-25 15:51:47 +02:00
|
|
|
|
|
|
|
import (
|
2020-05-17 17:37:48 +02:00
|
|
|
"crypto/tls"
|
2018-10-25 15:51:47 +02:00
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
|
|
|
"net/smtp"
|
2020-05-31 17:46:56 +02:00
|
|
|
"net/textproto"
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2020-09-20 13:01:24 +02:00
|
|
|
"github.com/knadh/listmonk/internal/messenger"
|
2020-05-16 19:11:30 +02:00
|
|
|
"github.com/knadh/smtppool"
|
2018-10-25 15:51:47 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const emName = "email"
|
|
|
|
|
|
|
|
// Server represents an SMTP server's credentials.
|
|
|
|
type Server struct {
|
2020-05-31 17:46:56 +02:00
|
|
|
Username string `json:"username"`
|
|
|
|
Password string `json:"password"`
|
|
|
|
AuthProtocol string `json:"auth_protocol"`
|
|
|
|
TLSEnabled bool `json:"tls_enabled"`
|
|
|
|
TLSSkipVerify bool `json:"tls_skip_verify"`
|
|
|
|
EmailHeaders map[string]string `json:"email_headers"`
|
2020-05-16 19:11:30 +02:00
|
|
|
|
|
|
|
// Rest of the options are embedded directly from the smtppool lib.
|
|
|
|
// The JSON tag is for config unmarshal to work.
|
|
|
|
smtppool.Opt `json:",squash"`
|
|
|
|
|
|
|
|
pool *smtppool.Pool
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
|
|
|
|
2020-05-16 19:11:30 +02:00
|
|
|
// Emailer is the SMTP e-mail messenger.
|
|
|
|
type Emailer struct {
|
2020-07-08 13:00:14 +02:00
|
|
|
servers []*Server
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
|
|
|
|
2020-09-20 13:01:24 +02:00
|
|
|
// New returns an SMTP e-mail Messenger backend with a the given SMTP servers.
|
|
|
|
func New(servers ...Server) (*Emailer, error) {
|
2020-05-16 19:11:30 +02:00
|
|
|
e := &Emailer{
|
2020-07-08 13:00:14 +02:00
|
|
|
servers: make([]*Server, 0, len(servers)),
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
|
|
|
|
2020-05-17 17:37:48 +02:00
|
|
|
for _, srv := range servers {
|
|
|
|
s := srv
|
2018-10-25 15:51:47 +02:00
|
|
|
var auth smtp.Auth
|
2020-04-01 16:26:40 +02:00
|
|
|
switch s.AuthProtocol {
|
|
|
|
case "cram":
|
2018-10-25 15:51:47 +02:00
|
|
|
auth = smtp.CRAMMD5Auth(s.Username, s.Password)
|
2020-04-01 16:26:40 +02:00
|
|
|
case "plain":
|
2018-10-25 15:51:47 +02:00
|
|
|
auth = smtp.PlainAuth("", s.Username, s.Password, s.Host)
|
2020-04-01 16:26:40 +02:00
|
|
|
case "login":
|
2020-05-16 20:08:19 +02:00
|
|
|
auth = &smtppool.LoginAuth{Username: s.Username, Password: s.Password}
|
2020-08-01 13:15:29 +02:00
|
|
|
case "", "none":
|
2020-04-01 16:26:40 +02:00
|
|
|
default:
|
2020-05-11 17:30:06 +02:00
|
|
|
return nil, fmt.Errorf("unknown SMTP auth type '%s'", s.AuthProtocol)
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
2020-05-16 19:11:30 +02:00
|
|
|
s.Opt.Auth = auth
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2020-05-17 17:37:48 +02:00
|
|
|
// TLS config.
|
|
|
|
if s.TLSEnabled {
|
|
|
|
s.TLSConfig = &tls.Config{}
|
|
|
|
if s.TLSSkipVerify {
|
|
|
|
s.TLSConfig.InsecureSkipVerify = s.TLSSkipVerify
|
|
|
|
} else {
|
|
|
|
s.TLSConfig.ServerName = s.Host
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-16 19:11:30 +02:00
|
|
|
pool, err := smtppool.New(s.Opt)
|
2018-10-25 15:51:47 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-05-16 19:11:30 +02:00
|
|
|
s.pool = pool
|
2020-07-08 13:00:14 +02:00
|
|
|
e.servers = append(e.servers, &s)
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return e, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Name returns the Server's name.
|
2020-05-16 19:11:30 +02:00
|
|
|
func (e *Emailer) Name() string {
|
2018-10-25 15:51:47 +02:00
|
|
|
return emName
|
|
|
|
}
|
|
|
|
|
|
|
|
// Push pushes a message to the server.
|
2020-09-20 13:01:24 +02:00
|
|
|
func (e *Emailer) Push(m messenger.Message) error {
|
2018-10-25 15:51:47 +02:00
|
|
|
// If there are more than one SMTP servers, send to a random
|
|
|
|
// one from the list.
|
2020-07-08 13:00:14 +02:00
|
|
|
var (
|
|
|
|
ln = len(e.servers)
|
|
|
|
srv *Server
|
|
|
|
)
|
|
|
|
if ln > 1 {
|
|
|
|
srv = e.servers[rand.Intn(ln)]
|
2018-10-25 15:51:47 +02:00
|
|
|
} else {
|
2020-07-08 13:00:14 +02:00
|
|
|
srv = e.servers[0]
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
|
|
|
|
2019-07-18 09:10:48 +02:00
|
|
|
// Are there attachments?
|
2020-05-16 19:11:30 +02:00
|
|
|
var files []smtppool.Attachment
|
2020-08-01 14:24:51 +02:00
|
|
|
if m.Attachments != nil {
|
|
|
|
files = make([]smtppool.Attachment, 0, len(m.Attachments))
|
|
|
|
for _, f := range m.Attachments {
|
2020-05-16 19:11:30 +02:00
|
|
|
a := smtppool.Attachment{
|
2019-07-18 09:10:48 +02:00
|
|
|
Filename: f.Name,
|
|
|
|
Header: f.Header,
|
|
|
|
Content: make([]byte, len(f.Content)),
|
|
|
|
}
|
|
|
|
copy(a.Content, f.Content)
|
|
|
|
files = append(files, a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-16 19:11:30 +02:00
|
|
|
em := smtppool.Email{
|
2020-08-01 14:24:51 +02:00
|
|
|
From: m.From,
|
|
|
|
To: m.To,
|
|
|
|
Subject: m.Subject,
|
2019-07-18 09:10:48 +02:00
|
|
|
Attachments: files,
|
2020-04-10 00:27:39 +02:00
|
|
|
}
|
|
|
|
|
2020-08-01 14:24:51 +02:00
|
|
|
em.Headers = textproto.MIMEHeader{}
|
|
|
|
// Attach e-mail level headers.
|
|
|
|
if len(m.Headers) > 0 {
|
|
|
|
em.Headers = m.Headers
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attach SMTP level headers.
|
2020-05-31 17:46:56 +02:00
|
|
|
if len(srv.EmailHeaders) > 0 {
|
|
|
|
for k, v := range srv.EmailHeaders {
|
|
|
|
em.Headers.Set(k, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-30 10:29:21 +01:00
|
|
|
switch m.ContentType {
|
2020-04-10 00:27:39 +02:00
|
|
|
case "plain":
|
2021-01-30 10:29:21 +01:00
|
|
|
em.Text = []byte(m.Body)
|
2020-04-10 00:27:39 +02:00
|
|
|
default:
|
2020-08-01 14:24:51 +02:00
|
|
|
em.HTML = m.Body
|
2021-01-30 10:29:21 +01:00
|
|
|
if len(m.AltBody) > 0 {
|
|
|
|
em.Text = m.AltBody
|
|
|
|
}
|
2020-04-10 00:27:39 +02:00
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2020-05-16 19:11:30 +02:00
|
|
|
return srv.pool.Send(em)
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Flush flushes the message queue to the server.
|
2020-05-16 19:11:30 +02:00
|
|
|
func (e *Emailer) Flush() error {
|
2018-10-25 15:51:47 +02:00
|
|
|
return nil
|
|
|
|
}
|
2020-07-08 13:00:14 +02:00
|
|
|
|
|
|
|
// Close closes the SMTP pools.
|
|
|
|
func (e *Emailer) Close() error {
|
|
|
|
for _, s := range e.servers {
|
|
|
|
s.pool.Close()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|