Allow email relaying to a real MTA
This commit is contained in:
164
internal/command/relay_email.go
Normal file
164
internal/command/relay_email.go
Normal file
@ -0,0 +1,164 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/emersion/go-sasl"
|
||||
|
||||
"github.com/emersion/go-smtp"
|
||||
|
||||
"forge.cadoles.com/wpetit/fake-smtp/internal/config"
|
||||
"github.com/jhillyerd/enmime"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/cqrs"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
)
|
||||
|
||||
type RelayEmailRequest struct {
|
||||
Envelope *enmime.Envelope
|
||||
}
|
||||
|
||||
func HandleRelayEmail(ctx context.Context, cmd cqrs.Command) error {
|
||||
req, ok := cmd.Request().(*RelayEmailRequest)
|
||||
if !ok {
|
||||
return cqrs.ErrUnexpectedRequest
|
||||
}
|
||||
|
||||
ctn, err := container.From(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not retrieve service container")
|
||||
}
|
||||
|
||||
conf, err := config.From(ctn)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not retrieve config service")
|
||||
}
|
||||
|
||||
relay := conf.Relay
|
||||
|
||||
if err := forwardMail(req.Envelope, relay); err != nil {
|
||||
return errors.Wrap(err, "could not forward mail")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func forwardMail(env *enmime.Envelope, conf config.RelayConfig) error {
|
||||
var tlsConfig *tls.Config
|
||||
|
||||
if conf.InsecureSkipVerify {
|
||||
tlsConfig = &tls.Config{
|
||||
// nolint: gosec
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
|
||||
addr := conf.Address
|
||||
|
||||
var (
|
||||
client *smtp.Client
|
||||
err error
|
||||
)
|
||||
|
||||
if conf.UseTLS {
|
||||
client, err = smtp.DialTLS(addr, tlsConfig)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
} else {
|
||||
client, err = smtp.Dial(addr)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
if err = client.Hello("localhost"); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if ok, _ := client.Extension("STARTTLS"); ok {
|
||||
if err = client.StartTLS(tlsConfig); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
if conf.Username != "" || conf.Password != "" {
|
||||
if ok, _ := client.Extension("AUTH"); ok {
|
||||
var auth sasl.Client
|
||||
if conf.Anonymous {
|
||||
auth = sasl.NewAnonymousClient("fakesmtp")
|
||||
} else {
|
||||
auth = sasl.NewPlainClient(conf.Identity, conf.Username, conf.Password)
|
||||
}
|
||||
|
||||
if err := client.Auth(auth); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var from string
|
||||
|
||||
if conf.FromOverride != "" {
|
||||
from = conf.FromOverride
|
||||
} else {
|
||||
from = env.GetHeader("From")
|
||||
}
|
||||
|
||||
if err = client.Mail(from, nil); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
to := env.GetHeaderValues("To")
|
||||
|
||||
for _, addr := range to {
|
||||
if err = client.Rcpt(addr); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
w, err := client.Data()
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer pw.Close()
|
||||
|
||||
if err = env.Root.Encode(pw); err != nil {
|
||||
err = errors.WithStack(err)
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = io.Copy(w, pr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err = w.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := client.Quit(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -12,9 +12,10 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
HTTP HTTPConfig `yaml:"http"`
|
||||
SMTP SMTPConfig `yaml:"smtp"`
|
||||
Data DataConfig `ymal:"data"`
|
||||
HTTP HTTPConfig `yaml:"http"`
|
||||
SMTP SMTPConfig `yaml:"smtp"`
|
||||
Data DataConfig `yaml:"data"`
|
||||
Relay RelayConfig `yaml:"relay"`
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
@ -36,6 +37,18 @@ type SMTPConfig struct {
|
||||
Debug bool `yaml:"debug" env:"FAKESMTP_SMTP_DEBUG"`
|
||||
}
|
||||
|
||||
type RelayConfig struct {
|
||||
Enabled bool `yaml:"enabled" env:"FAKESMTP_RELAY_ENABLED"`
|
||||
Address string `yaml:"address" env:"FAKESMTP_RELAY_ADDRESS"`
|
||||
Identity string `yaml:"identity" env:"FAKESMTP_RELAY_IDENTITY"`
|
||||
Username string `yaml:"username" env:"FAKESMTP_RELAY_USERNAME"`
|
||||
Password string `yaml:"password" env:"FAKESMTP_RELAY_PASSWORD"`
|
||||
Anonymous bool `yaml:"anonymous" env:"FAKESMTP_RELAY_ANONYMOUS"`
|
||||
UseTLS bool `yaml:"useTLS" env:"FAKESMTP_RELAY_USE_TLS"`
|
||||
InsecureSkipVerify bool `yaml:"insecureSkipVerify" env:"FAKESMTP_RELAY_INSECURE_SKIP_VERIFY"`
|
||||
FromOverride string `yaml:"fromOverride" env:"FAKESMTP_RELAY_FROM_OVERRIDE"`
|
||||
}
|
||||
|
||||
type DataConfig struct {
|
||||
Path string `yaml:"path" env:"FAKESMTP_DATA_PATH"`
|
||||
}
|
||||
@ -91,6 +104,9 @@ func NewDefault() *Config {
|
||||
Data: DataConfig{
|
||||
Path: "fakesmtp.db",
|
||||
},
|
||||
Relay: RelayConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user