Added support for sending with relay hosts

This commit is contained in:
Niels Bouma
2023-05-10 19:33:05 +00:00
committed by Aine
parent 84102d5b5b
commit ee8d8680ac
7 changed files with 153 additions and 36 deletions

View File

@@ -70,6 +70,10 @@ env vars
* **POSTMOOGLE_MAILBOXES_ACTIVATION** - activation flow for new mailboxes, [docs/mailboxes.md](docs/mailboxes.md)
* **POSTMOOGLE_MAXSIZE** - max email size (including attachments) in megabytes
* **POSTMOOGLE_ADMINS** - a space-separated list of admin users. See `POSTMOOGLE_USERS` for syntax examples
* **POSTMOOGLE_RELAY_HOST** - SMTP hostname of relay host (e.g. Sendgrid)
* **POSTMOOGLE_RELAY_PORT** - SMTP port of relay host
* **POSTMOOGLE_RELAY_USERNAME** - Username of relay host
* **POSTMOOGLE_RELAY_PASSWORD** - Password of relay host
You can find default values in [config/defaults.go](config/defaults.go)

View File

@@ -143,6 +143,12 @@ func initSMTP(cfg *config.Config) {
MaxSize: cfg.MaxSize,
Bot: mxb,
Callers: []smtp.Caller{mxb, q},
Relay: smtp.RelayConfig{
Host: cfg.Relay.Host,
Port: cfg.Relay.Port,
Usename: cfg.Relay.Username,
Password: cfg.Relay.Password,
},
})
}

View File

@@ -46,6 +46,12 @@ func New() *Config {
DSN: env.String("db.dsn", defaultConfig.DB.DSN),
Dialect: env.String("db.dialect", defaultConfig.DB.Dialect),
},
Relay: Relay{
Host: env.String("relay.host", defaultConfig.Relay.Host),
Port: env.String("relay.port", defaultConfig.Relay.Port),
Username: env.String("relay.username", defaultConfig.Relay.Username),
Password: env.String("relay.password", defaultConfig.Relay.Password),
},
}
return cfg

View File

@@ -41,6 +41,8 @@ type Config struct {
// Monitoring config
Monitoring Monitoring
Relay Relay
}
// DB config
@@ -72,3 +74,11 @@ type Mailboxes struct {
Reserved []string
Activation string
}
// Relay config
type Relay struct {
Host string
Port string
Username string
Password string
}

107
smtp/client.go Normal file
View File

@@ -0,0 +1,107 @@
package smtp
import (
"crypto/tls"
"io"
"net/smtp"
"strings"
"gitlab.com/etke.cc/go/logger"
"gitlab.com/etke.cc/go/trysmtp"
)
type MailSender interface {
Send(from string, to string, data string) error
}
type Client struct {
config *RelayConfig
log *logger.Logger
}
func newClient(cfg *RelayConfig, log *logger.Logger) Client {
return Client{
config: cfg,
log: log,
}
}
func (c Client) Send(from string, to string, data string) error {
c.log.Debug("Sending email from %s to %s", from, to)
conn, err := c.createSmtpClient(from, to)
if conn == nil {
c.log.Error("cannot connect to SMTP server of %s: %v", to, err)
return err
}
if err != nil {
c.log.Warn("connection to the SMTP server of %s returned the following non-fatal error(-s): %v", err)
}
defer conn.Close()
var w io.WriteCloser
w, err = conn.Data()
if err != nil {
c.log.Error("cannot send DATA command: %v", err)
return err
}
defer w.Close()
c.log.Debug("sending DATA:\n%s", data)
_, err = strings.NewReader(data).WriteTo(w)
if err != nil {
c.log.Debug("cannot write DATA: %v", err)
return err
}
c.log.Debug("email has been sent")
return nil
}
func (c *Client) createSmtpClient(from string, to string) (*smtp.Client, error) {
if c.config.Host != "" {
return c.createDirectClient(from, to)
}
return trysmtp.Connect(from, to)
}
func (c *Client) createDirectClient(from string, to string) (*smtp.Client, error) {
localname := strings.SplitN(from, "@", 2)[1]
target := c.config.Host + ":" + c.config.Port
conn, err := smtp.Dial(target)
if err != nil {
return nil, err
}
err = conn.Hello(localname)
if err != nil {
return nil, err
}
if ok, _ := conn.Extension("STARTTLS"); ok {
config := &tls.Config{ServerName: c.config.Host}
conn.StartTLS(config) //nolint:errcheck // if it doesn't work - we can't do anything anyway
}
if c.config.Usename != "" {
err = conn.Auth(smtp.PlainAuth("", c.config.Usename, c.config.Password, c.config.Host))
if err != nil {
conn.Close()
return nil, err
}
}
err = conn.Mail(from)
if err != nil {
conn.Close()
return nil, err
}
err = conn.Rcpt(to)
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
}

View File

@@ -29,6 +29,7 @@ type Config struct {
MaxSize int
Bot matrixbot
Callers []Caller
Relay RelayConfig
}
type TLSConfig struct {
@@ -40,6 +41,13 @@ type TLSConfig struct {
Mu sync.Mutex
}
type RelayConfig struct {
Host string
Port string
Usename string
Password string
}
type Manager struct {
log *logger.Logger
bot matrixbot
@@ -71,10 +79,14 @@ type Caller interface {
// NewManager creates new SMTP server manager
func NewManager(cfg *Config) *Manager {
log := logger.New("smtp.", cfg.LogLevel)
smtpClient := newClient(&cfg.Relay, log)
mailsrv := &mailServer{
log: log,
bot: cfg.Bot,
domains: cfg.Domains,
log: log,
bot: cfg.Bot,
domains: cfg.Domains,
mailSender: smtpClient,
}
for _, caller := range cfg.Callers {
caller.SetSendmail(mailsrv.SendEmail)

View File

@@ -2,13 +2,10 @@ package smtp
import (
"context"
"io"
"strings"
"github.com/emersion/go-smtp"
"github.com/getsentry/sentry-go"
"gitlab.com/etke.cc/go/logger"
"gitlab.com/etke.cc/go/trysmtp"
"gitlab.com/etke.cc/postmoogle/email"
)
@@ -29,9 +26,10 @@ var (
)
type mailServer struct {
bot matrixbot
log *logger.Logger
domains []string
bot matrixbot
log *logger.Logger
domains []string
mailSender MailSender
}
// Login used for outgoing mail submissions only (when you use postmoogle as smtp server in your scripts)
@@ -91,33 +89,7 @@ func (m *mailServer) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session,
// SendEmail to external mail server
func (m *mailServer) SendEmail(from, to, data string) error {
m.log.Debug("Sending email from %s to %s", from, to)
conn, err := trysmtp.Connect(from, to)
if conn == nil {
m.log.Error("cannot connect to SMTP server of %s: %v", to, err)
return err
}
if err != nil {
m.log.Warn("connection to the SMTP server of %s returned the following non-fatal error(-s): %v", err)
}
defer conn.Close()
var w io.WriteCloser
w, err = conn.Data()
if err != nil {
m.log.Error("cannot send DATA command: %v", err)
return err
}
defer w.Close()
m.log.Debug("sending DATA:\n%s", data)
_, err = strings.NewReader(data).WriteTo(w)
if err != nil {
m.log.Debug("cannot write DATA: %v", err)
return err
}
m.log.Debug("email has been sent")
return nil
return m.mailSender.Send(from, to, data)
}
// ReceiveEmail - incoming mail into matrix room