mirror of
https://github.com/jhillyerd/inbucket.git
synced 2026-10-29 05:32:31 +00:00
bd4cc4b842
* feat(luahost): capture config settings set by Lua scripts Add inbucket.config, a Lua-settable settings namespace, with its first field decode_headers registering additional message headers for Inbucket to decode. Values are captured once at startup from the first pooled LState and exposed to the assembly via Host.ScriptConfig(). Settings that only have value to the Lua script belong here rather than in env config. No consumers of decode_headers yet; that follows. * feat(message): expose decoded headers to extensions StoreManager gains ExtraDecodeHeaders, passed through to enmime.DecodeHeaders, and the decoded header set (defaults plus any registered extras) is attached to the InboundMessage event as Headers (net/textproto.MIMEHeader). Lua scripts access it via the read-only inbound_message.header field, added with the new inbound_header binding. Lookups are exact-then- case-insensitive, since enmime canonicalizes keys with email-specific rules that differ from net/textproto. FullAssembly wires ScriptConfig().DecodeHeaders into StoreManager. Part of #627. * docs(extension): document shared and untrusted header semantics Reviewer note: BeforeMessageStored listeners receive the same decoded header map, so a Go listener's mutations are visible to later listeners. Also record that sender-supplied header values (Authentication-Results, X-Spam-Status) are untrusted unless the deployment controls provenance. Part of #627. --------- Co-authored-by: hillyerd-bot <hillyerd-bot@users.noreply.github.com>
140 lines
4.1 KiB
Go
140 lines
4.1 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
"github.com/inbucket/inbucket/v3/pkg/config"
|
|
"github.com/inbucket/inbucket/v3/pkg/extension"
|
|
"github.com/inbucket/inbucket/v3/pkg/extension/luahost"
|
|
"github.com/inbucket/inbucket/v3/pkg/message"
|
|
"github.com/inbucket/inbucket/v3/pkg/msghub"
|
|
"github.com/inbucket/inbucket/v3/pkg/policy"
|
|
"github.com/inbucket/inbucket/v3/pkg/rest"
|
|
"github.com/inbucket/inbucket/v3/pkg/server/pop3"
|
|
"github.com/inbucket/inbucket/v3/pkg/server/smtp"
|
|
"github.com/inbucket/inbucket/v3/pkg/server/web"
|
|
"github.com/inbucket/inbucket/v3/pkg/storage"
|
|
"github.com/inbucket/inbucket/v3/pkg/stringutil"
|
|
"github.com/inbucket/inbucket/v3/pkg/webui"
|
|
)
|
|
|
|
// Services holds the configured services.
|
|
type Services struct {
|
|
MsgHub *msghub.Hub
|
|
POP3Server *pop3.Server
|
|
RetentionScanner *storage.RetentionScanner
|
|
SMTPServer *smtp.Server
|
|
WebServer *web.Server
|
|
ExtHost *extension.Host
|
|
LuaHost *luahost.Host
|
|
notify chan error // Combined notification for failed services.
|
|
ready *sync.WaitGroup // Tracks services that have not reported ready.
|
|
}
|
|
|
|
// FullAssembly wires up a complete Inbucket environment.
|
|
func FullAssembly(conf *config.Root) (*Services, error) {
|
|
// Configure extensions.
|
|
extHost := extension.NewHost()
|
|
luaHost, err := luahost.New(conf.Lua, extHost)
|
|
if err != nil && err != luahost.ErrNoScript {
|
|
return nil, err
|
|
}
|
|
|
|
// Configure storage.
|
|
store, err := storage.FromConfig(conf.Storage, extHost)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
addrPolicy := &policy.Addressing{Config: conf}
|
|
// Configure shared components.
|
|
msgHub := msghub.New(conf.Web.MonitorHistory, extHost)
|
|
mmanager := &message.StoreManager{
|
|
AddrPolicy: addrPolicy,
|
|
Store: store,
|
|
ExtHost: extHost,
|
|
}
|
|
if luaHost != nil {
|
|
// Lua scripts may register extra headers to decode.
|
|
mmanager.ExtraDecodeHeaders = luaHost.ScriptConfig().DecodeHeaders
|
|
}
|
|
|
|
// Start Retention scanner.
|
|
retentionScanner := storage.NewRetentionScanner(conf.Storage, store)
|
|
|
|
// Configure routes and build HTTP server.
|
|
prefix := stringutil.MakePathPrefixer(conf.Web.BasePath)
|
|
webui.SetupRoutes(web.Router.PathPrefix(prefix("/serve/")).Subrouter())
|
|
rest.SetupRoutes(web.Router.PathPrefix(prefix("/api/")).Subrouter())
|
|
webServer := web.NewServer(conf, mmanager, msgHub)
|
|
|
|
pop3Server, err := pop3.NewServer(conf.POP3, store)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
smtpServer := smtp.NewServer(conf.SMTP, mmanager, addrPolicy, extHost)
|
|
|
|
s := &Services{
|
|
MsgHub: msgHub,
|
|
RetentionScanner: retentionScanner,
|
|
POP3Server: pop3Server,
|
|
SMTPServer: smtpServer,
|
|
WebServer: webServer,
|
|
ExtHost: extHost,
|
|
LuaHost: luaHost,
|
|
ready: &sync.WaitGroup{},
|
|
}
|
|
s.setupNotify()
|
|
|
|
return s, nil
|
|
}
|
|
|
|
// Start all services, returns immediately. Callers may use Notify to detect failed services.
|
|
func (s *Services) Start(ctx context.Context, readyFunc func()) {
|
|
go s.MsgHub.Start(ctx)
|
|
go s.WebServer.Start(ctx, s.makeReadyFunc())
|
|
go s.SMTPServer.Start(ctx, s.makeReadyFunc())
|
|
go s.POP3Server.Start(ctx, s.makeReadyFunc())
|
|
go s.RetentionScanner.Start(ctx)
|
|
|
|
// Notify when all services report ready.
|
|
go func() {
|
|
s.ready.Wait()
|
|
readyFunc()
|
|
}()
|
|
}
|
|
|
|
// Notify returns a merged channel of the error notification channels of all fallible services,
|
|
// allowing the process to be shutdown if needed.
|
|
func (s *Services) Notify() <-chan error {
|
|
return s.notify
|
|
}
|
|
|
|
// setupNotify merges the error notification channels of all fallible services.
|
|
func (s *Services) setupNotify() {
|
|
c := make(chan error, 1)
|
|
s.notify = c
|
|
go func() {
|
|
// TODO: What level to log failure.
|
|
select {
|
|
case err := <-s.POP3Server.Notify():
|
|
c <- err
|
|
case err := <-s.SMTPServer.Notify():
|
|
c <- err
|
|
case err := <-s.WebServer.Notify():
|
|
c <- err
|
|
}
|
|
}()
|
|
}
|
|
|
|
// makeReadyFunc returns a function used to signal that a service is ready. The `Services.ready`
|
|
// wait group can then be used to await all services being ready.
|
|
func (s *Services) makeReadyFunc() func() {
|
|
s.ready.Add(1)
|
|
var once sync.Once
|
|
return func() {
|
|
once.Do(s.ready.Done)
|
|
}
|
|
}
|