2021-12-27 16:39:28 +01:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"github.com/emersion/go-smtp"
|
|
|
|
"io"
|
|
|
|
"net/mail"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
2021-12-27 22:06:40 +01:00
|
|
|
var (
|
|
|
|
errInvalidDomain = errors.New("invalid domain")
|
|
|
|
errInvalidAddress = errors.New("invalid address")
|
|
|
|
errInvalidTopic = errors.New("invalid topic")
|
|
|
|
errTooManyRecipients = errors.New("too many recipients")
|
|
|
|
)
|
|
|
|
|
2021-12-27 16:39:28 +01:00
|
|
|
// smtpBackend implements SMTP server methods.
|
|
|
|
type smtpBackend struct {
|
2021-12-27 22:06:40 +01:00
|
|
|
config *Config
|
|
|
|
sub subscriber
|
|
|
|
success int64
|
|
|
|
failure int64
|
|
|
|
mu sync.Mutex
|
2021-12-27 16:39:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func newMailBackend(conf *Config, sub subscriber) *smtpBackend {
|
|
|
|
return &smtpBackend{
|
|
|
|
config: conf,
|
|
|
|
sub: sub,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *smtpBackend) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
|
2021-12-27 22:06:40 +01:00
|
|
|
return &smtpSession{backend: b}, nil
|
2021-12-27 16:39:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *smtpBackend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
|
2021-12-27 22:06:40 +01:00
|
|
|
return &smtpSession{backend: b}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *smtpBackend) Counts() (success int64, failure int64) {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
return b.success, b.failure
|
2021-12-27 16:39:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// smtpSession is returned after EHLO.
|
|
|
|
type smtpSession struct {
|
2021-12-27 22:06:40 +01:00
|
|
|
backend *smtpBackend
|
|
|
|
topic string
|
|
|
|
mu sync.Mutex
|
2021-12-27 16:39:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *smtpSession) AuthPlain(username, password string) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *smtpSession) Mail(from string, opts smtp.MailOptions) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *smtpSession) Rcpt(to string) error {
|
2021-12-27 22:06:40 +01:00
|
|
|
return s.withFailCount(func() error {
|
|
|
|
conf := s.backend.config
|
|
|
|
addressList, err := mail.ParseAddressList(to)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
} else if len(addressList) != 1 {
|
|
|
|
return errTooManyRecipients
|
|
|
|
}
|
|
|
|
to = addressList[0].Address
|
|
|
|
if !strings.HasSuffix(to, "@"+conf.SMTPServerDomain) {
|
|
|
|
return errInvalidDomain
|
|
|
|
}
|
|
|
|
to = strings.TrimSuffix(to, "@"+conf.SMTPServerDomain)
|
|
|
|
if conf.SMTPServerAddrPrefix != "" {
|
|
|
|
if !strings.HasPrefix(to, conf.SMTPServerAddrPrefix) {
|
|
|
|
return errInvalidAddress
|
|
|
|
}
|
|
|
|
to = strings.TrimPrefix(to, conf.SMTPServerAddrPrefix)
|
|
|
|
}
|
|
|
|
if !topicRegex.MatchString(to) {
|
|
|
|
return errInvalidTopic
|
|
|
|
}
|
|
|
|
s.mu.Lock()
|
|
|
|
s.topic = to
|
|
|
|
s.mu.Unlock()
|
|
|
|
return nil
|
|
|
|
})
|
2021-12-27 16:39:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *smtpSession) Data(r io.Reader) error {
|
2021-12-27 22:06:40 +01:00
|
|
|
return s.withFailCount(func() error {
|
|
|
|
b, err := io.ReadAll(r) // Protected by MaxMessageBytes
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
msg, err := mail.ReadMessage(bytes.NewReader(b))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
body, err := io.ReadAll(io.LimitReader(msg.Body, int64(s.backend.config.MessageLimit)))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
m := newDefaultMessage(s.topic, string(body))
|
|
|
|
subject := msg.Header.Get("Subject")
|
|
|
|
if subject != "" {
|
|
|
|
m.Title = subject
|
|
|
|
}
|
|
|
|
if err := s.backend.sub(m); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.backend.mu.Lock()
|
|
|
|
s.backend.success++
|
|
|
|
s.backend.mu.Unlock()
|
|
|
|
return nil
|
|
|
|
})
|
2021-12-27 16:39:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *smtpSession) Reset() {
|
|
|
|
s.mu.Lock()
|
2021-12-27 22:06:40 +01:00
|
|
|
s.topic = ""
|
2021-12-27 16:39:28 +01:00
|
|
|
s.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *smtpSession) Logout() error {
|
|
|
|
return nil
|
|
|
|
}
|
2021-12-27 22:06:40 +01:00
|
|
|
|
|
|
|
func (s *smtpSession) withFailCount(fn func() error) error {
|
|
|
|
err := fn()
|
|
|
|
s.backend.mu.Lock()
|
|
|
|
defer s.backend.mu.Unlock()
|
|
|
|
if err != nil {
|
|
|
|
s.backend.failure++
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|