Allow email relaying to a real MTA
This commit is contained in:
parent
9035280818
commit
62344993f5
|
@ -53,6 +53,11 @@ func getServiceContainer(conf *config.Config) (*service.Container, error) {
|
||||||
cqrs.CommandHandlerFunc(command.HandleDeleteEmail),
|
cqrs.CommandHandlerFunc(command.HandleDeleteEmail),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bus.RegisterCommand(
|
||||||
|
cqrs.MatchCommandRequest(&command.RelayEmailRequest{}),
|
||||||
|
cqrs.CommandHandlerFunc(command.HandleRelayEmail),
|
||||||
|
)
|
||||||
|
|
||||||
bus.RegisterQuery(
|
bus.RegisterQuery(
|
||||||
cqrs.MatchQueryRequest(&query.GetInboxRequest{}),
|
cqrs.MatchQueryRequest(&query.GetInboxRequest{}),
|
||||||
cqrs.QueryHandlerFunc(query.HandleGetInbox),
|
cqrs.QueryHandlerFunc(query.HandleGetInbox),
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/jhillyerd/enmime"
|
"github.com/jhillyerd/enmime"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
"gitlab.com/wpetit/goweb/cqrs"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
"gitlab.com/wpetit/goweb/service"
|
"gitlab.com/wpetit/goweb/service"
|
||||||
)
|
)
|
||||||
|
@ -74,12 +75,31 @@ func (s *Session) Data(r io.Reader) error {
|
||||||
return errors.Wrap(err, "could not retrieve cqrs service")
|
return errors.Wrap(err, "could not retrieve cqrs service")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conf, err := config.From(s.ctn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not retrieve config service")
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.Relay.Enabled {
|
||||||
|
cmd := &command.RelayEmailRequest{
|
||||||
|
Envelope: env,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := bus.Exec(s.ctx, cmd); err != nil {
|
||||||
|
logger.Error(s.ctx, "could not exec command", logger.E(err))
|
||||||
|
|
||||||
|
return errors.Wrapf(err, "could not exec '%T' command", cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cmd := &command.StoreEmailRequest{
|
cmd := &command.StoreEmailRequest{
|
||||||
Envelope: env,
|
Envelope: env,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := bus.Exec(s.ctx, cmd); err != nil {
|
if _, err := bus.Exec(s.ctx, cmd); err != nil {
|
||||||
return errors.Wrap(err, "could not exec command")
|
logger.Error(s.ctx, "could not exec command", logger.E(err))
|
||||||
|
|
||||||
|
return errors.Wrapf(err, "could not exec '%T' command", cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -5,7 +5,7 @@ go 1.14
|
||||||
require (
|
require (
|
||||||
github.com/asdine/storm/v3 v3.1.1
|
github.com/asdine/storm/v3 v3.1.1
|
||||||
github.com/caarlos0/env/v6 v6.2.1
|
github.com/caarlos0/env/v6 v6.2.1
|
||||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b // indirect
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
||||||
github.com/emersion/go-smtp v0.12.1
|
github.com/emersion/go-smtp v0.12.1
|
||||||
github.com/go-chi/chi v4.1.1+incompatible
|
github.com/go-chi/chi v4.1.1+incompatible
|
||||||
github.com/jhillyerd/enmime v0.8.0
|
github.com/jhillyerd/enmime v0.8.0
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -53,8 +53,8 @@ github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk
|
||||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e h1:ba7YsgX5OV8FjGi5ZWml8Jng6oBrJAb3ahqWMJ5Ce8Q=
|
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e h1:ba7YsgX5OV8FjGi5ZWml8Jng6oBrJAb3ahqWMJ5Ce8Q=
|
||||||
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
||||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b h1:uhWtEWBHgop1rqEk2klKaxPAkVDCXexai6hSuRQ7Nvs=
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-smtp v0.12.1 h1:1R8BDqrR2HhlGwgFYcOi+BVTvK1bMjAB65QcVpJ5sNA=
|
github.com/emersion/go-smtp v0.12.1 h1:1R8BDqrR2HhlGwgFYcOi+BVTvK1bMjAB65QcVpJ5sNA=
|
||||||
github.com/emersion/go-smtp v0.12.1/go.mod h1:SD9V/xa4ndMw77lR3Mf7htkp8RBNYuPh9UeuBs9tpUQ=
|
github.com/emersion/go-smtp v0.12.1/go.mod h1:SD9V/xa4ndMw77lR3Mf7htkp8RBNYuPh9UeuBs9tpUQ=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -14,7 +14,8 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
HTTP HTTPConfig `yaml:"http"`
|
HTTP HTTPConfig `yaml:"http"`
|
||||||
SMTP SMTPConfig `yaml:"smtp"`
|
SMTP SMTPConfig `yaml:"smtp"`
|
||||||
Data DataConfig `ymal:"data"`
|
Data DataConfig `yaml:"data"`
|
||||||
|
Relay RelayConfig `yaml:"relay"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
|
@ -36,6 +37,18 @@ type SMTPConfig struct {
|
||||||
Debug bool `yaml:"debug" env:"FAKESMTP_SMTP_DEBUG"`
|
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 {
|
type DataConfig struct {
|
||||||
Path string `yaml:"path" env:"FAKESMTP_DATA_PATH"`
|
Path string `yaml:"path" env:"FAKESMTP_DATA_PATH"`
|
||||||
}
|
}
|
||||||
|
@ -91,6 +104,9 @@ func NewDefault() *Config {
|
||||||
Data: DataConfig{
|
Data: DataConfig{
|
||||||
Path: "fakesmtp.db",
|
Path: "fakesmtp.db",
|
||||||
},
|
},
|
||||||
|
Relay: RelayConfig{
|
||||||
|
Enabled: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ for i in {1..10}; do
|
||||||
-s "Test ${i}" \
|
-s "Test ${i}" \
|
||||||
-a README.md \
|
-a README.md \
|
||||||
-a package.json \
|
-a package.json \
|
||||||
foo_${i}@bar_${i}.com \
|
foo_${i}@bar${i}.com \
|
||||||
<<EOF
|
<<EOF
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus eget metus ornare, placerat mi nec, ornare magna. Vivamus volutpat nisi et aliquet mollis. Phasellus eu malesuada erat, vel molestie ipsum. Etiam tincidunt ligula eget scelerisque blandit. Vivamus nec quam vitae felis rutrum cursus a eget elit. Integer vestibulum ultrices iaculis. Integer varius sapien ac ante accumsan euismod. Quisque fermentum dui nec porttitor pellentesque. Vivamus lobortis mauris eget metus sagittis tempor.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus eget metus ornare, placerat mi nec, ornare magna. Vivamus volutpat nisi et aliquet mollis. Phasellus eu malesuada erat, vel molestie ipsum. Etiam tincidunt ligula eget scelerisque blandit. Vivamus nec quam vitae felis rutrum cursus a eget elit. Integer vestibulum ultrices iaculis. Integer varius sapien ac ante accumsan euismod. Quisque fermentum dui nec porttitor pellentesque. Vivamus lobortis mauris eget metus sagittis tempor.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue