From 3c4fe105cda6d222bd17f143b7829ffd73dfcd35 Mon Sep 17 00:00:00 2001 From: Aleksei Zhukov Date: Wed, 9 Mar 2022 18:49:53 -0800 Subject: [PATCH 001/111] Proxy protocol support for SMTP and IMAP --- docs/reference/endpoints/imap.md | 19 +++++ docs/reference/endpoints/smtp.md | 15 ++++ go.mod | 1 + go.sum | 2 + internal/endpoint/imap/imap.go | 15 +++- internal/endpoint/smtp/smtp.go | 23 ++++-- internal/proxy_protocol/proxy_protocol.go | 86 ++++++++++++++++++++++ tests/smtp_test.go | 89 +++++++++++++++++++++++ 8 files changed, 238 insertions(+), 12 deletions(-) create mode 100644 internal/proxy_protocol/proxy_protocol.go diff --git a/docs/reference/endpoints/imap.md b/docs/reference/endpoints/imap.md index 06247c32..943291a0 100644 --- a/docs/reference/endpoints/imap.md +++ b/docs/reference/endpoints/imap.md @@ -40,6 +40,25 @@ tls cert.crt key.key { See [TLS configuration / Server](/reference/tls/#server-side) for details. +**Syntax**: proxy_protocol _trusted ips..._ { ... }
+**Default**: not enabled + +Enable use of HAProxy PROXY protocol. Supports both v1 and v2 protocols. +If a list of trusted IP addresses or subnets is provided, only connections +from those will be trusted. + +TLS for the channel between the proxies and maddy can be configured +using a 'tls' directive: +``` +proxy_protocol { + trust 127.0.0.1 ::1 192.168.0.1/24 + tls &proxy_tls +} +``` +Note that the top-level 'tls' directive is not inherited here. If you +need TLS on top of the PROXY protocol, securing the protocol header, +you must declare TLS explicitly. + **Syntax**: io\_debug _boolean_
**Default**: no diff --git a/docs/reference/endpoints/smtp.md b/docs/reference/endpoints/smtp.md index cd99df9b..8849d25f 100644 --- a/docs/reference/endpoints/smtp.md +++ b/docs/reference/endpoints/smtp.md @@ -58,6 +58,21 @@ tls cert.crt key.key { See [TLS configuration / Server](/reference/tls/#server-side) for details. +**Syntax**: proxy_protocol _trusted ips..._ { ... }
+**Default**: not enabled + +Enable use of HAProxy PROXY protocol. Supports both v1 and v2 protocols. +If a list of trusted IP addresses or subnets is provided, only connections +from those will be trusted. + +TLS for the channel between the proxies and maddy can be configured +using a 'tls' directive: +``` +proxy_protocol { + trust 127.0.0.1 ::1 192.168.0.1/24 + tls &proxy_tls +} +``` **Syntax**: io\_debug _boolean_
**Default**: no diff --git a/go.mod b/go.mod index 2b3f9a16..1aaad679 100644 --- a/go.mod +++ b/go.mod @@ -73,6 +73,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/c0va23/go-proxyprotocol v0.9.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/digitalocean/godo v1.96.0 // indirect diff --git a/go.sum b/go.sum index ddf6797a..868d84de 100644 --- a/go.sum +++ b/go.sum @@ -236,6 +236,8 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/c0va23/go-proxyprotocol v0.9.1 h1:5BCkp0fDJOhzzH1lhjUgHhmZz9VvRMMif1U2D31hb34= +github.com/c0va23/go-proxyprotocol v0.9.1/go.mod h1:TNjUV+llvk8TvWJxlPYAeAYZgSzT/iicNr3nWBWX320= github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE= github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= diff --git a/internal/endpoint/imap/imap.go b/internal/endpoint/imap/imap.go index c7aac5e8..d047e882 100644 --- a/internal/endpoint/imap/imap.go +++ b/internal/endpoint/imap/imap.go @@ -44,14 +44,16 @@ import ( "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/auth" "github.com/foxcpp/maddy/internal/authz" + "github.com/foxcpp/maddy/internal/proxy_protocol" "github.com/foxcpp/maddy/internal/updatepipe" ) type Endpoint struct { - addrs []string - serv *imapserver.Server - listeners []net.Listener - Store module.Storage + addrs []string + serv *imapserver.Server + listeners []net.Listener + proxyProtocol *proxy_protocol.ProxyProtocol + Store module.Storage tlsConfig *tls.Config listenersWg sync.WaitGroup @@ -90,6 +92,7 @@ func (endp *Endpoint) Init(cfg *config.Map) error { }) cfg.Custom("storage", false, true, nil, modconfig.StorageDirective, &endp.Store) cfg.Custom("tls", true, true, nil, tls2.TLSDirective, &endp.tlsConfig) + cfg.Custom("proxy_protocol", false, false, nil, proxy_protocol.ProxyProtocolDirective, &endp.proxyProtocol) cfg.Bool("insecure_auth", false, false, &insecureAuth) cfg.Bool("io_debug", false, false, &ioDebug) cfg.Bool("io_errors", false, false, &ioErrors) @@ -167,6 +170,10 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { l = tls.NewListener(l, endp.tlsConfig) } + if endp.proxyProtocol != nil { + l = proxy_protocol.NewListener(l, endp.proxyProtocol, endp.Log) + } + endp.listeners = append(endp.listeners, l) endp.listenersWg.Add(1) diff --git a/internal/endpoint/smtp/smtp.go b/internal/endpoint/smtp/smtp.go index a322b6d4..e01ae57a 100644 --- a/internal/endpoint/smtp/smtp.go +++ b/internal/endpoint/smtp/smtp.go @@ -46,18 +46,20 @@ import ( "github.com/foxcpp/maddy/internal/authz" "github.com/foxcpp/maddy/internal/limits" "github.com/foxcpp/maddy/internal/msgpipeline" + "github.com/foxcpp/maddy/internal/proxy_protocol" "golang.org/x/net/idna" ) type Endpoint struct { - saslAuth auth.SASLAuth - serv *smtp.Server - name string - addrs []string - listeners []net.Listener - pipeline *msgpipeline.MsgPipeline - resolver dns.Resolver - limits *limits.Group + saslAuth auth.SASLAuth + serv *smtp.Server + name string + addrs []string + listeners []net.Listener + proxyProtocol *proxy_protocol.ProxyProtocol + pipeline *msgpipeline.MsgPipeline + resolver dns.Resolver + limits *limits.Group buffer func(r io.Reader) (buffer.Buffer, error) @@ -263,6 +265,7 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { return autoBufferMode(1*1024*1024 /* 1 MiB */, path), nil }, bufferModeDirective, &endp.buffer) cfg.Custom("tls", true, endp.name != "lmtp", nil, tls2.TLSDirective, &endp.serv.TLSConfig) + cfg.Custom("proxy_protocol", false, false, nil, proxy_protocol.ProxyProtocolDirective, &endp.proxyProtocol) cfg.Bool("insecure_auth", endp.name == "lmtp", false, &endp.serv.AllowInsecureAuth) cfg.Int("smtp_max_line_length", false, false, 4000, &endp.serv.MaxLineLength) cfg.Bool("io_debug", false, false, &ioDebug) @@ -350,6 +353,10 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { l = tls.NewListener(l, endp.serv.TLSConfig) } + if endp.proxyProtocol != nil { + l = proxy_protocol.NewListener(l, endp.proxyProtocol, endp.Log) + } + endp.listeners = append(endp.listeners, l) endp.listenersWg.Add(1) diff --git a/internal/proxy_protocol/proxy_protocol.go b/internal/proxy_protocol/proxy_protocol.go new file mode 100644 index 00000000..1a3a7873 --- /dev/null +++ b/internal/proxy_protocol/proxy_protocol.go @@ -0,0 +1,86 @@ +package proxy_protocol + +import ( + "crypto/tls" + "net" + "strings" + + "github.com/c0va23/go-proxyprotocol" + "github.com/foxcpp/maddy/framework/config" + tls2 "github.com/foxcpp/maddy/framework/config/tls" + "github.com/foxcpp/maddy/framework/log" +) + +type ProxyProtocol struct { + trust []net.IPNet + tlsConfig *tls.Config +} + +func ProxyProtocolDirective(_ *config.Map, node config.Node) (interface{}, error) { + p := ProxyProtocol{} + + childM := config.NewMap(nil, node) + var trustList []string + + childM.StringList("trust", false, false, nil, &trustList) + childM.Custom("tls", true, false, nil, tls2.TLSDirective, &p.tlsConfig) + + if _, err := childM.Process(); err != nil { + return nil, err + } + + if len(node.Args)> 0 { + if trustList == nil { + trustList = make([]string, 0) + } + trustList = append(trustList, node.Args...) + } + + for _, trust := range trustList { + if !strings.Contains(trust, "/") { + trust += "/32" + } + _, ipNet, err := net.ParseCIDR(trust) + if err != nil { + return nil, err + } + p.trust = append(p.trust, *ipNet) + } + + return &p, nil +} + +func NewListener(inner net.Listener, p *ProxyProtocol, logger log.Logger) net.Listener { + var listener net.Listener + + sourceChecker := func(upstream net.Addr) (bool, error) { + if tcpAddr, ok := upstream.(*net.TCPAddr); ok { + if len(p.trust) == 0 { + return true, nil + } + for _, trusted := range p.trust { + if trusted.Contains(tcpAddr.IP) { + return true, nil + } + } + } else if _, ok := upstream.(*net.UnixAddr); ok { + // UNIX local socket connection, always trusted + return true, nil + } + + logger.Printf("proxy_protocol: connection from untrusted source %s", upstream) + return false, nil + } + + listener = proxyprotocol.NewDefaultListener(inner). + WithLogger(proxyprotocol.LoggerFunc(func(format string, v ...interface{}) { + logger.Debugf("proxy_protocol: "+format, v...) + })). + WithSourceChecker(sourceChecker) + + if p.tlsConfig != nil { + listener = tls.NewListener(listener, p.tlsConfig) + } + + return listener +} diff --git a/tests/smtp_test.go b/tests/smtp_test.go index 85a5173a..7e5eda16 100644 --- a/tests/smtp_test.go +++ b/tests/smtp_test.go @@ -23,6 +23,7 @@ package tests_test import ( "errors" + "fmt" "io/ioutil" "path/filepath" "strings" @@ -68,6 +69,94 @@ func TestCheckRequireTLS(tt *testing.T) { conn.ExpectPattern("221 *") } +func TestProxyProtocolTrustedSource(tt *testing.T) { + tt.Parallel() + t := tests.NewT(tt) + t.DNS(map[string]mockdns.Zone{ + "one.maddy.test.": { + TXT: []string{"v=spf1 ip4:127.0.0.17 -all"}, + }, + }) + t.Port("smtp") + t.Config(` + smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} { + hostname mx.maddy.test + tls off + + proxy_protocol { + trust ` + tests.DefaultSourceIP.String() + ` ::1/128 + tls off + } + + defer_sender_reject no + + check { + spf { + enforce_early yes + fail_action reject + } + } + + deliver_to dummy + } + `) + t.Run(1) + defer t.Close() + + conn := t.Conn("smtp") + defer conn.Close() + conn.Writeln(fmt.Sprintf("PROXY TCP4 127.0.0.17 %s 12345 %d", tests.DefaultSourceIP.String(), t.Port("smtp"))) + conn.SMTPNegotation("localhost", nil, nil) + conn.Writeln("MAIL FROM:") + conn.ExpectPattern("250 *") + conn.Writeln("QUIT") + conn.ExpectPattern("221 *") +} + +func TestProxyProtocolUntrustedSource(tt *testing.T) { + tt.Parallel() + t := tests.NewT(tt) + t.DNS(map[string]mockdns.Zone{ + "one.maddy.test.": { + TXT: []string{"v=spf1 ip4:127.0.0.17 -all"}, + }, + }) + t.Port("smtp") + t.Config(` + smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} { + hostname mx.maddy.test + tls off + + proxy_protocol { + trust fe80::bad/128 + tls off + } + + defer_sender_reject no + + check { + spf { + enforce_early yes + fail_action reject + } + } + + deliver_to dummy + } + `) + t.Run(1) + defer t.Close() + + conn := t.Conn("smtp") + defer conn.Close() + conn.Writeln(fmt.Sprintf("PROXY TCP4 127.0.0.17 %s 12345 %d", tests.DefaultSourceIP.String(), t.Port("smtp"))) + conn.SMTPNegotation("localhost", nil, nil) + conn.Writeln("MAIL FROM:") + conn.ExpectPattern("550 *") + conn.Writeln("QUIT") + conn.ExpectPattern("221 *") +} + func TestCheckSPF(tt *testing.T) { tt.Parallel() t := tests.NewT(tt) From dbb424f1649f36c8f0121b724ca72a57ea30520d Mon Sep 17 00:00:00 2001 From: Martin Matous Date: 2023年8月22日 17:52:22 +0200 Subject: [PATCH 002/111] fix(milter): remove erroneous path check A remnant of d0e7df023cadb3d7068e5b09509bc562ad63f10b when milter.NewClient() didn't accept path. Signed-off-by: Martin Matous --- internal/check/milter/milter.go | 3 -- internal/check/milter/milter_test.go | 61 ++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 internal/check/milter/milter_test.go diff --git a/internal/check/milter/milter.go b/internal/check/milter/milter.go index c0f3700d..37704d43 100644 --- a/internal/check/milter/milter.go +++ b/internal/check/milter/milter.go @@ -90,9 +90,6 @@ func (c *Check) Init(cfg *config.Map) error { default: return fmt.Errorf("%s: scheme unsupported: %v", modName, endp.Scheme) } - if endp.Path != "" { - return fmt.Errorf("%s: stray path in endpoint: %v", modName, endp) - } c.cl = milter.NewClientWithOptions(endp.Network(), endp.Address(), milter.ClientOptions{ Dialer: &net.Dialer{ diff --git a/internal/check/milter/milter_test.go b/internal/check/milter/milter_test.go new file mode 100644 index 00000000..d2ec19d3 --- /dev/null +++ b/internal/check/milter/milter_test.go @@ -0,0 +1,61 @@ +/* +Maddy Mail Server - Composable all-in-one email server. +Copyright © 2019-2020 Max Mazurov , Maddy Mail Server contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package milter + +import ( + "testing" + + "github.com/foxcpp/maddy/framework/config" +) + +func TestAcceptValidEndpoints(t *testing.T) { + for _, endpoint := range []string{ + "tcp://0.0.0.0:10025", + "tcp://[::]:10025", + "tcp:127.0.0.1:10025", + "unix://path", + "unix:path", + "unix:/path", + "unix:///path", + "unix://also/path", + "unix:///also/path", + } { + c := &Check{milterUrl: endpoint} + + err := c.Init(&config.Map{}) + if err != nil { + t.Errorf("Unexpected failure for %s: %v", endpoint, err) + return + } + } +} + +func TestRejectInvalidEndpoints(t *testing.T) { + for _, endpoint := range []string{ + "tls://0.0.0.0:10025", + "tls:0.0.0.0:10025", + } { + c := &Check{milterUrl: endpoint} + err := c.Init(&config.Map{}) + if err == nil { + t.Errorf("Accepted invalid endpoint: %s", endpoint) + return + } + } +} \ No newline at end of file From 4b64657ed16f3fcad229db19a25f24241a31a5cc Mon Sep 17 00:00:00 2001 From: Martin Matous Date: 2023年8月22日 18:00:44 +0200 Subject: [PATCH 003/111] fix(f2b): use correct retry directive Signed-off-by: Martin Matous --- dist/fail2ban/jail.d/maddy-dictonary-attack.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/fail2ban/jail.d/maddy-dictonary-attack.conf b/dist/fail2ban/jail.d/maddy-dictonary-attack.conf index c4f7ff3f..ebeb33fa 100644 --- a/dist/fail2ban/jail.d/maddy-dictonary-attack.conf +++ b/dist/fail2ban/jail.d/maddy-dictonary-attack.conf @@ -2,6 +2,6 @@ port = 993,465,25 filter = maddy-dictonary-attack bantime = 72h -maxtries = 3 +maxretry = 3 findtime = 6h backend = systemd From 250ee6fd876298e7df24974e887dcf643df07557 Mon Sep 17 00:00:00 2001 From: reind33r Date: 2024年2月12日 23:25:25 +0100 Subject: [PATCH 004/111] libdns/rfc2136 added as a provider --- docs/reference/tls-acme.md | 11 +++++++++++ go.mod | 3 ++- go.sum | 30 +++++++++++++++--------------- internal/libdns/rfc2136.go | 28 ++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 16 deletions(-) create mode 100644 internal/libdns/rfc2136.go diff --git a/docs/reference/tls-acme.md b/docs/reference/tls-acme.md index 9baccb5d..1cc7423c 100644 --- a/docs/reference/tls-acme.md +++ b/docs/reference/tls-acme.md @@ -263,3 +263,14 @@ dns namedotcom { } ``` +- rfc2136 (non-default) + +``` +dns rfc2136 { + KeyName "..." + Key "..." + KeyAlg "..." + Server "..." +} +``` + diff --git a/go.mod b/go.mod index 68bf8a7a..7b2334a9 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( blitiri.com.ar/go/spf v1.5.1 github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 + github.com/c0va23/go-proxyprotocol v0.9.1 github.com/caddyserver/certmagic v0.20.0 github.com/emersion/go-imap v1.2.2-0.20220928192137-6fac715be9cf github.com/emersion/go-imap-compress v0.0.0-20201103190257-14809af1d1b9 @@ -39,6 +40,7 @@ require ( github.com/libdns/metaname v0.3.0 github.com/libdns/namecheap v0.0.0-20211109042440-fc7440785c8e github.com/libdns/namedotcom v0.3.3 + github.com/libdns/rfc2136 v0.1.0 github.com/libdns/route53 v1.3.3 github.com/libdns/vultr v1.0.0 github.com/mattn/go-sqlite3 v1.14.19 @@ -75,7 +77,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect github.com/aws/smithy-go v1.19.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/c0va23/go-proxyprotocol v0.9.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/digitalocean/godo v1.108.0 // indirect diff --git a/go.sum b/go.sum index 005925b8..672f327f 100644 --- a/go.sum +++ b/go.sum @@ -237,6 +237,7 @@ github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/c0va23/go-proxyprotocol v0.9.1 h1:5BCkp0fDJOhzzH1lhjUgHhmZz9VvRMMif1U2D31hb34= github.com/c0va23/go-proxyprotocol v0.9.1/go.mod h1:TNjUV+llvk8TvWJxlPYAeAYZgSzT/iicNr3nWBWX320= github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE= @@ -261,6 +262,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -341,6 +344,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -348,6 +352,7 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -476,10 +481,12 @@ github.com/johannesboyne/gofakes3 v0.0.0-20210704111953-6a9f95c2941c h1:lx/uPI+m github.com/johannesboyne/gofakes3 v0.0.0-20210704111953-6a9f95c2941c/go.mod h1:LIAXxPvcUXwOcTIj9LSNSUpE9/eMHalTWxsP/kmWxQI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -489,6 +496,7 @@ github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -521,6 +529,8 @@ github.com/libdns/namecheap v0.0.0-20211109042440-fc7440785c8e h1:WCcKyxiiK/sJnS github.com/libdns/namecheap v0.0.0-20211109042440-fc7440785c8e/go.mod h1:dED6sMLZxIcilF1GjrcpwgVoCglXGMn86irqQzRhqRY= github.com/libdns/namedotcom v0.3.3 h1:R10C7+IqQGVeC4opHHMiFNBxdNBg1bi65ZwqLESl+jE= github.com/libdns/namedotcom v0.3.3/go.mod h1:GbYzsAF2yRUpI0WgIK5fs5UX+kDVUPaYCFLpTnKQm0s= +github.com/libdns/rfc2136 v0.1.0 h1:BlGOPfx/R3xqKrgHT9TlreA8Ulw8ti8+VtJj8E0H9hE= +github.com/libdns/rfc2136 v0.1.0/go.mod h1:tgXWavE+5OiAfdKxBnuG8OBEwQFAu7uuiS3+laspAGs= github.com/libdns/route53 v1.3.3 h1:16sTxbbRGm0zODz0p0aVHHIyTqtHzEn3j0s4dGzQvNI= github.com/libdns/route53 v1.3.3/go.mod h1:n1Xy55lpfdxMIx4CVWAM16GQac+/OZcnm1xBjMyhZAo= github.com/libdns/vultr v1.0.0 h1:W8B4+k2bm9ro3bZLSZV9hMOQI+uO6Svu+GmD+Olz7ZI= @@ -553,6 +563,7 @@ github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0 github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -560,10 +571,12 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/netauth/netauth v0.6.2-0.20220831214440-1df568cd25d6 h1:TsF5Cl0Mj5JMvPOP2ySVq+CZoiPrTGwvNPbuQotuSAE= github.com/netauth/netauth v0.6.2-0.20220831214440-1df568cd25d6/go.mod h1:4PEbISVqRCQaXaDAt289w3nK9UhoF8/ZOLy31Hbv7ds= github.com/netauth/protocol v0.0.0-20210918062754-7fee492ffcbd h1:4yVpQ/+li28lQ/daYCWeDB08obRmjaoAw2qfFFaCQ40= github.com/netauth/protocol v0.0.0-20210918062754-7fee492ffcbd/go.mod h1:wpK5wqysOJU1w2OxgG65du8M7UqBkxzsNaJdjwiRqAs= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= @@ -572,7 +585,6 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -586,7 +598,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -597,8 +608,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 h1:J6qvD6rbmOil46orKqJaRPG+zTpoGlBTUdyv8ki63L0= github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63/go.mod h1:n+VKSARF5y/tS9XFSP7vWDfS+GUC5vs/YT7M5XDTUEM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -609,6 +620,7 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= @@ -626,7 +638,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= @@ -643,11 +654,9 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= -github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -668,7 +677,6 @@ go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xC go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= @@ -1150,8 +1158,6 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55/go.mod h1:45EK0dUbEZ2NHjCeAd2LXmyjAgGUGrpGROgjhC3ADck= -google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1211,7 +1217,6 @@ google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7 google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1219,7 +1224,6 @@ gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3M gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -1239,8 +1243,6 @@ modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= -modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= -modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/libc v1.40.6 h1:141JHq3SjhOOCjECBgD4K8VgTFOy19CnHwroC08DAig= modernc.org/libc v1.40.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= @@ -1253,10 +1255,8 @@ modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= -modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/libdns/rfc2136.go b/internal/libdns/rfc2136.go new file mode 100644 index 00000000..aa755623 --- /dev/null +++ b/internal/libdns/rfc2136.go @@ -0,0 +1,28 @@ +//go:build libdns_rfc2136 || libdns_all +// +build libdns_rfc2136 libdns_all + +package libdns + +import ( + "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/module" + "github.com/libdns/rfc2136" +) + +func init() { + module.Register("libdns.rfc2136", func(modName, instName string, _, _ []string) (module.Module, error) { + p := rfc2136.Provider{} + return &ProviderModule{ + RecordDeleter: &p, + RecordAppender: &p, + setConfig: func(c *config.Map) { + c.String("KeyName", false, true, "", &p.KeyName) + c.String("Key", false, true, "", &p.Key) + c.String("KeyAlg", false, true, "", &p.KeyAlg) + c.String("Server", false, true, "", &p.Server) + }, + instName: instName, + modName: modName, + }, nil + }) +} From eeb49621f48886300452975dc32437bd8d8a7d07 Mon Sep 17 00:00:00 2001 From: Louis Guidez reind33r Date: 2024年2月15日 23:55:55 +0100 Subject: [PATCH 005/111] libdns/rfc2136 use snake_case instead of PascalCase for configuration directives; clarify the meaning of the configuration directives --- docs/reference/tls-acme.md | 12 ++++++++---- internal/libdns/rfc2136.go | 8 ++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/reference/tls-acme.md b/docs/reference/tls-acme.md index 1cc7423c..d6479de1 100644 --- a/docs/reference/tls-acme.md +++ b/docs/reference/tls-acme.md @@ -267,10 +267,14 @@ dns namedotcom { ``` dns rfc2136 { - KeyName "..." - Key "..." - KeyAlg "..." - Server "..." + key_name "..." + # Secret + key "..." + # HMAC algorithm used to generate the key, lowercase, e.g. hmac-sha512 + key_alg "..." + # server to which the dynamic update will be sent, e.g. 127.0.0.1 + # you can also specify the port: 127.0.0.1:53 + server "..." } ``` diff --git a/internal/libdns/rfc2136.go b/internal/libdns/rfc2136.go index aa755623..19751f6f 100644 --- a/internal/libdns/rfc2136.go +++ b/internal/libdns/rfc2136.go @@ -16,10 +16,10 @@ func init() { RecordDeleter: &p, RecordAppender: &p, setConfig: func(c *config.Map) { - c.String("KeyName", false, true, "", &p.KeyName) - c.String("Key", false, true, "", &p.Key) - c.String("KeyAlg", false, true, "", &p.KeyAlg) - c.String("Server", false, true, "", &p.Server) + c.String("key_name", false, true, "", &p.KeyName) + c.String("key", false, true, "", &p.Key) + c.String("key_alg", false, true, "", &p.KeyAlg) + c.String("server", false, true, "", &p.Server) }, instName: instName, modName: modName, From bb2c0c9bcc8a52af22991a7445d718f762c24c68 Mon Sep 17 00:00:00 2001 From: Louis Guidez reind33r Date: 2024年2月16日 00:14:44 +0100 Subject: [PATCH 006/111] docs: corrected typo for rspamd check configuration --- docs/third-party/rspamd.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/third-party/rspamd.md b/docs/third-party/rspamd.md index b528215c..3d2ce483 100644 --- a/docs/third-party/rspamd.md +++ b/docs/third-party/rspamd.md @@ -7,7 +7,7 @@ If rspamd is running locally, it is enough to just add `rspamd` check with default configuration into appropriate check block (probably in local_routing): ``` -checks { +check { ... rspamd } From 62799b4b1e23735851939d40c9b31aba67b126fa Mon Sep 17 00:00:00 2001 From: oidq Date: 2024年3月13日 09:26:57 +0100 Subject: [PATCH 007/111] build: make "build.sh install" reusable * prevent the script form blindly overwriting the current configuration * force maddyctl linking so that we can reuse the script * fix indentation in build.sh --- build.sh | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/build.sh b/build.sh index 68e71dbd..3dc76de2 100755 --- a/build.sh +++ b/build.sh @@ -3,6 +3,7 @@ destdir=/ builddir="$PWD/build" prefix=/usr/local +configdir="${destdir}etc/maddy" version= static=0 if [ "${GOFLAGS}" = "" ]; then @@ -139,9 +140,18 @@ install() { command install -m 0755 -d "${destdir}/${prefix}/bin/" command install -m 0755 "${builddir}/maddy" "${destdir}/${prefix}/bin/" - command ln -s maddy "${destdir}/${prefix}/bin/maddyctl" - command install -m 0755 -d "${destdir}/etc/maddy/" - command install -m 0644 ./maddy.conf "${destdir}/etc/maddy/maddy.conf" + command ln -sf maddy "${destdir}/${prefix}/bin/maddyctl" + command install -m 0755 -d "${configdir}" + + + # We do not want to overwrite existing configuration. + # If the file exists, then save it with .default suffix and warn user. + if [ ! -e "${configdir}/maddy.conf" ]; then + command install -m 0644 ./maddy.conf "${configdir}/maddy.conf" + else + echo "-- [!] Configuration file ${configdir}/maddy.conf exists, saving to ${configdir}/maddy.conf.default">&2 + command install -m 0644 ./maddy.conf "${configdir}/maddy.conf.default" + fi # Attempt to install systemd units only for Linux. # Check is done using GOOS instead of uname -s to account for possible @@ -150,19 +160,19 @@ install() { # with sudo and go installation is user-specific, so fallback # to using uname -s in the end. set +e - if command -v go>/dev/null 2>/dev/null; then - set -e - if [ "$(go env GOOS)" = "linux" ]; then - command install -m 0755 -d "${destdir}/${prefix}/lib/systemd/system/" - command install -m 0644 "${builddir}"/systemd/*.service "${destdir}/${prefix}/lib/systemd/system/" - fi - else - set -e - if [ "$(uname -s)" = "Linux" ]; then - command install -m 0755 -d "${destdir}/${prefix}/lib/systemd/system/" - command install -m 0644 "${builddir}"/systemd/*.service "${destdir}/${prefix}/lib/systemd/system/" - fi - fi + if command -v go>/dev/null 2>/dev/null; then + set -e + if [ "$(go env GOOS)" = "linux" ]; then + command install -C -m 0755 -d "${destdir}/${prefix}/lib/systemd/system/" + command install -C -m 0644 "${builddir}"/systemd/*.service "${destdir}/${prefix}/lib/systemd/system/" + fi + else + set -e + if [ "$(uname -s)" = "Linux" ]; then + command install -C -m 0755 -d "${destdir}/${prefix}/lib/systemd/system/" + command install -C -m 0644 "${builddir}"/systemd/*.service "${destdir}/${prefix}/lib/systemd/system/" + fi + fi if [ -e "${builddir}"/man ]; then command install -m 0755 -d "${destdir}/${prefix}/share/man/man1/" From 198a9a3fb57da4f69c897ac4f2489d1d7415736e Mon Sep 17 00:00:00 2001 From: Robert Coleman Date: 2024年3月16日 16:03:57 +1300 Subject: [PATCH 008/111] add ACME-DNS provider for libdns --- docs/reference/tls-acme.md | 12 +++++++++++- go.mod | 1 + go.sum | 2 ++ internal/libdns/acmedns.go | 28 ++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 internal/libdns/acmedns.go diff --git a/docs/reference/tls-acme.md b/docs/reference/tls-acme.md index 9baccb5d..be29f94a 100644 --- a/docs/reference/tls-acme.md +++ b/docs/reference/tls-acme.md @@ -149,7 +149,7 @@ To be able to use these, you need to compile maddy with "libdns_PROVIDER" build tag. E.g. ``` -./build.sh -tags 'libdns_googleclouddns' +./build.sh --tags 'libdns_googleclouddns' ``` - gandi @@ -263,3 +263,13 @@ dns namedotcom { } ``` +- acmedns (non-default) + +``` +dns acmedns { + username "..." + password "..." + subdomain "..." + server_url "..." +} +``` diff --git a/go.mod b/go.mod index 68bf8a7a..3ac84825 100644 --- a/go.mod +++ b/go.mod @@ -103,6 +103,7 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/libdns/acmedns v0.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index 005925b8..be1dc81b 100644 --- a/go.sum +++ b/go.sum @@ -496,6 +496,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libdns/acmedns v0.2.0 h1:zTXdHZwe3r2issdVRyqt5/4X2yHpiBVmFnTrwBA29ik= +github.com/libdns/acmedns v0.2.0/go.mod h1:XlKHilQQK/IGHYY//vCb903PdG4Wc/XnDQzcMp2hV3g= github.com/libdns/alidns v1.0.3-0.20230628155627-8d5d630d5516 h1:tPVSANkA4lo+K65YjsQcaQ1uh6sb0zRBQDz78l1Fo4Y= github.com/libdns/alidns v1.0.3-0.20230628155627-8d5d630d5516/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= github.com/libdns/cloudflare v0.1.1-0.20221006221909-9d3ab3c3cddd h1:c5hc0b5/pFqFeyQaOTVmYJbyr+QwZZFcMnjgtZGIk6k= diff --git a/internal/libdns/acmedns.go b/internal/libdns/acmedns.go new file mode 100644 index 00000000..cb657ee2 --- /dev/null +++ b/internal/libdns/acmedns.go @@ -0,0 +1,28 @@ +//go:build libdns_acmedns || libdns_all +// +build libdns_acmedns libdns_all + +package libdns + +import ( + "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/module" + "github.com/libdns/acmedns" +) + +func init() { + module.Register("libdns.acmedns", func(modName, instName string, _, _ []string) (module.Module, error) { + p := acmedns.Provider{} + return &ProviderModule{ + RecordDeleter: &p, + RecordAppender: &p, + setConfig: func(c *config.Map) { + c.String("username", false, true, "", &p.Username) + c.String("password", false, true, "", &p.Password) + c.String("subdomain", false, true, "", &p.Subdomain) + c.String("server_url", false, true, "", &p.ServerURL) + }, + instName: instName, + modName: modName, + }, nil + }) +} From 3d81feeeaadcc951eb177e0d346a049c247c00ea Mon Sep 17 00:00:00 2001 From: Robert Coleman Date: 2024年3月16日 16:17:46 +1300 Subject: [PATCH 009/111] Add additional build tags to Dockerfile --- Dockerfile | 4 +++- docs/docker.md | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5ceb6f7f..0cad071e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,7 @@ FROM golang:1.19-alpine AS build-env +ARG ADDITIONAL_BUILD_TAGS="" + RUN set -ex && \ apk upgrade --no-cache --available && \ apk add --no-cache build-base @@ -12,7 +14,7 @@ RUN go mod download COPY . ./ RUN mkdir -p /pkg/data && \ cp maddy.conf.docker /pkg/data/maddy.conf && \ - ./build.sh --builddir /tmp --destdir /pkg/ --tags docker build install + ./build.sh --builddir /tmp --destdir /pkg/ --tags "docker ${ADDITIONAL_BUILD_TAGS}" build install FROM alpine:3.18.4 LABEL maintainer="fox.cpp@disroot.org" diff --git a/docs/docker.md b/docs/docker.md index 2898a07b..decb7870 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -54,6 +54,11 @@ command. One way to it is to run it using `docker exec` instead of `docker run`: docker exec -it container_name_here maddy creds create foxcpp@maddy.test ``` +## Build Tags + +Some Maddy features (such as automatic certificate management via ACME with [a non-default libdns provider](../reference/tls-acme/#dns-providers)) require build tags to be passed to Maddy's `build.sh`, as this is run in the Dockerfile you must compile your own Docker image. Build tags can be set via the docker build argument `ADDITIONAL_BUILD_TAGS` e.g. `docker build --build-arg ADDITIONAL_BUILD_TAGS="libdns_acmedns libdns_route53" -t yourorgname/maddy:yourtagname .`. + + ## TL;DR ``` From 72416b0456726805c1c3b91aad825261c7098aa2 Mon Sep 17 00:00:00 2001 From: Matthias Schneider Date: Mon, 9 Dec 2024 09:03:45 +0100 Subject: [PATCH 010/111] #736: dmarc evalute added check if fromDomain is a TLD --- internal/dmarc/evaluate.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/dmarc/evaluate.go b/internal/dmarc/evaluate.go index adae7b8c..ff978e5a 100644 --- a/internal/dmarc/evaluate.go +++ b/internal/dmarc/evaluate.go @@ -207,6 +207,10 @@ func isAligned(fromDomain, authDomain string, mode AlignmentMode) bool { return strings.EqualFold(fromDomain, authDomain) } + tld, _ := publicsuffix.PublicSuffix(fromDomain) + if strings.EqualFold(fromDomain, tld) { + return strings.EqualFold(fromDomain, authDomain) + } orgDomainFrom, err := publicsuffix.EffectiveTLDPlusOne(fromDomain) if err != nil { return false From 5be9baa7be094adca6c2b461ee43ca58e8569e1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: 2024年12月11日 23:57:09 +0000 Subject: [PATCH 011/111] build(deps): bump golang.org/x/crypto from 0.21.0 to 0.31.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.21.0 to 0.31.0. - [Commits](https://github.com/golang/crypto/compare/v0.21.0...v0.31.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 29 ++++++++++++++--------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index 8ece169e..fc1b5104 100644 --- a/go.mod +++ b/go.mod @@ -48,10 +48,10 @@ require ( github.com/prometheus/client_golang v1.18.0 github.com/urfave/cli/v2 v2.27.1 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.21.0 - golang.org/x/net v0.23.0 - golang.org/x/sync v0.6.0 - golang.org/x/text v0.14.0 + golang.org/x/crypto v0.31.0 + golang.org/x/net v0.25.0 + golang.org/x/sync v0.10.0 + golang.org/x/text v0.21.0 modernc.org/sqlite v1.28.0 ) @@ -143,11 +143,11 @@ require ( go.opentelemetry.io/otel/trace v1.22.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect - golang.org/x/mod v0.14.0 // indirect + golang.org/x/mod v0.17.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.28.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.17.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/api v0.157.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect diff --git a/go.sum b/go.sum index 09293af0..db449327 100644 --- a/go.sum +++ b/go.sum @@ -318,8 +318,6 @@ github.com/foxcpp/go-imap-mess v0.0.0-20230108134257-b7ec3a649613 h1:fw9OWfPxP1C github.com/foxcpp/go-imap-mess v0.0.0-20230108134257-b7ec3a649613/go.mod h1:P/O/qz4gaVkefzJ40BUtN/ZzBnaEg0YYe1no/SMp7Aw= github.com/foxcpp/go-imap-namespace v0.0.0-20200802091432-08496dd8e0ed h1:1Jo7geyvunrPSjL6F6D9EcXoNApS5v3LQaro7aUNPnE= github.com/foxcpp/go-imap-namespace v0.0.0-20200802091432-08496dd8e0ed/go.mod h1:Shows1vmkBWO40ChOClaUe6DUnZrsP1UPAuoWzIUdgQ= -github.com/foxcpp/go-imap-sql v0.5.1-0.20240214172211-ee5bc28d4278 h1:7LGp/ryQH/MOTWgWgv7+cPEFKgKH1aADCEnus13G5Kg= -github.com/foxcpp/go-imap-sql v0.5.1-0.20240214172211-ee5bc28d4278/go.mod h1:LMlfyNkVs7v2zE6OVeGe9qWPmKFdXDmLNddPLodPVIw= github.com/foxcpp/go-imap-sql v0.5.1-0.20240831122236-655e4cb87d20 h1:q4NtuuK7Kuf8zHC1CF8p1GB1owd/IyN+zfqy8DQL9Ig= github.com/foxcpp/go-imap-sql v0.5.1-0.20240831122236-655e4cb87d20/go.mod h1:LMlfyNkVs7v2zE6OVeGe9qWPmKFdXDmLNddPLodPVIw= github.com/foxcpp/go-mockdns v0.0.0-20191216195825-5eabd8dbfe1f/go.mod h1:tPg4cp4nseejPd+UKxtCVQ2hUxNTZ7qQZJa7CLriIeo= @@ -680,8 +678,8 @@ golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -721,8 +719,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -775,8 +773,8 @@ golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -819,8 +817,8 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -894,8 +892,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -916,8 +914,9 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -978,8 +977,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From c0fd00de3e1dd7202dda756618cfed10f3ef0acc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: 2025年1月24日 13:51:38 +0000 Subject: [PATCH 012/111] build(deps): bump golang.org/x/net from 0.25.0 to 0.33.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.25.0 to 0.33.0. - [Commits](https://github.com/golang/net/compare/v0.25.0...v0.33.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fc1b5104..0655d423 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,7 @@ require ( github.com/urfave/cli/v2 v2.27.1 go.uber.org/zap v1.26.0 golang.org/x/crypto v0.31.0 - golang.org/x/net v0.25.0 + golang.org/x/net v0.33.0 golang.org/x/sync v0.10.0 golang.org/x/text v0.21.0 modernc.org/sqlite v1.28.0 diff --git a/go.sum b/go.sum index db449327..e1f551df 100644 --- a/go.sum +++ b/go.sum @@ -773,8 +773,8 @@ golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From a8d639d374707d4f10420b74519bcde3a1da375e Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月24日 17:02:52 +0300 Subject: [PATCH 013/111] Bump go-imap-sql version See #723 comments --- go.mod | 2 +- go.sum | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0655d423..12173b91 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/foxcpp/go-imap-i18nlevel v0.0.0-20200208001533-d6ec88553005 github.com/foxcpp/go-imap-mess v0.0.0-20230108134257-b7ec3a649613 github.com/foxcpp/go-imap-namespace v0.0.0-20200802091432-08496dd8e0ed - github.com/foxcpp/go-imap-sql v0.5.1-0.20240831122236-655e4cb87d20 + github.com/foxcpp/go-imap-sql v0.5.1-0.20250124140007-8da5567429d5 github.com/foxcpp/go-mockdns v1.0.0 github.com/foxcpp/go-mtasts v0.0.0-20191219193356-62bc3f1f74b8 github.com/go-ldap/ldap/v3 v3.4.6 diff --git a/go.sum b/go.sum index e1f551df..e958abc4 100644 --- a/go.sum +++ b/go.sum @@ -237,6 +237,7 @@ github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -257,6 +258,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -320,6 +323,8 @@ github.com/foxcpp/go-imap-namespace v0.0.0-20200802091432-08496dd8e0ed h1:1Jo7ge github.com/foxcpp/go-imap-namespace v0.0.0-20200802091432-08496dd8e0ed/go.mod h1:Shows1vmkBWO40ChOClaUe6DUnZrsP1UPAuoWzIUdgQ= github.com/foxcpp/go-imap-sql v0.5.1-0.20240831122236-655e4cb87d20 h1:q4NtuuK7Kuf8zHC1CF8p1GB1owd/IyN+zfqy8DQL9Ig= github.com/foxcpp/go-imap-sql v0.5.1-0.20240831122236-655e4cb87d20/go.mod h1:LMlfyNkVs7v2zE6OVeGe9qWPmKFdXDmLNddPLodPVIw= +github.com/foxcpp/go-imap-sql v0.5.1-0.20250124140007-8da5567429d5 h1:jMxhw9qmwqg70qfMDWq0ImRHAduQjkTZOC9vBs5t2ug= +github.com/foxcpp/go-imap-sql v0.5.1-0.20250124140007-8da5567429d5/go.mod h1:LMlfyNkVs7v2zE6OVeGe9qWPmKFdXDmLNddPLodPVIw= github.com/foxcpp/go-mockdns v0.0.0-20191216195825-5eabd8dbfe1f/go.mod h1:tPg4cp4nseejPd+UKxtCVQ2hUxNTZ7qQZJa7CLriIeo= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= @@ -339,6 +344,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -346,6 +352,7 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -474,10 +481,12 @@ github.com/johannesboyne/gofakes3 v0.0.0-20210704111953-6a9f95c2941c h1:lx/uPI+m github.com/johannesboyne/gofakes3 v0.0.0-20210704111953-6a9f95c2941c/go.mod h1:LIAXxPvcUXwOcTIj9LSNSUpE9/eMHalTWxsP/kmWxQI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -487,6 +496,7 @@ github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -549,6 +559,7 @@ github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0 github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -556,10 +567,12 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/netauth/netauth v0.6.2-0.20220831214440-1df568cd25d6 h1:TsF5Cl0Mj5JMvPOP2ySVq+CZoiPrTGwvNPbuQotuSAE= github.com/netauth/netauth v0.6.2-0.20220831214440-1df568cd25d6/go.mod h1:4PEbISVqRCQaXaDAt289w3nK9UhoF8/ZOLy31Hbv7ds= github.com/netauth/protocol v0.0.0-20210918062754-7fee492ffcbd h1:4yVpQ/+li28lQ/daYCWeDB08obRmjaoAw2qfFFaCQ40= github.com/netauth/protocol v0.0.0-20210918062754-7fee492ffcbd/go.mod h1:wpK5wqysOJU1w2OxgG65du8M7UqBkxzsNaJdjwiRqAs= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= @@ -595,6 +608,7 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 h1:J6qvD6rbmOil46orKqJaRPG+zTpoGlBTUdyv8ki63L0= github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63/go.mod h1:n+VKSARF5y/tS9XFSP7vWDfS+GUC5vs/YT7M5XDTUEM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -605,6 +619,7 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= From 87f3ed165adf3ca9794c3e93fe1b81fb025e896e Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月24日 18:56:33 +0300 Subject: [PATCH 014/111] maddy 0.8.0 --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index 39e898a4..a3df0a69 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -0.7.1 +0.8.0 From 78e4600a17c2eb660958daa8e07cd9d6d8c4438d Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月24日 21:29:48 +0300 Subject: [PATCH 015/111] Upgrade all dependencies 1. go-smtp is replaced by a fork that reverts StartTLS removal. 2. SASL LOGIN is no longer supported by upstream go-sasl, readded disabled by default. 3. Updated endpoint code to match new go-smtp authentication interfaces. 4. certmagic repo had some renames 5. Minimum Go version increased to 1.23 to match dependencies. --- .golangci.yml | 5 +- Dockerfile | 4 +- docs/reference/endpoints/imap.md | 11 + docs/reference/endpoints/smtp.md | 9 + docs/reference/targets/smtp.md | 18 +- docs/tutorials/building-from-source.md | 13 +- go.mod | 210 ++++--- go.sum | 544 ++++++++++-------- internal/auth/sasl.go | 33 +- internal/auth/sasl_test.go | 6 +- internal/auth/sasllogin/sasllogin.go | 54 ++ .../endpoint/dovecot_sasld/dovecot_sasl.go | 3 +- internal/endpoint/imap/imap.go | 3 +- internal/endpoint/smtp/session.go | 23 +- internal/endpoint/smtp/smtp.go | 19 +- internal/smtpconn/smtpconn.go | 12 + internal/target/remote/connect.go | 26 +- internal/testutils/smtp_server.go | 33 +- internal/tls/acme/acme.go | 10 +- tests/basic_test.go | 3 +- 20 files changed, 608 insertions(+), 431 deletions(-) create mode 100644 internal/auth/sasllogin/sasllogin.go diff --git a/.golangci.yml b/.golangci.yml index 47b5bb3e..8617544c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,12 +1,9 @@ linters: enable: - gosimple - - structcheck - - varcheck - errcheck - staticcheck - ineffassign - - deadcode - typecheck - govet - unused @@ -17,4 +14,4 @@ linters: - whitespace - nakedret - dogsled - - exportloopref + - copyloopvar diff --git a/Dockerfile b/Dockerfile index 0cad071e..2da6211f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.19-alpine AS build-env +FROM golang:1.23-alpine AS build-env ARG ADDITIONAL_BUILD_TAGS="" @@ -16,7 +16,7 @@ RUN mkdir -p /pkg/data && \ cp maddy.conf.docker /pkg/data/maddy.conf && \ ./build.sh --builddir /tmp --destdir /pkg/ --tags "docker ${ADDITIONAL_BUILD_TAGS}" build install -FROM alpine:3.18.4 +FROM alpine:3.21.2 LABEL maintainer="fox.cpp@disroot.org" LABEL org.opencontainers.image.source=https://github.com/foxcpp/maddy diff --git a/docs/reference/endpoints/imap.md b/docs/reference/endpoints/imap.md index 0382da28..ec0d2e60 100644 --- a/docs/reference/endpoints/imap.md +++ b/docs/reference/endpoints/imap.md @@ -18,6 +18,7 @@ imap tcp://0.0.0.0:143 tls://0.0.0.0:993 { io_debug no debug no insecure_auth no + sasl_login no auth pam storage &local_mailboxes auth_map identity @@ -88,6 +89,16 @@ Enable verbose logging. ### insecure_auth _boolean_ Default: `no` (`yes` if TLS is disabled) +Allow plain-text authentication over unencrypted connections. + +--- + +### sasl_login _boolean_ +Default: `no` + +Enable support for SASL LOGIN authentication mechanism used by +some outdated clients. + --- ### auth _module-reference_ diff --git a/docs/reference/endpoints/smtp.md b/docs/reference/endpoints/smtp.md index 740d41a0..4dfa723a 100644 --- a/docs/reference/endpoints/smtp.md +++ b/docs/reference/endpoints/smtp.md @@ -11,6 +11,7 @@ smtp tcp://0.0.0.0:25 { io_debug no debug no insecure_auth no + sasl_login no read_timeout 10m write_timeout 1m max_message_size 32M @@ -102,6 +103,14 @@ Allow plain-text authentication over unencrypted connections. Not recommended! --- +### sasl_login _boolean_ +Default: `no` + +Enable support for SASL LOGIN authentication mechanism used by +some outdated clients. + +--- + ### read_timeout _duration_ Default: `10m` diff --git a/docs/reference/targets/smtp.md b/docs/reference/targets/smtp.md index 7ee56abd..ebbc4308 100644 --- a/docs/reference/targets/smtp.md +++ b/docs/reference/targets/smtp.md @@ -49,19 +49,27 @@ Advanced TLS client configuration options. See [TLS configuration / Client](/ref --- +### starttls _boolean_ +Default: `yes` (`no` for `target.lmtp`) + +Use STARTTLS to enable TLS encryption. If STARTTLS is not supported +by the remote server - connection will fail. + +maddy will use `localhost` as HELO hostname before STARTTLS +and will only send its actual hostname after STARTTLS. + ### attempt_starttls _boolean_ Default: `yes` (`no` for `target.lmtp`) -Attempt to use STARTTLS if it is supported by the remote server. -If TLS handshake fails, connection will be retried without STARTTLS -unless `require_tls` is also specified. +DEPRECATED: Equivalent to `starttls`. Plaintext fallback is no longer +supported. --- ### require_tls _boolean_ Default: `no` -Refuse to pass messages over plain-text connections. +DEPRECATED: Ignored. Set `starttls yes` to use STARTLS. --- @@ -112,4 +120,4 @@ Same as for target.remote. ### submission_timeout _duration_ Default: `12m` -Same as for target.remote. \ No newline at end of file +Same as for target.remote. diff --git a/docs/tutorials/building-from-source.md b/docs/tutorials/building-from-source.md index a2e37971..55b6852e 100644 --- a/docs/tutorials/building-from-source.md +++ b/docs/tutorials/building-from-source.md @@ -6,7 +6,7 @@ You need C toolchain, Go toolchain and Make: On Debian-based system this should work: ``` -apt-get install golang-1.19 gcc libc6-dev make +apt-get install golang-1.23 gcc libc6-dev make ``` Additionally, if you want manual pages, you should also have scdoc installed. @@ -18,10 +18,13 @@ reader (for Ubuntu 22.04 LTS it is in repositories). maddy depends on a rather recent Go toolchain version that may not be available in some distributions (*cough* Debian *cough*). -It should not be hard to grab a recent built toolchain from golang.org: +`go` command in Go 1.21 or newer will automatically download up-to-date +toolchain to build maddy. It is necessary to run commands below only +if you have `go` command version older than 1.21. + ``` -wget "https://dl.google.com/go/go1.19.9.linux-amd64.tar.gz" -tar xf "go1.19.19.linux-amd64.tar.gz" +wget "https://go.dev/dl/go1.23.5.linux-amd64.tar.gz" +tar xf "go1.23.5.linux-amd64.tar.gz" export GOROOT="$PWD/go" export PATH="$PWD/go/bin:$PATH" ``` @@ -36,7 +39,7 @@ $ cd maddy 2. Select the appropriate version to build: ``` -$ git checkout v0.7.0 # a specific release +$ git checkout v0.8.0 # a specific release $ git checkout master # next bugfix release $ git checkout dev # next feature release ``` diff --git a/go.mod b/go.mod index 70ac58cb..bfa5673c 100644 --- a/go.mod +++ b/go.mod @@ -1,175 +1,169 @@ module github.com/foxcpp/maddy -go 1.19 +go 1.23 require ( blitiri.com.ar/go/spf v1.5.1 github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 github.com/c0va23/go-proxyprotocol v0.9.1 - github.com/caddyserver/certmagic v0.20.0 + github.com/caddyserver/certmagic v0.21.7 github.com/emersion/go-imap v1.2.2-0.20220928192137-6fac715be9cf github.com/emersion/go-imap-compress v0.0.0-20201103190257-14809af1d1b9 github.com/emersion/go-imap-sortthread v1.2.0 - github.com/emersion/go-message v0.18.0 - github.com/emersion/go-milter v0.4.0 + github.com/emersion/go-message v0.18.2 + github.com/emersion/go-milter v0.4.1 github.com/emersion/go-msgauth v0.6.8 - github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 - github.com/emersion/go-smtp v0.20.2-0.20240121112028-434ddca4792e + github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 + github.com/emersion/go-smtp v0.21.3 github.com/foxcpp/go-dovecot-sasl v0.0.0-20200522223722-c4699d7a24bf github.com/foxcpp/go-imap-backend-tests v0.0.0-20220105184719-e80aa29a5e16 github.com/foxcpp/go-imap-i18nlevel v0.0.0-20200208001533-d6ec88553005 github.com/foxcpp/go-imap-mess v0.0.0-20230108134257-b7ec3a649613 github.com/foxcpp/go-imap-namespace v0.0.0-20200802091432-08496dd8e0ed github.com/foxcpp/go-imap-sql v0.5.1-0.20250124140007-8da5567429d5 - github.com/foxcpp/go-mockdns v1.0.0 - github.com/foxcpp/go-mtasts v0.0.0-20191219193356-62bc3f1f74b8 - github.com/go-ldap/ldap/v3 v3.4.6 - github.com/go-sql-driver/mysql v1.7.1 - github.com/google/uuid v1.5.0 - github.com/hashicorp/go-hclog v1.6.2 + github.com/foxcpp/go-mockdns v1.1.0 + github.com/foxcpp/go-mtasts v0.0.0-20240130093538-1438da2e5932 + github.com/go-ldap/ldap/v3 v3.4.10 + github.com/go-sql-driver/mysql v1.8.1 + github.com/google/uuid v1.6.0 + github.com/hashicorp/go-hclog v1.6.3 github.com/johannesboyne/gofakes3 v0.0.0-20210704111953-6a9f95c2941c github.com/lib/pq v1.10.9 - github.com/libdns/alidns v1.0.3-0.20230628155627-8d5d630d5516 - github.com/libdns/cloudflare v0.1.1-0.20221006221909-9d3ab3c3cddd + github.com/libdns/acmedns v0.2.0 + github.com/libdns/alidns v1.0.3 + github.com/libdns/cloudflare v0.1.1 github.com/libdns/digitalocean v0.0.0-20230728223659-4f9064657aea github.com/libdns/gandi v1.0.3 github.com/libdns/googleclouddns v1.1.0 github.com/libdns/hetzner v0.0.1 - github.com/libdns/leaseweb v0.3.1 - github.com/libdns/libdns v0.2.2-0.20230227175549-2dc480633939 + github.com/libdns/leaseweb v0.4.0 + github.com/libdns/libdns v0.2.2 github.com/libdns/metaname v0.3.0 github.com/libdns/namecheap v0.0.0-20211109042440-fc7440785c8e github.com/libdns/namedotcom v0.3.3 - github.com/libdns/rfc2136 v0.1.0 - github.com/libdns/route53 v1.3.3 + github.com/libdns/rfc2136 v0.1.1 + github.com/libdns/route53 v1.5.1 github.com/libdns/vultr v1.0.0 - github.com/mattn/go-sqlite3 v1.14.19 - github.com/miekg/dns v1.1.58 - github.com/minio/minio-go/v7 v7.0.66 - github.com/netauth/netauth v0.6.2-0.20220831214440-1df568cd25d6 - github.com/prometheus/client_golang v1.18.0 - github.com/urfave/cli/v2 v2.27.1 - go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.31.0 - golang.org/x/net v0.33.0 + github.com/mattn/go-sqlite3 v1.14.24 + github.com/miekg/dns v1.1.63 + github.com/minio/minio-go/v7 v7.0.84 + github.com/netauth/netauth v0.6.2 + github.com/prometheus/client_golang v1.20.5 + github.com/urfave/cli/v2 v2.27.5 + go.uber.org/zap v1.27.0 + golang.org/x/crypto v0.32.0 + golang.org/x/net v0.34.0 golang.org/x/sync v0.10.0 golang.org/x/text v0.21.0 - modernc.org/sqlite v1.28.0 + modernc.org/sqlite v1.34.5 ) require ( - cloud.google.com/go/compute v1.23.3 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/auth v0.14.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/compute/metadata v0.6.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/aws/aws-sdk-go v1.44.40 // indirect - github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect - github.com/aws/aws-sdk-go-v2/config v1.26.5 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect - github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect - github.com/aws/smithy-go v1.19.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.33.0 // indirect + github.com/aws/aws-sdk-go-v2/config v1.29.1 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.54 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9 // indirect + github.com/aws/aws-sdk-go-v2/service/route53 v1.48.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.11 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 // indirect + github.com/aws/smithy-go v1.22.2 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect - github.com/digitalocean/godo v1.108.0 // indirect + github.com/caddyserver/zerossl v0.1.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect + github.com/digitalocean/godo v1.134.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect + github.com/go-ini/ini v1.67.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/goccy/go-json v0.10.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/s2a-go v0.1.7 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.5 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/klauspost/compress v1.17.4 // indirect - github.com/klauspost/cpuid/v2 v2.2.6 // indirect - github.com/libdns/acmedns v0.2.0 // indirect - github.com/magiconair/properties v1.8.7 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/magiconair/properties v1.8.9 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mholt/acmez v1.2.0 // indirect + github.com/mholt/acmez/v3 v3.0.1 // indirect github.com/minio/md5-simd v1.1.2 // indirect - github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/netauth/protocol v0.0.0-20210918062754-7fee492ffcbd // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.46.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rs/xid v1.5.0 // indirect + github.com/rs/xid v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.18.2 // indirect + github.com/spf13/viper v1.19.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/vultr/govultr/v3 v3.6.1 // indirect - github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect - github.com/zeebo/blake3 v0.2.3 // indirect - go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect - go.opentelemetry.io/otel v1.22.0 // indirect - go.opentelemetry.io/otel/metric v1.22.0 // indirect - go.opentelemetry.io/otel/trace v1.22.0 // indirect + github.com/vultr/govultr/v3 v3.14.1 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/api v0.157.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect - google.golang.org/grpc v1.60.1 // indirect - google.golang.org/protobuf v1.32.0 // indirect + go.uber.org/zap/exp v0.3.0 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/oauth2 v0.25.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.29.0 // indirect + google.golang.org/api v0.218.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 // indirect + google.golang.org/grpc v1.70.0 // indirect + google.golang.org/protobuf v1.36.4 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools v2.2.0+incompatible // indirect - lukechampine.com/uint128 v1.3.0 // indirect - modernc.org/cc/v3 v3.41.0 // indirect - modernc.org/ccgo/v3 v3.16.15 // indirect - modernc.org/libc v1.40.6 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect - modernc.org/opt v0.1.3 // indirect - modernc.org/strutil v1.2.0 // indirect - modernc.org/token v1.1.0 // indirect + modernc.org/libc v1.61.9 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.8.2 // indirect ) replace github.com/emersion/go-imap => github.com/foxcpp/go-imap v1.0.0-beta.1.0.20220623182312-df940c324887 +replace github.com/emersion/go-smtp => github.com/foxcpp/go-smtp v1.21.4-0.20250124171104-c8519ae4fb23 // v1.21.3+maddy.1 + replace github.com/libdns/gandi => github.com/foxcpp/libdns-gandi v1.0.4-0.20240127130558-4782f9d5ce3e // v1.0.3+maddy.1 diff --git a/go.sum b/go.sum index 4feb0755..2f99728a 100644 --- a/go.sum +++ b/go.sum @@ -32,7 +32,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9 cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= @@ -47,6 +48,10 @@ cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjby cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM= +cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -69,10 +74,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= @@ -180,6 +183,8 @@ cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuW cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -188,67 +193,53 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI= github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= -github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.44.40 h1:MR0qefjBJrZuXE0VoeKMQFtjS2tUeVpbQNfb7NzQNgI= github.com/aws/aws-sdk-go v1.44.40/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go-v2 v1.17.8/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= -github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2/config v1.18.21/go.mod h1:+jPQiVPz1diRnjj6VGqWcLK6EzNmQ42l7J3OqGTLsSY= -github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw= -github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU= -github.com/aws/aws-sdk-go-v2/credentials v1.13.20/go.mod h1:xtZnXErtbZ8YGXC3+8WfajpMBn5Ga/3ojZdxHq6iI8o= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.2/go.mod h1:cDh1p6XkSGSwSRIArWRc6+UqAQ7x4alQ0QfpVR6f+co= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.32/go.mod h1:RudqOgadTWdcS3t/erPQo24pcVEoYyqj/kKW5Vya21I= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.26/go.mod h1:vq86l7956VgFr0/FWQ2BWnK07QC3WYsepKzy33qqY5U= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.33/go.mod h1:zG2FcwjQarWaqXSCGpgcr3RSjZ6dHGguZSppUL0XR7Q= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.26/go.mod h1:Bd4C/4PkVGubtNe5iMXu5BNnaBi/9t/UsFspPt4ram8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= -github.com/aws/aws-sdk-go-v2/service/route53 v1.27.7/go.mod h1:Jhu94omkrksnqX6Xs4Qo10eA1Fx+2NYKjZMU4GvZLp0= -github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0 h1:f3hBZWtpn9clZGXJoqahQeec9ZPZnu22g8pg+zNyif0= -github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0/go.mod h1:8qqfpG4mug2JLlEyWPSFhEGvJiaZ9iPmMDDMYc5Xtas= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.8/go.mod h1:GNIveDnP+aE3jujyUSH5aZ/rktsTM5EvtKnCqBZawdw= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.8/go.mod h1:44qFP1g7pfd+U+sQHLPalAPKnyfTZjJsYR4xIwsJy5o= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.9/go.mod h1:yyW88BEPXA2fGFyI2KCcZC3dNpiT0CZAHaF+i656/tQ= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= -github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= +github.com/aws/aws-sdk-go-v2 v1.33.0 h1:Evgm4DI9imD81V0WwD+TN4DCwjUMdc94TrduMLbgZJs= +github.com/aws/aws-sdk-go-v2 v1.33.0/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2/config v1.29.1 h1:JZhGawAyZ/EuJeBtbQYnaoftczcb2drR2Iq36Wgz4sQ= +github.com/aws/aws-sdk-go-v2/config v1.29.1/go.mod h1:7bR2YD5euaxBhzt2y/oDkt3uNRb6tjFp98GlTFueRwk= +github.com/aws/aws-sdk-go-v2/credentials v1.17.54 h1:4UmqeOqJPvdvASZWrKlhzpRahAulBfyTJQUaYy4+hEI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.54/go.mod h1:RTdfo0P0hbbTxIhmQrOsC/PquBZGabEPnCaxxKRPSnI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 h1:5grmdTdMsovn9kPZPI23Hhvp0ZyNm5cRO+IZFIYiAfw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24/go.mod h1:zqi7TVKTswH3Ozq28PkmBmgzG1tona7mo9G2IJg4Cis= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28 h1:igORFSiH3bfq4lxKFkTSYDhJEUCYo6C8VKiWJjYwQuQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28/go.mod h1:3So8EA/aAYm36L7XIvCVwLa0s5N0P7o2b1oqnx/2R4g= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28 h1:1mOW9zAUMhTSrMDssEHS/ajx8JcAj/IcftzcmNlmVLI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28/go.mod h1:kGlXVIWDfvt2Ox5zEaNglmq0hXPHgQFNMix33Tw22jA= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9 h1:TQmKDyETFGiXVhZfQ/I0cCFziqqX58pi4tKJGYGFSz0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9/go.mod h1:HVLPK2iHQBUx7HfZeOQSEu3v2ubZaAY2YPbAm5/WUyY= +github.com/aws/aws-sdk-go-v2/service/route53 v1.48.2 h1:Rxg1R0CHxVb9ggQLufOkr4an3yFEkTDN+N5+LFU4aEg= +github.com/aws/aws-sdk-go-v2/service/route53 v1.48.2/go.mod h1:TN4PcCL0lvqmYcv+AV8iZFC4Sd0FM06QDaoBXrFEftU= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.11 h1:kuIyu4fTT38Kj7YCC7ouNbVZSSpqkZ+LzIfhCr6Dg+I= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.11/go.mod h1:Ro744S4fKiCCuZECXgOi760TiYylUM8ZBf6OGiZzJtY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 h1:l+dgv/64iVlQ3WsBbnn+JSbkj01jIi+SM0wYsj3y/hY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10/go.mod h1:Fzsj6lZEb8AkTE5S68OhcbBqeWPsR8RnGuKPr8Todl8= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 h1:BRVDbewN6VZcwr+FBOszDKvYeXY1kJ+GGMCcpghlw0U= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.9/go.mod h1:f6vjfZER1M17Fokn0IzssOTMT2N8ZSq+7jnNF0tArvw= +github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= +github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/c0va23/go-proxyprotocol v0.9.1 h1:5BCkp0fDJOhzzH1lhjUgHhmZz9VvRMMif1U2D31hb34= github.com/c0va23/go-proxyprotocol v0.9.1/go.mod h1:TNjUV+llvk8TvWJxlPYAeAYZgSzT/iicNr3nWBWX320= -github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE= -github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE= -github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= -github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= +github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg= +github.com/caddyserver/certmagic v0.21.7/go.mod h1:LCPG3WLxcnjVKl/xpjzM0gqh0knrKKKiO5WVttX2eEI= +github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= +github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -262,17 +253,17 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/digitalocean/godo v1.41.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU= -github.com/digitalocean/godo v1.108.0 h1:fWyMENvtxpCpva1UbKzOFnyAS04N1FNuBWWfPeTGquQ= -github.com/digitalocean/godo v1.108.0/go.mod h1:R6EmmWI8CT1+fCtjWY9UCB+L5uufuZH13wk3YhxycCs= +github.com/digitalocean/godo v1.134.0 h1:dT7aQR9jxNOQEZwzP+tAYcxlj5szFZScC33+PAYGQVM= +github.com/digitalocean/godo v1.134.0/go.mod h1:PU8JB6I1XYkQIdHFop8lLAY9ojp6M0XcU0TWaQSxbrc= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a/go.mod h1:ikgISoP7pRAolqsVP64yMteJa2FIpS6ju88eBT6K1yQ= @@ -281,22 +272,20 @@ github.com/emersion/go-imap-compress v0.0.0-20201103190257-14809af1d1b9/go.mod h github.com/emersion/go-imap-move v0.0.0-20180601155324-5eb20cb834bf/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w= github.com/emersion/go-imap-sortthread v1.2.0 h1:EMVEJXPWAhXMWECjR82Rn/tza6MddcvTwGAdTu1vJKU= github.com/emersion/go-imap-sortthread v1.2.0/go.mod h1:UhenCBupR+vSYRnqJkpjSq84INUCsyAK1MLpogv14pE= -github.com/emersion/go-message v0.11.2/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY= github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= -github.com/emersion/go-message v0.18.0 h1:7LxAXHRpSeoO/Wom3ZApVZYG7c3d17yCScYce8WiXA8= github.com/emersion/go-message v0.18.0/go.mod h1:Zi69ACvzaoV/MBnrxfVBPV3xWEuCmC2nEN39oJF4B8A= -github.com/emersion/go-milter v0.4.0 h1:HysxeAzNEToJw1VQEwLrjJqgmd1iuDzYg2329T/q6/Y= -github.com/emersion/go-milter v0.4.0/go.mod h1:ablHK0pbLB83kMFBznp/Rj8aV+Kc3jw8cxzzmCNLIOY= +github.com/emersion/go-message v0.18.1/go.mod h1:XpJyL70LwRvq2a8rVbHXikPgKj8+aI0kGdHlg16ibYA= +github.com/emersion/go-message v0.18.2 h1:rl55SQdjd9oJcIoQNhubD2Acs1E6IzlZISRTK7x/Lpg= +github.com/emersion/go-message v0.18.2/go.mod h1:XpJyL70LwRvq2a8rVbHXikPgKj8+aI0kGdHlg16ibYA= +github.com/emersion/go-milter v0.4.1 h1:gLs9QD0zEHF8omgEw8M+aGz6iwBNpWLAcwgSur0ra4M= +github.com/emersion/go-milter v0.4.1/go.mod h1:erCQVl0mH4SX9jEvwe+wyndit0rQtmvMLH86V6NGtkI= github.com/emersion/go-msgauth v0.6.8 h1:kW/0E9E8Zx5CdKsERC/WnAvnXvX7q9wTHia1OA4944A= github.com/emersion/go-msgauth v0.6.8/go.mod h1:YDwuyTCUHu9xxmAeVj0eW4INnwB6NNZoPdLerpSxRrc= 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-sasl v0.0.0-20231106173351-e73c9f7bad43 h1:hH4PQfOndHDlpzYfLAAfl63E8Le6F2+EL/cdhlkyRJY= github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= -github.com/emersion/go-smtp v0.20.2-0.20240121112028-434ddca4792e h1:WAPhaiA+bDO/mFgCDQJKCQI/RbH/73lCcis4Jb8Y2ec= -github.com/emersion/go-smtp v0.20.2-0.20240121112028-434ddca4792e/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= -github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= -github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY= +github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk= +github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -309,8 +298,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-dovecot-sasl v0.0.0-20200522223722-c4699d7a24bf h1:rmBPY5fryjp9zLQYsUmQqqgsYq7qeVfrjtr96Tf9vD8= @@ -325,43 +314,46 @@ github.com/foxcpp/go-imap-mess v0.0.0-20230108134257-b7ec3a649613 h1:fw9OWfPxP1C github.com/foxcpp/go-imap-mess v0.0.0-20230108134257-b7ec3a649613/go.mod h1:P/O/qz4gaVkefzJ40BUtN/ZzBnaEg0YYe1no/SMp7Aw= github.com/foxcpp/go-imap-namespace v0.0.0-20200802091432-08496dd8e0ed h1:1Jo7geyvunrPSjL6F6D9EcXoNApS5v3LQaro7aUNPnE= github.com/foxcpp/go-imap-namespace v0.0.0-20200802091432-08496dd8e0ed/go.mod h1:Shows1vmkBWO40ChOClaUe6DUnZrsP1UPAuoWzIUdgQ= -github.com/foxcpp/go-imap-sql v0.5.1-0.20240831122236-655e4cb87d20 h1:q4NtuuK7Kuf8zHC1CF8p1GB1owd/IyN+zfqy8DQL9Ig= -github.com/foxcpp/go-imap-sql v0.5.1-0.20240831122236-655e4cb87d20/go.mod h1:LMlfyNkVs7v2zE6OVeGe9qWPmKFdXDmLNddPLodPVIw= github.com/foxcpp/go-imap-sql v0.5.1-0.20250124140007-8da5567429d5 h1:jMxhw9qmwqg70qfMDWq0ImRHAduQjkTZOC9vBs5t2ug= github.com/foxcpp/go-imap-sql v0.5.1-0.20250124140007-8da5567429d5/go.mod h1:LMlfyNkVs7v2zE6OVeGe9qWPmKFdXDmLNddPLodPVIw= github.com/foxcpp/go-mockdns v0.0.0-20191216195825-5eabd8dbfe1f/go.mod h1:tPg4cp4nseejPd+UKxtCVQ2hUxNTZ7qQZJa7CLriIeo= -github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= -github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= -github.com/foxcpp/go-mtasts v0.0.0-20191219193356-62bc3f1f74b8 h1:k8w0iy6GP9oeSZWUH3p2DqZHaXDKZGNs3NZGZMGfQHc= -github.com/foxcpp/go-mtasts v0.0.0-20191219193356-62bc3f1f74b8/go.mod h1:HO1YOCbBM8KjpgThMMFejHx6K/UsnEv2Oh9YGtBIlOU= +github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= +github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= +github.com/foxcpp/go-mtasts v0.0.0-20240130093538-1438da2e5932 h1:p04U/s8IZEc+PVWIDWGUgdqGq3xsixI7XRZ6Bp/xZbQ= +github.com/foxcpp/go-mtasts v0.0.0-20240130093538-1438da2e5932/go.mod h1:RtHIZCsScdjIzXpTTjmEljtUrIjQbPBTvw7F1tKQbKk= +github.com/foxcpp/go-smtp v1.21.4-0.20250124171104-c8519ae4fb23 h1:JSnsCrRrHNBlgfKVFBxFzp3fN/wS21t8fAHcZ9B1uWI= +github.com/foxcpp/go-smtp v1.21.4-0.20250124171104-c8519ae4fb23/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= github.com/foxcpp/libdns-gandi v1.0.4-0.20240127130558-4782f9d5ce3e h1:hKk+CGUtwnKDGKINPEojeo91kx0tnV6V4tlzHehJPfg= github.com/foxcpp/libdns-gandi v1.0.4-0.20240127130558-4782f9d5ce3e/go.mod h1:G6dw58Xnji2xX+lb+uZxGbtmfxKllm1CGHE2bOPG3WA= github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= -github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk= +github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= -github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU= +github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= +github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -371,6 +363,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -389,8 +382,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -414,7 +407,6 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -436,20 +428,20 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -459,23 +451,39 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= -github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= -github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -485,154 +493,147 @@ github.com/johannesboyne/gofakes3 v0.0.0-20210704111953-6a9f95c2941c h1:lx/uPI+m github.com/johannesboyne/gofakes3 v0.0.0-20210704111953-6a9f95c2941c/go.mod h1:LIAXxPvcUXwOcTIj9LSNSUpE9/eMHalTWxsP/kmWxQI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= -github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libdns/acmedns v0.2.0 h1:zTXdHZwe3r2issdVRyqt5/4X2yHpiBVmFnTrwBA29ik= github.com/libdns/acmedns v0.2.0/go.mod h1:XlKHilQQK/IGHYY//vCb903PdG4Wc/XnDQzcMp2hV3g= -github.com/libdns/alidns v1.0.3-0.20230628155627-8d5d630d5516 h1:tPVSANkA4lo+K65YjsQcaQ1uh6sb0zRBQDz78l1Fo4Y= -github.com/libdns/alidns v1.0.3-0.20230628155627-8d5d630d5516/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= -github.com/libdns/cloudflare v0.1.1-0.20221006221909-9d3ab3c3cddd h1:c5hc0b5/pFqFeyQaOTVmYJbyr+QwZZFcMnjgtZGIk6k= -github.com/libdns/cloudflare v0.1.1-0.20221006221909-9d3ab3c3cddd/go.mod h1:ob9J/elFVmPWKNHOMynwtH0h+T3pBrEL18amCSliwAQ= +github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ= +github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= +github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054= +github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU= github.com/libdns/digitalocean v0.0.0-20230728223659-4f9064657aea h1:IGlMNZCUp8Ho7NYYorpP5ZJgg2mFXARs6eHs/pSqFkA= github.com/libdns/digitalocean v0.0.0-20230728223659-4f9064657aea/go.mod h1:B2TChhOTxvBflpRTHlguXWtwa1Ha5WI6JkB6aCViM+0= github.com/libdns/googleclouddns v1.1.0 h1:murPR1LfTZZObLV2OLxUVmymWH25glkMFKpDjkk2m0E= github.com/libdns/googleclouddns v1.1.0/go.mod h1:3tzd056dfqKlf71V8Oy19En4WjJ3ybyuWx6P9bQSCIw= github.com/libdns/hetzner v0.0.1 h1:WsmcsOKnfpKmzwhfyqhGQEIlEeEaEUvb7ezoJgBKaqU= github.com/libdns/hetzner v0.0.1/go.mod h1:Jj12aJipO9Ir7OGaXueJ5J1RnerFMD0auGa6k9kujG4= -github.com/libdns/leaseweb v0.3.1 h1:39R0d4RYAP6llXUnTsvM5ge7WMjLN9Zkinzj3/J+qVY= -github.com/libdns/leaseweb v0.3.1/go.mod h1:OeZtd+s2M1RfC3wIJF9SHZDFpD7H5RRiC6OPK3AWYjA= +github.com/libdns/leaseweb v0.4.0 h1:WG9R5AwewpYM4goymFwnG2SB0qwL8gMsSzwRHZHee/U= +github.com/libdns/leaseweb v0.4.0/go.mod h1:dvTvEn11JN6+ebhAQ60l+jiaBiEqyJFs3EIo0YBcQkU= github.com/libdns/libdns v0.1.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= -github.com/libdns/libdns v0.2.2-0.20230227175549-2dc480633939 h1:EvTiXkv78P20yfk4CUPmAkH3Cmumt3s/48WWiC2babY= -github.com/libdns/libdns v0.2.2-0.20230227175549-2dc480633939/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= +github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/libdns/metaname v0.3.0 h1:HJudLYthdv52TupOPczojip/nEQHW7xqk5+whGReva4= github.com/libdns/metaname v0.3.0/go.mod h1:a3hqEgj59tjWaWlF4WxQGhvMVtjz1E4Ngs1GfVS+VhQ= github.com/libdns/namecheap v0.0.0-20211109042440-fc7440785c8e h1:WCcKyxiiK/sJnST1ulVBKNg4J8luCYDdgUrp2ySMO2s= github.com/libdns/namecheap v0.0.0-20211109042440-fc7440785c8e/go.mod h1:dED6sMLZxIcilF1GjrcpwgVoCglXGMn86irqQzRhqRY= github.com/libdns/namedotcom v0.3.3 h1:R10C7+IqQGVeC4opHHMiFNBxdNBg1bi65ZwqLESl+jE= github.com/libdns/namedotcom v0.3.3/go.mod h1:GbYzsAF2yRUpI0WgIK5fs5UX+kDVUPaYCFLpTnKQm0s= -github.com/libdns/rfc2136 v0.1.0 h1:BlGOPfx/R3xqKrgHT9TlreA8Ulw8ti8+VtJj8E0H9hE= -github.com/libdns/rfc2136 v0.1.0/go.mod h1:tgXWavE+5OiAfdKxBnuG8OBEwQFAu7uuiS3+laspAGs= -github.com/libdns/route53 v1.3.3 h1:16sTxbbRGm0zODz0p0aVHHIyTqtHzEn3j0s4dGzQvNI= -github.com/libdns/route53 v1.3.3/go.mod h1:n1Xy55lpfdxMIx4CVWAM16GQac+/OZcnm1xBjMyhZAo= +github.com/libdns/rfc2136 v0.1.1 h1:GKh2r08xt4aYeGlXR9eFrJMfFKD5i9QHBOpT1FIww/U= +github.com/libdns/rfc2136 v0.1.1/go.mod h1:tgXWavE+5OiAfdKxBnuG8OBEwQFAu7uuiS3+laspAGs= +github.com/libdns/route53 v1.5.1 h1:dkdcc2CKY/EHBBzAKqE0Cko7MKR8uVJ3GvpzwKu/UKM= +github.com/libdns/route53 v1.5.1/go.mod h1:joT4hKmaTNKHEwb7GmZ65eoDz1whTu7KKYPS8ZqIh6Q= github.com/libdns/vultr v1.0.0 h1:W8B4+k2bm9ro3bZLSZV9hMOQI+uO6Svu+GmD+Olz7ZI= github.com/libdns/vultr v1.0.0/go.mod h1:8K1HJExcbeHS4YPkFHRZpqpXZzZ+DZAA0m0VikJgEqk= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= -github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mholt/acmez/v3 v3.0.1 h1:4PcjKjaySlgXK857aTfDuRbmnM5gb3Ruz3tvoSJAUp8= +github.com/mholt/acmez/v3 v3.0.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= -github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw= -github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs= -github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= -github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/minio/minio-go/v7 v7.0.84 h1:D1HVmAF8JF8Bpi6IU4V9vIEj+8pc+xU88EWMs2yed0E= +github.com/minio/minio-go/v7 v7.0.84/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/netauth/netauth v0.6.2-0.20220831214440-1df568cd25d6 h1:TsF5Cl0Mj5JMvPOP2ySVq+CZoiPrTGwvNPbuQotuSAE= -github.com/netauth/netauth v0.6.2-0.20220831214440-1df568cd25d6/go.mod h1:4PEbISVqRCQaXaDAt289w3nK9UhoF8/ZOLy31Hbv7ds= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/netauth/netauth v0.6.2 h1:Gtx/Xxa6YUaGny+iVvWyp+FAmtLQ1IlbB2uWTZEpWxQ= +github.com/netauth/netauth v0.6.2/go.mod h1:4PEbISVqRCQaXaDAt289w3nK9UhoF8/ZOLy31Hbv7ds= github.com/netauth/protocol v0.0.0-20210918062754-7fee492ffcbd h1:4yVpQ/+li28lQ/daYCWeDB08obRmjaoAw2qfFFaCQ40= github.com/netauth/protocol v0.0.0-20210918062754-7fee492ffcbd/go.mod h1:wpK5wqysOJU1w2OxgG65du8M7UqBkxzsNaJdjwiRqAs= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= -github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 h1:J6qvD6rbmOil46orKqJaRPG+zTpoGlBTUdyv8ki63L0= github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63/go.mod h1:n+VKSARF5y/tS9XFSP7vWDfS+GUC5vs/YT7M5XDTUEM= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -643,24 +644,28 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= -github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= -github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= -github.com/vultr/govultr/v3 v3.6.1 h1:l1hAXGtqWVnobBpLRzW/BxoocYFI7SSBwQHw65ntLk4= -github.com/vultr/govultr/v3 v3.6.1/go.mod h1:rt9v2x114jZmmLAE/h5N5jnxTmsK9ewwS2oQZ0UBQzM= -github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI= -github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/vultr/govultr/v3 v3.14.1 h1:9BpyZgsWasuNoR39YVMcq44MSaF576Z4D+U3ro58eJQ= +github.com/vultr/govultr/v3 v3.14.1/go.mod h1:q34Wd76upKmf+vxFMgaNMH3A8BbsPBmSYZUGC8oZa5w= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= -github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= +github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -670,32 +675,46 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= -go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= -go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= -go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= -go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= -go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= -go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= +go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -706,8 +725,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -735,8 +754,12 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -788,9 +811,17 @@ golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -816,8 +847,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -833,6 +864,11 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -899,23 +935,34 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -925,19 +972,19 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -993,8 +1040,11 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1051,8 +1101,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.157.0 h1:ORAeqmbrrozeyw5NjnMxh7peHO0UzV4wWYSwZeCUb20= -google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g= +google.golang.org/api v0.218.0 h1:x6JCjEWeZ9PFCRe9z0FBrNwj7pB7DOAqT35N+IPnAUA= +google.golang.org/api v0.218.0/go.mod h1:5VGHBAkxrA/8EFjLVEYmMUJ8/8+gWWQ3s4cFH0FxG2M= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1060,8 +1110,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1163,8 +1211,11 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55/go.mod h1:45EK0dUbEZ2NHjCeAd2LXmyjAgGUGrpGROgjhC3ADck= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 h1:91mG8dNTpkC0uChJUQ9zCiRqx3GEEFOWaRZ0mI6Oj2I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1200,8 +1251,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1218,10 +1269,12 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1229,6 +1282,7 @@ gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3M gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -1242,24 +1296,28 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019年2月3日/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020年1月3日/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020年1月4日/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= -lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= -modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= -modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= -modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= -modernc.org/libc v1.40.6 h1:141JHq3SjhOOCjECBgD4K8VgTFOy19CnHwroC08DAig= -modernc.org/libc v1.40.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= -modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0= +modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.23.13 h1:PFiaemQwE/jdwi8XEHyEV+qYWoIuikLP3T4rvDeJb00= +modernc.org/ccgo/v4 v4.23.13/go.mod h1:vdN4h2WR5aEoNondUx26K7G8X+nuBscYnAEWSRmN2/0= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.6.1 h1:+Qf6xdG8l7B27TQ8D8lw/iFMUj1RXRBOuMUWziJOsk8= +modernc.org/gc/v2 v2.6.1/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.61.9 h1:PLSBXVkifXGELtJ5BOnBUyAHr7lsatNwFU/RRo4kfJM= +modernc.org/libc v1.61.9/go.mod h1:61xrnzk/aR8gr5bR7Uj/lLFLuXu2/zMpIjcry63Eumk= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI= +modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g= +modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/internal/auth/sasl.go b/internal/auth/sasl.go index d34ba168..00d25ffa 100644 --- a/internal/auth/sasl.go +++ b/internal/auth/sasl.go @@ -29,6 +29,7 @@ import ( modconfig "github.com/foxcpp/maddy/framework/config/module" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/internal/auth/sasllogin" "github.com/foxcpp/maddy/internal/authz" ) @@ -48,6 +49,7 @@ var ( type SASLAuth struct { Log log.Logger OnlyFirstID bool + EnableLogin bool AuthMap module.Table AuthNormalize authz.NormalizeFunc @@ -59,7 +61,10 @@ func (s *SASLAuth) SASLMechanisms() []string { var mechs []string if len(s.Plain) != 0 { - mechs = append(mechs, sasl.Plain, sasl.Login) + mechs = append(mechs, sasl.Plain) + if s.OnlyFirstID { + mechs = append(mechs, sasl.Login) + } } return mechs @@ -114,8 +119,16 @@ func (s *SASLAuth) AuthPlain(username, password string) error { return fmt.Errorf("no auth. provider accepted creds, last err: %w", lastErr) } +type ContextData struct { + // Authentication username. May be different from identity. + Username string + + // Password used for password-based mechanisms. + Password string +} + // CreateSASL creates the sasl.Server instance for the corresponding mechanism. -func (s *SASLAuth) CreateSASL(mech string, remoteAddr net.Addr, successCb func(identity string) error) sasl.Server { +func (s *SASLAuth) CreateSASL(mech string, remoteAddr net.Addr, successCb func(identity string, data ContextData) error) sasl.Server { switch mech { case sasl.Plain: return sasl.NewPlainServer(func(identity, username, password string) error { @@ -132,17 +145,27 @@ func (s *SASLAuth) CreateSASL(mech string, remoteAddr net.Addr, successCb func(i return ErrInvalidAuthCred } - return successCb(identity) + return successCb(identity, ContextData{ + Username: username, + Password: password, + }) }) case sasl.Login: - return sasl.NewLoginServer(func(username, password string) error { + if !s.EnableLogin { + return FailingSASLServ{Err: ErrUnsupportedMech} + } + + return sasllogin.NewLoginServer(func(username, password string) error { err := s.AuthPlain(username, password) if err != nil { s.Log.Error("authentication failed", err, "username", username, "src_ip", remoteAddr) return ErrInvalidAuthCred } - return successCb(username) + return successCb(username, ContextData{ + Username: username, + Password: password, + }) }) } return FailingSASLServ{Err: ErrUnsupportedMech} diff --git a/internal/auth/sasl_test.go b/internal/auth/sasl_test.go index fcc193aa..a59cfc79 100644 --- a/internal/auth/sasl_test.go +++ b/internal/auth/sasl_test.go @@ -52,7 +52,7 @@ func TestCreateSASL(t *testing.T) { } t.Run("XWHATEVER", func(t *testing.T) { - srv := a.CreateSASL("XWHATEVER", &net.TCPAddr{}, func(string) error { return nil }) + srv := a.CreateSASL("XWHATEVER", &net.TCPAddr{}, func(string, ContextData) error { return nil }) _, _, err := srv.Next([]byte("")) if err == nil { t.Error("No error for XWHATEVER use") @@ -60,7 +60,7 @@ func TestCreateSASL(t *testing.T) { }) t.Run("PLAIN", func(t *testing.T) { - srv := a.CreateSASL("PLAIN", &net.TCPAddr{}, func(id string) error { + srv := a.CreateSASL("PLAIN", &net.TCPAddr{}, func(id string, data ContextData) error { if id != "user1" { t.Fatal("Wrong auth. identities passed to callback:", id) } @@ -74,7 +74,7 @@ func TestCreateSASL(t *testing.T) { }) t.Run("PLAIN with authorization identity", func(t *testing.T) { - srv := a.CreateSASL("PLAIN", &net.TCPAddr{}, func(id string) error { + srv := a.CreateSASL("PLAIN", &net.TCPAddr{}, func(id string, data ContextData) error { if id != "user1" { t.Fatal("Wrong authorization identity passed:", id) } diff --git a/internal/auth/sasllogin/sasllogin.go b/internal/auth/sasllogin/sasllogin.go new file mode 100644 index 00000000..fac50260 --- /dev/null +++ b/internal/auth/sasllogin/sasllogin.go @@ -0,0 +1,54 @@ +package sasllogin + +import "github.com/emersion/go-sasl" + +// Copy-pasted from old emersion/go-sasl version + +// Authenticates users with an username and a password. +type LoginAuthenticator func(username, password string) error +type loginState int + +const ( + loginNotStarted loginState = iota + loginWaitingUsername + loginWaitingPassword +) + +type loginServer struct { + state loginState + username, password string + authenticate LoginAuthenticator +} + +// A server implementation of the LOGIN authentication mechanism, as described +// in https://tools.ietf.org/html/draft-murchison-sasl-login-00. +// +// LOGIN is obsolete and should only be enabled for legacy clients that cannot +// be updated to use PLAIN. +func NewLoginServer(authenticator LoginAuthenticator) sasl.Server { + return &loginServer{authenticate: authenticator} +} + +func (a *loginServer) Next(response []byte) (challenge []byte, done bool, err error) { + switch a.state { + case loginNotStarted: + // Check for initial response field, as per RFC4422 section 3 + if response == nil { + challenge = []byte("Username:") + break + } + a.state++ + fallthrough + case loginWaitingUsername: + a.username = string(response) + challenge = []byte("Password:") + case loginWaitingPassword: + a.password = string(response) + err = a.authenticate(a.username, a.password) + done = true + default: + err = sasl.ErrUnexpectedClientResponse + } + a.state++ + return +} diff --git a/internal/endpoint/dovecot_sasld/dovecot_sasl.go b/internal/endpoint/dovecot_sasld/dovecot_sasl.go index df8c3d1a..b4067bb5 100644 --- a/internal/endpoint/dovecot_sasld/dovecot_sasl.go +++ b/internal/endpoint/dovecot_sasld/dovecot_sasl.go @@ -72,6 +72,7 @@ func (endp *Endpoint) Init(cfg *config.Map) error { cfg.Callback("auth", func(m *config.Map, node config.Node) error { return endp.saslAuth.AddProvider(m, node) }) + cfg.Bool("sasl_login", false, false, &endp.saslAuth.EnableLogin) config.EnumMapped(cfg, "auth_map_normalize", true, false, authz.NormalizeFuncs, authz.NormalizeAuto, &endp.authNormalize) modconfig.Table(cfg, "auth_map", true, false, nil, &endp.authMap) @@ -92,7 +93,7 @@ func (endp *Endpoint) Init(cfg *config.Map) error { remoteAddr = &net.TCPAddr{IP: req.RemoteIP, Port: int(req.RemotePort)} } - return endp.saslAuth.CreateSASL(mech, remoteAddr, func(_ string) error { return nil }) + return endp.saslAuth.CreateSASL(mech, remoteAddr, func(_ string, _ auth.ContextData) error { return nil }) }) } diff --git a/internal/endpoint/imap/imap.go b/internal/endpoint/imap/imap.go index d047e882..1be76ec8 100644 --- a/internal/endpoint/imap/imap.go +++ b/internal/endpoint/imap/imap.go @@ -90,6 +90,7 @@ func (endp *Endpoint) Init(cfg *config.Map) error { cfg.Callback("auth", func(m *config.Map, node config.Node) error { return endp.saslAuth.AddProvider(m, node) }) + cfg.Bool("sasl_login", false, false, &endp.saslAuth.EnableLogin) cfg.Custom("storage", false, true, nil, modconfig.StorageDirective, &endp.Store) cfg.Custom("tls", true, true, nil, tls2.TLSDirective, &endp.tlsConfig) cfg.Custom("proxy_protocol", false, false, nil, proxy_protocol.ProxyProtocolDirective, &endp.proxyProtocol) @@ -144,7 +145,7 @@ func (endp *Endpoint) Init(cfg *config.Map) error { for _, mech := range endp.saslAuth.SASLMechanisms() { mech := mech endp.serv.EnableAuth(mech, func(c imapserver.Conn) sasl.Server { - return endp.saslAuth.CreateSASL(mech, c.Info().RemoteAddr, func(identity string) error { + return endp.saslAuth.CreateSASL(mech, c.Info().RemoteAddr, func(identity string, data auth.ContextData) error { return endp.openAccount(c, identity) }) }) diff --git a/internal/endpoint/smtp/session.go b/internal/endpoint/smtp/session.go index 6f2c68e5..5dfec134 100644 --- a/internal/endpoint/smtp/session.go +++ b/internal/endpoint/smtp/session.go @@ -31,6 +31,7 @@ import ( "sync" "github.com/emersion/go-message/textproto" + "github.com/emersion/go-sasl" "github.com/emersion/go-smtp" "github.com/foxcpp/maddy/framework/address" "github.com/foxcpp/maddy/framework/buffer" @@ -38,6 +39,7 @@ import ( "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/internal/auth" ) func limitReader(r io.Reader, n int64, err error) *limitedReader { @@ -96,6 +98,23 @@ type Session struct { log log.Logger } +func (s *Session) AuthMechanisms() []string { + return s.endp.saslAuth.SASLMechanisms() +} + +func (s *Session) Auth(mech string) (sasl.Server, error) { + // Executed before authentication and session initialization. + if err := s.endp.pipeline.RunEarlyChecks(s.sessionCtx, &s.connState); err != nil { + return nil, s.endp.wrapErr("", true, "AUTH", err) + } + + return s.endp.saslAuth.CreateSASL(mech, s.connState.RemoteAddr, func(identity string, data auth.ContextData) error { + s.connState.AuthUser = identity + s.connState.AuthPassword = data.Password + return nil + }), nil +} + func (s *Session) Reset() { s.msgLock.Lock() defer s.msgLock.Unlock() @@ -145,10 +164,6 @@ func (s *Session) cleanSession() { } func (s *Session) AuthPlain(username, password string) error { - if s.endp.serv.AuthDisabled { - return smtp.ErrAuthUnsupported - } - // Executed before authentication and session initialization. if err := s.endp.pipeline.RunEarlyChecks(context.TODO(), &s.connState); err != nil { return s.endp.wrapErr("", true, "AUTH", err) diff --git a/internal/endpoint/smtp/smtp.go b/internal/endpoint/smtp/smtp.go index abc01d94..69023cb5 100644 --- a/internal/endpoint/smtp/smtp.go +++ b/internal/endpoint/smtp/smtp.go @@ -33,7 +33,6 @@ import ( "sync/atomic" "time" - "github.com/emersion/go-sasl" "github.com/emersion/go-smtp" "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" @@ -250,6 +249,7 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { cfg.Callback("auth", func(m *config.Map, node config.Node) error { return endp.saslAuth.AddProvider(m, node) }) + cfg.Bool("sasl_login", false, false, &endp.saslAuth.EnableLogin) cfg.String("hostname", true, true, "", &hostname) config.EnumMapped(cfg, "auth_map_normalize", true, false, authz.NormalizeFuncs, authz.NormalizeAuto, &endp.authNormalize) @@ -305,7 +305,6 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { endp.pipeline.Log = log.Logger{Name: "smtp/pipeline", Debug: endp.Log.Debug} endp.pipeline.FirstPipeline = true - endp.serv.AuthDisabled = len(endp.saslAuth.SASLMechanisms()) == 0 if endp.submission { endp.authAlwaysRequired = true if len(endp.saslAuth.SASLMechanisms()) == 0 { @@ -314,22 +313,6 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { } endp.saslAuth.AuthNormalize = endp.authNormalize endp.saslAuth.AuthMap = endp.authMap - for _, mech := range endp.saslAuth.SASLMechanisms() { - // The code below lacks handling to set AuthPassword. Don't - // override sasl.Plain handler so Login() will be called as usual. - if mech == sasl.Plain { - continue - } - - mech := mech - - endp.serv.EnableAuth(mech, func(c *smtp.Conn) sasl.Server { - return endp.saslAuth.CreateSASL(mech, c.Conn().RemoteAddr(), func(id string) error { - c.Session().(*Session).connState.AuthUser = id - return nil - }) - }) - } if ioDebug { endp.serv.Debug = endp.Log.DebugWriter() diff --git a/internal/smtpconn/smtpconn.go b/internal/smtpconn/smtpconn.go index d7b42459..0c73ed76 100644 --- a/internal/smtpconn/smtpconn.go +++ b/internal/smtpconn/smtpconn.go @@ -281,6 +281,18 @@ func (c *C) attemptConnect(ctx context.Context, lmtp bool, endp config.Endpoint, return false, nil, nil, TLSError{err} } + // Re-do HELO using our hostname instead of localhost. + if err := cl.Hello(c.Hostname); err != nil { + cl.Close() + + var tlsErr *tls.CertificateVerificationError + if errors.As(err, &tlsErr) { + return false, nil, nil, TLSError{Err: tlsErr} + } + + return false, nil, nil, err + } + return true, cl, conn, nil } diff --git a/internal/target/remote/connect.go b/internal/target/remote/connect.go index 2290d137..f9d317ef 100644 --- a/internal/target/remote/connect.go +++ b/internal/target/remote/connect.go @@ -21,7 +21,6 @@ package remote import ( "context" "crypto/tls" - "crypto/x509" "errors" "net" "runtime/trace" @@ -72,19 +71,8 @@ func (c *mxConn) Close() error { } func isVerifyError(err error) bool { - if errors.As(err, &x509.UnknownAuthorityError{}) { - return true - } - if errors.As(err, &x509.HostnameError{}) { - return true - } - if errors.As(err, &x509.ConstraintViolationError{}) { - return true - } - if errors.As(err, &x509.CertificateInvalidError{}) { - return true - } - return false + var e *tls.CertificateVerificationError + return errors.As(err, &e) } // connect attempts to connect to the MX, first trying STARTTLS with X.509 @@ -117,6 +105,16 @@ retry: starttlsOk, _ := conn.Client().Extension("STARTTLS") if starttlsOk && tlsCfg != nil { if err := conn.Client().StartTLS(tlsCfg); err != nil { + // Here we just issue STARTTLS command. If it fails for some + // reason - this is either a connection problem or server actively + // rejecting STARTTLS (despite advertising STARTTLS). + // We err on the caution side here and do not perform any fallbacks. + conn.DirectClose() + return module.TLSNone, nil, err + } + + // TLS handshake is deferred to here, this is where we check errors and allow fallback. + if err := conn.Client().Hello(rd.rt.hostname); err != nil { tlsErr = err // Attempt TLS without authentication. It is still better than diff --git a/internal/testutils/smtp_server.go b/internal/testutils/smtp_server.go index 6fb105a6..9af52c4f 100644 --- a/internal/testutils/smtp_server.go +++ b/internal/testutils/smtp_server.go @@ -21,6 +21,7 @@ package testutils import ( "crypto/tls" "crypto/x509" + "fmt" "io" "net" "reflect" @@ -29,6 +30,7 @@ import ( "testing" "time" + "github.com/emersion/go-sasl" "github.com/emersion/go-smtp" "github.com/foxcpp/maddy/framework/exterrors" ) @@ -107,6 +109,24 @@ type session struct { msg *SMTPMessage } +func (s *session) AuthMechanisms() []string { + return []string{sasl.Plain} +} + +func (s *session) Auth(mech string) (sasl.Server, error) { + if mech != sasl.Plain { + return nil, fmt.Errorf("mechanisms other than plain are unsupported") + } + return sasl.NewPlainServer(func(identity, username, password string) error { + if s.backend.AuthErr != nil { + return s.backend.AuthErr + } + s.user = username + s.password = password + return nil + }), nil +} + func (s *session) Reset() { s.msg = &SMTPMessage{} } @@ -116,15 +136,6 @@ func (s *session) Logout() error { return nil } -func (s *session) AuthPlain(username, password string) error { - if s.backend.AuthErr != nil { - return s.backend.AuthErr - } - s.user = username - s.password = password - return nil -} - func (s *session) Mail(from string, opts *smtp.MailOptions) error { s.backend.MailFromCounter++ @@ -188,10 +199,6 @@ func (s *session) LMTPData(r io.Reader, status smtp.StatusCollector) error { type SMTPServerConfigureFunc func(*smtp.Server) -var AuthDisabled = func(s *smtp.Server) { - s.AuthDisabled = true -} - func SMTPServer(t *testing.T, addr string, fn ...SMTPServerConfigureFunc) (*SMTPBackend, *smtp.Server) { t.Helper() diff --git a/internal/tls/acme/acme.go b/internal/tls/acme/acme.go index 39d6213d..a09c3e09 100644 --- a/internal/tls/acme/acme.go +++ b/internal/tls/acme/acme.go @@ -48,7 +48,7 @@ func (l *Loader) Init(cfg *config.Map) error { agreed bool challenge string overrideDomain string - provider certmagic.ACMEDNSProvider + provider certmagic.DNSProvider ) cfg.Bool("debug", true, false, &l.log.Debug) cfg.String("hostname", true, true, "", &hostname) @@ -69,7 +69,7 @@ func (l *Loader) Init(cfg *config.Map) error { cfg.Custom("dns", false, false, func() (interface{}, error) { return nil, nil }, func(m *config.Map, node config.Node) (interface{}, error) { - var p certmagic.ACMEDNSProvider + var p certmagic.DNSProvider err := modconfig.ModuleFromNode("libdns", node.Args, node, m.Globals, &p) return p, err }, &provider) @@ -108,8 +108,10 @@ func (l *Loader) Init(cfg *config.Map) error { return fmt.Errorf("tls.loader.acme: dns-01 challenge requires a configured DNS provider") } issuer.DNS01Solver = &certmagic.DNS01Solver{ - DNSProvider: provider, - OverrideDomain: overrideDomain, + DNSManager: certmagic.DNSManager{ + DNSProvider: provider, + OverrideDomain: overrideDomain, + }, } default: return fmt.Errorf("tls.loader.acme: challenge not supported") diff --git a/tests/basic_test.go b/tests/basic_test.go index ac7af2a2..80cf1aef 100644 --- a/tests/basic_test.go +++ b/tests/basic_test.go @@ -56,7 +56,8 @@ func TestBasic(tt *testing.T) { conn.ExpectPattern("250-ENHANCEDSTATUSCODES") conn.ExpectPattern("250-CHUNKING") conn.ExpectPattern("250-SMTPUTF8") - conn.ExpectPattern("250 SIZE *") + conn.ExpectPattern("250-SIZE *") + conn.ExpectPattern("250 LIMITS RCPTMAX=20000") conn.Writeln("QUIT") conn.ExpectPattern("221 *") } From 44a23f21cead3b513591ee98d22a27b1c4be4f77 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月24日 21:55:52 +0300 Subject: [PATCH 016/111] ci: Refactor CI workflows --- .github/workflows/{cicd.yml => release.yml} | 44 +-------------- .github/workflows/test.yml | 61 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 43 deletions(-) rename .github/workflows/{cicd.yml => release.yml} (73%) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/cicd.yml b/.github/workflows/release.yml similarity index 73% rename from .github/workflows/cicd.yml rename to .github/workflows/release.yml index 738f672b..28ed9873 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/release.yml @@ -1,52 +1,10 @@ -name: "Testing and release preparation" +name: "Prepare release artifacts" on: push: - branches: [ master, dev ] tags: [ "v*" ] - pull_request: - branches: [ master, dev ] jobs: - build-and-test: - name: "Build and test" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: "Install libpam" - run: | - sudo apt-get update - sudo apt-get install -y libpam-dev - - uses: actions/cache@v2 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - uses: actions/setup-go@v2 - with: - go-version: 1.19 - - name: "Verify build.sh" - run: | - ./build.sh - ./build.sh --destdir destdir/ install - find destdir/ - - name: "Unit & module tests" - run: | - go test ./... -coverprofile=coverage.out -covermode=atomic - - name: "Integration tests" - run: | - cd tests/ - ./run.sh - - uses: codecov/codecov-action@v2 - with: - files: ./coverage.out - flags: unit - - uses: codecov/codecov-action@v2 - with: - files: ./tests/coverage.out - flags: integration artifact-builder: name: "Prepare release artifacts" needs: build-and-test diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..7d6256d7 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,61 @@ +name: "Testing" + +on: + push: + branches: [ master, dev ] + tags: [ "v*" ] + pull_request: + branches: [ master, dev ] + +jobs: + golangci: + name: lint + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - uses: golangci/golangci-lint-action@v6 + with: + version: v1.60 + buildsh: + name: "Verify build.sh" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - name: "Install libpam" + run: | + sudo apt-get update + sudo apt-get install -y libpam-dev + - name: "Verify build.sh" + run: | + ./build.sh + ./build.sh --destdir destdir/ install + find destdir/ + test: + name: "Build and test" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - name: "Unit & module tests" + run: | + go test ./... -coverprofile=coverage.out -covermode=atomic + - name: "Integration tests" + run: | + cd tests/ + ./run.sh + - uses: codecov/codecov-action@v2 + with: + files: ./coverage.out + flags: unit + - uses: codecov/codecov-action@v2 + with: + files: ./tests/coverage.out + flags: integration From c2cb732eef29e94b4e9d031c3a7600cb38910f62 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月24日 21:58:11 +0300 Subject: [PATCH 017/111] Fix-up --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7d6256d7..8f208b71 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ on: jobs: golangci: - name: lint + name: Lint runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -44,6 +44,10 @@ jobs: - uses: actions/setup-go@v5 with: go-version-file: 'go.mod' + - name: "Install libpam" + run: | + sudo apt-get update + sudo apt-get install -y libpam-dev - name: "Unit & module tests" run: | go test ./... -coverprofile=coverage.out -covermode=atomic From 4e1cb7c5c1b90e3178299c7bcbe98699fda29064 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月24日 22:09:17 +0300 Subject: [PATCH 018/111] build.sh: Fix-up config overwrite check to work when installing not to / --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 3dc76de2..f0a15922 100755 --- a/build.sh +++ b/build.sh @@ -3,7 +3,6 @@ destdir=/ builddir="$PWD/build" prefix=/usr/local -configdir="${destdir}etc/maddy" version= static=0 if [ "${GOFLAGS}" = "" ]; then @@ -77,6 +76,7 @@ while :; do shift done +configdir="${destdir}etc/maddy" if [ "$version" = "" ]; then version=unknown From 7ad6925c0d67e6a768fb52c9d8b7b483c7e3dcb7 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月24日 22:15:59 +0300 Subject: [PATCH 019/111] ci: Increase lint timeout --- .github/workflows/test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8f208b71..6cc0f56f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,11 @@ on: pull_request: branches: [ master, dev ] +permissions: + contents: read + pull-requests: read + checks: write + jobs: golangci: name: Lint @@ -19,6 +24,7 @@ jobs: - uses: golangci/golangci-lint-action@v6 with: version: v1.60 + args: "--timeout=30m" buildsh: name: "Verify build.sh" runs-on: ubuntu-latest From f9d49170af0b3b69bc02ba4d07d7a880317f3d23 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月24日 22:18:06 +0300 Subject: [PATCH 020/111] ci: Fixup --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6cc0f56f..32c02e54 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,10 @@ jobs: - uses: actions/setup-go@v5 with: go-version-file: 'go.mod' + - name: "Install libpam" + run: | + sudo apt-get update + sudo apt-get install -y libpam-dev - uses: golangci/golangci-lint-action@v6 with: version: v1.60 From 120c5c9ea2e836b2844e029b35cb32626917d4e1 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月24日 23:12:45 +0300 Subject: [PATCH 021/111] endpoint: Clean-up dead auth_map code --- .../endpoint/dovecot_sasld/dovecot_sasl.go | 9 ++---- internal/endpoint/imap/imap.go | 29 ++----------------- internal/endpoint/smtp/session.go | 2 +- internal/endpoint/smtp/smtp.go | 29 ++----------------- 4 files changed, 7 insertions(+), 62 deletions(-) diff --git a/internal/endpoint/dovecot_sasld/dovecot_sasl.go b/internal/endpoint/dovecot_sasld/dovecot_sasl.go index b4067bb5..f1184e86 100644 --- a/internal/endpoint/dovecot_sasld/dovecot_sasl.go +++ b/internal/endpoint/dovecot_sasld/dovecot_sasl.go @@ -44,9 +44,6 @@ type Endpoint struct { listenersWg sync.WaitGroup - authNormalize authz.NormalizeFunc - authMap module.Table - srv *dovecotsasl.Server } @@ -74,8 +71,8 @@ func (endp *Endpoint) Init(cfg *config.Map) error { }) cfg.Bool("sasl_login", false, false, &endp.saslAuth.EnableLogin) config.EnumMapped(cfg, "auth_map_normalize", true, false, authz.NormalizeFuncs, authz.NormalizeAuto, - &endp.authNormalize) - modconfig.Table(cfg, "auth_map", true, false, nil, &endp.authMap) + &endp.saslAuth.AuthNormalize) + modconfig.Table(cfg, "auth_map", true, false, nil, &endp.saslAuth.AuthMap) if _, err := cfg.Process(); err != nil { return err } @@ -83,8 +80,6 @@ func (endp *Endpoint) Init(cfg *config.Map) error { endp.srv = dovecotsasl.NewServer() endp.srv.Log = stdlog.New(endp.log, "", 0) - endp.saslAuth.AuthMap = endp.authMap - endp.saslAuth.AuthNormalize = endp.authNormalize for _, mech := range endp.saslAuth.SASLMechanisms() { mech := mech endp.srv.AddMechanism(mech, mechInfo[mech], func(req *dovecotsasl.AuthReq) sasl.Server { diff --git a/internal/endpoint/imap/imap.go b/internal/endpoint/imap/imap.go index 1be76ec8..3525b8ed 100644 --- a/internal/endpoint/imap/imap.go +++ b/internal/endpoint/imap/imap.go @@ -62,8 +62,6 @@ type Endpoint struct { storageNormalize authz.NormalizeFunc storageMap module.Table - authNormalize authz.NormalizeFunc - authMap module.Table Log log.Logger } @@ -102,8 +100,8 @@ func (endp *Endpoint) Init(cfg *config.Map) error { &endp.storageNormalize) modconfig.Table(cfg, "storage_map", false, false, nil, &endp.storageMap) config.EnumMapped(cfg, "auth_map_normalize", true, false, authz.NormalizeFuncs, authz.NormalizeAuto, - &endp.authNormalize) - modconfig.Table(cfg, "auth_map", true, false, nil, &endp.authMap) + &endp.saslAuth.AuthNormalize) + modconfig.Table(cfg, "auth_map", true, false, nil, &endp.saslAuth.AuthMap) if _, err := cfg.Process(); err != nil { return err } @@ -140,8 +138,6 @@ func (endp *Endpoint) Init(cfg *config.Map) error { return err } - endp.saslAuth.AuthNormalize = endp.authNormalize - endp.saslAuth.AuthMap = endp.authMap for _, mech := range endp.saslAuth.SASLMechanisms() { mech := mech endp.serv.EnableAuth(mech, func(c imapserver.Conn) sasl.Server { @@ -217,27 +213,6 @@ func (endp *Endpoint) Close() error { return nil } -func (endp *Endpoint) usernameForAuth(ctx context.Context, saslUsername string) (string, error) { - saslUsername, err := endp.authNormalize(saslUsername) - if err != nil { - return "", err - } - - if endp.authMap == nil { - return saslUsername, nil - } - - mapped, ok, err := endp.authMap.Lookup(ctx, saslUsername) - if err != nil { - return "", err - } - if !ok { - return "", imapbackend.ErrInvalidCredentials - } - - return mapped, nil -} - func (endp *Endpoint) usernameForStorage(ctx context.Context, saslUsername string) (string, error) { saslUsername, err := endp.storageNormalize(saslUsername) if err != nil { diff --git a/internal/endpoint/smtp/session.go b/internal/endpoint/smtp/session.go index 5dfec134..0eb6427f 100644 --- a/internal/endpoint/smtp/session.go +++ b/internal/endpoint/smtp/session.go @@ -435,7 +435,7 @@ func (s *Session) Logout() error { } func (s *Session) prepareBody(r io.Reader) (textproto.Header, buffer.Buffer, error) { - limitr := limitReader(r, int64(s.endp.maxHeaderBytes), &exterrors.SMTPError{ + limitr := limitReader(r, s.endp.maxHeaderBytes, &exterrors.SMTPError{ Code: 552, EnhancedCode: exterrors.EnhancedCode{5, 3, 4}, Message: "Message header size exceeds limit", diff --git a/internal/endpoint/smtp/smtp.go b/internal/endpoint/smtp/smtp.go index 69023cb5..28ba85e2 100644 --- a/internal/endpoint/smtp/smtp.go +++ b/internal/endpoint/smtp/smtp.go @@ -252,8 +252,8 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { cfg.Bool("sasl_login", false, false, &endp.saslAuth.EnableLogin) cfg.String("hostname", true, true, "", &hostname) config.EnumMapped(cfg, "auth_map_normalize", true, false, authz.NormalizeFuncs, authz.NormalizeAuto, - &endp.authNormalize) - modconfig.Table(cfg, "auth_map", true, false, nil, &endp.authMap) + &endp.saslAuth.AuthNormalize) + modconfig.Table(cfg, "auth_map", true, false, nil, &endp.saslAuth.AuthMap) cfg.Duration("write_timeout", false, false, 1*time.Minute, &endp.serv.WriteTimeout) cfg.Duration("read_timeout", false, false, 10*time.Minute, &endp.serv.ReadTimeout) cfg.DataSize("max_message_size", false, false, 32*1024*1024, &endp.serv.MaxMessageBytes) @@ -358,31 +358,6 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { return nil } -func (endp *Endpoint) usernameForAuth(ctx context.Context, saslUsername string) (string, error) { - saslUsername, err := endp.authNormalize(saslUsername) - if err != nil { - return "", err - } - - if endp.authMap == nil { - return saslUsername, nil - } - - mapped, ok, err := endp.authMap.Lookup(ctx, saslUsername) - if err != nil { - return "", err - } - if !ok { - return "", &smtp.SMTPError{ - Code: 535, - EnhancedCode: smtp.EnhancedCode{5, 7, 8}, - Message: "Invalid credentials", - } - } - - return mapped, nil -} - func (endp *Endpoint) NewSession(conn *smtp.Conn) (smtp.Session, error) { sess := endp.newSession(conn) From dbc030c267540c2b5d44e08c452c6bdb7dc8a300 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月24日 23:34:09 +0300 Subject: [PATCH 022/111] Clean-up lint warnings --- framework/buffer/file.go | 2 +- framework/cfgparser/imports.go | 2 +- framework/cfgparser/parse_test.go | 1 - framework/config/tls/server.go | 4 ---- framework/log/log.go | 7 +++++-- internal/check/dnsbl/dnsbl.go | 1 - internal/endpoint/dovecot_sasld/dovecot_sasl.go | 1 - internal/endpoint/imap/imap.go | 2 -- internal/endpoint/openmetrics/om.go | 1 - internal/endpoint/smtp/smtp.go | 4 ---- internal/endpoint/smtp/smtp_test.go | 1 - internal/msgpipeline/check_runner.go | 2 -- internal/msgpipeline/config_test.go | 1 - internal/smtpconn/smtpconn_test.go | 2 -- internal/target/remote/remote.go | 2 -- internal/updatepipe/unix_pipe.go | 3 ++- maddy.go | 1 - 17 files changed, 9 insertions(+), 28 deletions(-) diff --git a/framework/buffer/file.go b/framework/buffer/file.go index dc2b7305..00259849 100644 --- a/framework/buffer/file.go +++ b/framework/buffer/file.go @@ -19,10 +19,10 @@ along with this program. If not, see . package buffer import ( + "crypto/rand" "encoding/hex" "fmt" "io" - "math/rand" "os" "path/filepath" ) diff --git a/framework/cfgparser/imports.go b/framework/cfgparser/imports.go index d8276d22..8078dd8d 100644 --- a/framework/cfgparser/imports.go +++ b/framework/cfgparser/imports.go @@ -89,7 +89,7 @@ func (ctx *parseContext) resolveImport(node Node, name string, expansionDepth in src, err = os.Open(file + ".conf") if err != nil { if os.IsNotExist(err) { - return nil, NodeErr(node, "unknown import: "+name) + return nil, NodeErr(node, "unknown import: %s", name) } return nil, err } diff --git a/framework/cfgparser/parse_test.go b/framework/cfgparser/parse_test.go index 9488e580..32929b9f 100644 --- a/framework/cfgparser/parse_test.go +++ b/framework/cfgparser/parse_test.go @@ -583,7 +583,6 @@ func TestRead(t *testing.T) { os.Setenv("TESTING_VARIABLE2", "ABC2 DEF2") for _, case_ := range cases { - case_ := case_ t.Run(case_.name, func(t *testing.T) { tree, err := Read(strings.NewReader(case_.cfg), "test") if !case_.fail && err != nil { diff --git a/framework/config/tls/server.go b/framework/config/tls/server.go index c23fdc32..4fe8e8d3 100644 --- a/framework/config/tls/server.go +++ b/framework/config/tls/server.go @@ -113,10 +113,6 @@ func readTLSBlock(globals map[string]interface{}, blockNode config.Node) (*TLSCo return nil, err } - if len(baseCfg.CipherSuites) != 0 { - baseCfg.PreferServerCipherSuites = true - } - baseCfg.MinVersion = tlsVersions[0] baseCfg.MaxVersion = tlsVersions[1] log.Debugf("tls: min version: %x, max version: %x", tlsVersions[0], tlsVersions[1]) diff --git a/framework/log/log.go b/framework/log/log.go index 98fb3a81..f9092a44 100644 --- a/framework/log/log.go +++ b/framework/log/log.go @@ -80,7 +80,8 @@ func (l Logger) Println(val ...interface{}) { // Msg writes an event log message in a machine-readable format (currently // JSON). -// name: msg\t{"key":"value","key2":"value2"} +// +// name: msg\t{"key":"value","key2":"value2"} // // Key-value pairs are built from fields slice which should contain key strings // followed by corresponding values. That is, for example, []interface{"key", @@ -102,7 +103,9 @@ func (l Logger) Msg(msg string, fields ...interface{}) { // JSON) containing information about the error. If err does have a Fields // method that returns map[string]interface{}, its result will be added to the // message. -// name: msg\t{"key":"value","key2":"value2"} +// +// name: msg\t{"key":"value","key2":"value2"} +// // Additionally, values from fields will be added to it, as handled by // Logger.Msg. // diff --git a/internal/check/dnsbl/dnsbl.go b/internal/check/dnsbl/dnsbl.go index 76cc7e34..2c91c838 100644 --- a/internal/check/dnsbl/dnsbl.go +++ b/internal/check/dnsbl/dnsbl.go @@ -301,7 +301,6 @@ func (bl *DNSBL) checkLists(ctx context.Context, ip net.IP, ehlo, mailFrom strin ) for _, list := range bl.bls { - list := list eg.Go(func() error { err := bl.checkList(ctx, list, ip, ehlo, mailFrom) if err != nil { diff --git a/internal/endpoint/dovecot_sasld/dovecot_sasl.go b/internal/endpoint/dovecot_sasld/dovecot_sasl.go index f1184e86..77eedd0e 100644 --- a/internal/endpoint/dovecot_sasld/dovecot_sasl.go +++ b/internal/endpoint/dovecot_sasld/dovecot_sasl.go @@ -81,7 +81,6 @@ func (endp *Endpoint) Init(cfg *config.Map) error { endp.srv.Log = stdlog.New(endp.log, "", 0) for _, mech := range endp.saslAuth.SASLMechanisms() { - mech := mech endp.srv.AddMechanism(mech, mechInfo[mech], func(req *dovecotsasl.AuthReq) sasl.Server { var remoteAddr net.Addr if req.RemoteIP != nil && req.RemotePort != 0 { diff --git a/internal/endpoint/imap/imap.go b/internal/endpoint/imap/imap.go index 3525b8ed..191d93d2 100644 --- a/internal/endpoint/imap/imap.go +++ b/internal/endpoint/imap/imap.go @@ -139,7 +139,6 @@ func (endp *Endpoint) Init(cfg *config.Map) error { } for _, mech := range endp.saslAuth.SASLMechanisms() { - mech := mech endp.serv.EnableAuth(mech, func(c imapserver.Conn) sasl.Server { return endp.saslAuth.CreateSASL(mech, c.Info().RemoteAddr, func(identity string, data auth.ContextData) error { return endp.openAccount(c, identity) @@ -174,7 +173,6 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { endp.listeners = append(endp.listeners, l) endp.listenersWg.Add(1) - addr := addr go func() { if err := endp.serv.Serve(l); err != nil && !strings.HasSuffix(err.Error(), "use of closed network connection") { endp.Log.Printf("imap: failed to serve %s: %s", addr, err) diff --git a/internal/endpoint/openmetrics/om.go b/internal/endpoint/openmetrics/om.go index 3d362f97..874a333d 100644 --- a/internal/endpoint/openmetrics/om.go +++ b/internal/endpoint/openmetrics/om.go @@ -60,7 +60,6 @@ func (e *Endpoint) Init(cfg *config.Map) error { e.serv.Handler = e.mux for _, a := range e.addrs { - a := a endp, err := config.ParseEndpoint(a) if err != nil { return fmt.Errorf("%s: malformed endpoint: %v", modName, err) diff --git a/internal/endpoint/smtp/smtp.go b/internal/endpoint/smtp/smtp.go index 28ba85e2..004d92e5 100644 --- a/internal/endpoint/smtp/smtp.go +++ b/internal/endpoint/smtp/smtp.go @@ -24,7 +24,6 @@ import ( "crypto/tls" "fmt" "io" - "math/rand" "net" "os" "path/filepath" @@ -346,7 +345,6 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { endp.listeners = append(endp.listeners, l) endp.listenersWg.Add(1) - addr := addr go func() { if err := endp.serv.Serve(l); err != nil { endp.Log.Printf("failed to serve %s: %s", addr, err) @@ -431,6 +429,4 @@ func init() { module.RegisterEndpoint("smtp", New) module.RegisterEndpoint("submission", New) module.RegisterEndpoint("lmtp", New) - - rand.Seed(time.Now().UnixNano()) } diff --git a/internal/endpoint/smtp/smtp_test.go b/internal/endpoint/smtp/smtp_test.go index c295d7c8..fa46c279 100644 --- a/internal/endpoint/smtp/smtp_test.go +++ b/internal/endpoint/smtp/smtp_test.go @@ -583,7 +583,6 @@ func TestMain(m *testing.M) { flag.Parse() if *remoteSmtpPort == "random" { - rand.Seed(time.Now().UnixNano()) *remoteSmtpPort = strconv.Itoa(rand.Intn(65536-10000) + 10000) } diff --git a/internal/msgpipeline/check_runner.go b/internal/msgpipeline/check_runner.go index ba9c7823..f6df9525 100644 --- a/internal/msgpipeline/check_runner.go +++ b/internal/msgpipeline/check_runner.go @@ -125,7 +125,6 @@ func (cr *checkRunner) checkStates(ctx context.Context, checks []module.Check) ( if len(cr.checkedRcpts) != 0 { for _, rcpt := range cr.checkedRcpts { - rcpt := rcpt err := cr.runAndMergeResults(states, func(s module.CheckState) module.CheckResult { // Avoid calling CheckRcpt for the same recipient for the same check // multiple times, even if requested. @@ -176,7 +175,6 @@ func (cr *checkRunner) runAndMergeResults(states []module.CheckState, runner fun }{} for _, state := range states { - state := state data.wg.Add(1) go func() { defer func() { diff --git a/internal/msgpipeline/config_test.go b/internal/msgpipeline/config_test.go index b7ac666a..24d7e51f 100644 --- a/internal/msgpipeline/config_test.go +++ b/internal/msgpipeline/config_test.go @@ -224,7 +224,6 @@ func TestMsgPipelineCfg(t *testing.T) { } for _, case_ := range cases { - case_ := case_ t.Run(case_.name, func(t *testing.T) { cfg, _ := parser.Read(strings.NewReader(case_.str), "literal") parsed, err := parseMsgPipelineRootCfg(nil, cfg) diff --git a/internal/smtpconn/smtpconn_test.go b/internal/smtpconn/smtpconn_test.go index 6279add9..b8fd647e 100644 --- a/internal/smtpconn/smtpconn_test.go +++ b/internal/smtpconn/smtpconn_test.go @@ -24,7 +24,6 @@ import ( "os" "strconv" "testing" - "time" ) var testPort string @@ -34,7 +33,6 @@ func TestMain(m *testing.M) { flag.Parse() if *remoteSmtpPort == "random" { - rand.Seed(time.Now().UnixNano()) *remoteSmtpPort = strconv.Itoa(rand.Intn(65536-10000) + 10000) } diff --git a/internal/target/remote/remote.go b/internal/target/remote/remote.go index 3e661c4f..d4c42ed6 100644 --- a/internal/target/remote/remote.go +++ b/internal/target/remote/remote.go @@ -406,8 +406,6 @@ func (rd *remoteDelivery) BodyNonAtomic(ctx context.Context, c module.StatusColl var wg sync.WaitGroup for i, conn := range rd.connections { - i := i - conn := conn wg.Add(1) go func() { defer wg.Done() diff --git a/internal/updatepipe/unix_pipe.go b/internal/updatepipe/unix_pipe.go index d2e2ce80..a8249f90 100644 --- a/internal/updatepipe/unix_pipe.go +++ b/internal/updatepipe/unix_pipe.go @@ -34,7 +34,8 @@ import ( // Listen goroutine can be running. // // The socket is stream-oriented and consists of the following messages: -// SENDER_ID;JSON_SERIALIZED_INTERNAL_OBJECT\n +// +// SENDER_ID;JSON_SERIALIZED_INTERNAL_OBJECT\n // // And SENDER_ID is Process ID and UnixSockPipe address concated as a string. // It is used to deduplicate updates sent to Push and recevied via Listen. diff --git a/maddy.go b/maddy.go index 16aa0eb0..e6a05cf1 100644 --- a/maddy.go +++ b/maddy.go @@ -385,7 +385,6 @@ func RegisterModules(globals map[string]interface{}, nodes []config.Node) (endpo return nil, nil, err } - block := block module.RegisterInstance(inst, config.NewMap(globals, block)) for _, alias := range modAliases { if module.HasInstance(alias) { From d7dd6ef8451057db8ebc0fa504647a494ec0f8df Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月24日 23:44:50 +0300 Subject: [PATCH 023/111] Fix more linter warnings --- framework/cfgparser/parse.go | 15 +++++++------- framework/config/module/check_action.go | 21 +++++++++++--------- internal/auth/auth_test.go | 1 - internal/check/milter/milter_test.go | 2 +- internal/smtpconn/smtpconn.go | 1 - internal/target/remote/remote_test.go | 2 -- internal/target/smtp/smtp_downstream_test.go | 2 -- 7 files changed, 21 insertions(+), 23 deletions(-) diff --git a/framework/cfgparser/parse.go b/framework/cfgparser/parse.go index 5eabc3c0..aed01e3d 100644 --- a/framework/cfgparser/parse.go +++ b/framework/cfgparser/parse.go @@ -31,10 +31,10 @@ import ( // Node struct describes a parsed configurtion block or a simple directive. // -// name arg0 arg1 { -// children0 -// children1 -// } +// name arg0 arg1 { +// children0 +// children1 +// } type Node struct { // Name is the first string at node's line. Name string @@ -209,9 +209,10 @@ func (ctx *parseContext) parseAsMacro(node *Node) (macroName string, args []stri // // The lexer's cursor should point to the opening brace // name arg0 arg1 { #< this one -// c0 -// c1 -// } +// +// c0 +// c1 +// } // // To stay consistent with readNode after this function returns the lexer's cursor points // to the last token of the black (closing brace). diff --git a/framework/config/module/check_action.go b/framework/config/module/check_action.go index 5061674a..cac3278c 100644 --- a/framework/config/module/check_action.go +++ b/framework/config/module/check_action.go @@ -36,19 +36,22 @@ import ( // returns. It is intended to be used as follows: // // Add the configuration directive to allow user to specify the action: -// cfg.Custom("SOME_action", false, false, -// func() (interface{}, error) { -// return modconfig.FailAction{Quarantine: true}, nil -// }, modconfig.FailActionDirective, &yourModule.SOMEAction) +// +// cfg.Custom("SOME_action", false, false, +// func() (interface{}, error) { +// return modconfig.FailAction{Quarantine: true}, nil +// }, modconfig.FailActionDirective, &yourModule.SOMEAction) +// // return in func literal is the default value, you might want to adjust it. // // Call yourModule.SOMEAction.Apply on CheckResult containing only the // Reason field: -// func (yourModule YourModule) CheckConnection() module.CheckResult { -// return yourModule.SOMEAction.Apply(module.CheckResult{ -// Reason: ..., -// }) -// } +// +// func (yourModule YourModule) CheckConnection() module.CheckResult { +// return yourModule.SOMEAction.Apply(module.CheckResult{ +// Reason: ..., +// }) +// } type FailAction struct { Quarantine bool Reject bool diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go index 61fcd364..65ffee4e 100644 --- a/internal/auth/auth_test.go +++ b/internal/auth/auth_test.go @@ -75,7 +75,6 @@ func TestCheckDomainAuth(t *testing.T) { } for _, case_ := range cases { - case_ := case_ t.Run(fmt.Sprintf("%+v", case_), func(t *testing.T) { loginName, allowed := CheckDomainAuth(case_.rawUsername, case_.perDomain, case_.allowedDomains) if case_.loginName != "" && !allowed { diff --git a/internal/check/milter/milter_test.go b/internal/check/milter/milter_test.go index d2ec19d3..97978517 100644 --- a/internal/check/milter/milter_test.go +++ b/internal/check/milter/milter_test.go @@ -58,4 +58,4 @@ func TestRejectInvalidEndpoints(t *testing.T) { return } } -} \ No newline at end of file +} diff --git a/internal/smtpconn/smtpconn.go b/internal/smtpconn/smtpconn.go index 0c73ed76..7f66bd23 100644 --- a/internal/smtpconn/smtpconn.go +++ b/internal/smtpconn/smtpconn.go @@ -530,7 +530,6 @@ func (c *C) Close() error { c.Log.DebugMsg("QUIT error", "reason", c.wrapClientErr(err, c.serverName)) } else if errors.As(err, &netErr) && (netErr.Timeout() || netErr.Err.Error() == "write: broken pipe" || netErr.Err.Error() == "read: connection reset") { - // The case for silently closed connections. c.Log.DebugMsg("QUIT error", "reason", c.wrapClientErr(err, c.serverName)) } else { diff --git a/internal/target/remote/remote_test.go b/internal/target/remote/remote_test.go index ae561582..4998e0c6 100644 --- a/internal/target/remote/remote_test.go +++ b/internal/target/remote/remote_test.go @@ -27,7 +27,6 @@ import ( "os" "strconv" "testing" - "time" "github.com/emersion/go-message/textproto" "github.com/emersion/go-smtp" @@ -1020,7 +1019,6 @@ func TestMain(m *testing.M) { flag.Parse() if *remoteSmtpPort == "random" { - rand.Seed(time.Now().UnixNano()) *remoteSmtpPort = strconv.Itoa(rand.Intn(65536-10000) + 10000) } diff --git a/internal/target/smtp/smtp_downstream_test.go b/internal/target/smtp/smtp_downstream_test.go index 93827e27..011ca04a 100644 --- a/internal/target/smtp/smtp_downstream_test.go +++ b/internal/target/smtp/smtp_downstream_test.go @@ -25,7 +25,6 @@ import ( "os" "strconv" "testing" - "time" "github.com/emersion/go-smtp" "github.com/foxcpp/maddy/framework/config" @@ -344,7 +343,6 @@ func TestMain(m *testing.M) { flag.Parse() if *remoteSmtpPort == "random" { - rand.Seed(time.Now().UnixNano()) *remoteSmtpPort = strconv.Itoa(rand.Intn(65536-10000) + 10000) } From 7f7903a645917df98890faecb7148f5751344a5f Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月24日 23:48:50 +0300 Subject: [PATCH 024/111] Fix more linter warnings --- internal/target/remote/dane_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/target/remote/dane_test.go b/internal/target/remote/dane_test.go index d8b6d5d2..470fbbbc 100644 --- a/internal/target/remote/dane_test.go +++ b/internal/target/remote/dane_test.go @@ -31,8 +31,9 @@ import ( ) // These certificates are related like this: -// Root A -> Intermediate A -> Leaf A -// Root B -> LeafB +// +// Root A -> Intermediate A -> Leaf A +// Root B -> LeafB var ( rootA = `-----BEGIN CERTIFICATE----- MIIBMDCB46ADAgECAhRDwag3n5CG90BEO87zEMAPejn6YTAFBgMrZXAwFjEUMBIG From 21329c8f6f406f0a00e0c0a5ff97546861f8dde0 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月25日 00:11:54 +0300 Subject: [PATCH 025/111] ci: Fix-up release pipeline --- .github/workflows/release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 28ed9873..34fc3fe8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,6 @@ on: jobs: artifact-builder: name: "Prepare release artifacts" - needs: build-and-test if: github.ref_type == 'tag' runs-on: ubuntu-latest container: @@ -48,7 +47,6 @@ jobs: if-no-files-found: error docker-builder: name: "Build & push Docker image" - needs: build-and-test # Upload if: github.ref_type == 'tag' runs-on: ubuntu-latest steps: From aaa838dd79e74d8ac096161df5b18b98b1de52be Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月25日 00:49:15 +0300 Subject: [PATCH 026/111] ci: Upgrade release pipeline actions --- .github/workflows/release.yml | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 34fc3fe8..ca911b72 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,11 @@ on: push: tags: [ "v*" ] +permissions: + id-token: write + contents: read + attestations: write + jobs: artifact-builder: name: "Prepare release artifacts" @@ -45,33 +50,37 @@ jobs: name: maddy-binary.tar.zst path: '~/maddy-x86_64-linux-musl.tar.zst' if-no-files-found: error + - name: "Generate artifact attestation" + uses: actions/attest-build-provenance@v2 + with: + subject-path: '~/maddy-x86_64-linux-musl.tar.zst' docker-builder: name: "Build & push Docker image" if: github.ref_type == 'tag' runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: "Set up QEMU" uses: docker/setup-qemu-action@v1 with: platforms: arm64 - name: "Set up Docker Buildx" id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: "Login to Docker Hub" - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: "Login to GitHub Container Registry" - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: registry: "ghcr.io" username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: "Generate container metadata" - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 id: meta with: images: | @@ -85,7 +94,8 @@ jobs: org.opencontainers.image.documentation=https://maddy.email/docker/ org.opencontainers.image.url=https://maddy.email - name: "Build and push" - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 + id: docker with: context: . platforms: linux/amd64,linux/arm64 @@ -93,3 +103,10 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + - name: "Generate container attestation" + uses: actions/attest-build-provenance@v2 + with: + subject-name: ghcr.io/foxcpp/maddy + subject-digest: ${{ steps.docker.outputs.digest }} + push-to-registry: true + From 96bd83316bb096788c154eb98d766b0e28737e1b Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月25日 00:51:05 +0300 Subject: [PATCH 027/111] ci: Fix-up release pipeline --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca911b72..f908c63a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,13 +39,13 @@ jobs: tar c ./maddy-$ver-src | zstd> ~/maddy-src.tar.zst cd - - name: "Upload source tree" - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: maddy-src.tar.zst path: '~/maddy-src.tar.zst' if-no-files-found: error - name: "Upload binary tree" - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: maddy-binary.tar.zst path: '~/maddy-x86_64-linux-musl.tar.zst' From 3e5044ee8e0572acdd4147f84f788c22d2b06211 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月25日 01:07:46 +0300 Subject: [PATCH 028/111] build.sh: Remove -C from install Not available in Busybox when building under Alpine Linux --- build.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sh b/build.sh index f0a15922..419dc1f6 100755 --- a/build.sh +++ b/build.sh @@ -163,14 +163,14 @@ install() { if command -v go>/dev/null 2>/dev/null; then set -e if [ "$(go env GOOS)" = "linux" ]; then - command install -C -m 0755 -d "${destdir}/${prefix}/lib/systemd/system/" - command install -C -m 0644 "${builddir}"/systemd/*.service "${destdir}/${prefix}/lib/systemd/system/" + command install -m 0755 -d "${destdir}/${prefix}/lib/systemd/system/" + command install -m 0644 "${builddir}"/systemd/*.service "${destdir}/${prefix}/lib/systemd/system/" fi else set -e if [ "$(uname -s)" = "Linux" ]; then - command install -C -m 0755 -d "${destdir}/${prefix}/lib/systemd/system/" - command install -C -m 0644 "${builddir}"/systemd/*.service "${destdir}/${prefix}/lib/systemd/system/" + command install -m 0755 -d "${destdir}/${prefix}/lib/systemd/system/" + command install -m 0644 "${builddir}"/systemd/*.service "${destdir}/${prefix}/lib/systemd/system/" fi fi From c5c8e4b5b5a11077a9df199500a4257c9feba483 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月25日 01:42:33 +0300 Subject: [PATCH 029/111] ci: Disable arm64 docker build because of SIGSEGV --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f908c63a..1063c356 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -98,7 +98,7 @@ jobs: id: docker with: context: . - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64 #,linux/arm64 Temporary disabled due to SIGSEGV in gcc. file: Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} From 5cd2356b28fdf7beedb5e0190bf48fc92a03784b Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月25日 01:54:31 +0300 Subject: [PATCH 030/111] ci: Try to fix ghcr.io image upload --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1063c356..bf95dbb1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,12 +73,14 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} + logout: false - name: "Login to GitHub Container Registry" uses: docker/login-action@v3 with: registry: "ghcr.io" username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + logout: false # https://news.ycombinator.com/item?id=28607735 - name: "Generate container metadata" uses: docker/metadata-action@v5 id: meta From cff6cfaca676d5ee936d78aa8834ec001b3f0b2e Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月25日 02:01:52 +0300 Subject: [PATCH 031/111] ci: Another attempt to get ghcr.io upload working... --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bf95dbb1..0b5dfab0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,7 @@ permissions: id-token: write contents: read attestations: write + packages: write jobs: artifact-builder: From be0ec6b7cf14645b012e8151e8e5fb387e21df7e Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月25日 14:51:35 +0300 Subject: [PATCH 032/111] target/smtp: Check-in accidentally reverted attempt_starttls changes --- framework/config/map.go | 21 +++-- internal/smtpconn/smtpconn.go | 9 +- internal/target/smtp/smtp_downstream.go | 59 ++++++++----- internal/target/smtp/smtp_downstream_test.go | 93 ++------------------ internal/target/smtp/smtputf8_test.go | 4 + 5 files changed, 70 insertions(+), 116 deletions(-) diff --git a/framework/config/map.go b/framework/config/map.go index 10b17623..c85c19fe 100644 --- a/framework/config/map.go +++ b/framework/config/map.go @@ -20,6 +20,7 @@ package config import ( "errors" + "fmt" "reflect" "strconv" "strings" @@ -305,6 +306,16 @@ func (m *Map) DataSize(name string, inheritGlobal, required bool, defaultVal int }, store) } +func ParseBool(s string) (bool, error) { + switch strings.ToLower(s) { + case "1", "true", "on", "yes": + return true, nil + case "0", "false", "off", "no": + return false, nil + } + return false, fmt.Errorf("bool argument should be 'yes' or 'no'") +} + // Bool maps presence of some configuration directive to a boolean variable. // Additionally, 'name yes' and 'name no' are mapped to true and false // correspondingly. @@ -327,13 +338,11 @@ func (m *Map) Bool(name string, inheritGlobal, defaultVal bool, store *bool) { return nil, NodeErr(node, "expected exactly 1 argument") } - switch strings.ToLower(node.Args[0]) { - case "1", "true", "on", "yes": - return true, nil - case "0", "false", "off", "no": - return false, nil + b, err := ParseBool(node.Args[0]) + if err != nil { + return nil, NodeErr(node, "bool argument should be 'yes' or 'no'") } - return nil, NodeErr(node, "bool argument should be 'yes' or 'no'") + return b, nil }, store) } diff --git a/internal/smtpconn/smtpconn.go b/internal/smtpconn/smtpconn.go index 7f66bd23..ec42974d 100644 --- a/internal/smtpconn/smtpconn.go +++ b/internal/smtpconn/smtpconn.go @@ -259,12 +259,15 @@ func (c *C) attemptConnect(ctx context.Context, lmtp bool, endp config.Endpoint, return false, nil, nil, err } - if endp.IsTLS() || !starttls { - return endp.IsTLS(), cl, conn, nil + if !starttls { + return false, cl, conn, nil } if ok, _ := cl.Extension("STARTTLS"); !ok { - return false, cl, conn, nil + if err := cl.Quit(); err != nil { + cl.Close() + } + return false, nil, nil, fmt.Errorf("TLS required but unsupported by downstream") } cfg := tlsConfig.Clone() diff --git a/internal/target/smtp/smtp_downstream.go b/internal/target/smtp/smtp_downstream.go index f03fc827..8880f6cb 100644 --- a/internal/target/smtp/smtp_downstream.go +++ b/internal/target/smtp/smtp_downstream.go @@ -29,7 +29,6 @@ package smtp_downstream import ( "context" "crypto/tls" - "errors" "fmt" "net" "runtime/trace" @@ -54,12 +53,11 @@ type Downstream struct { lmtp bool targetsArg []string - requireTLS bool - attemptStartTLS bool - hostname string - endpoints []config.Endpoint - saslFactory saslClientFactory - tlsConfig tls.Config + starttls bool + hostname string + endpoints []config.Endpoint + saslFactory saslClientFactory + tlsConfig tls.Config connectTimeout time.Duration commandTimeout time.Duration @@ -89,10 +87,34 @@ func NewDownstream(modName, instName string, _, inlineArgs []string) (module.Mod } func (u *Downstream) Init(cfg *config.Map) error { + var attemptTLS *bool + var targetsArg []string cfg.Bool("debug", true, false, &u.log.Debug) - cfg.Bool("require_tls", false, false, &u.requireTLS) - cfg.Bool("attempt_starttls", false, !u.lmtp, &u.attemptStartTLS) + cfg.Callback("require_tls", func(m *config.Map, node config.Node) error { + u.log.Msg("require_tls directive is deprecated and ignored") + return nil + }) + cfg.Callback("attempt_starttls", func(m *config.Map, node config.Node) error { + u.log.Msg("attempt_starttls directive is deprecated and equivalent to starttls") + + if len(node.Args) == 0 { + trueVal := true + attemptTLS = &trueVal + return nil + } + if len(node.Args) != 1 { + return config.NodeErr(node, "expected exactly 1 argument") + } + + b, err := config.ParseBool(node.Args[0]) + if err != nil { + return err + } + attemptTLS = &b + return nil + }) + cfg.Bool("starttls", false, !u.lmtp, &u.starttls) cfg.String("hostname", true, true, "", &u.hostname) cfg.StringList("targets", false, false, nil, &targetsArg) cfg.Custom("auth", false, false, func() (interface{}, error) { @@ -109,6 +131,10 @@ func (u *Downstream) Init(cfg *config.Map) error { return err } + if attemptTLS != nil { + u.starttls = *attemptTLS + } + // INTERNATIONALIZATION: See RFC 6531 Section 3.7.1. var err error u.hostname, err = idna.ToASCII(u.hostname) @@ -201,14 +227,11 @@ func (d *delivery) connect(ctx context.Context) error { } for _, endp := range d.u.endpoints { - var ( - didTLS bool - err error - ) + var err error if d.u.lmtp { - didTLS, err = conn.ConnectLMTP(ctx, endp, d.u.attemptStartTLS, &d.u.tlsConfig) + _, err = conn.ConnectLMTP(ctx, endp, d.u.starttls, &d.u.tlsConfig) } else { - didTLS, err = conn.Connect(ctx, endp, d.u.attemptStartTLS, &d.u.tlsConfig) + _, err = conn.Connect(ctx, endp, d.u.starttls, &d.u.tlsConfig) } if err != nil { if len(d.u.endpoints) != 1 { @@ -220,12 +243,6 @@ func (d *delivery) connect(ctx context.Context) error { d.log.DebugMsg("connected", "downstream_server", conn.ServerName()) - if !didTLS && d.u.requireTLS { - conn.Close() - lastErr = errors.New("TLS is required, but unsupported by downstream") - continue - } - lastErr = nil break } diff --git a/internal/target/smtp/smtp_downstream_test.go b/internal/target/smtp/smtp_downstream_test.go index 011ca04a..31ef2954 100644 --- a/internal/target/smtp/smtp_downstream_test.go +++ b/internal/target/smtp/smtp_downstream_test.go @@ -207,7 +207,7 @@ func TestDownstreamDelivery_MAILErr(t *testing.T) { testutils.CheckSMTPErr(t, err, 550, exterrors.EnhancedCode{5, 1, 2}, "Hey") } -func TestDownstreamDelivery_AttemptTLS(t *testing.T) { +func TestDownstreamDelivery_StartTLS(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+testPort) defer srv.Close() defer testutils.CheckSMTPConnLeak(t, srv) @@ -221,9 +221,9 @@ func TestDownstreamDelivery_AttemptTLS(t *testing.T) { Port: testPort, }, }, - tlsConfig: *clientCfg.Clone(), - attemptStartTLS: true, - log: testutils.Logger(t, "target.smtp"), + tlsConfig: *clientCfg.Clone(), + starttls: true, + log: testutils.Logger(t, "target.smtp"), } testutils.DoTestDelivery(t, mod, "test@example.invalid", []string{"rcpt@example.invalid"}) @@ -235,85 +235,7 @@ func TestDownstreamDelivery_AttemptTLS(t *testing.T) { } } -func TestDownstreamDelivery_AttemptTLS_Fallback(t *testing.T) { - be, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort) - defer srv.Close() - defer testutils.CheckSMTPConnLeak(t, srv) - - mod := &Downstream{ - hostname: "mx.example.invalid", - endpoints: []config.Endpoint{ - { - Scheme: "tcp", - Host: "127.0.0.1", - Port: testPort, - }, - }, - attemptStartTLS: true, - log: testutils.Logger(t, "target.smtp"), - } - - testutils.DoTestDelivery(t, mod, "test@example.invalid", []string{"rcpt@example.invalid"}) - be.CheckMsg(t, 0, "test@example.invalid", []string{"rcpt@example.invalid"}) -} - -func TestDownstreamDelivery_RequireTLS(t *testing.T) { - clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+testPort) - defer srv.Close() - defer testutils.CheckSMTPConnLeak(t, srv) - - mod := &Downstream{ - hostname: "mx.example.invalid", - endpoints: []config.Endpoint{ - { - Scheme: "tcp", - Host: "127.0.0.1", - Port: testPort, - }, - }, - tlsConfig: *clientCfg.Clone(), - attemptStartTLS: true, - requireTLS: true, - log: testutils.Logger(t, "target.smtp"), - } - - testutils.DoTestDelivery(t, mod, "test@example.invalid", []string{"rcpt@example.invalid"}) - be.CheckMsg(t, 0, "test@example.invalid", []string{"rcpt@example.invalid"}) - tlsState, ok := be.Messages[0].Conn.TLSConnectionState() - if !ok || !tlsState.HandshakeComplete { - t.Fatal("Message was not delivered over TLS") - } -} - -func TestDownstreamDelivery_RequireTLS_Implicit(t *testing.T) { - clientCfg, be, srv := testutils.SMTPServerTLS(t, "127.0.0.1:"+testPort) - defer srv.Close() - defer testutils.CheckSMTPConnLeak(t, srv) - - mod := &Downstream{ - hostname: "mx.example.invalid", - endpoints: []config.Endpoint{ - { - Scheme: "tls", - Host: "127.0.0.1", - Port: testPort, - }, - }, - tlsConfig: *clientCfg.Clone(), - attemptStartTLS: true, - requireTLS: true, - log: testutils.Logger(t, "target.smtp"), - } - - testutils.DoTestDelivery(t, mod, "test@example.invalid", []string{"rcpt@example.invalid"}) - be.CheckMsg(t, 0, "test@example.invalid", []string{"rcpt@example.invalid"}) - tlsState, ok := be.Messages[0].Conn.TLSConnectionState() - if !ok || !tlsState.HandshakeComplete { - t.Fatal("Message was not delivered over TLS") - } -} - -func TestDownstreamDelivery_RequireTLS_Fail(t *testing.T) { +func TestDownstreamDelivery_StartTLS_NoFallback(t *testing.T) { _, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort) defer srv.Close() defer testutils.CheckSMTPConnLeak(t, srv) @@ -327,9 +249,8 @@ func TestDownstreamDelivery_RequireTLS_Fail(t *testing.T) { Port: testPort, }, }, - attemptStartTLS: true, - requireTLS: true, - log: testutils.Logger(t, "target.smtp"), + starttls: true, + log: testutils.Logger(t, "target.smtp"), } _, err := testutils.DoTestDeliveryErr(t, mod, "test@example.invalid", []string{"rcpt@example.invalid"}) diff --git a/internal/target/smtp/smtputf8_test.go b/internal/target/smtp/smtputf8_test.go index d99eaf19..74aae232 100644 --- a/internal/target/smtp/smtputf8_test.go +++ b/internal/target/smtp/smtputf8_test.go @@ -40,6 +40,10 @@ func TestDownstreamDelivery_EHLO_ALabel(t *testing.T) { Name: "hostname", Args: []string{"тест.invalid"}, }, + { + Name: "starttls", + Args: []string{"no"}, + }, }, })); err != nil { t.Fatal(err) From 21485e99d2da2b663f040eadd6c4f9ae57a7f914 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月25日 14:52:02 +0300 Subject: [PATCH 033/111] maddy 0.8.1 --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index a3df0a69..6f4eebdf 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -0.8.0 +0.8.1 From 69b434f3417ec47f2741240fa85bf17bb9a543a0 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月28日 23:33:37 +0300 Subject: [PATCH 034/111] tests: Allow to run maddyctl commands in integration tests --- internal/cli/app.go | 30 ++++---- internal/cli/extflag.go | 60 +++++++++++++++ maddy.go | 2 +- tests/cover_test.go | 20 ++--- tests/t.go | 157 ++++++++++++++++++++++++++++------------ 5 files changed, 198 insertions(+), 71 deletions(-) create mode 100644 internal/cli/extflag.go diff --git a/internal/cli/app.go b/internal/cli/app.go index fc4273d7..59108965 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -1,7 +1,6 @@ package maddycli import ( - "flag" "fmt" "os" "strings" @@ -30,10 +29,6 @@ databases used by it (all other subcommands). } app.ExitErrHandler = func(c *cli.Context, err error) { cli.HandleExitCoder(err) - if err != nil { - log.Println(err) - cli.OsExiter(1) - } } app.EnableBashCompletion = true app.Commands = []*cli.Command{ @@ -66,9 +61,6 @@ databases used by it (all other subcommands). func AddGlobalFlag(f cli.Flag) { app.Flags = append(app.Flags, f) - if err := f.Apply(flag.CommandLine); err != nil { - log.Println("GlobalFlag", f, "could not be mapped to stdlib flag:", err) - } } func AddSubcommand(cmd *cli.Command) { @@ -83,15 +75,27 @@ func AddSubcommand(cmd *cli.Command) { return cmd.Action(c) } app.Flags = append(app.Flags, cmd.Flags...) - for _, f := range cmd.Flags { - if err := f.Apply(flag.CommandLine); err != nil { - log.Println("GlobalFlag", f, "could not be mapped to stdlib flag:", err) - } - } } } +// RunWithoutExit is like Run but returns exit code instead of calling os.Exit +// To be used in maddy.cover. +func RunWithoutExit() int { + code := 0 + + cli.OsExiter = func(c int) { code = c } + defer func() { + cli.OsExiter = os.Exit + }() + + Run() + + return code +} + func Run() { + mapStdlibFlags(app) + // Actual entry point is registered in maddy.go. // Print help when called via maddyctl executable. To be removed diff --git a/internal/cli/extflag.go b/internal/cli/extflag.go new file mode 100644 index 00000000..8cfc27c3 --- /dev/null +++ b/internal/cli/extflag.go @@ -0,0 +1,60 @@ +package maddycli + +import ( + "flag" + + "github.com/urfave/cli/v2" +) + +// extFlag implements cli.Flag via standard flag.Flag. +type extFlag struct { + f *flag.Flag +} + +func (e *extFlag) Apply(fs *flag.FlagSet) error { + fs.Var(e.f.Value, e.f.Name, e.f.Usage) + return nil +} + +func (e *extFlag) Names() []string { + return []string{e.f.Name} +} + +func (e *extFlag) IsSet() bool { + return false +} + +func (e *extFlag) String() string { + return cli.FlagStringer(e) +} + +func (e *extFlag) IsVisible() bool { + return true +} + +func (e *extFlag) TakesValue() bool { + return false +} + +func (e *extFlag) GetUsage() string { + return e.f.Usage +} + +func (e *extFlag) GetValue() string { + return e.f.Value.String() +} + +func (e *extFlag) GetDefaultText() string { + return e.f.DefValue +} + +func (e *extFlag) GetEnvVars() []string { + return nil +} + +func mapStdlibFlags(app *cli.App) { + // Modified AllowExtFlags from cli lib with -test.* exception removed. + flag.VisitAll(func(f *flag.Flag) { + app.Flags = append(app.Flags, &extFlag{f}) + }) +} diff --git a/maddy.go b/maddy.go index e6a05cf1..f838e96e 100644 --- a/maddy.go +++ b/maddy.go @@ -284,7 +284,7 @@ func ensureDirectoryWritable(path string) error { return err } testFile.Close() - return os.Remove(testFile.Name()) + return os.RemoveAll(testFile.Name()) } func ReadGlobals(cfg []config.Node) (map[string]interface{}, []config.Node, error) { diff --git a/tests/cover_test.go b/tests/cover_test.go index d47c8840..298e3e9f 100644 --- a/tests/cover_test.go +++ b/tests/cover_test.go @@ -42,8 +42,10 @@ import ( "os" "testing" - "github.com/foxcpp/maddy" - "github.com/urfave/cli/v2" + _ "github.com/foxcpp/maddy" // To register run command + _ "github.com/foxcpp/maddy/internal/cli/ctl" // To register other CLI commands. + + maddycli "github.com/foxcpp/maddy/internal/cli" ) func TestMain(m *testing.M) { @@ -56,16 +58,14 @@ func TestMain(m *testing.M) { panic(err) } + // Skip flag parsing and make flag.Parse no-op so when + // m.Run calls it it will not error out on maddy flags. + args := os.Args + os.Args = []string{"command"} flag.Parse() + os.Args = args - app := cli.NewApp() - // maddycli wrapper registers all necessary flags with flag.CommandLine by default - ctx := cli.NewContext(app, flag.CommandLine, nil) - err = maddy.Run(ctx) - code := 0 - if ec, ok := err.(cli.ExitCoder); ok { - code = ec.ExitCode() - } + code := maddycli.RunWithoutExit() if err := os.Chdir(wd); err != nil { panic(err) diff --git a/tests/t.go b/tests/t.go index 2243662a..3ae27c65 100644 --- a/tests/t.go +++ b/tests/t.go @@ -25,6 +25,7 @@ package tests import ( "bufio" + "bytes" "flag" "fmt" "math/rand" @@ -34,6 +35,7 @@ import ( "path/filepath" "strconv" "strings" + "sync" "testing" "time" @@ -129,14 +131,7 @@ func (t *T) Env(kv string) { t.env = append(t.env, kv) } -// Run completes the configuration of test environment and starts the test server. -// -// T.Close should be called by the end of test to release any resources and -// shutdown the server. -// -// The parameter waitListeners specifies the amount of listeners the server is -// supposed to configure. Run() will block before all of them are up. -func (t *T) Run(waitListeners int) { +func (t *T) ensureCanRun() { if t.cfg == "" { panic("tests: Run called without configuration set") } @@ -146,66 +141,75 @@ func (t *T) Run(waitListeners int) { // any DNS queries to the real world. t.Log("NOTE: Explicit DNS(nil) is recommended.") t.DNS(nil) - } - // Setup file system, create statedir, runtimedir, write out config. - testDir, err := os.MkdirTemp("", "maddy-tests-") - if err != nil { - t.Fatal("Test configuration failed:", err) + t.Cleanup(func() { + // Shutdown the DNS server after maddy to make sure it will not spend time + // timing out queries. + if err := t.dnsServ.Close(); err != nil { + t.Log("Unable to stop the DNS server:", err) + } + t.dnsServ = nil + }) } - t.testDir = testDir - - t.Log("Using", t.testDir) - defer func() { - if !t.Failed() { - return + // Setup file system, create statedir, runtimedir, write out config. + if t.testDir == "" { + testDir, err := os.MkdirTemp("", "maddy-tests-") + if err != nil { + t.Fatal("Test configuration failed:", err) } + t.testDir = testDir + t.Log("using", t.testDir) - // Clean-up on test failure (if Run failed somewhere) - - t.dnsServ.Close() - t.dnsServ = nil + if err := os.MkdirAll(filepath.Join(t.testDir, "statedir"), os.ModePerm); err != nil { + t.Fatal("Test configuration failed:", err) + } + if err := os.MkdirAll(filepath.Join(t.testDir, "runtimedir"), os.ModePerm); err != nil { + t.Fatal("Test configuration failed:", err) + } - os.RemoveAll(t.testDir) - t.testDir = "" - }() + t.Cleanup(func() { + if !t.Failed() { + return + } - if err := os.MkdirAll(filepath.Join(t.testDir, "statedir"), os.ModePerm); err != nil { - t.Fatal("Test configuration failed:", err) - } - if err := os.MkdirAll(filepath.Join(t.testDir, "runtimedir"), os.ModePerm); err != nil { - t.Fatal("Test configuration failed:", err) + t.Log("removing", t.testDir) + os.RemoveAll(t.testDir) + t.testDir = "" + }) } configPreable := "state_dir " + filepath.Join(t.testDir, "statedir") + "\n" + - "runtime_dir " + filepath.Join(t.testDir, "runtime") + "\n\n" + "runtime_dir " + filepath.Join(t.testDir, "runtimedir") + "\n\n" - err = os.WriteFile(filepath.Join(t.testDir, "maddy.conf"), []byte(configPreable+t.cfg), os.ModePerm) + err := os.WriteFile(filepath.Join(t.testDir, "maddy.conf"), []byte(configPreable+t.cfg), os.ModePerm) if err != nil { t.Fatal("Test configuration failed:", err) } +} +func (t *T) buildCmd(additionalArgs ...string) *exec.Cmd { // Assigning 0 by default will make outbound SMTP unusable. remoteSmtp := "0" if port := t.ports["remote_smtp"]; port != 0 { remoteSmtp = strconv.Itoa(int(port)) } - cmd := exec.Command(TestBinary, - "-config", filepath.Join(t.testDir, "maddy.conf"), + args := []string{"-config", filepath.Join(t.testDir, "maddy.conf"), "-debug.smtpport", remoteSmtp, "-debug.dnsoverride", t.dnsServ.LocalAddr().String(), - "-log", "stderr") + "-log", "/tmp/test.log"} if CoverageOut != "" { - cmd.Args = append(cmd.Args, "-test.coverprofile", CoverageOut+"."+strconv.FormatInt(time.Now().UnixNano(), 16)) + args = append(args, "-test.coverprofile", CoverageOut+"."+strconv.FormatInt(time.Now().UnixNano(), 16)) } if DebugLog { - cmd.Args = append(cmd.Args, "-debug") + args = append(args, "-debug") } - t.Logf("launching %v", cmd.Args) + args = append(args, additionalArgs...) + + cmd := exec.Command(TestBinary, args...) pwd, err := os.Getwd() if err != nil { @@ -217,19 +221,79 @@ func (t *T) Run(waitListeners int) { cmd.Env = append(cmd.Env, "TEST_PWD="+pwd, "TEST_STATE_DIR="+filepath.Join(t.testDir, "statedir"), - "TEST_RUNTIME_DIR="+filepath.Join(t.testDir, "statedir"), + "TEST_RUNTIME_DIR="+filepath.Join(t.testDir, "runtimedir"), ) for name, port := range t.ports { cmd.Env = append(cmd.Env, fmt.Sprintf("TEST_PORT_%s=%d", name, port)) } cmd.Env = append(cmd.Env, t.env...) + return cmd +} + +func (t *T) MustRunCLIGroup(args ...[]string) { + t.ensureCanRun() + + wg := sync.WaitGroup{} + for _, arg := range args { + wg.Add(1) + go func() { + defer wg.Done() + + _, err := t.RunCLI(arg...) + if err != nil { + t.Fatalf("maddy %v: %v", arg, err) + } + }() + } + wg.Wait() +} + +func (t *T) MustRunCLI(args ...string) string { + s, err := t.RunCLI(args...) + if err != nil { + t.Fatalf("maddy %v: %v", args, err) + } + return s +} + +func (t *T) RunCLI(args ...string) (string, error) { + t.ensureCanRun() + cmd := t.buildCmd(args...) + + var stderr, stdout bytes.Buffer + cmd.Stderr = &stderr + cmd.Stdout = &stdout + + t.Log("launching maddy", cmd.Args) + if err := cmd.Run(); err != nil { + t.Log("Stderr:", stderr.String()) + t.Fatal("Test configuration failed:", err) + } + + t.Log("Stderr:", stderr.String()) + + return stdout.String(), nil +} + +// Run completes the configuration of test environment and starts the test server. +// +// T.Close should be called by the end of test to release any resources and +// shutdown the server. +// +// The parameter waitListeners specifies the amount of listeners the server is +// supposed to configure. Run() will block before all of them are up. +func (t *T) Run(waitListeners int) { + t.ensureCanRun() + cmd := t.buildCmd("run") + // Capture maddy log and redirect it. logOut, err := cmd.StderrPipe() if err != nil { t.Fatal("Test configuration failed:", err) } + t.Log("launching maddy", cmd.Args) if err := cmd.Start(); err != nil { t.Fatal("Test configuration failed:", err) } @@ -264,6 +328,8 @@ func (t *T) Run(waitListeners int) { } t.servProc = cmd + + t.Cleanup(t.killServer) } func (t *T) StateDir() string { @@ -274,7 +340,7 @@ func (t *T) RuntimeDir() string { return filepath.Join(t.testDir, "statedir") } -func (t *T) Close() { +func (t *T) killServer() { if err := t.servProc.Process.Signal(os.Interrupt); err != nil { t.Log("Unable to kill the server process:", err) os.RemoveAll(t.testDir) @@ -299,13 +365,10 @@ func (t *T) Close() { t.Log("Failed to remove test directory:", err) } t.testDir = "" +} - // Shutdown the DNS server after maddy to make sure it will not spend time - // timing out queries. - if err := t.dnsServ.Close(); err != nil { - t.Log("Unable to stop the DNS server:", err) - } - t.dnsServ = nil +func (t *T) Close() { + t.Log("close is no-op") } // Printf implements Logger interfaces used by some libraries. From 06fd5249d2230245d0cf080ae0fc6cc7e0d86319 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月28日 23:34:03 +0300 Subject: [PATCH 035/111] cli/ctl: Add --no-specialuse flag for imap-acct create --- internal/cli/ctl/imapacct.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/internal/cli/ctl/imapacct.go b/internal/cli/ctl/imapacct.go index ee7d19c2..2541a228 100644 --- a/internal/cli/ctl/imapacct.go +++ b/internal/cli/ctl/imapacct.go @@ -81,6 +81,11 @@ creates a set of default folder (mailboxes) with special-use attribute set.`, EnvVars: []string{"MADDY_CFGBLOCK"}, Value: "local_mailboxes", }, + &cli.BoolFlag{ + Name: "no-specialuse", + Usage: "Do not create special-use folders", + Value: false, + }, &cli.StringFlag{ Name: "sent-name", Usage: "Name of special mailbox for sent messages, use empty string to not create any", @@ -235,6 +240,10 @@ func imapAcctCreate(be module.Storage, ctx *cli.Context) error { fmt.Fprintf(os.Stderr, "Note: Storage backend does not support SPECIAL-USE IMAP extension") } + if ctx.Bool("no-specialuse") { + return nil + } + createMbox := func(name, specialUseAttr string) error { if suu == nil { return act.CreateMailbox(name) From c48332a9401151a93df75fae55dd34bea2cafc56 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月28日 23:34:52 +0300 Subject: [PATCH 036/111] auth/sasl: Add missing usernameForAuth call --- internal/auth/sasl.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/auth/sasl.go b/internal/auth/sasl.go index 00d25ffa..5968c3c0 100644 --- a/internal/auth/sasl.go +++ b/internal/auth/sasl.go @@ -139,7 +139,12 @@ func (s *SASLAuth) CreateSASL(mech string, remoteAddr net.Addr, successCb func(i return ErrInvalidAuthCred } - err := s.AuthPlain(username, password) + username, err := s.usernameForAuth(context.Background(), username) + if err != nil { + return err + } + + err = s.AuthPlain(username, password) if err != nil { s.Log.Error("authentication failed", err, "username", username, "src_ip", remoteAddr) return ErrInvalidAuthCred @@ -156,6 +161,11 @@ func (s *SASLAuth) CreateSASL(mech string, remoteAddr net.Addr, successCb func(i } return sasllogin.NewLoginServer(func(username, password string) error { + username, err := s.usernameForAuth(context.Background(), username) + if err != nil { + return err + } + err := s.AuthPlain(username, password) if err != nil { s.Log.Error("authentication failed", err, "username", username, "src_ip", remoteAddr) From b3ebe4b81e3ca1293f4da0505bb8811e8263b051 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月28日 23:35:08 +0300 Subject: [PATCH 037/111] endpoint/smtp: Drop duplicate RunEarlyChecks call --- internal/endpoint/smtp/session.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/endpoint/smtp/session.go b/internal/endpoint/smtp/session.go index 0eb6427f..cff1c01e 100644 --- a/internal/endpoint/smtp/session.go +++ b/internal/endpoint/smtp/session.go @@ -103,11 +103,6 @@ func (s *Session) AuthMechanisms() []string { } func (s *Session) Auth(mech string) (sasl.Server, error) { - // Executed before authentication and session initialization. - if err := s.endp.pipeline.RunEarlyChecks(s.sessionCtx, &s.connState); err != nil { - return nil, s.endp.wrapErr("", true, "AUTH", err) - } - return s.endp.saslAuth.CreateSASL(mech, s.connState.RemoteAddr, func(identity string, data auth.ContextData) error { s.connState.AuthUser = identity s.connState.AuthPassword = data.Password From 41b49bd44fbdef06e7d8e98facea54f73b997bf3 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月28日 23:35:23 +0300 Subject: [PATCH 038/111] tests: Add integration test for multidomain setups with shared namespaces --- tests/multiple_domains_test.go | 271 +++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 tests/multiple_domains_test.go diff --git a/tests/multiple_domains_test.go b/tests/multiple_domains_test.go new file mode 100644 index 00000000..971ca91b --- /dev/null +++ b/tests/multiple_domains_test.go @@ -0,0 +1,271 @@ +//go:build integration + +/* +Maddy Mail Server - Composable all-in-one email server. +Copyright © 2019-2025 Max Mazurov , Maddy Mail Server contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package tests_test + +import ( + "testing" + + "github.com/foxcpp/maddy/tests" +) + +// Test cases based on https://maddy.email/multiple-domains/ + +func TestMultipleDomains_SeparateNamespace(tt *testing.T) { + tt.Parallel() + t := tests.NewT(tt) + t.DNS(nil) + t.Port("submission") + t.Port("imap") + t.Config(` + tls off + hostname test.maddy.email + + auth.pass_table local_authdb { + table sql_table { + driver sqlite3 + dsn credentials.db + table_name passwords + } + } + storage.imapsql local_mailboxes { + driver sqlite3 + dsn imapsql.db + } + + submission tcp://0.0.0.0:{env:TEST_PORT_submission} { + auth &local_authdb + reject + } + imap tcp://127.0.0.1:{env:TEST_PORT_imap} { + auth &local_authdb + storage &local_mailboxes + } + `) + + t.MustRunCLIGroup( + []string{"creds", "create", "-p", "user1", "user1@test1.maddy.email"}, + []string{"creds", "create", "-p", "user2", "user2@test1.maddy.email"}, + []string{"creds", "create", "-p", "user3", "user1@test2.maddy.email"}, + []string{"imap-acct", "create", "--no-specialuse", "user1@test1.maddy.email"}, + []string{"imap-acct", "create", "--no-specialuse", "user2@test1.maddy.email"}, + []string{"imap-acct", "create", "--no-specialuse", "user1@test2.maddy.email"}, + ) + t.Run(2) + + user1 := t.Conn("imap") + user1.ExpectPattern(`\* OK *`) + user1.Writeln(`. LOGIN user1@test1.maddy.email user1`) + user1.ExpectPattern(`. OK *`) + user1.Writeln(`. CREATE user1`) + user1.ExpectPattern(`. OK *`) + + user2 := t.Conn("imap") + user2.ExpectPattern(`\* OK *`) + user2.Writeln(`. LOGIN user2@test1.maddy.email user2`) + user2.ExpectPattern(`. OK *`) + user2.Writeln(`. CREATE user2`) + user2.ExpectPattern(`. OK *`) + + user3 := t.Conn("imap") + user3.ExpectPattern(`\* OK *`) + user3.Writeln(`. LOGIN user1@test2.maddy.email user3`) + user3.ExpectPattern(`. OK *`) + user3.Writeln(`. CREATE user3`) + user3.ExpectPattern(`. OK *`) + + user1.Writeln(`. LIST "" "*"`) + user1.Expect(`* LIST (\HasNoChildren) "." INBOX`) + user1.Expect(`* LIST (\HasNoChildren) "." "user1"`) + user1.ExpectPattern(". OK *") + + user2.Writeln(`. LIST "" "*"`) + user2.Expect(`* LIST (\HasNoChildren) "." INBOX`) + user2.Expect(`* LIST (\HasNoChildren) "." "user2"`) + user2.ExpectPattern(". OK *") + + user3.Writeln(`. LIST "" "*"`) + user3.Expect(`* LIST (\HasNoChildren) "." INBOX`) + user3.Expect(`* LIST (\HasNoChildren) "." "user3"`) + user3.ExpectPattern(". OK *") +} + +func TestMultipleDomains_SharedCredentials_DistinctMailboxes(tt *testing.T) { + tt.Parallel() + t := tests.NewT(tt) + t.DNS(nil) + t.Port("submission") + t.Port("imap") + t.Config(` + tls off + hostname test.maddy.email + auth_map email_localpart + + auth.pass_table local_authdb { + table sql_table { + driver sqlite3 + dsn credentials.db + table_name passwords + } + } + storage.imapsql local_mailboxes { + driver sqlite3 + dsn imapsql.db + } + + submission tcp://0.0.0.0:{env:TEST_PORT_submission} { + auth &local_authdb + reject + } + imap tcp://127.0.0.1:{env:TEST_PORT_imap} { + auth &local_authdb + storage &local_mailboxes + } + `) + + t.MustRunCLIGroup( + []string{"creds", "create", "-p", "user1", "user1"}, + []string{"creds", "create", "-p", "user2", "user2"}, + []string{"imap-acct", "create", "--no-specialuse", "user1@test1.maddy.email"}, + []string{"imap-acct", "create", "--no-specialuse", "user2@test1.maddy.email"}, + []string{"imap-acct", "create", "--no-specialuse", "user1@test2.maddy.email"}, + ) + t.Run(2) + + user1 := t.Conn("imap") + user1.ExpectPattern(`\* OK *`) + user1.Writeln(`. LOGIN user1@test1.maddy.email user1`) + user1.ExpectPattern(`. OK *`) + user1.Writeln(`. CREATE user1`) + user1.ExpectPattern(`. OK *`) + + user2 := t.Conn("imap") + user2.ExpectPattern(`\* OK *`) + user2.Writeln(`. LOGIN user2@test1.maddy.email user2`) + user2.ExpectPattern(`. OK *`) + user2.Writeln(`. CREATE user2`) + user2.ExpectPattern(`. OK *`) + + user3 := t.Conn("imap") + user3.ExpectPattern(`\* OK *`) + user3.Writeln(`. LOGIN user1@test2.maddy.email user1`) + user3.ExpectPattern(`. OK *`) + user3.Writeln(`. CREATE user3`) + user3.ExpectPattern(`. OK *`) + + user1.Writeln(`. LIST "" "*"`) + user1.Expect(`* LIST (\HasNoChildren) "." INBOX`) + user1.Expect(`* LIST (\HasNoChildren) "." "user1"`) + user1.ExpectPattern(". OK *") + + user2.Writeln(`. LIST "" "*"`) + user2.Expect(`* LIST (\HasNoChildren) "." INBOX`) + user2.Expect(`* LIST (\HasNoChildren) "." "user2"`) + user2.ExpectPattern(". OK *") + + user3.Writeln(`. LIST "" "*"`) + user3.Expect(`* LIST (\HasNoChildren) "." INBOX`) + user3.Expect(`* LIST (\HasNoChildren) "." "user3"`) + user3.ExpectPattern(". OK *") +} + +func TestMultipleDomains_SharedCredentials_SharedMailboxes(tt *testing.T) { + tt.Parallel() + t := tests.NewT(tt) + t.DNS(nil) + t.Port("submission") + t.Port("imap") + t.Config(` + tls off + hostname test.maddy.email + auth_map email_localpart_optional + + auth.pass_table local_authdb { + table sql_table { + driver sqlite3 + dsn credentials.db + table_name passwords + } + } + storage.imapsql local_mailboxes { + driver sqlite3 + dsn imapsql.db + + delivery_map email_localpart_optional + } + + submission tcp://0.0.0.0:{env:TEST_PORT_submission} { + auth &local_authdb + reject + } + imap tcp://127.0.0.1:{env:TEST_PORT_imap} { + auth &local_authdb + storage &local_mailboxes + + storage_map email_localpart_optional + } + `) + + t.MustRunCLIGroup( + []string{"creds", "create", "-p", "user1", "user1"}, + []string{"creds", "create", "-p", "user2", "user2"}, + []string{"imap-acct", "create", "--no-specialuse", "user1"}, + []string{"imap-acct", "create", "--no-specialuse", "user2"}, + ) + t.Run(2) + + user1 := t.Conn("imap") + user1.ExpectPattern(`\* OK *`) + user1.Writeln(`. LOGIN user1 user1`) + user1.ExpectPattern(`. OK *`) + user1.Writeln(`. CREATE user1`) + user1.ExpectPattern(`. OK *`) + + user2 := t.Conn("imap") + user2.ExpectPattern(`\* OK *`) + user2.Writeln(`. LOGIN user2@test1.maddy.email user2`) + user2.ExpectPattern(`. OK *`) + user2.Writeln(`. CREATE user2`) + user2.ExpectPattern(`. OK *`) + + user12 := t.Conn("imap") + user12.ExpectPattern(`\* OK *`) + user12.Writeln(`. LOGIN user1@test2.maddy.email user1`) + user12.ExpectPattern(`. OK *`) + user12.Writeln(`. CREATE user12`) + user12.ExpectPattern(`. OK *`) + + user1.Writeln(`. LIST "" "*"`) + user1.Expect(`* LIST (\HasNoChildren) "." INBOX`) + user1.Expect(`* LIST (\HasNoChildren) "." "user1"`) + user1.Expect(`* LIST (\HasNoChildren) "." "user12"`) + user1.ExpectPattern(". OK *") + + user2.Writeln(`. LIST "" "*"`) + user2.Expect(`* LIST (\HasNoChildren) "." INBOX`) + user2.Expect(`* LIST (\HasNoChildren) "." "user2"`) + user2.ExpectPattern(". OK *") + + user12.Writeln(`. LIST "" "*"`) + user12.Expect(`* LIST (\HasNoChildren) "." INBOX`) + user12.Expect(`* LIST (\HasNoChildren) "." "user1"`) + user12.Expect(`* LIST (\HasNoChildren) "." "user12"`) + user12.ExpectPattern(". OK *") +} From 503c9f8849af59fe0490e78c71feb618da9a4150 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月29日 21:40:15 +0300 Subject: [PATCH 039/111] Implement Configure-Start-Stop module lifetime and non-global Registry object container.Global is temporarily added until we start passing container during module initialization. --- framework/config/module/modconfig.go | 30 +-- framework/container/container.go | 70 ++++++ framework/log/log.go | 11 + framework/module/delivery_target.go | 6 +- framework/module/dummy.go | 6 +- framework/module/instances.go | 105 -------- framework/module/lifetime.go | 121 +++++++++ framework/module/module.go | 26 +- framework/module/modules.go | 96 ++++++++ framework/module/mxauth.go | 2 +- framework/module/partial_delivery.go | 2 +- framework/module/registry.go | 162 ++++++++----- internal/auth/dovecot_sasl/dovecot_sasl.go | 20 +- internal/auth/external/externalauth.go | 12 +- internal/auth/ldap/ldap.go | 32 ++- internal/auth/netauth/netauth.go | 10 +- internal/auth/pam/module.go | 11 +- internal/auth/pass_table/table.go | 18 +- internal/auth/pass_table/table_test.go | 4 +- .../auth/plain_separate/plain_separate.go | 12 +- internal/auth/shadow/module.go | 11 +- .../authorize_sender/authorize_sender.go | 9 +- internal/check/command/command.go | 18 +- internal/check/dkim/dkim.go | 11 +- internal/check/dkim/dkim_test.go | 4 +- internal/check/dnsbl/dnsbl.go | 12 +- internal/check/milter/milter.go | 20 +- internal/check/milter/milter_test.go | 4 +- internal/check/rspamd/rspamd.go | 22 +- internal/check/spf/spf.go | 4 +- internal/check/stateless_check.go | 11 +- internal/cli/ctl/moduleinit.go | 58 ++--- .../endpoint/dovecot_sasld/dovecot_sasl.go | 24 +- internal/endpoint/imap/imap.go | 56 +++-- internal/endpoint/openmetrics/om.go | 38 +-- internal/endpoint/smtp/session.go | 2 +- internal/endpoint/smtp/smtp.go | 23 +- internal/endpoint/smtp/smtp_test.go | 30 ++- internal/endpoint/smtp/smtputf8_test.go | 20 +- internal/endpoint/smtp/submission_test.go | 2 +- internal/imap_filter/command/command.go | 18 +- internal/imap_filter/group.go | 4 +- internal/libdns/acmedns.go | 2 +- internal/libdns/alidns.go | 2 +- internal/libdns/cloudflare.go | 2 +- internal/libdns/digitalocean.go | 2 +- internal/libdns/gandi.go | 2 +- internal/libdns/googleclouddns.go | 2 +- internal/libdns/hetzner.go | 2 +- internal/libdns/leaseweb.go | 2 +- internal/libdns/metaname.go | 2 +- internal/libdns/namecheap.go | 2 +- internal/libdns/namedotcom.go | 2 +- internal/libdns/provider_module.go | 2 +- internal/libdns/rfc2136.go | 2 +- internal/libdns/route53.go | 2 +- internal/libdns/vultr.go | 2 +- internal/limits/limits.go | 4 +- internal/modify/dkim/dkim.go | 23 +- internal/modify/dkim/dkim_test.go | 4 +- internal/modify/group.go | 6 +- internal/modify/replace_addr.go | 28 +-- internal/modify/replace_addr_test.go | 4 +- internal/msgpipeline/check_group.go | 8 +- internal/msgpipeline/dmarc_test.go | 2 +- internal/msgpipeline/module.go | 4 +- internal/msgpipeline/msgpipeline.go | 10 +- internal/msgpipeline/msgpipeline_test.go | 14 +- internal/smtpconn/smtpconn.go | 2 +- internal/storage/blob/fs/fs.go | 27 ++- internal/storage/blob/s3/s3.go | 12 +- internal/storage/blob/s3/s3_test.go | 2 +- internal/storage/imapsql/delivery.go | 4 +- internal/storage/imapsql/imapsql.go | 52 ++-- internal/table/chain.go | 4 +- internal/table/email_localpart.go | 4 +- internal/table/email_with_domain.go | 7 +- internal/table/file.go | 44 ++-- internal/table/file_test.go | 27 ++- internal/table/identity.go | 4 +- internal/table/regexp.go | 20 +- internal/table/sql_query.go | 67 ++--- internal/table/sql_query_test.go | 7 +- internal/table/sql_table.go | 12 +- internal/table/static.go | 4 +- internal/target/queue/queue.go | 34 +-- internal/target/queue/queue_test.go | 38 +-- internal/target/remote/mxauth_test.go | 28 +-- internal/target/remote/policy_group.go | 8 +- internal/target/remote/remote.go | 21 +- internal/target/remote/remote_test.go | 90 +++---- internal/target/remote/security.go | 79 +++--- internal/target/smtp/smtp_downstream.go | 28 +-- internal/target/smtp/smtputf8_test.go | 4 +- internal/testutils/bench_delivery.go | 2 +- internal/testutils/check.go | 5 +- internal/testutils/modifier.go | 5 +- internal/testutils/target.go | 16 +- internal/tls/acme/acme.go | 35 +-- internal/tls/file.go | 45 ++-- internal/tls/self_signed.go | 8 +- maddy.go | 229 +++++++++++------- signal_nonposix.go | 2 +- 103 files changed, 1305 insertions(+), 965 deletions(-) create mode 100644 framework/container/container.go delete mode 100644 framework/module/instances.go create mode 100644 framework/module/lifetime.go create mode 100644 framework/module/modules.go diff --git a/framework/config/module/modconfig.go b/framework/config/module/modconfig.go index 3183bb93..443bfebd 100644 --- a/framework/config/module/modconfig.go +++ b/framework/config/module/modconfig.go @@ -28,19 +28,18 @@ package modconfig import ( "fmt" - "io" "reflect" "strings" parser "github.com/foxcpp/maddy/framework/cfgparser" "github.com/foxcpp/maddy/framework/config" - "github.com/foxcpp/maddy/framework/hooks" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" ) // createInlineModule is a helper function for config matchers that can create inline modules. -func createInlineModule(preferredNamespace, modName string, args []string) (module.Module, error) { +func createInlineModule(preferredNamespace, modName string) (module.Module, error) { var newMod module.FuncNewModule originalModName := modName @@ -61,26 +60,21 @@ func createInlineModule(preferredNamespace, modName string, args []string) (modu return nil, fmt.Errorf("unknown module: %s (namespace: %s)", originalModName, preferredNamespace) } - return newMod(modName, "", nil, args) + return newMod(modName, "") } -// initInlineModule constructs "faked" config tree and passes it to module +// configureInlineModule constructs "faked" config tree and passes it to module // Init function to make it look like it is defined at top-level. // -// args must contain at least one argument, otherwise initInlineModule panics. -func initInlineModule(modObj module.Module, globals map[string]interface{}, block config.Node) error { - err := modObj.Init(config.NewMap(globals, block)) +// args must contain at least one argument, otherwise configureInlineModule panics. +func configureInlineModule(modObj module.Module, args []string, globals map[string]interface{}, block config.Node) error { + err := modObj.Configure(args, config.NewMap(globals, block)) if err != nil { return err } - if closer, ok := modObj.(io.Closer); ok { - hooks.AddHook(hooks.EventShutdown, func() { - log.Debugf("close %s (%s)", modObj.Name(), modObj.InstanceName()) - if err := closer.Close(); err != nil { - log.Printf("module %s (%s) close failed: %v", modObj.Name(), modObj.InstanceName(), err) - } - }) + if li, ok := modObj.(module.LifetimeModule); ok { + container.Global.Lifetime.Add(li) } return nil @@ -117,11 +111,11 @@ func ModuleFromNode(preferredNamespace string, args []string, inlineCfg config.N if len(args) != 1 || inlineCfg.Children != nil { return parser.NodeErr(inlineCfg, "exactly one argument is required to use existing config block") } - modObj, err = module.GetInstance(args[0][1:]) + modObj, err = container.Global.Modules.Get(args[0][1:]) log.Debugf("%s:%d: reference %s", inlineCfg.File, inlineCfg.Line, args[0]) } else { log.Debugf("%s:%d: new module %s %v", inlineCfg.File, inlineCfg.Line, args[0], args[1:]) - modObj, err = createInlineModule(preferredNamespace, args[0], args[1:]) + modObj, err = createInlineModule(preferredNamespace, args[0]) } if err != nil { return err @@ -144,7 +138,7 @@ func ModuleFromNode(preferredNamespace string, args []string, inlineCfg config.N reflect.ValueOf(moduleIface).Elem().Set(reflect.ValueOf(modObj)) if !referenceExisting { - if err := initInlineModule(modObj, globals, inlineCfg); err != nil { + if err := configureInlineModule(modObj, args[1:], globals, inlineCfg); err != nil { return err } } diff --git a/framework/container/container.go b/framework/container/container.go new file mode 100644 index 00000000..dc1e0124 --- /dev/null +++ b/framework/container/container.go @@ -0,0 +1,70 @@ +/* +Maddy Mail Server - Composable all-in-one email server. +Copyright © 2019-2020 Max Mazurov , Maddy Mail Server contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package container + +import ( + "github.com/foxcpp/maddy/framework/log" + "github.com/foxcpp/maddy/framework/module" +) + +type GlobalConfig struct { + // StateDirectory contains the path to the directory that + // should be used to store any data that should be + // preserved between sessions. + // + // Value of this variable must not change after initialization + // in cmd/maddy/main.go. + StateDirectory string + + // RuntimeDirectory contains the path to the directory that + // should be used to store any temporary data. + // + // It should be preferred over os.TempDir, which is + // global and world-readable on most systems, while + // RuntimeDirectory can be dedicated for maddy. + // + // Value of this variable must not change after initialization + // in cmd/maddy/main.go. + RuntimeDirectory string + + // LibexecDirectory contains the path to the directory + // where helper binaries should be searched. + // + // Value of this variable must not change after initialization + // in cmd/maddy/main.go. + LibexecDirectory string +} + +type C struct { + Config GlobalConfig + DefaultLogger log.Logger + Modules *module.Registry + Lifetime *module.LifetimeTracker +} + +func New() *C { + return &C{ + DefaultLogger: log.DefaultLogger, + Modules: module.NewRegistry(log.DefaultLogger.Sublogger("registry")), + Lifetime: module.NewLifetime(log.DefaultLogger.Sublogger("lifetime")), + } +} + +// Global is the default instance while refactoring is in progress. +var Global *C diff --git a/framework/log/log.go b/framework/log/log.go index f9092a44..da8647f6 100644 --- a/framework/log/log.go +++ b/framework/log/log.go @@ -224,6 +224,17 @@ func (l Logger) log(debug bool, s string) { // Logging is disabled - do nothing. } +func (l Logger) Sublogger(name string) Logger { + if l.Name != "" { + name = l.Name + "/" + name + } + return Logger{ + Out: l.Out, + Name: name, + Debug: l.Debug, + } +} + // DefaultLogger is the global Logger object that is used by // package-level logging functions. // diff --git a/framework/module/delivery_target.go b/framework/module/delivery_target.go index 9a7c1e09..40e5881a 100644 --- a/framework/module/delivery_target.go +++ b/framework/module/delivery_target.go @@ -33,12 +33,12 @@ import ( // Modules implementing this interface should be registered with "target." // prefix in name. type DeliveryTarget interface { - // Start starts the delivery of a new message. + // StartDelivery starts the delivery of a new message. // // The domain part of the MAIL FROM address is assumed to be U-labels with // NFC normalization and case-folding applied. The message source should // ensure that by calling address.CleanDomain if necessary. - Start(ctx context.Context, msgMeta *MsgMetadata, mailFrom string) (Delivery, error) + StartDelivery(ctx context.Context, msgMeta *MsgMetadata, mailFrom string) (Delivery, error) } type Delivery interface { @@ -54,7 +54,7 @@ type Delivery interface { // however. They should be silently ignored. // // Implementation should do as much checks as possible here and reject - // recipients that can't be used. Note: MsgMetadata object passed to Start + // recipients that can't be used. Note: MsgMetadata object passed to StartDelivery // contains BodyLength field. If it is non-zero, it can be used to check // storage quota for the user before Body. AddRcpt(ctx context.Context, rcptTo string, opts smtp.RcptOptions) error diff --git a/framework/module/dummy.go b/framework/module/dummy.go index 0930d4db..0722fb26 100644 --- a/framework/module/dummy.go +++ b/framework/module/dummy.go @@ -54,11 +54,11 @@ func (d *Dummy) InstanceName() string { return d.instName } -func (d *Dummy) Init(_ *config.Map) error { +func (d *Dummy) Configure(_ []string, _ *config.Map) error { return nil } -func (d *Dummy) Start(ctx context.Context, msgMeta *MsgMetadata, mailFrom string) (Delivery, error) { +func (d *Dummy) StartDelivery(ctx context.Context, msgMeta *MsgMetadata, mailFrom string) (Delivery, error) { return dummyDelivery{}, nil } @@ -81,7 +81,7 @@ func (dd dummyDelivery) Commit(ctx context.Context) error { } func init() { - Register("dummy", func(_, instName string, _, _ []string) (Module, error) { + Register("dummy", func(_, instName string) (Module, error) { return &Dummy{instName: instName}, nil }) } diff --git a/framework/module/instances.go b/framework/module/instances.go deleted file mode 100644 index aa6f1489..00000000 --- a/framework/module/instances.go +++ /dev/null @@ -1,105 +0,0 @@ -/* -Maddy Mail Server - Composable all-in-one email server. -Copyright © 2019-2020 Max Mazurov , Maddy Mail Server contributors - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package module - -import ( - "fmt" - "io" - - "github.com/foxcpp/maddy/framework/config" - "github.com/foxcpp/maddy/framework/hooks" - "github.com/foxcpp/maddy/framework/log" -) - -var ( - instances = make(map[string]struct { - mod Module - cfg *config.Map - }) - aliases = make(map[string]string) - - Initialized = make(map[string]bool) -) - -// RegisterInstance adds module instance to the global registry. -// -// Instance name must be unique. Second RegisterInstance with same instance -// name will replace previous. -func RegisterInstance(inst Module, cfg *config.Map) { - instances[inst.InstanceName()] = struct { - mod Module - cfg *config.Map - }{inst, cfg} -} - -// RegisterAlias creates an association between a certain name and instance name. -// -// After RegisterAlias, module.GetInstance(aliasName) will return the same -// result as module.GetInstance(instName). -func RegisterAlias(aliasName, instName string) { - aliases[aliasName] = instName -} - -func HasInstance(name string) bool { - aliasedName := aliases[name] - if aliasedName != "" { - name = aliasedName - } - - _, ok := instances[name] - return ok -} - -// GetInstance returns module instance from global registry, initializing it if -// necessary. -// -// Error is returned if module initialization fails or module instance does not -// exists. -func GetInstance(name string) (Module, error) { - aliasedName := aliases[name] - if aliasedName != "" { - name = aliasedName - } - - mod, ok := instances[name] - if !ok { - return nil, fmt.Errorf("unknown config block: %s", name) - } - - // Break circular dependencies. - if Initialized[name] { - return mod.mod, nil - } - - Initialized[name] = true - if err := mod.mod.Init(mod.cfg); err != nil { - return mod.mod, err - } - - if closer, ok := mod.mod.(io.Closer); ok { - hooks.AddHook(hooks.EventShutdown, func() { - log.Debugf("close %s (%s)", mod.mod.Name(), mod.mod.InstanceName()) - if err := closer.Close(); err != nil { - log.Printf("module %s (%s) close failed: %v", mod.mod.Name(), mod.mod.InstanceName(), err) - } - }) - } - - return mod.mod, nil -} diff --git a/framework/module/lifetime.go b/framework/module/lifetime.go new file mode 100644 index 00000000..1b0339da --- /dev/null +++ b/framework/module/lifetime.go @@ -0,0 +1,121 @@ +/* +Maddy Mail Server - Composable all-in-one email server. +Copyright © 2019-2025 Max Mazurov , Maddy Mail Server contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package module + +import ( + "fmt" + + "github.com/foxcpp/maddy/framework/log" +) + +// LifetimeModule is a stateful module that needs to have post-configuration +// startup and graceful shutdown functionality. +type LifetimeModule interface { + Module + Start() error + Stop() error +} + +type ReloadModule interface { + Module + Reload() error +} + +type LifetimeTracker struct { + logger log.Logger + instances []*struct { + mod LifetimeModule + started bool + } +} + +func (lt *LifetimeTracker) Add(mod LifetimeModule) { + lt.instances = append(lt.instances, &struct { + mod LifetimeModule + started bool + }{mod: mod, started: false}) +} + +// StartAll calls Start for all registered LifetimeModule instances. +func (lt *LifetimeTracker) StartAll() error { + for _, entry := range lt.instances { + if entry.started { + continue + } + + if err := entry.mod.Start(); err != nil { + lt.StopAll() + return fmt.Errorf("failed to start module %v: %w", + entry.mod.InstanceName(), err) + } + lt.logger.DebugMsg("module started", + "mod_name", entry.mod.Name(), "inst_name", entry.mod.InstanceName()) + entry.started = true + } + return nil +} + +func (lt *LifetimeTracker) ReloadAll() error { + for _, entry := range lt.instances { + if !entry.started { + continue + } + + rm, ok := entry.mod.(ReloadModule) + if !ok { + continue + } + + if err := rm.Reload(); err != nil { + lt.logger.Error("module reload failed", err, + "mod_name", entry.mod.Name(), "inst_name", entry.mod.InstanceName()) + continue + } + + lt.logger.DebugMsg("module reloaded", + "mod_name", entry.mod.Name(), "inst_name", entry.mod.InstanceName()) + } + return nil +} + +// StopAll calls Stop for all registered LifetimeModule instances. +func (lt *LifetimeTracker) StopAll() error { + for _, entry := range lt.instances { + if !entry.started { + continue + } + + if err := entry.mod.Stop(); err != nil { + lt.logger.Error("module stop failed", err, + "mod_name", entry.mod.Name(), "inst_name", entry.mod.InstanceName()) + continue + } + lt.logger.DebugMsg("module stopped", + "mod_name", entry.mod.Name(), "inst_name", entry.mod.InstanceName()) + + entry.started = false + } + return nil +} + +func NewLifetime(log log.Logger) *LifetimeTracker { + return &LifetimeTracker{ + logger: log, + } +} diff --git a/framework/module/module.go b/framework/module/module.go index 2dbb45e9..2f949c14 100644 --- a/framework/module/module.go +++ b/framework/module/module.go @@ -38,22 +38,8 @@ import ( ) // Module is the interface implemented by all maddy module instances. -// -// It defines basic methods used to identify instances. -// -// Additionally, module can implement io.Closer if it needs to perform clean-up -// on shutdown. If module starts long-lived goroutines - they should be stopped -// *before* Close method returns to ensure graceful shutdown. type Module interface { - // Init performs actual initialization of the module. - // - // It is not done in FuncNewModule so all module instances are - // registered at time of initialization, thus initialization does not - // depends on ordering of configuration blocks and modules can reference - // each other without any problems. - // - // Module can use passed config.Map to read its configuration variables. - Init(*config.Map) error + Configure(inlineArgs []string, config *config.Map) error // Name method reports module name. // @@ -68,12 +54,10 @@ type Module interface { // FuncNewModule is function that creates new instance of module with specified name. // // Module.InstanceName() of the returned module object should return instName. -// aliases slice contains other names that can be used to reference created -// module instance. +// If module is defined inline, instName will be empty. // -// If module is defined inline, instName will be empty and all values -// specified after module name in configuration will be in inlineArgs. -type FuncNewModule func(modName, instName string, aliases, inlineArgs []string) (Module, error) +// Returned Module may additionally implement LifetimeModule. +type FuncNewModule func(modName, instName string) (Module, error) // FuncNewEndpoint is a function that creates new instance of endpoint // module. @@ -87,4 +71,4 @@ type FuncNewModule func(modName, instName string, aliases, inlineArgs []string) // // As a consequence of having no per-instance name, InstanceName of the module // object always returns the same value as Name. -type FuncNewEndpoint func(modName string, addrs []string) (Module, error) +type FuncNewEndpoint func(modName string, addrs []string) (LifetimeModule, error) diff --git a/framework/module/modules.go b/framework/module/modules.go new file mode 100644 index 00000000..d7669808 --- /dev/null +++ b/framework/module/modules.go @@ -0,0 +1,96 @@ +/* +Maddy Mail Server - Composable all-in-one email server. +Copyright © 2019-2020 Max Mazurov , Maddy Mail Server contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package module + +import ( + "sync" + + "github.com/foxcpp/maddy/framework/log" +) + +var ( + modules = make(map[string]FuncNewModule) + endpoints = make(map[string]FuncNewEndpoint) + modulesLock sync.RWMutex +) + +// Register adds module factory function to global registry. +// +// name must be unique. Register will panic if module with specified name +// already exists in registry. +// +// You probably want to call this function from func init() of module package. +func Register(name string, factory FuncNewModule) { + modulesLock.Lock() + defer modulesLock.Unlock() + + if _, ok := modules[name]; ok { + panic("Register: module with specified name is already registered: " + name) + } + + modules[name] = factory +} + +// RegisterDeprecated adds module factory function to global registry. +// +// It prints warning to the log about name being deprecated and suggests using +// a new name. +func RegisterDeprecated(name, newName string, factory FuncNewModule) { + Register(name, func(modName, instName string) (Module, error) { + log.Printf("module initialized via deprecated name %s, %s should be used instead; deprecated name may be removed in the next version", name, newName) + return factory(modName, instName) + }) +} + +// Get returns module from global registry. +// +// This function does not return endpoint-type modules, use GetEndpoint for +// that. +// Nil is returned if no module with specified name is registered. +func Get(name string) FuncNewModule { + modulesLock.RLock() + defer modulesLock.RUnlock() + + return modules[name] +} + +// GetEndpoint returns an endpoint module from global registry. +// +// Nil is returned if no module with specified name is registered. +func GetEndpoint(name string) FuncNewEndpoint { + modulesLock.RLock() + defer modulesLock.RUnlock() + + return endpoints[name] +} + +// RegisterEndpoint registers an endpoint module. +// +// See FuncNewEndpoint for information about +// differences of endpoint modules from regular modules. +func RegisterEndpoint(name string, factory FuncNewEndpoint) { + modulesLock.Lock() + defer modulesLock.Unlock() + + if _, ok := endpoints[name]; ok { + panic("Register: module with specified name is already registered: " + name) + } + + endpoints[name] = factory +} diff --git a/framework/module/mxauth.go b/framework/module/mxauth.go index 5226fb02..ac2167ca 100644 --- a/framework/module/mxauth.go +++ b/framework/module/mxauth.go @@ -96,7 +96,7 @@ type ( // Modules implementing this interface should be registered with "mx_auth." // prefix in name. MXAuthPolicy interface { - Start(*MsgMetadata) DeliveryMXAuthPolicy + StartDelivery(*MsgMetadata) DeliveryMXAuthPolicy // Weight is an integer in range 0-1000 that represents relative // ordering of policy application. diff --git a/framework/module/partial_delivery.go b/framework/module/partial_delivery.go index beeb46e8..fa1f7471 100644 --- a/framework/module/partial_delivery.go +++ b/framework/module/partial_delivery.go @@ -45,7 +45,7 @@ type StatusCollector interface { } // PartialDelivery is an optional interface that may be implemented -// by the object returned by DeliveryTarget.Start. See PartialDelivery.BodyNonAtomic +// by the object returned by DeliveryTarget.StartDelivery. See PartialDelivery.BodyNonAtomic // documentation for details. type PartialDelivery interface { // BodyNonAtomic is similar to Body method of the regular Delivery interface diff --git a/framework/module/registry.go b/framework/module/registry.go index c52210f8..ec62edf7 100644 --- a/framework/module/registry.go +++ b/framework/module/registry.go @@ -19,85 +19,129 @@ along with this program. If not, see . package module import ( - "sync" + "errors" "github.com/foxcpp/maddy/framework/log" ) var ( - // NoRun makes sure modules do not start any bacground tests. - // - // If it set - modules should not perform any actual work and should stop - // once the configuration is read and verified to be correct. - // TODO: Replace it with separation of Init and Run at interface level. - NoRun = false - - modules = make(map[string]FuncNewModule) - endpoints = make(map[string]FuncNewEndpoint) - modulesLock sync.RWMutex + ErrInstanceNameDuplicate = errors.New("instance name already registered") + ErrInstanceUnknown = errors.New("no such instance registered") ) -// Register adds module factory function to global registry. -// -// name must be unique. Register will panic if module with specified name -// already exists in registry. -// -// You probably want to call this function from func init() of module package. -func Register(name string, factory FuncNewModule) { - modulesLock.Lock() - defer modulesLock.Unlock() +type registryEntry struct { + Mod Module + LazyInit func() error +} - if _, ok := modules[name]; ok { - panic("Register: module with specified name is already registered: " + name) - } +type Registry struct { + logger log.Logger + instances map[string]registryEntry + initialized map[string]struct{} + started map[string]struct{} + aliases map[string]string +} - modules[name] = factory +func NewRegistry(log log.Logger) *Registry { + return &Registry{ + logger: log, + instances: make(map[string]registryEntry), + initialized: make(map[string]struct{}), + started: make(map[string]struct{}), + aliases: make(map[string]string), + } } -// RegisterDeprecated adds module factory function to global registry. +// Register adds not-initialized (configured) module into registry. // -// It prints warning to the log about name being deprecated and suggests using -// a new name. -func RegisterDeprecated(name, newName string, factory FuncNewModule) { - Register(name, func(modName, instName string, aliases, inlineArgs []string) (Module, error) { - log.Printf("module initialized via deprecated name %s, %s should be used instead; deprecated name may be removed in the next version", name, newName) - return factory(modName, instName, aliases, inlineArgs) - }) +// lazyInit function will be called on first request to get the module from +// registry. +func (r *Registry) Register(mod Module, lazyInit func() error) error { + instName := mod.InstanceName() + if instName == "" { + panic("module with empty instance name cannot be added to the registry") + } + + _, ok := r.instances[instName] + if ok { + return ErrInstanceNameDuplicate + } + + r.instances[instName] = registryEntry{ + Mod: mod, + LazyInit: lazyInit, + } + return nil } -// Get returns module from global registry. -// -// This function does not return endpoint-type modules, use GetEndpoint for -// that. -// Nil is returned if no module with specified name is registered. -func Get(name string) FuncNewModule { - modulesLock.RLock() - defer modulesLock.RUnlock() - - return modules[name] +func (r *Registry) AddAlias(instanceName string, alias string) error { + if instanceName == "" { + panic("cannot add an alias for empty instance name") + } + if alias == "" { + panic("cannot add an empty alias") + } + _, ok := r.aliases[alias] + if ok { + return ErrInstanceNameDuplicate + } + _, ok = r.instances[instanceName] + if ok { + return ErrInstanceNameDuplicate + } + + r.aliases[alias] = instanceName + return nil } -// GetEndpoints returns an endpoint module from global registry. -// -// Nil is returned if no module with specified name is registered. -func GetEndpoint(name string) FuncNewEndpoint { - modulesLock.RLock() - defer modulesLock.RUnlock() +func (r *Registry) ensureInitialized(name string, entry *registryEntry) error { + _, ok := r.initialized[name] + if ok { + return nil + } + if entry.LazyInit == nil { + return nil + } + + r.logger.DebugMsg("module configure", + "mod_name", entry.Mod.Name(), "inst_name", entry.Mod.InstanceName()) + err := entry.LazyInit() + if err != nil { + return err + } + r.initialized[name] = struct{}{} - return endpoints[name] + return nil } -// RegisterEndpoint registers an endpoint module. -// -// See FuncNewEndpoint for information about -// differences of endpoint modules from regular modules. -func RegisterEndpoint(name string, factory FuncNewEndpoint) { - modulesLock.Lock() - defer modulesLock.Unlock() +func (r *Registry) Get(name string) (Module, error) { + if name == "" { + panic("cannot get module with empty name") + } + aliasedName := r.aliases[name] + if aliasedName != "" { + name = aliasedName + } + + mod, ok := r.instances[name] + if !ok { + return nil, ErrInstanceUnknown + } - if _, ok := endpoints[name]; ok { - panic("Register: module with specified name is already registered: " + name) + if err := r.ensureInitialized(name, &mod); err != nil { + return nil, err } - endpoints[name] = factory + return mod.Mod, nil +} + +func (r *Registry) NotInitialized() []Module { + notinit := make([]Module, 0, len(r.instances)-len(r.initialized)) + for name, mod := range r.instances { + if _, ok := r.initialized[name]; ok { + continue + } + notinit = append(notinit, mod.Mod) + } + return notinit } diff --git a/internal/auth/dovecot_sasl/dovecot_sasl.go b/internal/auth/dovecot_sasl/dovecot_sasl.go index c7dd6cc9..cc5dd370 100644 --- a/internal/auth/dovecot_sasl/dovecot_sasl.go +++ b/internal/auth/dovecot_sasl/dovecot_sasl.go @@ -44,20 +44,12 @@ type Auth struct { const modName = "dovecot_sasl" -func New(_, instName string, _, inlineArgs []string) (module.Module, error) { +func New(_, instName string) (module.Module, error) { a := &Auth{ instName: instName, log: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug}, } - switch len(inlineArgs) { - case 0: - case 1: - a.serverEndpoint = inlineArgs[0] - default: - return nil, fmt.Errorf("%s: one or none arguments needed", modName) - } - return a, nil } @@ -88,7 +80,15 @@ func (a *Auth) returnConn(cl *dovecotsasl.Client) { cl.Close() } -func (a *Auth) Init(cfg *config.Map) error { +func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { + switch len(inlineArgs) { + case 0: + case 1: + a.serverEndpoint = inlineArgs[0] + default: + return fmt.Errorf("%s: one or none arguments needed", modName) + } + cfg.String("endpoint", false, false, a.serverEndpoint, &a.serverEndpoint) if _, err := cfg.Process(); err != nil { return err diff --git a/internal/auth/external/externalauth.go b/internal/auth/external/externalauth.go index 59d71fb3..144864da 100644 --- a/internal/auth/external/externalauth.go +++ b/internal/auth/external/externalauth.go @@ -41,17 +41,13 @@ type ExternalAuth struct { Log log.Logger } -func NewExternalAuth(modName, instName string, _, inlineArgs []string) (module.Module, error) { +func NewExternalAuth(modName, instName string) (module.Module, error) { ea := &ExternalAuth{ modName: modName, instName: instName, Log: log.Logger{Name: modName}, } - if len(inlineArgs) != 0 { - return nil, errors.New("external: inline arguments are not used") - } - return ea, nil } @@ -63,7 +59,11 @@ func (ea *ExternalAuth) InstanceName() string { return ea.instName } -func (ea *ExternalAuth) Init(cfg *config.Map) error { +func (ea *ExternalAuth) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) != 0 { + return errors.New("external: inline arguments are not used") + } + cfg.Bool("debug", false, false, &ea.Log.Debug) cfg.Bool("perdomain", false, false, &ea.perDomain) cfg.StringList("domains", false, false, nil, &ea.domains) diff --git a/internal/auth/ldap/ldap.go b/internal/auth/ldap/ldap.go index 04cfe9f9..a2392d56 100644 --- a/internal/auth/ldap/ldap.go +++ b/internal/auth/ldap/ldap.go @@ -40,15 +40,16 @@ type Auth struct { log log.Logger } -func New(modName, instName string, _, inlineArgs []string) (module.Module, error) { +func New(modName, instName string) (module.Module, error) { return &Auth{ instName: instName, log: log.Logger{Name: modName}, - urls: inlineArgs, }, nil } -func (a *Auth) Init(cfg *config.Map) error { +func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { + a.urls = inlineArgs + a.dialer = &net.Dialer{} cfg.Bool("debug", true, false, &a.log.Debug) @@ -87,15 +88,6 @@ func (a *Auth) Init(cfg *config.Map) error { } } - if module.NoRun { - return nil - } - - var err error - a.conn, err = a.newConn() - if err != nil { - return fmt.Errorf("auth.ldap: %w", err) - } return nil } @@ -281,6 +273,22 @@ func (a *Auth) AuthPlain(username, password string) error { return nil } +func (a *Auth) Start() error { + var err error + a.conn, err = a.newConn() + if err != nil { + return fmt.Errorf("auth.ldap: %w", err) + } + return nil +} + +func (a *Auth) Stop() error { + a.connLock.Lock() + defer a.connLock.Unlock() + a.conn.Close() + return nil +} + func init() { var _ module.PlainAuth = &Auth{} var _ module.Table = &Auth{} diff --git a/internal/auth/netauth/netauth.go b/internal/auth/netauth/netauth.go index 3348eefe..62d6c6cc 100644 --- a/internal/auth/netauth/netauth.go +++ b/internal/auth/netauth/netauth.go @@ -31,15 +31,17 @@ type Auth struct { } // New creates a new instance of the NetAuth module. -func New(modName, instName string, _, inlineArgs []string) (module.Module, error) { +func New(modName, instName string) (module.Module, error) { return &Auth{ instName: instName, log: log.Logger{Name: modName}, }, nil } +func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs)> 0 { + return fmt.Errorf("%s: inline arguments are not used", modName) + } -// Init performs deferred initialization actions. -func (a *Auth) Init(cfg *config.Map) error { l := hclog.New(&hclog.LoggerOptions{Output: a.log}) n, err := netauth.NewWithLog(l) if err != nil { @@ -53,8 +55,6 @@ func (a *Auth) Init(cfg *config.Map) error { return err } - a.log.Debugln("Debug logging enabled") - a.log.Debugf("mustGroups status: %s", a.mustGroup) return nil } diff --git a/internal/auth/pam/module.go b/internal/auth/pam/module.go index c93269d3..da8e59f9 100644 --- a/internal/auth/pam/module.go +++ b/internal/auth/pam/module.go @@ -38,10 +38,7 @@ type Auth struct { Log log.Logger } -func New(modName, instName string, _, inlineArgs []string) (module.Module, error) { - if len(inlineArgs) != 0 { - return nil, errors.New("pam: inline arguments are not used") - } +func New(modName, instName string) (module.Module, error) { return &Auth{ instName: instName, Log: log.Logger{Name: modName}, @@ -56,7 +53,11 @@ func (a *Auth) InstanceName() string { return a.instName } -func (a *Auth) Init(cfg *config.Map) error { +func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) != 0 { + return errors.New("pam: inline arguments are not used") + } + cfg.Bool("debug", true, false, &a.Log.Debug) cfg.Bool("use_helper", false, false, &a.useHelper) if _, err := cfg.Process(); err != nil { diff --git a/internal/auth/pass_table/table.go b/internal/auth/pass_table/table.go index 4d0e3137..626913b4 100644 --- a/internal/auth/pass_table/table.go +++ b/internal/auth/pass_table/table.go @@ -31,24 +31,22 @@ import ( ) type Auth struct { - modName string - instName string - inlineArgs []string + modName string + instName string table module.Table } -func New(modName, instName string, _, inlineArgs []string) (module.Module, error) { +func New(modName, instName string) (module.Module, error) { return &Auth{ - modName: modName, - instName: instName, - inlineArgs: inlineArgs, + modName: modName, + instName: instName, }, nil } -func (a *Auth) Init(cfg *config.Map) error { - if len(a.inlineArgs) != 0 { - return modconfig.ModuleFromNode("table", a.inlineArgs, cfg.Block, cfg.Globals, &a.table) +func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) != 0 { + return modconfig.ModuleFromNode("table", inlineArgs, cfg.Block, cfg.Globals, &a.table) } cfg.Custom("table", false, true, nil, modconfig.TableDirective, &a.table) diff --git a/internal/auth/pass_table/table_test.go b/internal/auth/pass_table/table_test.go index 666e2b60..7f688cac 100644 --- a/internal/auth/pass_table/table_test.go +++ b/internal/auth/pass_table/table_test.go @@ -28,11 +28,11 @@ import ( func TestAuth_AuthPlain(t *testing.T) { addSHA256() - mod, err := New("pass_table", "", nil, []string{"dummy"}) + mod, err := New("pass_table", "") if err != nil { t.Fatal(err) } - err = mod.Init(config.NewMap(nil, config.Node{ + err = mod.Configure([]string{"dummy"}, config.NewMap(nil, config.Node{ Children: []config.Node{}, })) if err != nil { diff --git a/internal/auth/plain_separate/plain_separate.go b/internal/auth/plain_separate/plain_separate.go index 893d1f01..b3e06016 100644 --- a/internal/auth/plain_separate/plain_separate.go +++ b/internal/auth/plain_separate/plain_separate.go @@ -41,7 +41,7 @@ type Auth struct { Log log.Logger } -func NewAuth(modName, instName string, _, inlinargs []string) (module.Module, error) { +func NewAuth(modName, instName string) (module.Module, error) { a := &Auth{ modName: modName, instName: instName, @@ -49,10 +49,6 @@ func NewAuth(modName, instName string, _, inlinargs []string) (module.Module, er Log: log.Logger{Name: modName}, } - if len(inlinargs) != 0 { - return nil, errors.New("plain_separate: inline arguments are not used") - } - return a, nil } @@ -64,7 +60,11 @@ func (a *Auth) InstanceName() string { return a.instName } -func (a *Auth) Init(cfg *config.Map) error { +func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) != 0 { + return errors.New("plain_separate: inline arguments are not used") + } + cfg.Bool("debug", false, false, &a.Log.Debug) cfg.Callback("user", func(m *config.Map, node config.Node) error { var tbl module.Table diff --git a/internal/auth/shadow/module.go b/internal/auth/shadow/module.go index 92307bfc..290d826e 100644 --- a/internal/auth/shadow/module.go +++ b/internal/auth/shadow/module.go @@ -41,10 +41,7 @@ type Auth struct { Log log.Logger } -func New(modName, instName string, _, inlineArgs []string) (module.Module, error) { - if len(inlineArgs) != 0 { - return nil, errors.New("shadow: inline arguments are not used") - } +func New(modName, instName string) (module.Module, error) { return &Auth{ instName: instName, Log: log.Logger{Name: modName}, @@ -59,7 +56,11 @@ func (a *Auth) InstanceName() string { return a.instName } -func (a *Auth) Init(cfg *config.Map) error { +func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) != 0 { + return errors.New("shadow: inline arguments are not used") + } + cfg.Bool("debug", true, false, &a.Log.Debug) cfg.Bool("use_helper", false, false, &a.useHelper) if _, err := cfg.Process(); err != nil { diff --git a/internal/check/authorize_sender/authorize_sender.go b/internal/check/authorize_sender/authorize_sender.go index 6827add2..ab091c56 100644 --- a/internal/check/authorize_sender/authorize_sender.go +++ b/internal/check/authorize_sender/authorize_sender.go @@ -20,6 +20,7 @@ package authorize_sender import ( "context" + "fmt" "net/mail" "github.com/emersion/go-message/textproto" @@ -52,7 +53,7 @@ type Check struct { authNorm authz.NormalizeFunc } -func New(_, instName string, _, inlineArgs []string) (module.Module, error) { +func New(_, instName string) (module.Module, error) { return &Check{ instName: instName, }, nil @@ -66,7 +67,11 @@ func (c *Check) InstanceName() string { return c.instName } -func (c *Check) Init(cfg *config.Map) error { +func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) != 0 { + return fmt.Errorf("%s: inline arguments are not used", modName) + } + cfg.Bool("debug", true, false, &c.log.Debug) cfg.Bool("check_header", false, true, &c.checkHeader) diff --git a/internal/check/command/command.go b/internal/check/command/command.go index 60937607..512f147a 100644 --- a/internal/check/command/command.go +++ b/internal/check/command/command.go @@ -66,7 +66,7 @@ type Check struct { cmdArgs []string } -func New(modName, instName string, aliases, inlineArgs []string) (module.Module, error) { +func New(modName, instName string) (module.Module, error) { c := &Check{ instName: instName, actions: map[int]modconfig.FailAction{ @@ -79,13 +79,6 @@ func New(modName, instName string, aliases, inlineArgs []string) (module.Module, }, } - if len(inlineArgs) == 0 { - return nil, errors.New("command: at least one argument is required (command name)") - } - - c.cmd = inlineArgs[0] - c.cmdArgs = inlineArgs[1:] - return c, nil } @@ -97,7 +90,14 @@ func (c *Check) InstanceName() string { return c.instName } -func (c *Check) Init(cfg *config.Map) error { +func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) == 0 { + return errors.New("command: at least one argument is required (command name)") + } + + c.cmd = inlineArgs[0] + c.cmdArgs = inlineArgs[1:] + // Check whether the inline argument command is usable. if _, err := exec.LookPath(c.cmd); err != nil { return fmt.Errorf("command: %w", err) diff --git a/internal/check/dkim/dkim.go b/internal/check/dkim/dkim.go index 563fc5b1..b6921d40 100644 --- a/internal/check/dkim/dkim.go +++ b/internal/check/dkim/dkim.go @@ -52,10 +52,7 @@ type Check struct { resolver dns.Resolver } -func New(_, instName string, _, inlineArgs []string) (module.Module, error) { - if len(inlineArgs) != 0 { - return nil, errors.New("check.dkim: inline arguments are not used") - } +func New(_, instName string) (module.Module, error) { return &Check{ instName: instName, log: log.Logger{Name: "check.dkim"}, @@ -63,7 +60,11 @@ func New(_, instName string, _, inlineArgs []string) (module.Module, error) { }, nil } -func (c *Check) Init(cfg *config.Map) error { +func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) != 0 { + return errors.New("check.dkim: inline arguments are not used") + } + var requiredFields []string cfg.Bool("debug", true, false, &c.log.Debug) diff --git a/internal/check/dkim/dkim_test.go b/internal/check/dkim/dkim_test.go index 020d50f5..b4054124 100644 --- a/internal/check/dkim/dkim_test.go +++ b/internal/check/dkim/dkim_test.go @@ -84,7 +84,7 @@ Joe. func testCheck(t *testing.T, zones map[string]mockdns.Zone, cfg []config.Node) *Check { t.Helper() - mod, err := New("check.dkim", "", nil, nil) + mod, err := New("check.dkim", "") if err != nil { t.Fatal(err) } @@ -92,7 +92,7 @@ func testCheck(t *testing.T, zones map[string]mockdns.Zone, cfg []config.Node) * check.resolver = &mockdns.Resolver{Zones: zones} check.log = testutils.Logger(t, mod.Name()) - if err := check.Init(config.NewMap(nil, config.Node{Children: cfg})); err != nil { + if err := check.Configure(nil, config.NewMap(nil, config.Node{Children: cfg})); err != nil { t.Fatal(err) } diff --git a/internal/check/dnsbl/dnsbl.go b/internal/check/dnsbl/dnsbl.go index 2c91c838..79738720 100644 --- a/internal/check/dnsbl/dnsbl.go +++ b/internal/check/dnsbl/dnsbl.go @@ -58,7 +58,6 @@ var defaultBL = List{ type DNSBL struct { instName string checkEarly bool - inlineBls []string bls []List quarantineThres int @@ -68,10 +67,9 @@ type DNSBL struct { log log.Logger } -func NewDNSBL(_, instName string, _, inlineArgs []string) (module.Module, error) { +func New(_, instName string) (module.Module, error) { return &DNSBL{ - instName: instName, - inlineBls: inlineArgs, + instName: instName, resolver: dns.DefaultResolver(), log: log.Logger{Name: "dnsbl"}, @@ -86,7 +84,7 @@ func (bl *DNSBL) InstanceName() string { return bl.instName } -func (bl *DNSBL) Init(cfg *config.Map) error { +func (bl *DNSBL) Configure(inlineArgs []string, cfg *config.Map) error { cfg.Bool("debug", false, false, &bl.log.Debug) cfg.Bool("check_early", false, false, &bl.checkEarly) cfg.Int("quarantine_threshold", false, false, 1, &bl.quarantineThres) @@ -97,7 +95,7 @@ func (bl *DNSBL) Init(cfg *config.Map) error { return err } - for _, inlineBl := range bl.inlineBls { + for _, inlineBl := range inlineArgs { cfg := defaultBL cfg.Zone = inlineBl go bl.testList(cfg) @@ -431,5 +429,5 @@ func (*state) Close() error { } func init() { - module.Register("check.dnsbl", NewDNSBL) + module.Register("check.dnsbl", New) } diff --git a/internal/check/milter/milter.go b/internal/check/milter/milter.go index 37704d43..38f81930 100644 --- a/internal/check/milter/milter.go +++ b/internal/check/milter/milter.go @@ -46,18 +46,12 @@ type Check struct { log log.Logger } -func New(_, instName string, _, inlineArgs []string) (module.Module, error) { +func New(_, instName string) (module.Module, error) { c := &Check{ instName: instName, log: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug}, } - switch len(inlineArgs) { - case 1: - c.milterUrl = inlineArgs[0] - case 0: - default: - return nil, fmt.Errorf("%s: unexpected amount of arguments, want 1 or 0", modName) - } + return c, nil } @@ -69,7 +63,15 @@ func (c *Check) InstanceName() string { return c.instName } -func (c *Check) Init(cfg *config.Map) error { +func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { + switch len(inlineArgs) { + case 1: + c.milterUrl = inlineArgs[0] + case 0: + default: + return fmt.Errorf("%s: unexpected amount of arguments, want 1 or 0", modName) + } + cfg.String("endpoint", false, false, c.milterUrl, &c.milterUrl) cfg.Bool("fail_open", false, false, &c.failOpen) if _, err := cfg.Process(); err != nil { diff --git a/internal/check/milter/milter_test.go b/internal/check/milter/milter_test.go index 97978517..50e3954e 100644 --- a/internal/check/milter/milter_test.go +++ b/internal/check/milter/milter_test.go @@ -38,7 +38,7 @@ func TestAcceptValidEndpoints(t *testing.T) { } { c := &Check{milterUrl: endpoint} - err := c.Init(&config.Map{}) + err := c.Configure(nil, &config.Map{}) if err != nil { t.Errorf("Unexpected failure for %s: %v", endpoint, err) return @@ -52,7 +52,7 @@ func TestRejectInvalidEndpoints(t *testing.T) { "tls:0.0.0.0:10025", } { c := &Check{milterUrl: endpoint} - err := c.Init(&config.Map{}) + err := c.Configure(nil, &config.Map{}) if err == nil { t.Errorf("Accepted invalid endpoint: %s", endpoint) return diff --git a/internal/check/rspamd/rspamd.go b/internal/check/rspamd/rspamd.go index e6afad68..559c94b6 100644 --- a/internal/check/rspamd/rspamd.go +++ b/internal/check/rspamd/rspamd.go @@ -61,22 +61,13 @@ type Check struct { client *http.Client } -func New(modName, instName string, _, inlineArgs []string) (module.Module, error) { +func New(modName, instName string) (module.Module, error) { c := &Check{ instName: instName, client: http.DefaultClient, log: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug}, } - switch len(inlineArgs) { - case 1: - c.apiPath = inlineArgs[0] - case 0: - c.apiPath = "http://127.0.0.1:11333" - default: - return nil, fmt.Errorf("%s: unexpected amount of inline arguments", modName) - } - return c, nil } @@ -88,7 +79,16 @@ func (c *Check) InstanceName() string { return c.instName } -func (c *Check) Init(cfg *config.Map) error { +func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { + switch len(inlineArgs) { + case 1: + c.apiPath = inlineArgs[0] + case 0: + c.apiPath = "http://127.0.0.1:11333" + default: + return fmt.Errorf("%s: unexpected amount of inline arguments", modName) + } + var ( tlsConfig tls.Config flags []string diff --git a/internal/check/spf/spf.go b/internal/check/spf/spf.go index c94dd915..a799cd85 100644 --- a/internal/check/spf/spf.go +++ b/internal/check/spf/spf.go @@ -60,7 +60,7 @@ type Check struct { resolver dns.Resolver } -func New(_, instName string, _, _ []string) (module.Module, error) { +func New(_, instName string) (module.Module, error) { return &Check{ instName: instName, log: log.Logger{Name: modName}, @@ -76,7 +76,7 @@ func (c *Check) InstanceName() string { return c.instName } -func (c *Check) Init(cfg *config.Map) error { +func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { cfg.Bool("debug", true, false, &c.log.Debug) cfg.Bool("enforce_early", true, false, &c.enforceEarly) cfg.Custom("none_action", false, false, diff --git a/internal/check/stateless_check.go b/internal/check/stateless_check.go index 4c5d2d56..729c106b 100644 --- a/internal/check/stateless_check.go +++ b/internal/check/stateless_check.go @@ -152,7 +152,11 @@ func (c *statelessCheck) CheckStateForMsg(ctx context.Context, msgMeta *module.M }, nil } -func (c *statelessCheck) Init(cfg *config.Map) error { +func (c *statelessCheck) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) != 0 { + return fmt.Errorf("%s: inline arguments are not used", c.modName) + } + cfg.Bool("debug", true, false, &c.logger.Debug) cfg.Custom("fail_action", false, false, func() (interface{}, error) { @@ -181,10 +185,7 @@ func (c *statelessCheck) InstanceName() string { // code doesn't need to know about it. It should assume that it is always "Reject" and hence it should // populate Reason field of the result object with the relevant error description. func RegisterStatelessCheck(name string, defaultFailAction modconfig.FailAction, connCheck FuncConnCheck, senderCheck FuncSenderCheck, rcptCheck FuncRcptCheck, bodyCheck FuncBodyCheck) { - module.Register(name, func(modName, instName string, aliases, inlineArgs []string) (module.Module, error) { - if len(inlineArgs) != 0 { - return nil, fmt.Errorf("%s: inline arguments are not used", modName) - } + module.Register(name, func(modName, instName string) (module.Module, error) { return &statelessCheck{ modName: modName, instName: instName, diff --git a/internal/cli/ctl/moduleinit.go b/internal/cli/ctl/moduleinit.go index 23e79e5c..ef58d7b4 100644 --- a/internal/cli/ctl/moduleinit.go +++ b/internal/cli/ctl/moduleinit.go @@ -25,9 +25,7 @@ import ( "os" "github.com/foxcpp/maddy" - parser "github.com/foxcpp/maddy/framework/cfgparser" - "github.com/foxcpp/maddy/framework/config" - "github.com/foxcpp/maddy/framework/hooks" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/updatepipe" "github.com/urfave/cli/v2" @@ -39,71 +37,61 @@ func closeIfNeeded(i interface{}) { } } -func getCfgBlockModule(ctx *cli.Context) (map[string]interface{}, *maddy.ModInfo, error) { +func getCfgBlockModule(ctx *cli.Context) (*container.C, module.Module, error) { cfgPath := ctx.String("config") if cfgPath == "" { return nil, nil, cli.Exit("Error: config is required", 2) } - cfgFile, err := os.Open(cfgPath) + + c := container.New() + + cfg, err := maddy.ReadConfig(cfgPath) if err != nil { return nil, nil, cli.Exit(fmt.Sprintf("Error: failed to open config: %v", err), 2) } - defer cfgFile.Close() - cfgNodes, err := parser.Read(cfgFile, cfgFile.Name()) - if err != nil { - return nil, nil, cli.Exit(fmt.Sprintf("Error: failed to parse config: %v", err), 2) - } - globals, cfgNodes, err := maddy.ReadGlobals(cfgNodes) + globals, cfgNodes, err := maddy.ReadGlobals(c, cfg) if err != nil { return nil, nil, err } - if err := maddy.InitDirs(); err != nil { + if err := maddy.InitDirs(c); err != nil { return nil, nil, err } - module.NoRun = true - _, mods, err := maddy.RegisterModules(globals, cfgNodes) + err = maddy.RegisterModules(c, globals, cfgNodes) if err != nil { return nil, nil, err } - defer hooks.RunHooks(hooks.EventShutdown) cfgBlock := ctx.String("cfg-block") if cfgBlock == "" { return nil, nil, cli.Exit("Error: cfg-block is required", 2) } - var mod maddy.ModInfo - for _, m := range mods { - if m.Instance.InstanceName() == cfgBlock { - mod = m - break + + mod, err := c.Modules.Get(cfgBlock) + if err != nil { + if errors.Is(err, module.ErrInstanceUnknown) { + return nil, nil, cli.Exit(fmt.Sprintf("Error: unknown configuration block: %s", cfgBlock), 2) } - } - if mod.Instance == nil { - return nil, nil, cli.Exit(fmt.Sprintf("Error: unknown configuration block: %s", cfgBlock), 2) + return nil, nil, err } - return globals, &mod, nil + return c, mod, nil } func openStorage(ctx *cli.Context) (module.Storage, error) { - globals, mod, err := getCfgBlockModule(ctx) + _, mod, err := getCfgBlockModule(ctx) if err != nil { return nil, err } - storage, ok := mod.Instance.(module.Storage) + storage, ok := mod.(module.Storage) if !ok { return nil, cli.Exit(fmt.Sprintf("Error: configuration block %s is not an IMAP storage", ctx.String("cfg-block")), 2) } - if err := mod.Instance.Init(config.NewMap(globals, mod.Cfg)); err != nil { - return nil, fmt.Errorf("Error: module initialization failed: %w", err) - } - - if updStore, ok := mod.Instance.(updatepipe.Backend); ok { + if updStore, ok := mod.(updatepipe.Backend); ok { if err := updStore.EnableUpdatePipe(updatepipe.ModePush); err != nil && !errors.Is(err, os.ErrNotExist) { fmt.Fprintf(os.Stderr, "Failed to initialize update pipe, do not remove messages from mailboxes open by clients: %v\n", err) } @@ -115,19 +103,15 @@ func openStorage(ctx *cli.Context) (module.Storage, error) { } func openUserDB(ctx *cli.Context) (module.PlainUserDB, error) { - globals, mod, err := getCfgBlockModule(ctx) + _, mod, err := getCfgBlockModule(ctx) if err != nil { return nil, err } - userDB, ok := mod.Instance.(module.PlainUserDB) + userDB, ok := mod.(module.PlainUserDB) if !ok { return nil, cli.Exit(fmt.Sprintf("Error: configuration block %s is not a local credentials store", ctx.String("cfg-block")), 2) } - if err := mod.Instance.Init(config.NewMap(globals, mod.Cfg)); err != nil { - return nil, fmt.Errorf("Error: module initialization failed: %w", err) - } - return userDB, nil } diff --git a/internal/endpoint/dovecot_sasld/dovecot_sasl.go b/internal/endpoint/dovecot_sasld/dovecot_sasl.go index 77eedd0e..b1bcd159 100644 --- a/internal/endpoint/dovecot_sasld/dovecot_sasl.go +++ b/internal/endpoint/dovecot_sasld/dovecot_sasl.go @@ -42,12 +42,13 @@ type Endpoint struct { log log.Logger saslAuth auth.SASLAuth + endpoints []config.Endpoint listenersWg sync.WaitGroup srv *dovecotsasl.Server } -func New(_ string, addrs []string) (module.Module, error) { +func New(_ string, addrs []string) (module.LifetimeModule, error) { return &Endpoint{ addrs: addrs, saslAuth: auth.SASLAuth{ @@ -65,7 +66,7 @@ func (endp *Endpoint) InstanceName() string { return modName } -func (endp *Endpoint) Init(cfg *config.Map) error { +func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { cfg.Callback("auth", func(m *config.Map, node config.Node) error { return endp.saslAuth.AddProvider(m, node) }) @@ -97,12 +98,20 @@ func (endp *Endpoint) Init(cfg *config.Map) error { return fmt.Errorf("%s: %v", modName, err) } - l, err := net.Listen(parsed.Network(), parsed.Address()) + endp.endpoints = append(endp.endpoints, parsed) + } + + return nil +} + +func (endp *Endpoint) Start() error { + for _, addr := range endp.endpoints { + l, err := net.Listen(addr.Network(), addr.Address()) if err != nil { return fmt.Errorf("%s: %v", modName, err) } - endp.log.Printf("listening on %v", l.Addr()) + endp.log.Printf("listening on %v", l.Addr()) endp.listenersWg.Add(1) go func() { defer endp.listenersWg.Done() @@ -113,12 +122,13 @@ func (endp *Endpoint) Init(cfg *config.Map) error { } }() } - return nil } -func (endp *Endpoint) Close() error { - return endp.srv.Close() +func (endp *Endpoint) Stop() error { + endp.srv.Close() + endp.listenersWg.Wait() + return nil } func init() { diff --git a/internal/endpoint/imap/imap.go b/internal/endpoint/imap/imap.go index 191d93d2..765c429f 100644 --- a/internal/endpoint/imap/imap.go +++ b/internal/endpoint/imap/imap.go @@ -51,11 +51,12 @@ import ( type Endpoint struct { addrs []string serv *imapserver.Server - listeners []net.Listener proxyProtocol *proxy_protocol.ProxyProtocol Store module.Storage + tlsConfig *tls.Config - tlsConfig *tls.Config + endpoints []config.Endpoint + listeners []net.Listener listenersWg sync.WaitGroup saslAuth auth.SASLAuth @@ -66,7 +67,7 @@ type Endpoint struct { Log log.Logger } -func New(modName string, addrs []string) (module.Module, error) { +func New(modName string, addrs []string) (module.LifetimeModule, error) { endp := &Endpoint{ addrs: addrs, Log: log.Logger{Name: modName}, @@ -78,7 +79,7 @@ func New(modName string, addrs []string) (module.Module, error) { return endp, nil } -func (endp *Endpoint) Init(cfg *config.Map) error { +func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { var ( insecureAuth bool ioDebug bool @@ -106,20 +107,18 @@ func (endp *Endpoint) Init(cfg *config.Map) error { return err } - if updBe, ok := endp.Store.(updatepipe.Backend); ok { - if err := updBe.EnableUpdatePipe(updatepipe.ModeReplicate); err != nil { - endp.Log.Error("failed to initialize updates pipe", err) - } - } - addresses := make([]config.Endpoint, 0, len(endp.addrs)) for _, addr := range endp.addrs { saddr, err := config.ParseEndpoint(addr) if err != nil { return fmt.Errorf("imap: invalid address: %s", addr) } + if saddr.IsTLS() && endp.tlsConfig == nil { + return errors.New("imap: can't bind on IMAPS endpoint without TLS configuration") + } addresses = append(addresses, saddr) } + endp.endpoints = addresses endp.serv = imapserver.New(endp) endp.serv.AllowInsecureAuth = insecureAuth @@ -146,7 +145,29 @@ func (endp *Endpoint) Init(cfg *config.Map) error { }) } - return endp.setupListeners(addresses) + if endp.serv.AllowInsecureAuth { + endp.Log.Println("authentication over unencrypted connections is allowed, this is insecure configuration and should be used only for testing!") + } + if endp.serv.TLSConfig == nil { + endp.Log.Println("TLS is disabled, this is insecure configuration and should be used only for testing!") + endp.serv.AllowInsecureAuth = true + } + + return nil +} + +func (endp *Endpoint) Start() error { + if updBe, ok := endp.Store.(updatepipe.Backend); ok { + if err := updBe.EnableUpdatePipe(updatepipe.ModeReplicate); err != nil { + endp.Log.Error("failed to initialize updates pipe", err) + } + } + + if err := endp.setupListeners(endp.endpoints); err != nil { + endp.Stop() + return err + } + return nil } func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { @@ -171,24 +192,15 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { } endp.listeners = append(endp.listeners, l) - endp.listenersWg.Add(1) go func() { + defer endp.listenersWg.Done() if err := endp.serv.Serve(l); err != nil && !strings.HasSuffix(err.Error(), "use of closed network connection") { endp.Log.Printf("imap: failed to serve %s: %s", addr, err) } - endp.listenersWg.Done() }() } - if endp.serv.AllowInsecureAuth { - endp.Log.Println("authentication over unencrypted connections is allowed, this is insecure configuration and should be used only for testing!") - } - if endp.serv.TLSConfig == nil { - endp.Log.Println("TLS is disabled, this is insecure configuration and should be used only for testing!") - endp.serv.AllowInsecureAuth = true - } - return nil } @@ -200,7 +212,7 @@ func (endp *Endpoint) InstanceName() string { return "imap" } -func (endp *Endpoint) Close() error { +func (endp *Endpoint) Stop() error { for _, l := range endp.listeners { l.Close() } diff --git a/internal/endpoint/openmetrics/om.go b/internal/endpoint/openmetrics/om.go index 874a333d..136d4af5 100644 --- a/internal/endpoint/openmetrics/om.go +++ b/internal/endpoint/openmetrics/om.go @@ -34,22 +34,23 @@ import ( const modName = "openmetrics" type Endpoint struct { - addrs []string - logger log.Logger + addrs []string + endpoints []config.Endpoint + logger log.Logger listenersWg sync.WaitGroup serv http.Server mux *http.ServeMux } -func New(_ string, args []string) (module.Module, error) { +func New(_ string, args []string) (module.LifetimeModule, error) { return &Endpoint{ addrs: args, logger: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug}, }, nil } -func (e *Endpoint) Init(cfg *config.Map) error { +func (e *Endpoint) Configure(inlineArgs []string, cfg *config.Map) error { cfg.Bool("debug", false, false, &e.logger.Debug) if _, err := cfg.Process(); err != nil { return err @@ -67,8 +68,24 @@ func (e *Endpoint) Init(cfg *config.Map) error { if endp.IsTLS() { return fmt.Errorf("%s: TLS is not supported yet", modName) } + } + + return nil +} + +func (e *Endpoint) Name() string { + return modName +} + +func (e *Endpoint) InstanceName() string { + return "" +} + +func (e *Endpoint) Start() error { + for _, endp := range e.endpoints { l, err := net.Listen(endp.Network(), endp.Address()) if err != nil { + e.Stop() return fmt.Errorf("%s: %v", modName, err) } @@ -77,24 +94,15 @@ func (e *Endpoint) Init(cfg *config.Map) error { e.logger.Println("listening on", endp.String()) err := e.serv.Serve(l) if err != nil && !errors.Is(err, http.ErrServerClosed) { - e.logger.Error("serve failed", err, "endpoint", a) + e.logger.Error("serve failed", err, "endpoint", endp) } e.listenersWg.Done() }() } - return nil } -func (e *Endpoint) Name() string { - return modName -} - -func (e *Endpoint) InstanceName() string { - return "" -} - -func (e *Endpoint) Close() error { +func (e *Endpoint) Stop() error { if err := e.serv.Close(); err != nil { return err } diff --git a/internal/endpoint/smtp/session.go b/internal/endpoint/smtp/session.go index 0eb6427f..ed842f98 100644 --- a/internal/endpoint/smtp/session.go +++ b/internal/endpoint/smtp/session.go @@ -274,7 +274,7 @@ func (s *Session) startDelivery(ctx context.Context, from string, opts smtp.Mail mailCtx, mailTask := trace.NewTask(s.msgCtx, "MAIL FROM") defer mailTask.End() - delivery, err := s.endp.pipeline.Start(mailCtx, msgMeta, cleanFrom) + delivery, err := s.endp.pipeline.StartDelivery(mailCtx, msgMeta, cleanFrom) if err != nil { s.msgCtx = nil s.msgTask.End() diff --git a/internal/endpoint/smtp/smtp.go b/internal/endpoint/smtp/smtp.go index 004d92e5..7e5f3ab0 100644 --- a/internal/endpoint/smtp/smtp.go +++ b/internal/endpoint/smtp/smtp.go @@ -54,6 +54,7 @@ type Endpoint struct { serv *smtp.Server name string addrs []string + endpoints []config.Endpoint listeners []net.Listener proxyProtocol *proxy_protocol.ProxyProtocol pipeline *msgpipeline.MsgPipeline @@ -88,7 +89,7 @@ func (endp *Endpoint) InstanceName() string { return endp.name } -func New(modName string, addrs []string) (module.Module, error) { +func New(modName string, addrs []string) (module.LifetimeModule, error) { endp := &Endpoint{ name: modName, addrs: addrs, @@ -104,7 +105,7 @@ func New(modName string, addrs []string) (module.Module, error) { return endp, nil } -func (endp *Endpoint) Init(cfg *config.Map) error { +func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { endp.serv = smtp.NewServer(endp) endp.serv.ErrorLog = endp.Log endp.serv.LMTP = endp.lmtp @@ -123,13 +124,7 @@ func (endp *Endpoint) Init(cfg *config.Map) error { addresses = append(addresses, saddr) } - - if err := endp.setupListeners(addresses); err != nil { - for _, l := range endp.listeners { - l.Close() - } - return err - } + endp.endpoints = addresses allLocal := true for _, addr := range addresses { @@ -321,6 +316,14 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { return nil } +func (endp *Endpoint) Start() error { + if err := endp.setupListeners(endp.endpoints); err != nil { + endp.Stop() + return err + } + return nil +} + func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { for _, addr := range addresses { var l net.Listener @@ -419,7 +422,7 @@ func (endp *Endpoint) ConnectionCount() int { return int(endp.sessionCnt.Load()) } -func (endp *Endpoint) Close() error { +func (endp *Endpoint) Stop() error { endp.serv.Close() endp.listenersWg.Wait() return nil diff --git a/internal/endpoint/smtp/smtp_test.go b/internal/endpoint/smtp/smtp_test.go index fa46c279..d6de22dc 100644 --- a/internal/endpoint/smtp/smtp_test.go +++ b/internal/endpoint/smtp/smtp_test.go @@ -89,7 +89,7 @@ func testEndpoint(t *testing.T, modName string, authMod module.PlainAuth, tgt mo }) } - err = endp.Init(config.NewMap(nil, config.Node{ + err = endp.Configure(nil, config.NewMap(nil, config.Node{ Children: cfg, })) if err != nil { @@ -107,6 +107,10 @@ func testEndpoint(t *testing.T, modName string, authMod module.PlainAuth, tgt mo endp.pipeline.FirstPipeline = true endp.pipeline.Log = testutils.Logger(t, "smtp/pipeline") + if err := endp.Start(); err != nil { + t.Fatal(err) + } + return endp } @@ -142,7 +146,7 @@ func submitMsgOpts(t *testing.T, cl *smtp.Client, from string, rcpts []string, o func TestSMTPDelivery(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Close() + defer endp.Stop() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { @@ -180,7 +184,7 @@ func TestSMTPDelivery(t *testing.T) { func TestSMTPDelivery_rDNSError(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Close() + defer endp.Stop() endp.resolver.(*mockdns.Resolver).Zones["1.0.0.127.in-addr.arpa."] = mockdns.Zone{ Err: &net.DNSError{ @@ -224,7 +228,7 @@ func TestSMTPDelivery_EarlyCheck_Fail(t *testing.T) { }, }, }, nil) - defer endp.Close() + defer endp.Stop() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { @@ -264,7 +268,7 @@ func TestSMTPDeliver_CheckError(t *testing.T) { }, }, nil) endp.deferServerReject = false - defer endp.Close() + defer endp.Stop() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { @@ -303,7 +307,7 @@ func TestSMTPDeliver_CheckError_Deferred(t *testing.T) { }, }, nil) endp.deferServerReject = true - defer endp.Close() + defer endp.Stop() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { @@ -342,7 +346,7 @@ func TestSMTPDeliver_CheckError_Deferred(t *testing.T) { func TestSMTPDelivery_Multi(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Close() + defer endp.Stop() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { @@ -380,7 +384,7 @@ func TestSMTPDelivery_Multi(t *testing.T) { func TestSMTPDelivery_AbortData(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Close() + defer endp.Stop() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { @@ -418,7 +422,7 @@ func TestSMTPDelivery_AbortData(t *testing.T) { func TestSMTPDelivery_EmptyMessage(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Close() + defer endp.Stop() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { @@ -457,7 +461,7 @@ func TestSMTPDelivery_EmptyMessage(t *testing.T) { func TestSMTPDelivery_AbortLogout(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Close() + defer endp.Stop() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { @@ -488,7 +492,7 @@ func TestSMTPDelivery_AbortLogout(t *testing.T) { func TestSMTPDelivery_Reset(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Close() + defer endp.Stop() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { @@ -523,7 +527,7 @@ func TestSMTPDelivery_Reset(t *testing.T) { func TestSMTPDelivery_SubmissionAuthRequire(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "submission", &module.Dummy{}, &tgt, nil, nil) - defer endp.Close() + defer endp.Stop() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { @@ -539,7 +543,7 @@ func TestSMTPDelivery_SubmissionAuthRequire(t *testing.T) { func TestSMTPDelivery_SubmissionAuthOK(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "submission", &module.Dummy{}, &tgt, nil, nil) - defer endp.Close() + defer endp.Stop() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { diff --git a/internal/endpoint/smtp/smtputf8_test.go b/internal/endpoint/smtp/smtputf8_test.go index b3f57015..2f157aba 100644 --- a/internal/endpoint/smtp/smtputf8_test.go +++ b/internal/endpoint/smtp/smtputf8_test.go @@ -43,7 +43,7 @@ func TestSMTPUTF8_MangleStatusMessage(t *testing.T) { }, }, nil) endp.deferServerReject = false - defer endp.Close() + defer endp.Stop() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) @@ -73,7 +73,7 @@ func TestSMTP_RejectNonASCIIFrom(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) endp.deferServerReject = false - defer endp.Close() + defer endp.Stop() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) @@ -100,7 +100,7 @@ func TestSMTPUTF8_NormalizeCaseFoldFrom(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) endp.deferServerReject = false - defer endp.Close() + defer endp.Stop() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) @@ -127,7 +127,7 @@ func TestSMTP_RejectNonASCIIRcpt(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) endp.deferServerReject = false - defer endp.Close() + defer endp.Stop() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) @@ -154,7 +154,7 @@ func TestSMTPUTF8_NormalizeCaseFoldRcpt(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) endp.deferServerReject = false - defer endp.Close() + defer endp.Stop() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) @@ -191,7 +191,7 @@ func TestSMTPUTF8_NoMangleStatusMessage(t *testing.T) { }, }, nil) endp.deferServerReject = false - defer endp.Close() + defer endp.Stop() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) @@ -222,7 +222,7 @@ func TestSMTPUTF8_NoMangleStatusMessage(t *testing.T) { func TestSMTPUTF8_Received_EHLO_ALabel(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Close() + defer endp.Stop() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) @@ -256,7 +256,7 @@ func TestSMTPUTF8_Received_EHLO_ALabel(t *testing.T) { func TestSMTPUTF8_Received_rDNS_ALabel(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Close() + defer endp.Stop() defer testutils.WaitForConnsClose(t, endp.serv) endp.resolver.(*mockdns.Resolver).Zones["1.0.0.127.in-addr.arpa."] = mockdns.Zone{ @@ -290,7 +290,7 @@ func TestSMTPUTF8_Received_rDNS_ALabel(t *testing.T) { func TestSMTPUTF8_Received_rDNS_ULabel(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Close() + defer endp.Stop() defer testutils.WaitForConnsClose(t, endp.serv) endp.resolver.(*mockdns.Resolver).Zones["1.0.0.127.in-addr.arpa."] = mockdns.Zone{ @@ -326,7 +326,7 @@ func TestSMTPUTF8_Received_rDNS_ULabel(t *testing.T) { func TestSMTPUTF8_Received_EHLO_ULabel(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Close() + defer endp.Stop() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) diff --git a/internal/endpoint/smtp/submission_test.go b/internal/endpoint/smtp/submission_test.go index f257364c..beb4cf5e 100644 --- a/internal/endpoint/smtp/submission_test.go +++ b/internal/endpoint/smtp/submission_test.go @@ -56,7 +56,7 @@ func TestSubmissionPrepare(t *testing.T) { cl, _ := smtp.Dial("127.0.0.1:" + testPort) cl.Close() - endp.Close() + endp.Stop() }() session, err := endp.NewSession(nil) diff --git a/internal/imap_filter/command/command.go b/internal/imap_filter/command/command.go index 58146dca..32f05c03 100644 --- a/internal/imap_filter/command/command.go +++ b/internal/imap_filter/command/command.go @@ -61,19 +61,12 @@ func (c *Check) IMAPFilter(accountName string, rcptTo string, msgMeta *module.Ms return c.run(cmd, args, io.MultiReader(bytes.NewReader(buf.Bytes()), bR)) } -func New(_, instName string, _, inlineArgs []string) (module.Module, error) { +func New(_, instName string) (module.Module, error) { c := &Check{ instName: instName, log: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug}, } - if len(inlineArgs) == 0 { - return nil, errors.New("command: at least one argument is required (command name)") - } - - c.cmd = inlineArgs[0] - c.cmdArgs = inlineArgs[1:] - return c, nil } @@ -85,7 +78,14 @@ func (c *Check) InstanceName() string { return c.instName } -func (c *Check) Init(cfg *config.Map) error { +func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) == 0 { + return errors.New("command: at least one argument is required (command name)") + } + + c.cmd = inlineArgs[0] + c.cmdArgs = inlineArgs[1:] + // Check whether the inline argument command is usable. if _, err := exec.LookPath(c.cmd); err != nil { return fmt.Errorf("command: %w", err) diff --git a/internal/imap_filter/group.go b/internal/imap_filter/group.go index c1c5ecc8..f86ea866 100644 --- a/internal/imap_filter/group.go +++ b/internal/imap_filter/group.go @@ -37,7 +37,7 @@ type Group struct { log log.Logger } -func NewGroup(_, instName string, _, _ []string) (module.Module, error) { +func NewGroup(_, instName string) (module.Module, error) { return &Group{ instName: instName, log: log.Logger{Name: "imap_filters", Debug: log.DefaultLogger.Debug}, @@ -66,7 +66,7 @@ func (g *Group) IMAPFilter(accountName string, rcptTo string, meta *module.MsgMe return finalFolder, finalFlags, nil } -func (g *Group) Init(cfg *config.Map) error { +func (g *Group) Configure(inlineArgs []string, cfg *config.Map) error { for _, node := range cfg.Block.Children { mod, err := modconfig.IMAPFilter(cfg.Globals, append([]string{node.Name}, node.Args...), node) if err != nil { diff --git a/internal/libdns/acmedns.go b/internal/libdns/acmedns.go index cb657ee2..d088811d 100644 --- a/internal/libdns/acmedns.go +++ b/internal/libdns/acmedns.go @@ -10,7 +10,7 @@ import ( ) func init() { - module.Register("libdns.acmedns", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.acmedns", func(modName, instName string) (module.Module, error) { p := acmedns.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/alidns.go b/internal/libdns/alidns.go index cb980beb..9e32399c 100644 --- a/internal/libdns/alidns.go +++ b/internal/libdns/alidns.go @@ -10,7 +10,7 @@ import ( ) func init() { - module.Register("libdns.alidns", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.alidns", func(modName, instName string) (module.Module, error) { p := alidns.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/cloudflare.go b/internal/libdns/cloudflare.go index 30319538..b69ec75d 100644 --- a/internal/libdns/cloudflare.go +++ b/internal/libdns/cloudflare.go @@ -10,7 +10,7 @@ import ( ) func init() { - module.Register("libdns.cloudflare", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.cloudflare", func(modName, instName string) (module.Module, error) { p := cloudflare.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/digitalocean.go b/internal/libdns/digitalocean.go index 98b77b07..20369c9a 100644 --- a/internal/libdns/digitalocean.go +++ b/internal/libdns/digitalocean.go @@ -10,7 +10,7 @@ import ( ) func init() { - module.Register("libdns.digitalocean", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.digitalocean", func(modName, instName string) (module.Module, error) { p := digitalocean.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/gandi.go b/internal/libdns/gandi.go index 62c7c2d0..59a493d5 100644 --- a/internal/libdns/gandi.go +++ b/internal/libdns/gandi.go @@ -13,7 +13,7 @@ import ( ) func init() { - module.Register("libdns.gandi", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.gandi", func(modName, instName string) (module.Module, error) { p := gandi.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/googleclouddns.go b/internal/libdns/googleclouddns.go index b59c0562..a9bc493e 100644 --- a/internal/libdns/googleclouddns.go +++ b/internal/libdns/googleclouddns.go @@ -10,7 +10,7 @@ import ( ) func init() { - module.Register("libdns.googleclouddns", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.googleclouddns", func(modName, instName string) (module.Module, error) { p := googleclouddns.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/hetzner.go b/internal/libdns/hetzner.go index b360641f..06620c46 100644 --- a/internal/libdns/hetzner.go +++ b/internal/libdns/hetzner.go @@ -10,7 +10,7 @@ import ( ) func init() { - module.Register("libdns.hetzner", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.hetzner", func(modName, instName string) (module.Module, error) { p := hetzner.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/leaseweb.go b/internal/libdns/leaseweb.go index 23af12d7..888d0870 100644 --- a/internal/libdns/leaseweb.go +++ b/internal/libdns/leaseweb.go @@ -10,7 +10,7 @@ import ( ) func init() { - module.Register("libdns.leaseweb", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.leaseweb", func(modName, instName string) (module.Module, error) { p := leaseweb.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/metaname.go b/internal/libdns/metaname.go index 2e37ddd3..2180509b 100644 --- a/internal/libdns/metaname.go +++ b/internal/libdns/metaname.go @@ -10,7 +10,7 @@ import ( ) func init() { - module.Register("libdns.metaname", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.metaname", func(modName, instName string) (module.Module, error) { p := metaname.Provider{ Endpoint: "https://metaname.net/api/1.1", } diff --git a/internal/libdns/namecheap.go b/internal/libdns/namecheap.go index 656ebe51..cb38461e 100644 --- a/internal/libdns/namecheap.go +++ b/internal/libdns/namecheap.go @@ -10,7 +10,7 @@ import ( ) func init() { - module.Register("libdns.namecheap", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.namecheap", func(modName, instName string) (module.Module, error) { p := namecheap.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/namedotcom.go b/internal/libdns/namedotcom.go index 3ce3c51c..0a5c9934 100644 --- a/internal/libdns/namedotcom.go +++ b/internal/libdns/namedotcom.go @@ -10,7 +10,7 @@ import ( ) func init() { - module.Register("libdns.namedotcom", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.namedotcom", func(modName, instName string) (module.Module, error) { p := namedotcom.Provider{ Server: "https://api.name.com", } diff --git a/internal/libdns/provider_module.go b/internal/libdns/provider_module.go index 75561501..6df107ff 100644 --- a/internal/libdns/provider_module.go +++ b/internal/libdns/provider_module.go @@ -15,7 +15,7 @@ type ProviderModule struct { modName string } -func (p *ProviderModule) Init(cfg *config.Map) error { +func (p *ProviderModule) Configure(inlineArgs []string, cfg *config.Map) error { p.setConfig(cfg) _, err := cfg.Process() if p.afterConfig != nil { diff --git a/internal/libdns/rfc2136.go b/internal/libdns/rfc2136.go index 19751f6f..90eca724 100644 --- a/internal/libdns/rfc2136.go +++ b/internal/libdns/rfc2136.go @@ -10,7 +10,7 @@ import ( ) func init() { - module.Register("libdns.rfc2136", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.rfc2136", func(modName, instName string) (module.Module, error) { p := rfc2136.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/route53.go b/internal/libdns/route53.go index dd507249..fbb5edf7 100644 --- a/internal/libdns/route53.go +++ b/internal/libdns/route53.go @@ -10,7 +10,7 @@ import ( ) func init() { - module.Register("libdns.route53", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.route53", func(modName, instName string) (module.Module, error) { p := route53.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/vultr.go b/internal/libdns/vultr.go index 9157258e..e94a2869 100644 --- a/internal/libdns/vultr.go +++ b/internal/libdns/vultr.go @@ -10,7 +10,7 @@ import ( ) func init() { - module.Register("libdns.vultr", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.vultr", func(modName, instName string) (module.Module, error) { p := vultr.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/limits/limits.go b/internal/limits/limits.go index 95d98a2f..7c1a39b0 100644 --- a/internal/limits/limits.go +++ b/internal/limits/limits.go @@ -46,13 +46,13 @@ type Group struct { dest *limiters.BucketSet // BucketSet of MultiLimit } -func New(_, instName string, _, _ []string) (module.Module, error) { +func New(_, instName string) (module.Module, error) { return &Group{ instName: instName, }, nil } -func (g *Group) Init(cfg *config.Map) error { +func (g *Group) Configure(inlineArgs []string, cfg *config.Map) error { var ( globalL []limiters.L ipL []func() limiters.L diff --git a/internal/modify/dkim/dkim.go b/internal/modify/dkim/dkim.go index ffeed4a2..f0894931 100644 --- a/internal/modify/dkim/dkim.go +++ b/internal/modify/dkim/dkim.go @@ -111,23 +111,13 @@ type Modifier struct { log log.Logger } -func New(_, instName string, _, inlineArgs []string) (module.Module, error) { +func New(_, instName string) (module.Module, error) { m := &Modifier{ instName: instName, signers: map[string]crypto.Signer{}, log: log.Logger{Name: "modify.dkim"}, } - if len(inlineArgs) == 0 { - return m, nil - } - if len(inlineArgs) == 1 { - return nil, errors.New("modify.dkim: at least two arguments required") - } - - m.domains = inlineArgs[0 : len(inlineArgs)-1] - m.selector = inlineArgs[len(inlineArgs)-1] - return m, nil } @@ -139,7 +129,16 @@ func (m *Modifier) InstanceName() string { return m.instName } -func (m *Modifier) Init(cfg *config.Map) error { +func (m *Modifier) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) != 0 { + if len(inlineArgs) == 1 { + return errors.New("modify.dkim: at least two arguments required") + } + + m.domains = inlineArgs[0 : len(inlineArgs)-1] + m.selector = inlineArgs[len(inlineArgs)-1] + } + var ( hashName string keyPathTemplate string diff --git a/internal/modify/dkim/dkim_test.go b/internal/modify/dkim/dkim_test.go index d4a9b5ad..172f0f34 100644 --- a/internal/modify/dkim/dkim_test.go +++ b/internal/modify/dkim/dkim_test.go @@ -37,14 +37,14 @@ import ( ) func newTestModifier(t *testing.T, dir, keyAlgo string, domains []string) *Modifier { - mod, err := New("", "test", nil, nil) + mod, err := New("", "test") if err != nil { t.Fatal(err) } m := mod.(*Modifier) m.log = testutils.Logger(t, m.Name()) - err = m.Init(config.NewMap(nil, config.Node{ + err = m.Configure(nil, config.NewMap(nil, config.Node{ Children: []config.Node{ { Name: "domains", diff --git a/internal/modify/group.go b/internal/modify/group.go index 3278b0ff..00fbe124 100644 --- a/internal/modify/group.go +++ b/internal/modify/group.go @@ -43,7 +43,7 @@ type ( } ) -func (g *Group) Init(cfg *config.Map) error { +func (g *Group) Configure(inlineArgs []string, cfg *config.Map) error { for _, node := range cfg.Block.Children { mod, err := modconfig.MsgModifier(cfg.Globals, append([]string{node.Name}, node.Args...), node) if err != nil { @@ -64,7 +64,7 @@ func (g *Group) InstanceName() string { return g.instName } -func (g Group) ModStateForMsg(ctx context.Context, msgMeta *module.MsgMetadata) (module.ModifierState, error) { +func (g *Group) ModStateForMsg(ctx context.Context, msgMeta *module.MsgMetadata) (module.ModifierState, error) { gs := groupState{} for _, modifier := range g.Modifiers { state, err := modifier.ModStateForMsg(ctx, msgMeta) @@ -132,7 +132,7 @@ func (gs groupState) Close() error { } func init() { - module.Register("modifiers", func(_, instName string, _, _ []string) (module.Module, error) { + module.Register("modifiers", func(_, instName string) (module.Module, error) { return &Group{ instName: instName, }, nil diff --git a/internal/modify/replace_addr.go b/internal/modify/replace_addr.go index 50dd5dff..f305ac95 100644 --- a/internal/modify/replace_addr.go +++ b/internal/modify/replace_addr.go @@ -37,20 +37,18 @@ import ( // If created with modName = "modify.replace_sender", it will change sender address. // If created with modName = "modify.replace_rcpt", it will change recipient addresses. type replaceAddr struct { - modName string - instName string - inlineArgs []string + modName string + instName string replaceSender bool replaceRcpt bool table module.MultiTable } -func NewReplaceAddr(modName, instName string, _, inlineArgs []string) (module.Module, error) { +func NewReplaceAddr(modName, instName string) (module.Module, error) { r := replaceAddr{ modName: modName, instName: instName, - inlineArgs: inlineArgs, replaceSender: modName == "modify.replace_sender", replaceRcpt: modName == "modify.replace_rcpt", } @@ -58,23 +56,23 @@ func NewReplaceAddr(modName, instName string, _, inlineArgs []string) (module.Mo return &r, nil } -func (r *replaceAddr) Init(cfg *config.Map) error { - return modconfig.ModuleFromNode("table", r.inlineArgs, cfg.Block, cfg.Globals, &r.table) +func (r *replaceAddr) Configure(inlineArgs []string, cfg *config.Map) error { + return modconfig.ModuleFromNode("table", inlineArgs, cfg.Block, cfg.Globals, &r.table) } -func (r replaceAddr) Name() string { +func (r *replaceAddr) Name() string { return r.modName } -func (r replaceAddr) InstanceName() string { +func (r *replaceAddr) InstanceName() string { return r.instName } -func (r replaceAddr) ModStateForMsg(ctx context.Context, msgMeta *module.MsgMetadata) (module.ModifierState, error) { +func (r *replaceAddr) ModStateForMsg(ctx context.Context, msgMeta *module.MsgMetadata) (module.ModifierState, error) { return r, nil } -func (r replaceAddr) RewriteSender(ctx context.Context, mailFrom string) (string, error) { +func (r *replaceAddr) RewriteSender(ctx context.Context, mailFrom string) (string, error) { if r.replaceSender { results, err := r.rewrite(ctx, mailFrom) if err != nil { @@ -85,22 +83,22 @@ func (r replaceAddr) RewriteSender(ctx context.Context, mailFrom string) (string return mailFrom, nil } -func (r replaceAddr) RewriteRcpt(ctx context.Context, rcptTo string) ([]string, error) { +func (r *replaceAddr) RewriteRcpt(ctx context.Context, rcptTo string) ([]string, error) { if r.replaceRcpt { return r.rewrite(ctx, rcptTo) } return []string{rcptTo}, nil } -func (r replaceAddr) RewriteBody(ctx context.Context, h *textproto.Header, body buffer.Buffer) error { +func (r *replaceAddr) RewriteBody(ctx context.Context, h *textproto.Header, body buffer.Buffer) error { return nil } -func (r replaceAddr) Close() error { +func (r *replaceAddr) Close() error { return nil } -func (r replaceAddr) rewrite(ctx context.Context, val string) ([]string, error) { +func (r *replaceAddr) rewrite(ctx context.Context, val string) ([]string, error) { normAddr, err := address.ForLookup(val) if err != nil { return []string{val}, fmt.Errorf("malformed address: %v", err) diff --git a/internal/modify/replace_addr_test.go b/internal/modify/replace_addr_test.go index e16cfd34..9c3b0b75 100644 --- a/internal/modify/replace_addr_test.go +++ b/internal/modify/replace_addr_test.go @@ -31,12 +31,12 @@ func testReplaceAddr(t *testing.T, modName string) { test := func(addr string, expectedMulti []string, aliases map[string][]string) { t.Helper() - mod, err := NewReplaceAddr(modName, "", nil, []string{"dummy"}) + mod, err := NewReplaceAddr(modName, "") if err != nil { t.Fatal(err) } m := mod.(*replaceAddr) - if err := m.Init(config.NewMap(nil, config.Node{})); err != nil { + if err := m.Configure([]string{"dummy"}, config.NewMap(nil, config.Node{})); err != nil { t.Fatal(err) } m.table = testutils.MultiTable{M: aliases} diff --git a/internal/msgpipeline/check_group.go b/internal/msgpipeline/check_group.go index 1fd6b245..27bcdb6c 100644 --- a/internal/msgpipeline/check_group.go +++ b/internal/msgpipeline/check_group.go @@ -37,7 +37,7 @@ type CheckGroup struct { L []module.Check } -func (cg *CheckGroup) Init(cfg *config.Map) error { +func (cg *CheckGroup) Configure(inlineArgs []string, cfg *config.Map) error { for _, node := range cfg.Block.Children { chk, err := modconfig.MessageCheck(cfg.Globals, append([]string{node.Name}, node.Args...), node) if err != nil { @@ -50,16 +50,16 @@ func (cg *CheckGroup) Init(cfg *config.Map) error { return nil } -func (CheckGroup) Name() string { +func (*CheckGroup) Name() string { return "checks" } -func (cg CheckGroup) InstanceName() string { +func (cg *CheckGroup) InstanceName() string { return cg.instName } func init() { - module.Register("checks", func(_, instName string, _, _ []string) (module.Module, error) { + module.Register("checks", func(_, instName string) (module.Module, error) { return &CheckGroup{ instName: instName, }, nil diff --git a/internal/msgpipeline/dmarc_test.go b/internal/msgpipeline/dmarc_test.go index f942baf8..e1c5656a 100644 --- a/internal/msgpipeline/dmarc_test.go +++ b/internal/msgpipeline/dmarc_test.go @@ -55,7 +55,7 @@ func doTestDelivery(t *testing.T, tgt module.DeliveryTarget, from string, to []s panic(err) } - delivery, err := tgt.Start(context.Background(), &ctx, from) + delivery, err := tgt.StartDelivery(context.Background(), &ctx, from) if err != nil { return encodedID, err } diff --git a/internal/msgpipeline/module.go b/internal/msgpipeline/module.go index cf30d221..a951ec88 100644 --- a/internal/msgpipeline/module.go +++ b/internal/msgpipeline/module.go @@ -30,14 +30,14 @@ type Module struct { *MsgPipeline } -func NewModule(modName, instName string, aliases, inlineArgs []string) (module.Module, error) { +func NewModule(modName, instName string) (module.Module, error) { return &Module{ log: log.Logger{Name: "msgpipeline"}, instName: instName, }, nil } -func (m *Module) Init(cfg *config.Map) error { +func (m *Module) Configure(inlineArgs []string, cfg *config.Map) error { var hostname string cfg.String("hostname", true, true, "", &hostname) cfg.Bool("debug", true, false, &m.log.Debug) diff --git a/internal/msgpipeline/msgpipeline.go b/internal/msgpipeline/msgpipeline.go index 388caab1..5caa0899 100644 --- a/internal/msgpipeline/msgpipeline.go +++ b/internal/msgpipeline/msgpipeline.go @@ -111,13 +111,13 @@ func (d *MsgPipeline) RunEarlyChecks(ctx context.Context, state *module.ConnStat return eg.Wait() } -// Start starts new message delivery, runs connection and sender checks, sender modifiers +// StartDelivery starts new message delivery, runs connection and sender checks, sender modifiers // and selects source block from config to use for handling. // // Returned module.Delivery implements PartialDelivery. If underlying target doesn't // support it, msgpipeline will copy the returned error for all recipients handled // by target. -func (d *MsgPipeline) Start(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { +func (d *MsgPipeline) StartDelivery(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { dd := msgpipelineDelivery{ d: d, rcptModifiersState: make(map[*rcptBlock]module.ModifierState), @@ -622,14 +622,14 @@ func (dd *msgpipelineDelivery) getDelivery(ctx context.Context, tgt module.Deliv return delivery_, nil } - deliveryObj, err := tgt.Start(ctx, dd.msgMeta, dd.sourceAddr) + deliveryObj, err := tgt.StartDelivery(ctx, dd.msgMeta, dd.sourceAddr) if err != nil { - dd.log.Debugf("tgt.Start(%s) failure, target = %s: %v", dd.sourceAddr, objectName(tgt), err) + dd.log.Debugf("tgt.StartDelivery(%s) failure, target = %s: %v", dd.sourceAddr, objectName(tgt), err) return nil, err } delivery_ = &delivery{Delivery: deliveryObj} - dd.log.Debugf("tgt.Start(%s) ok, target = %s", dd.sourceAddr, objectName(tgt)) + dd.log.Debugf("tgt.StartDelivery(%s) ok, target = %s", dd.sourceAddr, objectName(tgt)) dd.deliveries[tgt] = delivery_ return delivery_, nil diff --git a/internal/msgpipeline/msgpipeline_test.go b/internal/msgpipeline/msgpipeline_test.go index 9899eb03..6d09da00 100644 --- a/internal/msgpipeline/msgpipeline_test.go +++ b/internal/msgpipeline/msgpipeline_test.go @@ -380,14 +380,14 @@ func TestMsgPipeline_PerSourceReject(t *testing.T) { testutils.DoTestDelivery(t, &d, "sender1@example.com", []string{"rcpt@example.com"}) - _, err := d.Start(context.Background(), &module.MsgMetadata{ID: "testing"}, "sender2@example.com") + _, err := d.StartDelivery(context.Background(), &module.MsgMetadata{ID: "testing"}, "sender2@example.com") if err == nil { - t.Error("expected error for delivery.Start, got nil") + t.Error("expected error for delivery.StartDelivery, got nil") } - _, err = d.Start(context.Background(), &module.MsgMetadata{ID: "testing"}, "sender2@example.org") + _, err = d.StartDelivery(context.Background(), &module.MsgMetadata{ID: "testing"}, "sender2@example.org") if err == nil { - t.Error("expected error for delivery.Start, got nil") + t.Error("expected error for delivery.StartDelivery, got nil") } } @@ -413,9 +413,9 @@ func TestMsgPipeline_PerRcptReject(t *testing.T) { Log: testutils.Logger(t, "msgpipeline"), } - delivery, err := d.Start(context.Background(), &module.MsgMetadata{ID: "testing"}, "sender@example.com") + delivery, err := d.StartDelivery(context.Background(), &module.MsgMetadata{ID: "testing"}, "sender@example.com") if err != nil { - t.Fatalf("unexpected Start err: %v", err) + t.Fatalf("unexpected StartDelivery err: %v", err) } defer func() { if err := delivery.Abort(context.Background()); err != nil { @@ -628,7 +628,7 @@ func TestMsgPipeline_MalformedSource(t *testing.T) { // Simple checks for violations that can make msgpipeline misbehave. for _, addr := range []string{"not_postmaster_but_no_at_sign", "@no_mailbox", "no_domain@"} { - _, err := d.Start(context.Background(), &module.MsgMetadata{ID: "testing"}, addr) + _, err := d.StartDelivery(context.Background(), &module.MsgMetadata{ID: "testing"}, addr) if err == nil { t.Errorf("%s is accepted as valid address", addr) } diff --git a/internal/smtpconn/smtpconn.go b/internal/smtpconn/smtpconn.go index ec42974d..3c4b4224 100644 --- a/internal/smtpconn/smtpconn.go +++ b/internal/smtpconn/smtpconn.go @@ -376,7 +376,7 @@ func (c *C) Rcpt(ctx context.Context, to string, opts smtp.RcptOptions) error { // TODO: DSN support } - // If necessary, the extension flag is enabled in Start. + // If necessary, the extension flag is enabled in StartDelivery. if ok, _ := c.cl.Extension("SMTPUTF8"); !address.IsASCII(to) && !ok { var err error to, err = address.ToASCII(to) diff --git a/internal/storage/blob/fs/fs.go b/internal/storage/blob/fs/fs.go index e8c9b389..48e5f21a 100644 --- a/internal/storage/blob/fs/fs.go +++ b/internal/storage/blob/fs/fs.go @@ -17,26 +17,27 @@ type FSStore struct { root string } -func New(_, instName string, _, inlineArgs []string) (module.Module, error) { - switch len(inlineArgs) { - case 0: - return &FSStore{instName: instName}, nil - case 1: - return &FSStore{instName: instName, root: inlineArgs[0]}, nil - default: - return nil, fmt.Errorf("storage.blob.fs: 1 or 0 arguments expected") - } +func New(_, instName string) (module.Module, error) { + return &FSStore{instName: instName}, nil } -func (s FSStore) Name() string { +func (s *FSStore) Name() string { return "storage.blob.fs" } -func (s FSStore) InstanceName() string { +func (s *FSStore) InstanceName() string { return s.instName } -func (s *FSStore) Init(cfg *config.Map) error { +func (s *FSStore) Configure(inlineArgs []string, cfg *config.Map) error { + switch len(inlineArgs) { + case 0: + case 1: + s.root = inlineArgs[0] + default: + return fmt.Errorf("storage.blob.fs: 1 or 0 arguments expected") + } + cfg.String("root", false, false, s.root, &s.root) if _, err := cfg.Process(); err != nil { return err @@ -91,5 +92,5 @@ func (s *FSStore) Delete(_ context.Context, keys []string) error { func init() { var _ module.BlobStore = &FSStore{} - module.Register(FSStore{}.Name(), New) + module.Register((&FSStore{}).Name(), New) } diff --git a/internal/storage/blob/s3/s3.go b/internal/storage/blob/s3/s3.go index af01d888..075c8368 100644 --- a/internal/storage/blob/s3/s3.go +++ b/internal/storage/blob/s3/s3.go @@ -34,18 +34,18 @@ type Store struct { objectPrefix string } -func New(_, instName string, _, inlineArgs []string) (module.Module, error) { - if len(inlineArgs) != 0 { - return nil, fmt.Errorf("%s: expected 0 arguments", modName) - } - +func New(_, instName string) (module.Module, error) { return &Store{ instName: instName, log: log.Logger{Name: modName}, }, nil } -func (s *Store) Init(cfg *config.Map) error { +func (s *Store) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) != 0 { + return fmt.Errorf("%s: expected 0 arguments", modName) + } + var ( secure bool accessKeyID string diff --git a/internal/storage/blob/s3/s3_test.go b/internal/storage/blob/s3/s3_test.go index 98dd228b..78ba9700 100644 --- a/internal/storage/blob/s3/s3_test.go +++ b/internal/storage/blob/s3/s3_test.go @@ -28,7 +28,7 @@ func TestFS(t *testing.T) { } st := &Store{instName: "test"} - err := st.Init(config.NewMap(map[string]interface{}{}, config.Node{ + err := st.Configure(nil, config.NewMap(map[string]interface{}{}, config.Node{ Children: []config.Node{ { Name: "endpoint", diff --git a/internal/storage/imapsql/delivery.go b/internal/storage/imapsql/delivery.go index 60cb2e1f..a9ce32e8 100644 --- a/internal/storage/imapsql/delivery.go +++ b/internal/storage/imapsql/delivery.go @@ -155,8 +155,8 @@ func (d *delivery) Commit(ctx context.Context) error { return d.d.Commit() } -func (store *Storage) Start(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { - defer trace.StartRegion(ctx, "sql/Start").End() +func (store *Storage) StartDelivery(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { + defer trace.StartRegion(ctx, "sql/StartDelivery").End() return &delivery{ store: store, diff --git a/internal/storage/imapsql/imapsql.go b/internal/storage/imapsql/imapsql.go index 711a34e1..ddee1358 100644 --- a/internal/storage/imapsql/imapsql.go +++ b/internal/storage/imapsql/imapsql.go @@ -28,6 +28,7 @@ package imapsql import ( "context" "crypto/sha1" + "database/sql" "encoding/hex" "errors" "fmt" @@ -61,8 +62,10 @@ type Storage struct { junkMbox string - driver string - dsn []string + driver string + dsn []string + blobStore module.BlobStore + opts *imapsql.Opts resolver dns.Resolver @@ -86,24 +89,25 @@ func (store *Storage) InstanceName() string { return store.instName } -func New(_, instName string, _, inlineArgs []string) (module.Module, error) { +func New(_, instName string) (module.Module, error) { store := &Storage{ instName: instName, Log: log.Logger{Name: "imapsql"}, resolver: dns.DefaultResolver(), } + return store, nil +} + +func (store *Storage) Configure(inlineArgs []string, cfg *config.Map) error { if len(inlineArgs) != 0 { if len(inlineArgs) == 1 { - return nil, errors.New("imapsql: expected at least 2 arguments") + return errors.New("imapsql: expected at least 2 arguments") } store.driver = inlineArgs[0] store.dsn = inlineArgs[1:] } - return store, nil -} -func (store *Storage) Init(cfg *config.Map) error { var ( driver string dsn []string @@ -115,7 +119,7 @@ func (store *Storage) Init(cfg *config.Map) error { blobStore module.BlobStore ) - opts := imapsql.Opts{} + opts := &imapsql.Opts{} cfg.String("driver", false, false, store.driver, &driver) cfg.StringList("dsn", false, false, store.dsn, &dsn) cfg.Callback("fsstore", func(m *config.Map, node config.Node) error { @@ -238,9 +242,6 @@ func (store *Storage) Init(cfg *config.Map) error { opts.MaxMsgBytes = new(uint32) *opts.MaxMsgBytes = uint32(appendlimitVal) } - var err error - - dsnStr := strings.Join(dsn, " ") if len(compression) != 0 { switch compression[0] { @@ -264,16 +265,33 @@ func (store *Storage) Init(cfg *config.Map) error { } } - store.Back, err = imapsql.New(driver, dsnStr, ExtBlobStore{Base: blobStore}, opts) - if err != nil { - return fmt.Errorf("imapsql: %s", err) + driverFound := false + for _, d := range sql.Drivers() { + if d == driver { + driverFound = true + break + } + } + if !driverFound { + return fmt.Errorf("imapsql: unknown driver %q", driver) } - - store.Log.Debugln("go-imap-sql version", imapsql.VersionStr) store.driver = driver store.dsn = dsn + store.blobStore = blobStore + store.opts = opts + store.Log.Debugln("go-imap-sql version", imapsql.VersionStr) + + return nil +} +func (store *Storage) Start() error { + dsnStr := strings.Join(store.dsn, " ") + var err error + store.Back, err = imapsql.New(store.driver, dsnStr, ExtBlobStore{Base: store.blobStore}, *store.opts) + if err != nil { + return fmt.Errorf("imapsql: %s", err) + } return nil } @@ -407,7 +425,7 @@ func (store *Storage) Lookup(ctx context.Context, key string) (string, bool, err return "", true, nil } -func (store *Storage) Close() error { +func (store *Storage) Stop() error { // Stop backend from generating new updates. store.Back.Close() diff --git a/internal/table/chain.go b/internal/table/chain.go index 72eceba0..3b29a1c0 100644 --- a/internal/table/chain.go +++ b/internal/table/chain.go @@ -34,14 +34,14 @@ type Chain struct { optional []bool } -func NewChain(modName, instName string, _, _ []string) (module.Module, error) { +func NewChain(modName, instName string) (module.Module, error) { return &Chain{ modName: modName, instName: instName, }, nil } -func (s *Chain) Init(cfg *config.Map) error { +func (s *Chain) Configure(inlineArgs []string, cfg *config.Map) error { cfg.Callback("step", func(m *config.Map, node config.Node) error { var tbl module.Table err := modconfig.ModuleFromNode("table", node.Args, node, m.Globals, &tbl) diff --git a/internal/table/email_localpart.go b/internal/table/email_localpart.go index a9d6f067..4371a3e3 100644 --- a/internal/table/email_localpart.go +++ b/internal/table/email_localpart.go @@ -32,7 +32,7 @@ type EmailLocalpart struct { allowNonEmail bool } -func NewEmailLocalpart(modName, instName string, _, _ []string) (module.Module, error) { +func NewEmailLocalpart(modName, instName string) (module.Module, error) { return &EmailLocalpart{ modName: modName, instName: instName, @@ -40,7 +40,7 @@ func NewEmailLocalpart(modName, instName string, _, _ []string) (module.Module, }, nil } -func (s *EmailLocalpart) Init(cfg *config.Map) error { +func (s *EmailLocalpart) Configure(inlineArgs []string, cfg *config.Map) error { return nil } diff --git a/internal/table/email_with_domain.go b/internal/table/email_with_domain.go index 4eb50b54..62d9c565 100644 --- a/internal/table/email_with_domain.go +++ b/internal/table/email_with_domain.go @@ -35,16 +35,17 @@ type EmailWithDomain struct { log log.Logger } -func NewEmailWithDomain(modName, instName string, _, inlineArgs []string) (module.Module, error) { +func NewEmailWithDomain(modName, instName string) (module.Module, error) { return &EmailWithDomain{ modName: modName, instName: instName, - domains: inlineArgs, log: log.Logger{Name: modName}, }, nil } -func (s *EmailWithDomain) Init(cfg *config.Map) error { +func (s *EmailWithDomain) Configure(inlineArgs []string, cfg *config.Map) error { + s.domains = inlineArgs + for _, d := range s.domains { if !address.ValidDomain(d) { return fmt.Errorf("%s: invalid domain: %s", s.modName, d) diff --git a/internal/table/file.go b/internal/table/file.go index a0286a9a..3b7fcc03 100644 --- a/internal/table/file.go +++ b/internal/table/file.go @@ -29,7 +29,6 @@ import ( "time" "github.com/foxcpp/maddy/framework/config" - "github.com/foxcpp/maddy/framework/hooks" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" ) @@ -50,7 +49,7 @@ type File struct { log log.Logger } -func NewFile(_, instName string, _, inlineArgs []string) (module.Module, error) { +func NewFile(_, instName string) (module.Module, error) { m := &File{ instName: instName, m: make(map[string][]string), @@ -59,14 +58,6 @@ func NewFile(_, instName string, _, inlineArgs []string) (module.Module, error) log: log.Logger{Name: FileModName}, } - switch len(inlineArgs) { - case 1: - m.file = inlineArgs[0] - case 0: - default: - return nil, fmt.Errorf("%s: cannot use multiple files with single %s, use %s multiple times to do so", FileModName, FileModName, FileModName) - } - return m, nil } @@ -78,7 +69,15 @@ func (f *File) InstanceName() string { return f.instName } -func (f *File) Init(cfg *config.Map) error { +func (f *File) Configure(inlineArgs []string, cfg *config.Map) error { + switch len(inlineArgs) { + case 1: + f.file = inlineArgs[0] + case 0: + default: + return fmt.Errorf("%s: cannot use multiple files with single %s, use %s multiple times to do so", FileModName, FileModName, FileModName) + } + var file string cfg.Bool("debug", true, false, &f.log.Debug) cfg.String("file", false, false, "", &file) @@ -100,11 +99,22 @@ func (f *File) Init(cfg *config.Map) error { f.log.Printf("ignoring non-existent file: %s", f.file) } + return nil +} + +func (f *File) Start() error { go f.reloader() - hooks.AddHook(hooks.EventReload, func() { - f.forceReload <- struct{}{} - }) + return nil +} +func (f *File) Reload() error { + f.forceReload <- struct{}{} + return nil +} + +func (f *File) Stop() error { + f.stopReloader <- struct{}{} + <-f.stopreloader return nil } @@ -181,12 +191,6 @@ func (f *File) reload() { f.mLck.Unlock() } -func (f *File) Close() error { - f.stopReloader <- struct{}{} - <-f.stopreloader - return nil -} - func readFile(path string, out map[string][]string) error { f, err := os.Open(path) if err != nil { diff --git a/internal/table/file_test.go b/internal/table/file_test.go index c51620d2..5c7389e3 100644 --- a/internal/table/file_test.go +++ b/internal/table/file_test.go @@ -98,15 +98,18 @@ func TestFileReload(t *testing.T) { } f.Close() - mod, err := NewFile("", "", nil, []string{f.Name()}) + mod, err := NewFile("", "") if err != nil { t.Fatal(err) } m := mod.(*File) + if err := m.Start(); err != nil { + t.Fatal(err) + } m.log = testutils.Logger(t, "file_map") - defer m.Close() + defer m.Stop() - if err := mod.Init(&config.Map{Block: config.Node{}}); err != nil { + if err := mod.Configure([]string{f.Name()}, &config.Map{Block: config.Node{}}); err != nil { t.Fatal(err) } @@ -149,15 +152,18 @@ func TestFileReload_Broken(t *testing.T) { } f.Close() - mod, err := NewFile("", "", nil, []string{f.Name()}) + mod, err := NewFile("", "") if err != nil { t.Fatal(err) } m := mod.(*File) + if err := m.Start(); err != nil { + t.Fatal(err) + } m.log = testutils.Logger(t, FileModName) - defer m.Close() + defer m.Stop() - if err := mod.Init(&config.Map{Block: config.Node{}}); err != nil { + if err := mod.Configure([]string{f.Name()}, &config.Map{Block: config.Node{}}); err != nil { t.Fatal(err) } @@ -194,15 +200,18 @@ func TestFileReload_Removed(t *testing.T) { } f.Close() - mod, err := NewFile("", "", nil, []string{f.Name()}) + mod, err := NewFile("", "") if err != nil { t.Fatal(err) } m := mod.(*File) + if err := m.Start(); err != nil { + t.Fatal(err) + } m.log = testutils.Logger(t, FileModName) - defer m.Close() + defer m.Stop() - if err := mod.Init(&config.Map{Block: config.Node{}}); err != nil { + if err := mod.Configure([]string{f.Name()}, &config.Map{Block: config.Node{}}); err != nil { t.Fatal(err) } diff --git a/internal/table/identity.go b/internal/table/identity.go index c405d3dc..6db17df8 100644 --- a/internal/table/identity.go +++ b/internal/table/identity.go @@ -30,14 +30,14 @@ type Identity struct { instName string } -func NewIdentity(modName, instName string, _, _ []string) (module.Module, error) { +func NewIdentity(modName, instName string) (module.Module, error) { return &Identity{ modName: modName, instName: instName, }, nil } -func (s *Identity) Init(cfg *config.Map) error { +func (s *Identity) Configure(inlineArgs []string, cfg *config.Map) error { return nil } diff --git a/internal/table/regexp.go b/internal/table/regexp.go index 069be6ce..0c22cd8a 100644 --- a/internal/table/regexp.go +++ b/internal/table/regexp.go @@ -29,9 +29,8 @@ import ( ) type Regexp struct { - modName string - instName string - inlineArgs []string + modName string + instName string re *regexp.Regexp replacements []string @@ -39,15 +38,14 @@ type Regexp struct { expandPlaceholders bool } -func NewRegexp(modName, instName string, _, inlineArgs []string) (module.Module, error) { +func NewRegexp(modName, instName string) (module.Module, error) { return &Regexp{ - modName: modName, - instName: instName, - inlineArgs: inlineArgs, + modName: modName, + instName: instName, }, nil } -func (r *Regexp) Init(cfg *config.Map) error { +func (r *Regexp) Configure(inlineArgs []string, cfg *config.Map) error { var ( fullMatch bool caseInsensitive bool @@ -59,9 +57,9 @@ func (r *Regexp) Init(cfg *config.Map) error { return err } - regex := r.inlineArgs[0] - if len(r.inlineArgs)> 1 { - r.replacements = r.inlineArgs[1:] + regex := inlineArgs[0] + if len(inlineArgs)> 1 { + r.replacements = inlineArgs[1:] } if fullMatch { diff --git a/internal/table/sql_query.go b/internal/table/sql_query.go index c15f710a..96bce8f0 100644 --- a/internal/table/sql_query.go +++ b/internal/table/sql_query.go @@ -32,6 +32,7 @@ import ( type SQL struct { modName string instName string + prepare func() error namedArgs bool @@ -43,7 +44,7 @@ type SQL struct { del *sql.Stmt } -func NewSQL(modName, instName string, _, _ []string) (module.Module, error) { +func NewSQL(modName, instName string) (module.Module, error) { return &SQL{ modName: modName, instName: instName, @@ -58,7 +59,7 @@ func (s *SQL) InstanceName() string { return s.instName } -func (s *SQL) Init(cfg *config.Map) error { +func (s *SQL) Configure(inlineArgs []string, cfg *config.Map) error { var ( driver string initQueries []string @@ -94,46 +95,52 @@ func (s *SQL) Init(cfg *config.Map) error { return config.NodeErr(cfg.Block, "failed to open db: %v", err) } s.db = db - - for _, init := range initQueries { - if _, err := db.Exec(init); err != nil { - return config.NodeErr(cfg.Block, "init query failed: %v", err) + s.prepare = func() error { + for _, init := range initQueries { + if _, err := db.Exec(init); err != nil { + return config.NodeErr(cfg.Block, "init query failed: %v", err) + } } - } - s.lookup, err = db.Prepare(lookupQuery) - if err != nil { - return config.NodeErr(cfg.Block, "failed to prepare lookup query: %v", err) - } - if addQuery != "" { - s.add, err = db.Prepare(addQuery) + s.lookup, err = db.Prepare(lookupQuery) if err != nil { - return config.NodeErr(cfg.Block, "failed to prepare add query: %v", err) + return fmt.Errorf("failed to prepare lookup query: %v", err) } - } - if listQuery != "" { - s.list, err = db.Prepare(listQuery) - if err != nil { - return config.NodeErr(cfg.Block, "failed to prepare list query: %v", err) + if addQuery != "" { + s.add, err = db.Prepare(addQuery) + if err != nil { + return fmt.Errorf("failed to prepare add query: %v", err) + } } - } - if setQuery != "" { - s.set, err = db.Prepare(setQuery) - if err != nil { - return config.NodeErr(cfg.Block, "failed to prepare set query: %v", err) + if listQuery != "" { + s.list, err = db.Prepare(listQuery) + if err != nil { + return config.NodeErr(cfg.Block, "failed to prepare list query: %v", err) + } } - } - if removeQuery != "" { - s.del, err = db.Prepare(removeQuery) - if err != nil { - return config.NodeErr(cfg.Block, "failed to prepare del query: %v", err) + if setQuery != "" { + s.set, err = db.Prepare(setQuery) + if err != nil { + return config.NodeErr(cfg.Block, "failed to prepare set query: %v", err) + } + } + if removeQuery != "" { + s.del, err = db.Prepare(removeQuery) + if err != nil { + return config.NodeErr(cfg.Block, "failed to prepare del query: %v", err) + } } + return nil } return nil } -func (s *SQL) Close() error { +func (s *SQL) Start() error { + return s.prepare() +} + +func (s *SQL) Stop() error { s.lookup.Close() return s.db.Close() } diff --git a/internal/table/sql_query_test.go b/internal/table/sql_query_test.go index fd160f76..99269c62 100644 --- a/internal/table/sql_query_test.go +++ b/internal/table/sql_query_test.go @@ -33,12 +33,12 @@ import ( func TestSQL(t *testing.T) { path := testutils.Dir(t) - mod, err := NewSQL("sql_table", "", nil, nil) + mod, err := NewSQL("sql_table", "") if err != nil { t.Fatal("Module create failed:", err) } tbl := mod.(*SQL) - err = tbl.Init(config.NewMap(nil, config.Node{ + err = tbl.Configure(nil, config.NewMap(nil, config.Node{ Children: []config.Node{ { Name: "driver", @@ -66,6 +66,9 @@ func TestSQL(t *testing.T) { if err != nil { t.Fatal("Init failed:", err) } + if err := tbl.Start(); err != nil { + t.Fatal(err) + } check := func(key, res string, ok, fail bool) { t.Helper() diff --git a/internal/table/sql_table.go b/internal/table/sql_table.go index e793dc48..edadb919 100644 --- a/internal/table/sql_table.go +++ b/internal/table/sql_table.go @@ -34,7 +34,7 @@ type SQLTable struct { wrapped *SQL } -func NewSQLTable(modName, instName string, _, _ []string) (module.Module, error) { +func NewSQLTable(modName, instName string) (module.Module, error) { return &SQLTable{ modName: modName, instName: instName, @@ -54,7 +54,7 @@ func (s *SQLTable) InstanceName() string { return s.instName } -func (s *SQLTable) Init(cfg *config.Map) error { +func (s *SQLTable) Configure(inlineArgs []string, cfg *config.Map) error { var ( driver string dsnParts []string @@ -99,7 +99,7 @@ func (s *SQLTable) Init(cfg *config.Map) error { delQuery = fmt.Sprintf("DELETE FROM %s WHERE %s = 1ドル", tableName, keyColumn) } - return s.wrapped.Init(config.NewMap(cfg.Globals, config.Node{ + return s.wrapped.Configure(nil, config.NewMap(cfg.Globals, config.Node{ Children: []config.Node{ { Name: "driver", @@ -144,8 +144,10 @@ func (s *SQLTable) Init(cfg *config.Map) error { })) } -func (s *SQLTable) Close() error { - return s.wrapped.Close() +func (s *SQLTable) Start() error { return s.wrapped.Start() } + +func (s *SQLTable) Stop() error { + return s.wrapped.Stop() } func (s *SQLTable) Lookup(ctx context.Context, val string) (string, bool, error) { diff --git a/internal/table/static.go b/internal/table/static.go index e55ffd58..4444f991 100644 --- a/internal/table/static.go +++ b/internal/table/static.go @@ -32,7 +32,7 @@ type Static struct { m map[string][]string } -func NewStatic(modName, instName string, _, _ []string) (module.Module, error) { +func NewStatic(modName, instName string) (module.Module, error) { return &Static{ modName: modName, instName: instName, @@ -40,7 +40,7 @@ func NewStatic(modName, instName string, _, _ []string) (module.Module, error) { }, nil } -func (s *Static) Init(cfg *config.Map) error { +func (s *Static) Configure(inlineArgs []string, cfg *config.Map) error { cfg.Callback("entry", func(_ *config.Map, node config.Node) error { if len(node.Args) < 2 { return config.NodeErr(node, "expected at least one value") diff --git a/internal/target/queue/queue.go b/internal/target/queue/queue.go index 264a70fb..663046b3 100644 --- a/internal/target/queue/queue.go +++ b/internal/target/queue/queue.go @@ -30,7 +30,7 @@ All scheduled deliveries are attempted to the configured DeliveryTarget. All metadata is preserved on disk. Failure status is determined on per-recipient basis: - - Delivery.Start fail handled as a failure for all recipients. + - Delivery.StartDelivery fail handled as a failure for all recipients. - Delivery.AddRcpt fail handled as a failure for the corresponding recipient. - Delivery.Body fail handled as a failure for all recipients. - If Delivery implements PartialDelivery, then @@ -133,6 +133,7 @@ type Queue struct { initialRetryTime time.Duration retryTimeScale float64 maxTries int + maxParallelism int // If any delivery is scheduled in less than postInitDelay // after Init, its delay will be increased by postInitDelay. @@ -188,7 +189,7 @@ type queueSlot struct { Body buffer.Buffer } -func NewQueue(_, instName string, _, inlineArgs []string) (module.Module, error) { +func NewQueue(_, instName string) (module.Module, error) { q := &Queue{ name: instName, initialRetryTime: 15 * time.Minute, @@ -196,22 +197,22 @@ func NewQueue(_, instName string, _, inlineArgs []string) (module.Module, error) postInitDelay: 10 * time.Second, Log: log.Logger{Name: "queue"}, } + return q, nil +} + +func (q *Queue) Configure(inlineArgs []string, cfg *config.Map) error { switch len(inlineArgs) { case 0: // Not inline definition. case 1: q.location = inlineArgs[0] default: - return nil, errors.New("queue: wrong amount of inline arguments") + return errors.New("queue: wrong amount of inline arguments") } - return q, nil -} -func (q *Queue) Init(cfg *config.Map) error { - var maxParallelism int cfg.Bool("debug", true, false, &q.Log.Debug) cfg.Int("max_tries", false, false, 20, &q.maxTries) - cfg.Int("max_parallelism", false, false, 16, &maxParallelism) + cfg.Int("max_parallelism", false, false, 16, &q.maxParallelism) cfg.String("location", false, false, q.location, &q.location) cfg.Custom("target", false, true, nil, modconfig.DeliveryDirective, &q.Target) cfg.String("hostname", true, true, "", &q.hostname) @@ -242,8 +243,11 @@ func (q *Queue) Init(cfg *config.Map) error { if err := os.MkdirAll(q.location, os.ModePerm); err != nil { return err } + return nil +} - return q.start(maxParallelism) +func (q *Queue) Start() error { + return q.start(q.maxParallelism) } func (q *Queue) start(maxParallelism int) error { @@ -259,7 +263,7 @@ func (q *Queue) start(maxParallelism int) error { return nil } -func (q *Queue) Close() error { +func (q *Queue) Stop() error { q.wheel.Close() q.deliveryWg.Wait() @@ -473,16 +477,16 @@ func (q *Queue) deliver(meta *QueueMetadata, header textproto.Header, body buffe defer msgTask.End() mailCtx, mailTask := trace.NewTask(msgCtx, "MAIL FROM") - delivery, err := q.Target.Start(mailCtx, msgMeta, meta.From) + delivery, err := q.Target.StartDelivery(mailCtx, msgMeta, meta.From) mailTask.End() if err != nil { - dl.Debugf("target.Start failed: %v", err) + dl.Debugf("target.StartDelivery failed: %v", err) for _, rcpt := range meta.To { perr.Errs[rcpt] = err } return perr } - dl.Debugf("target.Start OK") + dl.Debugf("target.StartDelivery OK") var acceptedRcpts []string for _, rcpt := range meta.To { @@ -605,7 +609,7 @@ func (qd *queueDelivery) Commit(ctx context.Context) error { return nil } -func (q *Queue) Start(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { +func (q *Queue) StartDelivery(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { meta := &QueueMetadata{ MsgMeta: msgMeta, From: mailFrom, @@ -958,7 +962,7 @@ func (q *Queue) emitDSN(meta *QueueMetadata, header textproto.Header, failedRcpt defer msgTask.End() mailCtx, mailTask := trace.NewTask(msgCtx, "MAIL FROM") - dsnDelivery, err := q.dsnPipeline.Start(mailCtx, dsnMeta, "") + dsnDelivery, err := q.dsnPipeline.StartDelivery(mailCtx, dsnMeta, "") mailTask.End() if err != nil { dl.Error("failed to enqueue DSN", err, "dsn_id", dsnID) diff --git a/internal/target/queue/queue_test.go b/internal/target/queue/queue_test.go index ff9a4f6a..205ba891 100644 --- a/internal/target/queue/queue_test.go +++ b/internal/target/queue/queue_test.go @@ -51,13 +51,13 @@ func newTestQueue(t *testing.T, target module.DeliveryTarget) *Queue { func cleanQueue(t *testing.T, q *Queue) { t.Log("--- queue.Close") - if err := q.Close(); err != nil { + if err := q.Stop(); err != nil { t.Fatal("queue.Close:", err) } } func newTestQueueDir(t *testing.T, target module.DeliveryTarget, dir string) *Queue { - mod, _ := NewQueue("", "queue", nil, nil) + mod, _ := NewQueue("", "queue") q := mod.(*Queue) q.initialRetryTime = 0 q.retryTimeScale = 1 @@ -159,7 +159,7 @@ func (utd *unreliableTargetDelivery) Commit(ctx context.Context) error { return nil } -func (ut *unreliableTarget) Start(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { +func (ut *unreliableTarget) StartDelivery(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { if ut.bodyFailuresPartial != nil { return &unreliableTargetDeliveryPartial{ &unreliableTargetDelivery{ @@ -245,7 +245,7 @@ func TestQueueDelivery(t *testing.T) { // Wait for the delivery to complete and stop processing. msg := readMsgChanTimeout(t, dt.committed, 5*time.Second) - q.Close() + q.Stop() testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester1@example.org", "tester2@example.org"}, "") @@ -269,7 +269,7 @@ func TestQueueDelivery_PermanentFail_NonPartial(t *testing.T) { // Queue will abort a delivery if it fails for all recipients. readMsgChanTimeout(t, dt.aborted, 5*time.Second) - q.Close() + q.Stop() // Delivery is failed permanently, hence no retry should be rescheduled. checkQueueDir(t, q, []string{}) @@ -296,7 +296,7 @@ func TestQueueDelivery_PermanentFail_Partial(t *testing.T) { // Here delivery fails for recipients too, but this is reported using PartialDelivery. readMsgChanTimeout(t, dt.aborted, 5*time.Second) - q.Close() + q.Stop() checkQueueDir(t, q, []string{}) } @@ -322,7 +322,7 @@ func TestQueueDelivery_TemporaryFail(t *testing.T) { msg := readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester1@example.org", "tester2@example.org"}, "") - q.Close() + q.Stop() // No more retries scheduled, queue storage is clear. defer checkQueueDir(t, q, []string{}) } @@ -355,7 +355,7 @@ func TestQueueDelivery_TemporaryFail_Partial(t *testing.T) { msg = readMsgChanTimeout(t, dt.committed, 5000*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester2@example.org"}, "") - q.Close() + q.Stop() // No more retries scheduled, queue storage is clear. checkQueueDir(t, q, []string{}) } @@ -395,7 +395,7 @@ func TestQueueDelivery_MultipleAttempts(t *testing.T) { msg = readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester2@example.org"}, "") - q.Close() + q.Stop() // No more retries should be scheduled. checkQueueDir(t, q, []string{}) } @@ -420,7 +420,7 @@ func TestQueueDelivery_PermanentRcptReject(t *testing.T) { msg := readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.org", []string{"tester2@example.org"}, "") - q.Close() + q.Stop() // No more retries should be scheduled. checkQueueDir(t, q, []string{}) } @@ -454,7 +454,7 @@ func TestQueueDelivery_TemporaryRcptReject(t *testing.T) { msg = readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester1@example.org"}, "") - q.Close() + q.Stop() // No more retries should be scheduled. checkQueueDir(t, q, []string{}) } @@ -488,7 +488,7 @@ func TestQueueDelivery_SerializationRoundtrip(t *testing.T) { testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester2@example.org"}, "") // Then stop it. - q.Close() + q.Stop() // Make sure it is saved. checkQueueDir(t, q, []string{deliveryID}) @@ -501,7 +501,7 @@ func TestQueueDelivery_SerializationRoundtrip(t *testing.T) { testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester1@example.org"}, "") // Close it again. - q.Close() + q.Stop() // No more retries should be scheduled. checkQueueDir(t, q, []string{}) } @@ -535,7 +535,7 @@ func TestQueueDelivery_DeserlizationCleanUp(t *testing.T) { msg := readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester2@example.org"}, "") - q.Close() + q.Stop() if err := os.Remove(filepath.Join(q.location, deliveryID+fileSuffix)); err != nil { t.Fatal(err) @@ -543,7 +543,7 @@ func TestQueueDelivery_DeserlizationCleanUp(t *testing.T) { // Dangling files should be removed during load. q = newTestQueueDir(t, &dt, q.location) - q.Close() + q.Stop() // Nothing should be left. checkQueueDir(t, q, []string{}) @@ -606,9 +606,9 @@ func TestQueueDelivery_AbortNoDangling(t *testing.T) { DontTraceSender: true, ID: encodedID, } - delivery, err := q.Start(context.Background(), &ctx, "test3@example.org") + delivery, err := q.StartDelivery(context.Background(), &ctx, "test3@example.org") if err != nil { - t.Fatalf("unexpected Start err: %v", err) + t.Fatalf("unexpected StartDelivery err: %v", err) } for _, rcpt := range [...]string{"test@example.org", "test2@example.org"} { if err := delivery.AddRcpt(context.Background(), rcpt, smtp.RcptOptions{}); err != nil { @@ -786,9 +786,9 @@ func TestQueueDSN_RcptRewrite(t *testing.T) { }, ID: encodedID, } - delivery, err := q.Start(context.Background(), &ctx, "test3@example.org") + delivery, err := q.StartDelivery(context.Background(), &ctx, "test3@example.org") if err != nil { - t.Fatalf("unexpected Start err: %v", err) + t.Fatalf("unexpected StartDelivery err: %v", err) } for _, rcpt := range [...]string{"test@example.org", "test2@example.org"} { if err := delivery.AddRcpt(context.Background(), rcpt, smtp.RcptOptions{}); err != nil { diff --git a/internal/target/remote/mxauth_test.go b/internal/target/remote/mxauth_test.go index 2bd8d2d4..e332bdb4 100644 --- a/internal/target/remote/mxauth_test.go +++ b/internal/target/remote/mxauth_test.go @@ -63,7 +63,7 @@ func TestRemoteDelivery_AuthMX_MTASTS(t *testing.T) { testSTSPolicy(t, zones, mtastsGet), }) tgt.tlsConfig = clientCfg - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -108,7 +108,7 @@ func TestRemoteDelivery_MTASTS_SkipNonMatching(t *testing.T) { &localPolicy{minMXLevel: module.MX_MTASTS}, }) tgt.tlsConfig = clientCfg - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -148,7 +148,7 @@ func TestRemoteDelivery_AuthMX_MTASTS_Fail(t *testing.T) { &localPolicy{minMXLevel: module.MX_MTASTS}, }) tgt.tlsConfig = clientCfg - defer tgt.Close() + defer tgt.Stop() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -189,7 +189,7 @@ func TestRemoteDelivery_AuthMX_MTASTS_NoTLS(t *testing.T) { testSTSPolicy(t, zones, mtastsGet), &localPolicy{minMXLevel: module.MX_MTASTS}, }) - defer tgt.Close() + defer tgt.Stop() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -230,7 +230,7 @@ func TestRemoteDelivery_AuthMX_MTASTS_RequirePKIX(t *testing.T) { testSTSPolicy(t, zones, mtastsGet), &localPolicy{minMXLevel: module.MX_MTASTS}, }) - defer tgt.Close() + defer tgt.Stop() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -280,7 +280,7 @@ func TestRemoteDelivery_AuthMX_MTASTS_NoPolicy(t *testing.T) { testSTSPolicy(t, zones, mtastsGet), &localPolicy{minMXLevel: module.MX_MTASTS}, }) - defer tgt.Close() + defer tgt.Stop() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -322,7 +322,7 @@ func TestRemoteDelivery_AuthMX_DNSSEC(t *testing.T) { extResolver.Cfg.Port = strconv.Itoa(addr.Port) tgt := testTarget(t, zones, extResolver, nil) - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -363,7 +363,7 @@ func TestRemoteDelivery_AuthMX_DNSSEC_Fail(t *testing.T) { tgt := testTarget(t, zones, extResolver, []module.MXAuthPolicy{ &localPolicy{minMXLevel: module.MX_DNSSEC}, }) - defer tgt.Close() + defer tgt.Stop() _, err = testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -405,7 +405,7 @@ func TestRemoteDelivery_REQUIRETLS(t *testing.T) { testSTSPolicy(t, zones, mtastsGet), }) tgt.tlsConfig = clientCfg - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDeliveryMeta(t, tgt, "test@example.com", []string{"test@example.invalid"}, &module.MsgMetadata{ OriginalFrom: "test@example.com", @@ -446,7 +446,7 @@ func TestRemoteDelivery_REQUIRETLS_Fail(t *testing.T) { testSTSPolicy(t, zones, mtastsGet), }) tgt.tlsConfig = clientCfg - defer tgt.Close() + defer tgt.Stop() if _, err := testutils.DoTestDeliveryErrMeta(t, tgt, "test@example.com", []string{"test@example.invalid"}, &module.MsgMetadata{ OriginalFrom: "test@example.com", @@ -492,7 +492,7 @@ func TestRemoteDelivery_REQUIRETLS_Relaxed(t *testing.T) { }) tgt.relaxedREQUIRETLS = true tgt.tlsConfig = clientCfg - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDeliveryMeta(t, tgt, "test@example.com", []string{"test@example.invalid"}, &module.MsgMetadata{ OriginalFrom: "test@example.com", @@ -529,7 +529,7 @@ func TestRemoteDelivery_REQUIRETLS_Relaxed_NoMXAuth(t *testing.T) { }) tgt.relaxedREQUIRETLS = true tgt.tlsConfig = clientCfg - defer tgt.Close() + defer tgt.Stop() if _, err := testutils.DoTestDeliveryErrMeta(t, tgt, "test@example.com", []string{"test@example.invalid"}, &module.MsgMetadata{ OriginalFrom: "test@example.com", @@ -575,7 +575,7 @@ func TestRemoteDelivery_REQUIRETLS_Relaxed_NoTLS(t *testing.T) { }) tgt.relaxedREQUIRETLS = true tgt.tlsConfig = nil - defer tgt.Close() + defer tgt.Stop() if _, err := testutils.DoTestDeliveryErrMeta(t, tgt, "test@example.com", []string{"test@example.invalid"}, &module.MsgMetadata{ OriginalFrom: "test@example.com", @@ -626,7 +626,7 @@ func TestRemoteDelivery_REQUIRETLS_Relaxed_TLSFail(t *testing.T) { srv.TLSConfig.MinVersion = tls.VersionTLS11 srv.TLSConfig.MaxVersion = tls.VersionTLS11 tgt.tlsConfig = clientCfg - defer tgt.Close() + defer tgt.Stop() if _, err := testutils.DoTestDeliveryErrMeta(t, tgt, "test@example.com", []string{"test@example.invalid"}, &module.MsgMetadata{ OriginalFrom: "test@example.com", diff --git a/internal/target/remote/policy_group.go b/internal/target/remote/policy_group.go index 68a2202b..992bcfa4 100644 --- a/internal/target/remote/policy_group.go +++ b/internal/target/remote/policy_group.go @@ -39,7 +39,7 @@ type PolicyGroup struct { pols map[string]module.MXAuthPolicy } -func (pg *PolicyGroup) Init(cfg *config.Map) error { +func (pg *PolicyGroup) Configure(inlineArgs []string, cfg *config.Map) error { var debugLog bool cfg.Bool("debug", true, false, &debugLog) cfg.AllowUnknown() @@ -87,16 +87,16 @@ func (pg *PolicyGroup) Init(cfg *config.Map) error { return nil } -func (PolicyGroup) Name() string { +func (*PolicyGroup) Name() string { return "mx_auth" } -func (pg PolicyGroup) InstanceName() string { +func (pg *PolicyGroup) InstanceName() string { return pg.instName } func init() { - module.Register("mx_auth", func(_, instName string, _, _ []string) (module.Module, error) { + module.Register("mx_auth", func(_, instName string) (module.Module, error) { return &PolicyGroup{ instName: instName, pols: map[string]module.MXAuthPolicy{}, diff --git a/internal/target/remote/remote.go b/internal/target/remote/remote.go index d4c42ed6..79b683fd 100644 --- a/internal/target/remote/remote.go +++ b/internal/target/remote/remote.go @@ -87,10 +87,7 @@ type Target struct { var _ module.DeliveryTarget = &Target{} -func New(_, instName string, _, inlineArgs []string) (module.Module, error) { - if len(inlineArgs) != 0 { - return nil, errors.New("remote: inline arguments are not used") - } +func New(_, instName string) (module.Module, error) { // Keep this synchronized with testTarget. return &Target{ name: instName, @@ -100,7 +97,11 @@ func New(_, instName string, _, inlineArgs []string) (module.Module, error) { }, nil } -func (rt *Target) Init(cfg *config.Map) error { +func (rt *Target) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) != 0 { + return errors.New("remote: inline arguments are not used") + } + var err error rt.extResolver, err = dns.NewExtResolver() if err != nil { @@ -184,7 +185,11 @@ func (rt *Target) Init(cfg *config.Map) error { return nil } -func (rt *Target) Close() error { +func (rt *Target) Start() error { + return nil +} + +func (rt *Target) Stop() error { rt.pool.Close() return nil @@ -210,11 +215,11 @@ type remoteDelivery struct { policies []module.DeliveryMXAuthPolicy } -func (rt *Target) Start(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { +func (rt *Target) StartDelivery(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { policies := make([]module.DeliveryMXAuthPolicy, 0, len(rt.policies)) if !(msgMeta.TLSRequireOverride && rt.allowSecOverride) { for _, p := range rt.policies { - policies = append(policies, p.Start(msgMeta)) + policies = append(policies, p.StartDelivery(msgMeta)) } } diff --git a/internal/target/remote/remote_test.go b/internal/target/remote/remote_test.go index 4998e0c6..99b2492a 100644 --- a/internal/target/remote/remote_test.go +++ b/internal/target/remote/remote_test.go @@ -72,12 +72,12 @@ func testTarget(t *testing.T, zones map[string]mockdns.Zone, extResolver *dns.Ex } func testSTSPolicy(t *testing.T, zones map[string]mockdns.Zone, mtastsGet func(context.Context, string) (*mtasts.Policy, error)) *mtastsPolicy { - m, err := NewMTASTSPolicy("mx_auth.mtasts", "test", nil, nil) + m, err := NewMTASTSPolicy("mx_auth.mtasts", "test") if err != nil { t.Fatal(err) } p := m.(*mtastsPolicy) - err = p.Init(config.NewMap(nil, config.Node{ + err = p.Configure(nil, config.NewMap(nil, config.Node{ Children: []config.Node{ { Name: "cache", @@ -98,12 +98,12 @@ func testSTSPolicy(t *testing.T, zones map[string]mockdns.Zone, mtastsGet func(c } func testDANEPolicy(t *testing.T, extR *dns.ExtResolver) *danePolicy { - m, err := NewDANEPolicy("mx_auth.dane", "test", nil, nil) + m, err := NewDANEPolicy("mx_auth.dane", "test") if err != nil { t.Fatal(err) } p := m.(*danePolicy) - err = p.Init(config.NewMap(nil, config.Node{ + err = p.Configure(nil, config.NewMap(nil, config.Node{ Children: nil, })) if err != nil { @@ -129,7 +129,7 @@ func TestRemoteDelivery(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -146,9 +146,9 @@ func TestRemoteDelivery_NoMXFallback(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() - delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") + delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { t.Fatal(err) } @@ -176,7 +176,7 @@ func TestRemoteDelivery_EmptySender(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "", []string{"test@example.invalid"}) @@ -202,7 +202,7 @@ func TestRemoteDelivery_IPLiteral(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@[127.0.0.1]"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@[127.0.0.1]"}) @@ -219,7 +219,7 @@ func TestRemoteDelivery_FallbackMX(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -239,7 +239,7 @@ func TestRemoteDelivery_BodyNonAtomic(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() c := multipleErrs{ errs: map[string]error{}, @@ -267,9 +267,9 @@ func TestRemoteDelivery_Abort(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() - delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") + delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { t.Fatal(err) } @@ -297,9 +297,9 @@ func TestRemoteDelivery_CommitWithoutBody(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() - delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") + delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { t.Fatal(err) } @@ -334,9 +334,9 @@ func TestRemoteDelivery_MAILFROMErr(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() - delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") + delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { t.Fatal(err) } @@ -360,9 +360,9 @@ func TestRemoteDelivery_NoMX(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() - delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") + delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { t.Fatal(err) } @@ -390,9 +390,9 @@ func TestRemoteDelivery_NullMX(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() - delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") + delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { t.Fatal(err) } @@ -419,11 +419,11 @@ func TestRemoteDelivery_Quarantined(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() meta := module.MsgMetadata{ID: "test..."} - delivery, err := tgt.Start(context.Background(), &meta, "test@example.com") + delivery, err := tgt.StartDelivery(context.Background(), &meta, "test@example.com") if err != nil { t.Fatal(err) } @@ -467,9 +467,9 @@ func TestRemoteDelivery_MAILFROMErr_Repeated(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() - delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") + delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { t.Fatal(err) } @@ -507,9 +507,9 @@ func TestRemoteDelivery_RcptErr(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() - delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") + delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { t.Fatal(err) } @@ -558,7 +558,7 @@ func TestRemoteDelivery_DownMX(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -581,7 +581,7 @@ func TestRemoteDelivery_AllMXDown(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -612,7 +612,7 @@ func TestRemoteDelivery_Split(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid", "test@example2.invalid"}) @@ -651,9 +651,9 @@ func TestRemoteDelivery_Split_Fail(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() - delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") + delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { t.Fatal(err) } @@ -704,9 +704,9 @@ func TestRemoteDelivery_BodyErr(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() - delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") + delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { t.Fatal(err) } @@ -758,9 +758,9 @@ func TestRemoteDelivery_Split_BodyErr(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() - delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") + delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { t.Fatal(err) } @@ -814,9 +814,9 @@ func TestRemoteDelivery_Split_BodyErr_NonAtomic(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Close() + defer tgt.Stop() - delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") + delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { t.Fatal(err) } @@ -874,7 +874,7 @@ func TestRemoteDelivery_TLSErrFallback(t *testing.T) { tgt := testTarget(t, zones, nil, nil) tgt.tlsConfig = clientCfg - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -896,7 +896,7 @@ func TestRemoteDelivery_RequireTLS_Missing(t *testing.T) { tgt := testTarget(t, zones, nil, []module.MXAuthPolicy{ &localPolicy{minTLSLevel: module.TLSEncrypted}, }) - defer tgt.Close() + defer tgt.Stop() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -921,7 +921,7 @@ func TestRemoteDelivery_RequireTLS_Present(t *testing.T) { &localPolicy{minTLSLevel: module.TLSEncrypted}, }) tgt.tlsConfig = clientCfg - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -950,7 +950,7 @@ func TestRemoteDelivery_RequireTLS_NoErrFallback(t *testing.T) { &localPolicy{minTLSLevel: module.TLSEncrypted}, }) tgt.tlsConfig = clientCfg - defer tgt.Close() + defer tgt.Stop() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -975,7 +975,7 @@ func TestRemoteDelivery_TLS_FallbackNoVerify(t *testing.T) { tgt := testTarget(t, zones, nil, []module.MXAuthPolicy{ &localPolicy{minTLSLevel: module.TLSEncrypted}, }) - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -1008,7 +1008,7 @@ func TestRemoteDelivery_TLS_FallbackPlaintext(t *testing.T) { tgt := testTarget(t, zones, nil, nil) tgt.tlsConfig = clientCfg - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -1041,7 +1041,7 @@ func TestRemoteDelivery_ConnReuse(t *testing.T) { tgt := testTarget(t, zones, nil, nil) tgt.connReuseLimit = 5 - defer tgt.Close() + defer tgt.Stop() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) diff --git a/internal/target/remote/security.go b/internal/target/remote/security.go index a8177fb1..51572b89 100644 --- a/internal/target/remote/security.go +++ b/internal/target/remote/security.go @@ -52,7 +52,7 @@ type ( } ) -func NewMTASTSPolicy(_, instName string, _, _ []string) (module.Module, error) { +func NewMTASTSPolicy(_, instName string) (module.Module, error) { return &mtastsPolicy{ instName: instName, log: log.Logger{Name: "mx_auth.mtasts", Debug: log.DefaultLogger.Debug}, @@ -71,7 +71,7 @@ func (c *mtastsPolicy) Weight() int { return 10 } -func (c *mtastsPolicy) Init(cfg *config.Map) error { +func (c *mtastsPolicy) Configure(inlineArgs []string, cfg *config.Map) error { var ( storeType string storeDir string @@ -99,6 +99,11 @@ func (c *mtastsPolicy) Init(cfg *config.Map) error { return nil } +func (c *mtastsPolicy) Start() error { + c.StartUpdater() + return nil +} + // StartUpdater starts a goroutine to update MTA-STS cache periodically until // Close is called. // @@ -108,6 +113,15 @@ func (c *mtastsPolicy) StartUpdater() { go c.updater() } +func (c *mtastsPolicy) Stop() error { + if c.updaterStop != nil { + c.updaterStop <- struct{}{} + <-c.updaterstop + c.updaterStop = nil + } + return nil +} + func (c *mtastsPolicy) updater() { defer func() { if err := recover(); err != nil { @@ -142,22 +156,13 @@ func (c *mtastsPolicy) updater() { } } -func (c *mtastsPolicy) Start(msgMeta *module.MsgMetadata) module.DeliveryMXAuthPolicy { +func (c *mtastsPolicy) StartDelivery(msgMeta *module.MsgMetadata) module.DeliveryMXAuthPolicy { return &mtastsDelivery{ c: c, log: target.DeliveryLogger(c.log, msgMeta), } } -func (c *mtastsPolicy) Close() error { - if c.updaterStop != nil { - c.updaterStop <- struct{}{} - <-c.updaterstop - c.updaterStop = nil - } - return nil -} - func (c *mtastsDelivery) PrepareDomain(ctx context.Context, domain string) { c.policyFut = future.New() go func() { @@ -237,7 +242,7 @@ type stsPreloadPolicy struct { instName string } -func NewSTSPreload(_, instName string, _, _ []string) (module.Module, error) { +func NewSTSPreload(_, instName string) (module.Module, error) { return &stsPreloadPolicy{ instName: instName, log: log.Logger{Name: "mx_auth.sts_preload", Debug: log.DefaultLogger.Debug}, @@ -256,7 +261,7 @@ func (c *stsPreloadPolicy) Weight() int { return 30 // after MTA-STS } -func (c *stsPreloadPolicy) Init(cfg *config.Map) error { +func (c *stsPreloadPolicy) Configure(inlineArgs []string, cfg *config.Map) error { c.log.Println("sts_preload module is deprecated and is no-op as the list is expired and unmaintained") var ( @@ -276,7 +281,7 @@ type preloadDelivery struct { *stsPreloadPolicy } -func (p *stsPreloadPolicy) Start(*module.MsgMetadata) module.DeliveryMXAuthPolicy { +func (p *stsPreloadPolicy) StartDelivery(*module.MsgMetadata) module.DeliveryMXAuthPolicy { return &preloadDelivery{stsPreloadPolicy: p} } @@ -291,15 +296,11 @@ func (p *preloadDelivery) CheckConn(ctx context.Context, mxLevel module.MXLevel, return tlsLevel, nil } -func (p *stsPreloadPolicy) Close() error { - return nil -} - type dnssecPolicy struct { instName string } -func NewDNSSECPolicy(_, instName string, _, _ []string) (module.Module, error) { +func NewDNSSECPolicy(_, instName string) (module.Module, error) { return &dnssecPolicy{ instName: instName, }, nil @@ -317,19 +318,15 @@ func (c *dnssecPolicy) Weight() int { return 1 } -func (c *dnssecPolicy) Init(cfg *config.Map) error { +func (c *dnssecPolicy) Configure(inlineArgs []string, cfg *config.Map) error { _, err := cfg.Process() // will fail if there is any directive return err } -func (dnssecPolicy) Start(*module.MsgMetadata) module.DeliveryMXAuthPolicy { +func (dnssecPolicy) StartDelivery(*module.MsgMetadata) module.DeliveryMXAuthPolicy { return dnssecPolicy{} } -func (dnssecPolicy) Close() error { - return nil -} - func (dnssecPolicy) Reset(*module.MsgMetadata) {} func (dnssecPolicy) PrepareDomain(ctx context.Context, domain string) {} func (dnssecPolicy) PrepareConn(ctx context.Context, mx string) {} @@ -357,7 +354,7 @@ type ( } ) -func NewDANEPolicy(_, instName string, _, _ []string) (module.Module, error) { +func NewDANEPolicy(_, instName string) (module.Module, error) { return &danePolicy{ instName: instName, log: log.Logger{Name: "remote/dane", Debug: log.DefaultLogger.Debug}, @@ -376,7 +373,7 @@ func (c *danePolicy) Weight() int { return 10 } -func (c *danePolicy) Init(cfg *config.Map) error { +func (c *danePolicy) Configure(inlineArgs []string, cfg *config.Map) error { var err error c.extResolver, err = dns.NewExtResolver() if err != nil { @@ -389,14 +386,10 @@ func (c *danePolicy) Init(cfg *config.Map) error { return err } -func (c *danePolicy) Start(*module.MsgMetadata) module.DeliveryMXAuthPolicy { +func (c *danePolicy) StartDelivery(*module.MsgMetadata) module.DeliveryMXAuthPolicy { return &daneDelivery{c: c} } -func (c *danePolicy) Close() error { - return nil -} - func (c *daneDelivery) PrepareDomain(ctx context.Context, domain string) {} func (c *daneDelivery) discoverTLSA(ctx context.Context, mx string) ([]dns.TLSA, error) { @@ -536,7 +529,7 @@ type ( } ) -func NewLocalPolicy(_, instName string, _, _ []string) (module.Module, error) { +func NewLocalPolicy(_, instName string) (module.Module, error) { return &localPolicy{ instName: instName, }, nil @@ -554,7 +547,7 @@ func (c *localPolicy) Weight() int { return 1000 } -func (c *localPolicy) Init(cfg *config.Map) error { +func (c *localPolicy) Configure(inlineArgs []string, cfg *config.Map) error { var ( minTLSLevel string minMXLevel string @@ -589,19 +582,15 @@ func (c *localPolicy) Init(cfg *config.Map) error { return nil } -func (l localPolicy) Start(msgMeta *module.MsgMetadata) module.DeliveryMXAuthPolicy { +func (l *localPolicy) StartDelivery(msgMeta *module.MsgMetadata) module.DeliveryMXAuthPolicy { return l } -func (l localPolicy) Close() error { - return nil -} - -func (l localPolicy) Reset(*module.MsgMetadata) {} -func (l localPolicy) PrepareDomain(ctx context.Context, domain string) {} -func (l localPolicy) PrepareConn(ctx context.Context, mx string) {} +func (l *localPolicy) Reset(*module.MsgMetadata) {} +func (l *localPolicy) PrepareDomain(ctx context.Context, domain string) {} +func (l *localPolicy) PrepareConn(ctx context.Context, mx string) {} -func (l localPolicy) CheckMX(ctx context.Context, mxLevel module.MXLevel, domain, mx string, dnssec bool) (module.MXLevel, error) { +func (l *localPolicy) CheckMX(ctx context.Context, mxLevel module.MXLevel, domain, mx string, dnssec bool) (module.MXLevel, error) { if mxLevel < l.minMXLevel { return module.MXNone, &exterrors.SMTPError{ // Err on the side of caution if policy evaluation was messed up by @@ -618,7 +607,7 @@ func (l localPolicy) CheckMX(ctx context.Context, mxLevel module.MXLevel, domain return module.MXNone, nil } -func (l localPolicy) CheckConn(ctx context.Context, mxLevel module.MXLevel, tlsLevel module.TLSLevel, domain, mx string, tlsState tls.ConnectionState) (module.TLSLevel, error) { +func (l *localPolicy) CheckConn(ctx context.Context, mxLevel module.MXLevel, tlsLevel module.TLSLevel, domain, mx string, tlsState tls.ConnectionState) (module.TLSLevel, error) { if tlsLevel < l.minTLSLevel { return module.TLSNone, &exterrors.SMTPError{ Code: 451, diff --git a/internal/target/smtp/smtp_downstream.go b/internal/target/smtp/smtp_downstream.go index 8880f6cb..8c50b71b 100644 --- a/internal/target/smtp/smtp_downstream.go +++ b/internal/target/smtp/smtp_downstream.go @@ -48,10 +48,9 @@ import ( ) type Downstream struct { - modName string - instName string - lmtp bool - targetsArg []string + modName string + instName string + lmtp bool starttls bool hostname string @@ -76,17 +75,16 @@ func (u *Downstream) moduleError(err error) error { }) } -func NewDownstream(modName, instName string, _, inlineArgs []string) (module.Module, error) { +func NewDownstream(modName, instName string) (module.Module, error) { return &Downstream{ - modName: modName, - instName: instName, - lmtp: modName == "target.lmtp" || modName == "lmtp_downstream", /* compatibility with 0.3 configs */ - targetsArg: inlineArgs, - log: log.Logger{Name: modName}, + modName: modName, + instName: instName, + lmtp: modName == "target.lmtp" || modName == "lmtp_downstream", /* compatibility with 0.3 configs */ + log: log.Logger{Name: modName}, }, nil } -func (u *Downstream) Init(cfg *config.Map) error { +func (u *Downstream) Configure(inlineArgs []string, cfg *config.Map) error { var attemptTLS *bool var targetsArg []string @@ -142,8 +140,8 @@ func (u *Downstream) Init(cfg *config.Map) error { return fmt.Errorf("%s: cannot represent the hostname as an A-label name: %w", u.modName, err) } - u.targetsArg = append(u.targetsArg, targetsArg...) - for _, tgt := range u.targetsArg { + targetsArg = append(targetsArg, inlineArgs...) + for _, tgt := range targetsArg { endp, err := config.ParseEndpoint(tgt) if err != nil { return err @@ -183,8 +181,8 @@ type lmtpDelivery struct { *delivery } -func (u *Downstream) Start(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { - defer trace.StartRegion(ctx, "target.smtp/Start").End() +func (u *Downstream) StartDelivery(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { + defer trace.StartRegion(ctx, "target.smtp/StartDelivery").End() d := &delivery{ u: u, diff --git a/internal/target/smtp/smtputf8_test.go b/internal/target/smtp/smtputf8_test.go index 74aae232..f47fc778 100644 --- a/internal/target/smtp/smtputf8_test.go +++ b/internal/target/smtp/smtputf8_test.go @@ -30,11 +30,11 @@ func TestDownstreamDelivery_EHLO_ALabel(t *testing.T) { defer srv.Close() defer testutils.CheckSMTPConnLeak(t, srv) - mod, err := NewDownstream("", "", nil, []string{"tcp://127.0.0.1:" + testPort}) + mod, err := NewDownstream("", "") if err != nil { t.Fatal(err) } - if err := mod.Init(config.NewMap(nil, config.Node{ + if err := mod.Configure([]string{"tcp://127.0.0.1:" + testPort}, config.NewMap(nil, config.Node{ Children: []config.Node{ { Name: "hostname", diff --git a/internal/testutils/bench_delivery.go b/internal/testutils/bench_delivery.go index f434efc0..eedd0d2a 100644 --- a/internal/testutils/bench_delivery.go +++ b/internal/testutils/bench_delivery.go @@ -117,7 +117,7 @@ func BenchDelivery(b *testing.B, target module.DeliveryTarget, sender string, re b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - delivery, err := target.Start(benchCtx, &meta, sender) + delivery, err := target.StartDelivery(benchCtx, &meta, sender) if err != nil { b.Fatal(err) } diff --git a/internal/testutils/check.go b/internal/testutils/check.go index 399a78d4..2c44883d 100644 --- a/internal/testutils/check.go +++ b/internal/testutils/check.go @@ -54,7 +54,7 @@ func (c *Check) CheckStateForMsg(ctx context.Context, msgMeta *module.MsgMetadat return &checkState{msgMeta, c}, nil } -func (c *Check) Init(*config.Map) error { +func (c *Check) Configure([]string, *config.Map) error { return nil } @@ -104,8 +104,7 @@ func (cs *checkState) Close() error { } func init() { - module.Register("test_check", func(_, _ string, _, _ []string) (module.Module, error) { + module.Register("test_check", func(_, _ string) (module.Module, error) { return &Check{}, nil }) - module.RegisterInstance(&Check{}, nil) } diff --git a/internal/testutils/modifier.go b/internal/testutils/modifier.go index a96cbe48..1c6f135a 100644 --- a/internal/testutils/modifier.go +++ b/internal/testutils/modifier.go @@ -42,7 +42,7 @@ type Modifier struct { UnclosedStates int } -func (m Modifier) Init(*config.Map) error { +func (m Modifier) Configure([]string, *config.Map) error { return nil } @@ -115,8 +115,7 @@ func (ms modifierState) Close() error { } func init() { - module.Register("test_modifier", func(_, _ string, _, _ []string) (module.Module, error) { + module.Register("test_modifier", func(_, _ string) (module.Module, error) { return &Modifier{}, nil }) - module.RegisterInstance(&Modifier{}, nil) } diff --git a/internal/testutils/target.go b/internal/testutils/target.go index 68f5b394..19585215 100644 --- a/internal/testutils/target.go +++ b/internal/testutils/target.go @@ -62,7 +62,7 @@ type Target struct { module.Module is implemented with dummy functions for logging done by MsgPipeline code. */ -func (dt Target) Init(*config.Map) error { +func (dt Target) Configure([]string, *config.Map) error { return nil } @@ -86,7 +86,7 @@ type testTargetDeliveryPartial struct { testTargetDelivery } -func (dt *Target) Start(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { +func (dt *Target) StartDelivery(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { if dt.PartialBodyErr != nil { return &testTargetDeliveryPartial{ testTargetDelivery: testTargetDelivery{ @@ -211,10 +211,10 @@ func DoTestDeliveryNonAtomic(t *testing.T, c module.StatusCollector, tgt module. ID: encodedID, OriginalFrom: from, } - t.Log("-- tgt.Start", from) - delivery, err := tgt.Start(testCtx, &msgMeta, from) + t.Log("-- tgt.StartDelivery", from) + delivery, err := tgt.StartDelivery(testCtx, &msgMeta, from) if err != nil { - t.Log("-- ... tgt.Start", from, err, exterrors.Fields(err)) + t.Log("-- ... tgt.StartDelivery", from, err, exterrors.Fields(err)) t.Fatalf("Unexpected err: %v %+v", err, exterrors.Fields(err)) return encodedID } @@ -262,10 +262,10 @@ func DoTestDeliveryErrMeta(t *testing.T, tgt module.DeliveryTarget, from string, body := buffer.MemoryBuffer{Slice: []byte("foobar\r\n")} msgMeta.DontTraceSender = true msgMeta.ID = encodedID - t.Log("-- tgt.Start", from) - delivery, err := tgt.Start(testCtx, msgMeta, from) + t.Log("-- tgt.StartDelivery", from) + delivery, err := tgt.StartDelivery(testCtx, msgMeta, from) if err != nil { - t.Log("-- ... tgt.Start", from, err, exterrors.Fields(err)) + t.Log("-- ... tgt.StartDelivery", from, err, exterrors.Fields(err)) return encodedID, err } for _, rcpt := range to { diff --git a/internal/tls/acme/acme.go b/internal/tls/acme/acme.go index a09c3e09..0ebcd241 100644 --- a/internal/tls/acme/acme.go +++ b/internal/tls/acme/acme.go @@ -19,6 +19,7 @@ const modName = "tls.loader.acme" type Loader struct { instName string + names []string store certmagic.Storage cache *certmagic.Cache cfg *certmagic.Config @@ -27,17 +28,18 @@ type Loader struct { log log.Logger } -func New(_, instName string, _, inlineArgs []string) (module.Module, error) { - if len(inlineArgs) != 0 { - return nil, fmt.Errorf("%s: no inline args expected", modName) - } +func New(_, instName string) (module.Module, error) { return &Loader{ instName: instName, log: log.Logger{Name: modName}, }, nil } -func (l *Loader) Init(cfg *config.Map) error { +func (l *Loader) Configure(inlineArgs []string, cfg *config.Map) error { + if len(inlineArgs) != 0 { + return fmt.Errorf("%s: no inline args expected", modName) + } + var ( hostname string extraNames []string @@ -118,27 +120,28 @@ func (l *Loader) Init(cfg *config.Map) error { } l.cfg.Issuers = []certmagic.Issuer{issuer} - if module.NoRun { - return nil - } + l.names = append([]string{hostname}, extraNames...) + + return nil +} + +func (l *Loader) ConfigureTLS(c *tls.Config) error { + c.GetCertificate = l.cfg.GetCertificate + return nil +} +func (l *Loader) Start() error { manageCtx, cancelManage := context.WithCancel(context.Background()) - err := l.cfg.ManageAsync(manageCtx, append([]string{hostname}, extraNames...)) + err := l.cfg.ManageAsync(manageCtx, l.names) if err != nil { cancelManage() return err } l.cancelManage = cancelManage - - return nil -} - -func (l *Loader) ConfigureTLS(c *tls.Config) error { - c.GetCertificate = l.cfg.GetCertificate return nil } -func (l *Loader) Close() error { +func (l *Loader) Stop() error { l.cancelManage() l.cache.Stop() return nil diff --git a/internal/tls/file.go b/internal/tls/file.go index 943a59ed..ae260ec6 100644 --- a/internal/tls/file.go +++ b/internal/tls/file.go @@ -27,17 +27,15 @@ import ( "time" "github.com/foxcpp/maddy/framework/config" - "github.com/foxcpp/maddy/framework/hooks" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" ) type FileLoader struct { - instName string - inlineArgs []string - certPaths []string - keyPaths []string - log log.Logger + instName string + certPaths []string + keyPaths []string + log log.Logger certs []tls.Certificate certsLock sync.RWMutex @@ -46,16 +44,15 @@ type FileLoader struct { stopTick chan struct{} } -func NewFileLoader(_, instName string, _, inlineArgs []string) (module.Module, error) { +func NewFileLoader(_, instName string) (module.Module, error) { return &FileLoader{ - instName: instName, - inlineArgs: inlineArgs, - log: log.Logger{Name: "tls.loader.file", Debug: log.DefaultLogger.Debug}, - stopTick: make(chan struct{}), + instName: instName, + log: log.Logger{Name: "tls.loader.file", Debug: log.DefaultLogger.Debug}, + stopTick: make(chan struct{}), }, nil } -func (f *FileLoader) Init(cfg *config.Map) error { +func (f *FileLoader) Configure(inlineArgs []string, cfg *config.Map) error { cfg.StringList("certs", false, false, nil, &f.certPaths) cfg.StringList("keys", false, false, nil, &f.keyPaths) if _, err := cfg.Process(); err != nil { @@ -66,12 +63,12 @@ func (f *FileLoader) Init(cfg *config.Map) error { return errors.New("tls.loader.file: mismatch in certs and keys count") } - if len(f.inlineArgs)%2 != 0 { + if len(inlineArgs)%2 != 0 { return errors.New("tls.loader.file: odd amount of arguments") } - for i := 0; i < len(f.inlineArgs); i += 2 { - f.certPaths = append(f.certPaths, f.inlineArgs[i]) - f.keyPaths = append(f.keyPaths, f.inlineArgs[i+1]) + for i := 0; i < len(inlineArgs); i += 2 { + f.certPaths = append(f.certPaths, inlineArgs[i]) + f.keyPaths = append(f.keyPaths, inlineArgs[i+1]) } for _, certPath := range f.certPaths { @@ -84,19 +81,21 @@ func (f *FileLoader) Init(cfg *config.Map) error { return err } - hooks.AddHook(hooks.EventReload, func() { - f.log.Println("reloading certificates") - if err := f.loadCerts(); err != nil { - f.log.Error("reload failed", err) - } - }) + return nil +} +func (f *FileLoader) Start() error { f.reloadTick = time.NewTicker(time.Minute) go f.reloadTicker() return nil } -func (f *FileLoader) Close() error { +func (f *FileLoader) Reload() error { + f.log.Println("reloading certificates") + return f.loadCerts() +} + +func (f *FileLoader) Stop() error { f.reloadTick.Stop() f.stopTick <- struct{}{} return nil diff --git a/internal/tls/self_signed.go b/internal/tls/self_signed.go index d5e174f3..269b0c0d 100644 --- a/internal/tls/self_signed.go +++ b/internal/tls/self_signed.go @@ -40,14 +40,14 @@ type SelfSignedLoader struct { cert tls.Certificate } -func NewSelfSignedLoader(_, instName string, _, inlineArgs []string) (module.Module, error) { +func NewSelfSignedLoader(_, instName string) (module.Module, error) { return &SelfSignedLoader{ - instName: instName, - serverNames: inlineArgs, + instName: instName, }, nil } -func (f *SelfSignedLoader) Init(cfg *config.Map) error { +func (f *SelfSignedLoader) Configure(inlineArgs []string, cfg *config.Map) error { + f.serverNames = inlineArgs if _, err := cfg.Process(); err != nil { return err } diff --git a/maddy.go b/maddy.go index e6a05cf1..4b7204e1 100644 --- a/maddy.go +++ b/maddy.go @@ -21,7 +21,6 @@ package maddy import ( "errors" "fmt" - "io" "net/http" "os" "path/filepath" @@ -33,6 +32,7 @@ import ( "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" "github.com/foxcpp/maddy/framework/config/tls" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/hooks" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" @@ -110,6 +110,18 @@ func init() { Value: filepath.Join(ConfigDirectory, "maddy.conf"), }, ) + maddycli.AddSubcommand(&cli.Command{ + Name: "verify-config", + Usage: "Check configuration file for errors", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "debug", + Usage: "enable debug logging early", + Destination: &log.DefaultLogger.Debug, + }, + }, + Action: VerifyConfig, + }) maddycli.AddSubcommand(&cli.Command{ Name: "run", Usage: "Start the server", @@ -189,26 +201,27 @@ func Run(c *cli.Context) error { os.Setenv("PATH", config.LibexecDirectory+string(filepath.ListSeparator)+os.Getenv("PATH")) - f, err := os.Open(c.Path("config")) - if err != nil { - systemdStatusErr(err) - return cli.Exit(err.Error(), 2) - } - defer f.Close() + hooks.AddHook(hooks.EventLogRotate, reinitLogging) + defer log.DefaultLogger.Out.Close() + defer hooks.RunHooks(hooks.EventShutdown) - cfg, err := parser.Read(f, c.Path("config")) - if err != nil { + if err := moduleMain(c.Path("config")); err != nil { systemdStatusErr(err) - return cli.Exit(err.Error(), 2) + return cli.Exit(err.Error(), 1) } - defer log.DefaultLogger.Out.Close() + return nil +} - if err := moduleMain(cfg); err != nil { - systemdStatusErr(err) - return cli.Exit(err.Error(), 1) +func VerifyConfig(c *cli.Context) error { + os.Setenv("PATH", config.LibexecDirectory+string(filepath.ListSeparator)+os.Getenv("PATH")) + + if _, err := moduleConfigure(c.Path("config")); err != nil { + return cli.Exit(err.Error(), 2) } + log.DefaultLogger.Msg("No errors detected") + return nil } @@ -235,42 +248,46 @@ func initDebug(c *cli.Context) { } } -func InitDirs() error { - if config.StateDirectory == "" { - config.StateDirectory = DefaultStateDirectory +func InitDirs(c *container.C) error { + if c.Config.StateDirectory == "" { + c.Config.StateDirectory = DefaultStateDirectory } - if config.RuntimeDirectory == "" { - config.RuntimeDirectory = DefaultRuntimeDirectory + if c.Config.RuntimeDirectory == "" { + c.Config.RuntimeDirectory = DefaultRuntimeDirectory } - if config.LibexecDirectory == "" { - config.LibexecDirectory = DefaultLibexecDirectory + if c.Config.LibexecDirectory == "" { + c.Config.LibexecDirectory = DefaultLibexecDirectory } - if err := ensureDirectoryWritable(config.StateDirectory); err != nil { + if err := ensureDirectoryWritable(c.Config.StateDirectory); err != nil { return err } - if err := ensureDirectoryWritable(config.RuntimeDirectory); err != nil { + if err := ensureDirectoryWritable(c.Config.RuntimeDirectory); err != nil { return err } // Make sure all paths we are going to use are absolute // before we change the working directory. - if !filepath.IsAbs(config.StateDirectory) { + if !filepath.IsAbs(c.Config.StateDirectory) { return errors.New("statedir should be absolute") } - if !filepath.IsAbs(config.RuntimeDirectory) { + if !filepath.IsAbs(c.Config.RuntimeDirectory) { return errors.New("runtimedir should be absolute") } - if !filepath.IsAbs(config.LibexecDirectory) { + if !filepath.IsAbs(c.Config.LibexecDirectory) { return errors.New("-libexec should be absolute") } // Change the working directory to make all relative paths // in configuration relative to state directory. - if err := os.Chdir(config.StateDirectory); err != nil { + if err := os.Chdir(c.Config.StateDirectory); err != nil { log.Println(err) } + config.StateDirectory = c.Config.StateDirectory + config.RuntimeDirectory = c.Config.RuntimeDirectory + config.LibexecDirectory = c.Config.LibexecDirectory + return nil } @@ -287,10 +304,10 @@ func ensureDirectoryWritable(path string) error { return os.Remove(testFile.Name()) } -func ReadGlobals(cfg []config.Node) (map[string]interface{}, []config.Node, error) { +func ReadGlobals(c *container.C, cfg []config.Node) (map[string]interface{}, []config.Node, error) { globals := config.NewMap(nil, config.Node{Children: cfg}) - globals.String("state_dir", false, false, DefaultStateDirectory, &config.StateDirectory) - globals.String("runtime_dir", false, false, DefaultRuntimeDirectory, &config.RuntimeDirectory) + globals.String("state_dir", false, false, DefaultStateDirectory, &c.Config.StateDirectory) + globals.String("runtime_dir", false, false, DefaultRuntimeDirectory, &c.Config.RuntimeDirectory) globals.String("hostname", false, false, "", nil) globals.String("autogenerated_msg_domain", false, false, "", nil) globals.Custom("tls", false, false, nil, tls.TLSDirective, nil) @@ -298,8 +315,8 @@ func ReadGlobals(cfg []config.Node) (map[string]interface{}, []config.Node, erro globals.Bool("storage_perdomain", false, false, nil) globals.Bool("auth_perdomain", false, false, nil) globals.StringList("auth_domains", false, false, nil, nil) - globals.Custom("log", false, false, defaultLogOutput, logOutput, &log.DefaultLogger.Out) - globals.Bool("debug", false, log.DefaultLogger.Debug, &log.DefaultLogger.Debug) + globals.Custom("log", false, false, defaultLogOutput, logOutput, &c.DefaultLogger.Out) + globals.Bool("debug", false, log.DefaultLogger.Debug, &c.DefaultLogger.Debug) config.EnumMapped(globals, "auth_map_normalize", true, false, authz.NormalizeFuncs, authz.NormalizeAuto, nil) modconfig.Table(globals, "auth_map", true, false, nil, nil) globals.AllowUnknown() @@ -307,46 +324,86 @@ func ReadGlobals(cfg []config.Node) (map[string]interface{}, []config.Node, erro return globals.Values, unknown, err } -func moduleMain(cfg []config.Node) error { - globals, modBlocks, err := ReadGlobals(cfg) +func ReadConfig(path string) ([]config.Node, error) { + f, err := os.Open(path) if err != nil { - return err + return nil, err + } + defer f.Close() + + return parser.Read(f, path) +} + +func moduleConfigure(configPath string) (*container.C, error) { + c := container.New() + container.Global = c + + cfg, err := ReadConfig(configPath) + if err != nil { + return nil, fmt.Errorf("failed to read config %s: %w", configPath, err) } - if err := InitDirs(); err != nil { - return err + globals, modBlocks, err := ReadGlobals(c, cfg) + if err != nil { + return nil, err } - hooks.AddHook(hooks.EventLogRotate, reinitLogging) + if err := InitDirs(c); err != nil { + return nil, err + } - endpoints, mods, err := RegisterModules(globals, modBlocks) + err = RegisterModules(c, globals, modBlocks) if err != nil { - return err + return nil, err + } + + for _, inst := range c.Modules.NotInitialized() { + return nil, fmt.Errorf("unused configuration block %s (%s)", + inst.InstanceName(), inst.Name()) } - err = initModules(globals, endpoints, mods) + return c, nil +} + +func moduleStart(c *container.C) error { + return c.Lifetime.StartAll() +} + +func moduleStop(c *container.C) { + c.Lifetime.StopAll() +} + +func moduleMain(configPath string) error { + log.DefaultLogger.Msg("loading configuration...") + + c, err := moduleConfigure(configPath) if err != nil { return err } - systemdStatus(SDReady, "Listening for incoming connections...") + c.DefaultLogger.Msg("configuration loaded") + if err := moduleStart(c); err != nil { + return err + } + c.DefaultLogger.Msg("server started", "version", Version) + + systemdStatus(SDReady, "Listening for incoming connections...") handleSignals() + c.DefaultLogger.Msg("server stopping...") systemdStatus(SDStopping, "Waiting for running transactions to complete...") - hooks.RunHooks(hooks.EventShutdown) - + moduleStop(c) + c.DefaultLogger.Msg("server stopped") return nil } -type ModInfo struct { - Instance module.Module - Cfg config.Node -} - -func RegisterModules(globals map[string]interface{}, nodes []config.Node) (endpoints, mods []ModInfo, err error) { - mods = make([]ModInfo, 0, len(nodes)) +func RegisterModules(c *container.C, globals map[string]interface{}, nodes []config.Node) (err error) { + var endpoints []struct { + Endpoint module.LifetimeModule + Cfg *config.Map + } for _, block := range nodes { var instName string @@ -364,70 +421,66 @@ func RegisterModules(globals map[string]interface{}, nodes []config.Node) (endpo if endpFactory != nil { inst, err := endpFactory(modName, block.Args) if err != nil { - return nil, nil, err + return err } - endpoints = append(endpoints, ModInfo{Instance: inst, Cfg: block}) + endpoints = append(endpoints, struct { + Endpoint module.LifetimeModule + Cfg *config.Map + }{Endpoint: inst, Cfg: config.NewMap(globals, block)}) continue } factory := module.Get(modName) if factory == nil { - return nil, nil, config.NodeErr(block, "unknown module or global directive: %s", modName) + return config.NodeErr(block, "unknown module or global directive: %s", modName) } - if module.HasInstance(instName) { - return nil, nil, config.NodeErr(block, "config block named %s already exists", instName) + inst, err := factory(modName, instName) + if err != nil { + return err } - inst, err := factory(modName, instName, modAliases, nil) + err = c.Modules.Register(inst, func() error { + err := inst.Configure(nil, config.NewMap(globals, block)) + if err != nil { + return err + } + + if lt, ok := inst.(module.LifetimeModule); ok { + c.Lifetime.Add(lt) + } + return nil + }) if err != nil { - return nil, nil, err + if errors.Is(err, module.ErrInstanceNameDuplicate) { + return config.NodeErr(block, "config block named %s already exists", inst.InstanceName()) + } + return err } - module.RegisterInstance(inst, config.NewMap(globals, block)) for _, alias := range modAliases { - if module.HasInstance(alias) { - return nil, nil, config.NodeErr(block, "config block named %s already exists", alias) + if err := c.Modules.AddAlias(instName, alias); err != nil { + if errors.Is(err, module.ErrInstanceNameDuplicate) { + return config.NodeErr(block, "config block named %s already exists", alias) + } + return err } - module.RegisterAlias(alias, instName) } log.Debugf("%v:%v: register config block %v %v", block.File, block.Line, instName, modAliases) - mods = append(mods, ModInfo{Instance: inst, Cfg: block}) } if len(endpoints) == 0 { - return nil, nil, fmt.Errorf("at least one endpoint should be configured") + return fmt.Errorf("at least one endpoint should be configured") } - return endpoints, mods, nil -} - -func initModules(globals map[string]interface{}, endpoints, mods []ModInfo) error { + // Endpoints are configured directly after registration. for _, endp := range endpoints { - if err := endp.Instance.Init(config.NewMap(globals, endp.Cfg)); err != nil { + if err := endp.Endpoint.Configure(nil, endp.Cfg); err != nil { return err } - - if closer, ok := endp.Instance.(io.Closer); ok { - endp := endp - hooks.AddHook(hooks.EventShutdown, func() { - log.Debugf("close %s (%s)", endp.Instance.Name(), endp.Instance.InstanceName()) - if err := closer.Close(); err != nil { - log.Printf("module %s (%s) close failed: %v", endp.Instance.Name(), endp.Instance.InstanceName(), err) - } - }) - } - } - - for _, inst := range mods { - if module.Initialized[inst.Instance.InstanceName()] { - continue - } - - return fmt.Errorf("Unused configuration block at %s:%d - %s (%s)", - inst.Cfg.File, inst.Cfg.Line, inst.Instance.InstanceName(), inst.Instance.Name()) + c.Lifetime.Add(endp.Endpoint) } return nil diff --git a/signal_nonposix.go b/signal_nonposix.go index 87ea0cd8..f5750a61 100644 --- a/signal_nonposix.go +++ b/signal_nonposix.go @@ -40,6 +40,6 @@ func handleSignals() os.Signal { os.Exit(1) }() - log.Printf("signal received (%v), next signal will force immediate shutdown.", s) + log.Printf("signal received (%v)", s) return s } From 3ce6ebf60c25114a91685cbcbd39bce0e3222b1a Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月29日 21:41:29 +0300 Subject: [PATCH 040/111] Add code to trigger module reinitialization on SIGUSR2 --- maddy.go | 40 +++++++++++++++++++++++++++++++++++++++- signal.go | 11 +++++------ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/maddy.go b/maddy.go index 4b7204e1..829cdf76 100644 --- a/maddy.go +++ b/maddy.go @@ -376,6 +376,12 @@ func moduleStop(c *container.C) { func moduleMain(configPath string) error { log.DefaultLogger.Msg("loading configuration...") + // Make path absolute to make sure we can still read it if current directory changes (in moduleConfigure). + configPath, err := filepath.Abs(configPath) + if err != nil { + return err + } + c, err := moduleConfigure(configPath) if err != nil { return err @@ -389,7 +395,12 @@ func moduleMain(configPath string) error { c.DefaultLogger.Msg("server started", "version", Version) systemdStatus(SDReady, "Listening for incoming connections...") - handleSignals() + for handleSignals() { + systemdStatus(SDReloading, "Reloading state...") + hooks.RunHooks(hooks.EventReload) + + c = moduleReload(c, configPath) + } c.DefaultLogger.Msg("server stopping...") systemdStatus(SDStopping, "Waiting for running transactions to complete...") @@ -399,6 +410,33 @@ func moduleMain(configPath string) error { return nil } +func moduleReload(oldContainer *container.C, configPath string) *container.C { + oldContainer.DefaultLogger.Msg("reloading server...") + + oldContainer.DefaultLogger.Msg("loading new configuration...") + newContainer, err := moduleConfigure(configPath) + if err != nil { + oldContainer.DefaultLogger.Error("failed to load new configuration", err) + return oldContainer + } + + oldContainer.DefaultLogger.Msg("configuration loaded") + + oldContainer.DefaultLogger.Msg("starting new server") + if err := moduleStart(newContainer); err != nil { + oldContainer.DefaultLogger.Error("failed to start new server", err) + container.Global = oldContainer + return oldContainer + } + + newContainer.DefaultLogger.Msg("server started", "version", Version) + oldContainer.DefaultLogger.Msg("stopping server") + moduleStop(oldContainer) + oldContainer.DefaultLogger.Msg("server stopped") + + return newContainer +} + func RegisterModules(c *container.C, globals map[string]interface{}, nodes []config.Node) (err error) { var endpoints []struct { Endpoint module.LifetimeModule diff --git a/signal.go b/signal.go index e952fcf6..4925b3db 100644 --- a/signal.go +++ b/signal.go @@ -36,9 +36,10 @@ import ( // (SIGTERM, SIGHUP, SIGINT) will cause this function to return. // // SIGUSR1 will call reinitLogging without returning. -func handleSignals() os.Signal { +func handleSignals() (reload bool) { sig := make(chan os.Signal, 5) signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGINT, syscall.SIGUSR1, syscall.SIGUSR2) + defer signal.Stop(sig) for { switch s := <-sig; s { @@ -48,10 +49,8 @@ func handleSignals() os.Signal { hooks.RunHooks(hooks.EventLogRotate) systemdStatus(SDReady, "Listening for incoming connections...") case syscall.SIGUSR2: - log.Printf("signal received (%s), reloading state", s.String()) - systemdStatus(SDReloading, "Reloading state...") - hooks.RunHooks(hooks.EventReload) - systemdStatus(SDReady, "Listening for incoming connections...") + log.Printf("signal received (%s), reloading configuration", s.String()) + return true default: go func() { s := handleSignals() @@ -60,7 +59,7 @@ func handleSignals() os.Signal { }() log.Printf("signal received (%v), next signal will force immediate shutdown.", s) - return s + return false } } } From 187fc66be1495349dee2c4993781398bf0654f0a Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月29日 23:38:08 +0300 Subject: [PATCH 041/111] Fix-up c48332a9401151a93df75fae55dd34bea2cafc56 --- internal/auth/sasl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/auth/sasl.go b/internal/auth/sasl.go index 5968c3c0..21a3d897 100644 --- a/internal/auth/sasl.go +++ b/internal/auth/sasl.go @@ -166,7 +166,7 @@ func (s *SASLAuth) CreateSASL(mech string, remoteAddr net.Addr, successCb func(i return err } - err := s.AuthPlain(username, password) + err = s.AuthPlain(username, password) if err != nil { s.Log.Error("authentication failed", err, "username", username, "src_ip", remoteAddr) return ErrInvalidAuthCred From f82742b392bfc459dc767ead2636a9df60e76356 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月30日 01:45:19 +0300 Subject: [PATCH 042/111] Implement net.Listener hand-over during config reload Log library refactored a little to make it easier to enable debug logging. --- framework/log/log.go | 67 ++++++++------ framework/log/orderedjson.go | 2 +- framework/log/zap.go | 2 +- framework/module/lifetime.go | 4 +- framework/module/registry.go | 4 +- framework/resource/netresource/dup.go | 27 ++++++ framework/resource/netresource/fd.go | 47 ++++++++++ framework/resource/netresource/listen.go | 38 ++++++++ framework/resource/netresource/tracker.go | 91 +++++++++++++++++++ framework/resource/resource.go | 18 ++++ framework/resource/singleton.go | 64 +++++++++++++ framework/resource/tracker.go | 47 ++++++++++ internal/auth/netauth/netauth.go | 2 +- .../endpoint/dovecot_sasld/dovecot_sasl.go | 5 +- internal/endpoint/imap/imap.go | 5 +- internal/endpoint/openmetrics/om.go | 4 +- internal/endpoint/smtp/smtp.go | 5 +- internal/updatepipe/unix_pipe.go | 3 +- maddy.go | 3 + 19 files changed, 394 insertions(+), 44 deletions(-) create mode 100644 framework/resource/netresource/dup.go create mode 100644 framework/resource/netresource/fd.go create mode 100644 framework/resource/netresource/listen.go create mode 100644 framework/resource/netresource/tracker.go create mode 100644 framework/resource/resource.go create mode 100644 framework/resource/singleton.go create mode 100644 framework/resource/tracker.go diff --git a/framework/log/log.go b/framework/log/log.go index da8647f6..366cf156 100644 --- a/framework/log/log.go +++ b/framework/log/log.go @@ -42,6 +42,8 @@ import ( // No serialization is provided by Logger, its log.Output responsibility to // ensure goroutine-safety if necessary. type Logger struct { + Parent *Logger + Out Output Name string Debug bool @@ -51,30 +53,37 @@ type Logger struct { Fields map[string]interface{} } -func (l Logger) Zap() *zap.Logger { +func (l *Logger) Zap() *zap.Logger { // TODO: Migrate to using zap natively. return zap.New(zapLogger{L: l}) } -func (l Logger) Debugf(format string, val ...interface{}) { - if !l.Debug { +func (l *Logger) IsDebug() bool { + if l.Parent == nil { + return l.Debug + } + return l.Debug || l.Parent.IsDebug() +} + +func (l *Logger) Debugf(format string, val ...interface{}) { + if !l.IsDebug() { return } l.log(true, l.formatMsg(fmt.Sprintf(format, val...), nil)) } -func (l Logger) Debugln(val ...interface{}) { - if !l.Debug { +func (l *Logger) Debugln(val ...interface{}) { + if !l.IsDebug() { return } l.log(true, l.formatMsg(strings.TrimRight(fmt.Sprintln(val...), "\n"), nil)) } -func (l Logger) Printf(format string, val ...interface{}) { +func (l *Logger) Printf(format string, val ...interface{}) { l.log(false, l.formatMsg(fmt.Sprintf(format, val...), nil)) } -func (l Logger) Println(val ...interface{}) { +func (l *Logger) Println(val ...interface{}) { l.log(false, l.formatMsg(strings.TrimRight(fmt.Sprintln(val...), "\n"), nil)) } @@ -87,13 +96,13 @@ func (l Logger) Println(val ...interface{}) { // followed by corresponding values. That is, for example, []interface{"key", // "value", "key2", "value2"}. // -// If value in fields implements LogFormatter, it will be represented by the +// If value in fields implements Formatter, it will be represented by the // string returned by FormatLog method. Same goes for fmt.Stringer and error // interfaces. // // Additionally, time.Time is written as a string in ISO 8601 format. // time.Duration follows fmt.Stringer rule above. -func (l Logger) Msg(msg string, fields ...interface{}) { +func (l *Logger) Msg(msg string, fields ...interface{}) { m := make(map[string]interface{}, len(fields)/2) fieldsToMap(fields, m) l.log(false, l.formatMsg(msg, m)) @@ -112,7 +121,7 @@ func (l Logger) Msg(msg string, fields ...interface{}) { // In the context of Error method, "msg" typically indicates the top-level // context in which the error is *handled*. For example, if error leads to // rejection of SMTP DATA command, msg will probably be "DATA error". -func (l Logger) Error(msg string, err error, fields ...interface{}) { +func (l *Logger) Error(msg string, err error, fields ...interface{}) { if err == nil { return } @@ -133,8 +142,8 @@ func (l Logger) Error(msg string, err error, fields ...interface{}) { l.log(false, l.formatMsg(msg, allFields)) } -func (l Logger) DebugMsg(kind string, fields ...interface{}) { - if !l.Debug { +func (l *Logger) DebugMsg(kind string, fields ...interface{}) { + if !l.IsDebug() { return } m := make(map[string]interface{}, len(fields)/2) @@ -162,7 +171,7 @@ func fieldsToMap(fields []interface{}, out map[string]interface{}) { } } -func (l Logger) formatMsg(msg string, fields map[string]interface{}) string { +func (l *Logger) formatMsg(msg string, fields map[string]interface{}) string { formatted := strings.Builder{} formatted.WriteString(msg) @@ -184,14 +193,17 @@ func (l Logger) formatMsg(msg string, fields map[string]interface{}) string { return formatted.String() } -type LogFormatter interface { +type Formatter interface { FormatLog() string } // Write implements io.Writer, all bytes sent // to it will be written as a separate log messages. // No line-buffering is done. -func (l Logger) Write(s []byte) (int, error) { +func (l *Logger) Write(s []byte) (int, error) { + if !l.IsDebug() { + return len(s), nil + } l.log(false, strings.TrimRight(string(s), "\n")) return len(s), nil } @@ -199,15 +211,13 @@ func (l Logger) Write(s []byte) (int, error) { // DebugWriter returns a writer that will act like Logger.Write // but will use debug flag on messages. If Logger.Debug is false, // Write method of returned object will be no-op. -func (l Logger) DebugWriter() io.Writer { - if !l.Debug { - return io.Discard - } - l.Debug = true - return &l +func (l *Logger) DebugWriter() io.Writer { + l2 := l.Sublogger("") + l2.Debug = true + return l2 } -func (l Logger) log(debug bool, s string) { +func (l *Logger) log(debug bool, s string) { if l.Name != "" { s = l.Name + ": " + s } @@ -224,14 +234,15 @@ func (l Logger) log(debug bool, s string) { // Logging is disabled - do nothing. } -func (l Logger) Sublogger(name string) Logger { - if l.Name != "" { +func (l *Logger) Sublogger(name string) *Logger { + if l.Name != "" && name != "" { name = l.Name + "/" + name } - return Logger{ - Out: l.Out, - Name: name, - Debug: l.Debug, + return &Logger{ + Parent: l, + Out: l.Out, + Name: name, + Debug: l.Debug, } } diff --git a/framework/log/orderedjson.go b/framework/log/orderedjson.go index 834f3a6c..9de32baf 100644 --- a/framework/log/orderedjson.go +++ b/framework/log/orderedjson.go @@ -58,7 +58,7 @@ func marshalOrderedJSON(output *strings.Builder, m map[string]interface{}) error val = casted.Format("2006-01-02T15:04:05.000") case time.Duration: val = casted.String() - case LogFormatter: + case Formatter: val = casted.FormatLog() case fmt.Stringer: val = casted.String() diff --git a/framework/log/zap.go b/framework/log/zap.go index 23821f84..893bf484 100644 --- a/framework/log/zap.go +++ b/framework/log/zap.go @@ -7,7 +7,7 @@ import ( // TODO: Migrate to using actual zapcore to improve logging performance type zapLogger struct { - L Logger + L *Logger } func (l zapLogger) Enabled(level zapcore.Level) bool { diff --git a/framework/module/lifetime.go b/framework/module/lifetime.go index 1b0339da..ede5d2b4 100644 --- a/framework/module/lifetime.go +++ b/framework/module/lifetime.go @@ -38,7 +38,7 @@ type ReloadModule interface { } type LifetimeTracker struct { - logger log.Logger + logger *log.Logger instances []*struct { mod LifetimeModule started bool @@ -114,7 +114,7 @@ func (lt *LifetimeTracker) StopAll() error { return nil } -func NewLifetime(log log.Logger) *LifetimeTracker { +func NewLifetime(log *log.Logger) *LifetimeTracker { return &LifetimeTracker{ logger: log, } diff --git a/framework/module/registry.go b/framework/module/registry.go index ec62edf7..ce133402 100644 --- a/framework/module/registry.go +++ b/framework/module/registry.go @@ -35,14 +35,14 @@ type registryEntry struct { } type Registry struct { - logger log.Logger + logger *log.Logger instances map[string]registryEntry initialized map[string]struct{} started map[string]struct{} aliases map[string]string } -func NewRegistry(log log.Logger) *Registry { +func NewRegistry(log *log.Logger) *Registry { return &Registry{ logger: log, instances: make(map[string]registryEntry), diff --git a/framework/resource/netresource/dup.go b/framework/resource/netresource/dup.go new file mode 100644 index 00000000..00d047dc --- /dev/null +++ b/framework/resource/netresource/dup.go @@ -0,0 +1,27 @@ +package netresource + +import "net" + +func dupTCPListener(l *net.TCPListener) (*net.TCPListener, error) { + f, err := l.File() + if err != nil { + return nil, err + } + l2, err := net.FileListener(f) + if err != nil { + return nil, err + } + return l2.(*net.TCPListener), nil +} + +func dupUnixListener(l *net.UnixListener) (*net.UnixListener, error) { + f, err := l.File() + if err != nil { + return nil, err + } + l2, err := net.FileListener(f) + if err != nil { + return nil, err + } + return l2.(*net.UnixListener), nil +} diff --git a/framework/resource/netresource/fd.go b/framework/resource/netresource/fd.go new file mode 100644 index 00000000..395ddbcc --- /dev/null +++ b/framework/resource/netresource/fd.go @@ -0,0 +1,47 @@ +package netresource + +import ( + "errors" + "fmt" + "net" + "os" + "strconv" + "strings" +) + +func ListenFD(fd uint) (net.Listener, error) { + file := os.NewFile(uintptr(fd), strconv.FormatUint(uint64(fd), 10)) + defer file.Close() + return net.FileListener(file) +} + +func ListenFDName(name string) (net.Listener, error) { + listenPDStr := os.Getenv("LISTEN_PID") + if listenPDStr == "" { + return nil, errors.New("$LISTEN_PID is not set") + } + listenPid, err := strconv.Atoi(listenPDStr) + if err != nil { + return nil, errors.New("$LISTEN_PID is not integer") + } + if listenPid != os.Getpid() { + return nil, fmt.Errorf("$LISTEN_PID (%d) is not our PID (%d)", listenPid, os.Getpid()) + } + + names := strings.Split(os.Getenv("LISTEN_FDNAMES"), ":") + fd := uintptr(0) + for i, fdName := range names { + if fdName == name { + fd = uintptr(3 + i) + break + } + } + + if fd == 0 { + return nil, fmt.Errorf("name %s not found in $LISTEN_FDNAMES", name) + } + + file := os.NewFile(3+fd, name) + defer file.Close() + return net.FileListener(file) +} diff --git a/framework/resource/netresource/listen.go b/framework/resource/netresource/listen.go new file mode 100644 index 00000000..e2328002 --- /dev/null +++ b/framework/resource/netresource/listen.go @@ -0,0 +1,38 @@ +package netresource + +import ( + "fmt" + "net" + "strconv" + + "github.com/foxcpp/maddy/framework/log" +) + +var ( + tracker = NewListenerTracker(log.DefaultLogger.Sublogger("netresource")) +) + +func CloseUnusedListeners() error { + return tracker.Close() +} + +func ResetListenersUsage() { + tracker.ResetUsage() +} + +func Listen(network, addr string) (net.Listener, error) { + switch network { + case "fd": + fd, err := strconv.ParseUint(addr, 10, strconv.IntSize) + if err != nil { + return nil, fmt.Errorf("invalid FD number: %v", addr) + } + return ListenFD(uint(fd)) + case "fdname": + return ListenFDName(addr) + case "tcp", "tcp4", "tcp6", "unix": + return tracker.Get(network, addr) + default: + return nil, fmt.Errorf("unsupported network: %v", network) + } +} diff --git a/framework/resource/netresource/tracker.go b/framework/resource/netresource/tracker.go new file mode 100644 index 00000000..97989c62 --- /dev/null +++ b/framework/resource/netresource/tracker.go @@ -0,0 +1,91 @@ +package netresource + +import ( + "fmt" + "net" + "net/netip" + + "github.com/foxcpp/maddy/framework/log" + "github.com/foxcpp/maddy/framework/resource" +) + +type ListenerTracker struct { + logger *log.Logger + tcp *resource.Tracker[*net.TCPListener] + unix *resource.Tracker[*net.UnixListener] +} + +func (lt *ListenerTracker) Get(network, addr string) (net.Listener, error) { + switch network { + case "tcp", "tcp4", "tcp6": + l, err := lt.tcp.GetOpen(addr, func() (*net.TCPListener, error) { + addrPort, err := netip.ParseAddrPort(addr) + if err != nil { + return nil, err + } + lt.logger.DebugMsg("new listener", "network", network, "address", addr) + return net.ListenTCP(network, net.TCPAddrFromAddrPort(addrPort)) + }) + if err != nil { + return nil, err + } + + // We return duplicated listener so when listener is closed by user endpoint + // the tracked resource remains available and listening on the port doesn't + // actually stop. + l2, err := dupTCPListener(l) + if err != nil { + return nil, err + } + return l2, nil + case "unix": + l, err := lt.unix.GetOpen(addr, func() (*net.UnixListener, error) { + addr, err := net.ResolveUnixAddr(network, addr) + if err != nil { + return nil, err + } + lt.logger.DebugMsg("new listener", "network", network, "address", addr) + return net.ListenUnix(network, addr) + }) + if err != nil { + return nil, err + } + + l2, err := dupUnixListener(l) + if err != nil { + return nil, err + } + return l2, nil + default: + return nil, fmt.Errorf("unsupported network type: %s", network) + } +} + +func (lt *ListenerTracker) ResetUsage() { + lt.tcp.MarkAllUnused() + lt.unix.MarkAllUnused() +} + +func (lt *ListenerTracker) CloseUnused() error { + lt.tcp.CloseUnused(func(key string) bool { + return false + }) + lt.unix.CloseUnused(func(key string) bool { + return false + }) + return nil +} + +func (lt *ListenerTracker) Close() error { + lt.tcp.Close() + lt.unix.Close() + return nil +} + +func NewListenerTracker(log *log.Logger) *ListenerTracker { + return &ListenerTracker{ + logger: log, + tcp: resource.NewTracker[*net.TCPListener](resource.NewSingleton[*net.TCPListener]()), + unix: resource.NewTracker[*net.UnixListener](resource.NewSingleton[*net.UnixListener]()), + } +} diff --git a/framework/resource/resource.go b/framework/resource/resource.go new file mode 100644 index 00000000..a34942a6 --- /dev/null +++ b/framework/resource/resource.go @@ -0,0 +1,18 @@ +package resource + +import ( + "io" +) + +type Resource = io.Closer + +type CheckableResource interface { + Resource + IsUsable() bool +} + +type Container[T Resource] interface { + io.Closer + GetOpen(key string, open func() (T, error)) (T, error) + CloseUnused(isUsed func(key string) bool) error +} diff --git a/framework/resource/singleton.go b/framework/resource/singleton.go new file mode 100644 index 00000000..2614cee2 --- /dev/null +++ b/framework/resource/singleton.go @@ -0,0 +1,64 @@ +package resource + +import ( + "sync" +) + +// Singleton represents a set of resources identified by an unique key. +type Singleton[T Resource] struct { + lock sync.RWMutex + resources map[string]T +} + +func NewSingleton[T Resource]() *Singleton[T] { + return &Singleton[T]{ + resources: make(map[string]T), + } +} + +func (s *Singleton[T]) GetOpen(key string, open func() (T, error)) (T, error) { + s.lock.Lock() + defer s.lock.Unlock() + + existing, ok := s.resources[key] + if ok { + return existing, nil + } + + res, err := open() + if err != nil { + var empty T + return empty, err + } + + s.resources[key] = res + + return res, nil +} + +func (s *Singleton[T]) CloseUnused(isUsed func(key string) bool) error { + s.lock.Lock() + defer s.lock.Unlock() + + for key, res := range s.resources { + if isUsed(key) { + continue + } + res.Close() + delete(s.resources, key) + } + + return nil +} + +func (s *Singleton[T]) Close() error { + s.lock.Lock() + defer s.lock.Unlock() + + for key, res := range s.resources { + res.Close() + delete(s.resources, key) + } + + return nil +} diff --git a/framework/resource/tracker.go b/framework/resource/tracker.go new file mode 100644 index 00000000..318cf298 --- /dev/null +++ b/framework/resource/tracker.go @@ -0,0 +1,47 @@ +package resource + +import ( + "sync" +) + +// Tracker is a container wrapper that tracks whether resources were used since +// last MarkAllUnused call. +type Tracker[T Resource] struct { + C Container[T] + + usedLock sync.Mutex + used map[string]bool +} + +func NewTracker[T Resource](c Container[T]) *Tracker[T] { + return &Tracker[T]{C: c, used: make(map[string]bool)} +} + +func (t *Tracker[T]) Close() error { + return t.C.Close() +} + +func (t *Tracker[T]) MarkAllUnused() { + t.usedLock.Lock() + defer t.usedLock.Unlock() + + t.used = make(map[string]bool) +} + +func (t *Tracker[T]) GetOpen(key string, open func() (T, error)) (T, error) { + t.usedLock.Lock() + t.used[key] = true + t.usedLock.Unlock() + + return t.C.GetOpen(key, open) +} + +func (t *Tracker[T]) CloseUnused(isUsed func(key string) bool) error { + t.usedLock.Lock() + defer t.usedLock.Unlock() + + return t.C.CloseUnused(func(key string) bool { + used := t.used[key] + return used && isUsed(key) + }) +} diff --git a/internal/auth/netauth/netauth.go b/internal/auth/netauth/netauth.go index 62d6c6cc..db84cd18 100644 --- a/internal/auth/netauth/netauth.go +++ b/internal/auth/netauth/netauth.go @@ -42,7 +42,7 @@ func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { return fmt.Errorf("%s: inline arguments are not used", modName) } - l := hclog.New(&hclog.LoggerOptions{Output: a.log}) + l := hclog.New(&hclog.LoggerOptions{Output: &a.log}) n, err := netauth.NewWithLog(l) if err != nil { return err diff --git a/internal/endpoint/dovecot_sasld/dovecot_sasl.go b/internal/endpoint/dovecot_sasld/dovecot_sasl.go index b1bcd159..f4dee1a7 100644 --- a/internal/endpoint/dovecot_sasld/dovecot_sasl.go +++ b/internal/endpoint/dovecot_sasld/dovecot_sasl.go @@ -31,6 +31,7 @@ import ( modconfig "github.com/foxcpp/maddy/framework/config/module" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/resource/netresource" "github.com/foxcpp/maddy/internal/auth" "github.com/foxcpp/maddy/internal/authz" ) @@ -79,7 +80,7 @@ func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { } endp.srv = dovecotsasl.NewServer() - endp.srv.Log = stdlog.New(endp.log, "", 0) + endp.srv.Log = stdlog.New(&endp.log, "", 0) for _, mech := range endp.saslAuth.SASLMechanisms() { endp.srv.AddMechanism(mech, mechInfo[mech], func(req *dovecotsasl.AuthReq) sasl.Server { @@ -106,7 +107,7 @@ func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { func (endp *Endpoint) Start() error { for _, addr := range endp.endpoints { - l, err := net.Listen(addr.Network(), addr.Address()) + l, err := netresource.Listen(addr.Network(), addr.Address()) if err != nil { return fmt.Errorf("%s: %v", modName, err) } diff --git a/internal/endpoint/imap/imap.go b/internal/endpoint/imap/imap.go index 765c429f..13116225 100644 --- a/internal/endpoint/imap/imap.go +++ b/internal/endpoint/imap/imap.go @@ -42,6 +42,7 @@ import ( tls2 "github.com/foxcpp/maddy/framework/config/tls" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/resource/netresource" "github.com/foxcpp/maddy/internal/auth" "github.com/foxcpp/maddy/internal/authz" "github.com/foxcpp/maddy/internal/proxy_protocol" @@ -126,7 +127,7 @@ func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { if ioErrors { endp.serv.ErrorLog = &endp.Log } else { - endp.serv.ErrorLog = log.Logger{Out: log.NopOutput{}} + endp.serv.ErrorLog = &log.Logger{Out: log.NopOutput{}} } if ioDebug { endp.serv.Debug = endp.Log.DebugWriter() @@ -174,7 +175,7 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { for _, addr := range addresses { var l net.Listener var err error - l, err = net.Listen(addr.Network(), addr.Address()) + l, err = netresource.Listen(addr.Network(), addr.Address()) if err != nil { return fmt.Errorf("imap: %v", err) } diff --git a/internal/endpoint/openmetrics/om.go b/internal/endpoint/openmetrics/om.go index 136d4af5..44ac64db 100644 --- a/internal/endpoint/openmetrics/om.go +++ b/internal/endpoint/openmetrics/om.go @@ -21,13 +21,13 @@ package openmetrics import ( "errors" "fmt" - "net" "net/http" "sync" "github.com/foxcpp/maddy/framework/config" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/resource/netresource" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -83,7 +83,7 @@ func (e *Endpoint) InstanceName() string { func (e *Endpoint) Start() error { for _, endp := range e.endpoints { - l, err := net.Listen(endp.Network(), endp.Address()) + l, err := netresource.Listen(endp.Network(), endp.Address()) if err != nil { e.Stop() return fmt.Errorf("%s: %v", modName, err) diff --git a/internal/endpoint/smtp/smtp.go b/internal/endpoint/smtp/smtp.go index 7e5f3ab0..ee433e86 100644 --- a/internal/endpoint/smtp/smtp.go +++ b/internal/endpoint/smtp/smtp.go @@ -41,6 +41,7 @@ import ( "github.com/foxcpp/maddy/framework/future" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/resource/netresource" "github.com/foxcpp/maddy/internal/auth" "github.com/foxcpp/maddy/internal/authz" "github.com/foxcpp/maddy/internal/limits" @@ -107,7 +108,7 @@ func New(modName string, addrs []string) (module.LifetimeModule, error) { func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { endp.serv = smtp.NewServer(endp) - endp.serv.ErrorLog = endp.Log + endp.serv.ErrorLog = &endp.Log endp.serv.LMTP = endp.lmtp endp.serv.EnableSMTPUTF8 = true endp.serv.EnableREQUIRETLS = true @@ -328,7 +329,7 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { for _, addr := range addresses { var l net.Listener var err error - l, err = net.Listen(addr.Network(), addr.Address()) + l, err = netresource.Listen(addr.Network(), addr.Address()) if err != nil { return fmt.Errorf("%s: %w", endp.name, err) } diff --git a/internal/updatepipe/unix_pipe.go b/internal/updatepipe/unix_pipe.go index a8249f90..945c4f95 100644 --- a/internal/updatepipe/unix_pipe.go +++ b/internal/updatepipe/unix_pipe.go @@ -27,6 +27,7 @@ import ( mess "github.com/foxcpp/go-imap-mess" "github.com/foxcpp/maddy/framework/log" + "github.com/foxcpp/maddy/framework/resource/netresource" ) // UnixSockPipe implements the UpdatePipe interface by serializating updates @@ -74,7 +75,7 @@ func (usp *UnixSockPipe) readUpdates(conn net.Conn, updCh chan<- mess.Update) { } func (usp *UnixSockPipe) Listen(upd chan<- mess.Update) error { - l, err := net.Listen("unix", usp.SockPath) + l, err := netresource.Listen("unix", usp.SockPath) if err != nil { return err } diff --git a/maddy.go b/maddy.go index 829cdf76..e0a33e1d 100644 --- a/maddy.go +++ b/maddy.go @@ -36,6 +36,7 @@ import ( "github.com/foxcpp/maddy/framework/hooks" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/resource/netresource" "github.com/foxcpp/maddy/internal/authz" maddycli "github.com/foxcpp/maddy/internal/cli" "github.com/urfave/cli/v2" @@ -422,12 +423,14 @@ func moduleReload(oldContainer *container.C, configPath string) *container.C { oldContainer.DefaultLogger.Msg("configuration loaded") + netresource.ResetListenersUsage() oldContainer.DefaultLogger.Msg("starting new server") if err := moduleStart(newContainer); err != nil { oldContainer.DefaultLogger.Error("failed to start new server", err) container.Global = oldContainer return oldContainer } + netresource.CloseUnusedListeners() newContainer.DefaultLogger.Msg("server started", "version", Version) oldContainer.DefaultLogger.Msg("stopping server") From d712d8cc64e6d01437dc74a0f5a3918d06a1e695 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月30日 03:36:55 +0300 Subject: [PATCH 043/111] netresource: Improve logging, fix bug with listeners being recreated on reload --- dist/systemd/maddy.service | 1 - dist/systemd/maddy@.service | 1 - docs/reference/endpoints/smtp.md | 9 ++++++ framework/module/lifetime.go | 4 ++- framework/resource/netresource/listen.go | 6 +++- framework/resource/netresource/tracker.go | 16 +++++----- framework/resource/singleton.go | 10 ++++++- framework/resource/tracker.go | 6 +++- internal/endpoint/smtp/smtp.go | 11 +++++-- maddy.go | 36 +++++++++++++++++------ 10 files changed, 74 insertions(+), 26 deletions(-) diff --git a/dist/systemd/maddy.service b/dist/systemd/maddy.service index ec1ac29c..18236568 100644 --- a/dist/systemd/maddy.service +++ b/dist/systemd/maddy.service @@ -75,7 +75,6 @@ RestartPreventExitStatus=2 ExecStart=/usr/local/bin/maddy run -ExecReload=/bin/kill -USR1 $MAINPID ExecReload=/bin/kill -USR2 $MAINPID [Install] diff --git a/dist/systemd/maddy@.service b/dist/systemd/maddy@.service index ea60ff84..4ba3b54d 100644 --- a/dist/systemd/maddy@.service +++ b/dist/systemd/maddy@.service @@ -71,7 +71,6 @@ RestartPreventExitStatus=2 ExecStart=/usr/local/bin/maddy --config /etc/maddy/%i.conf run -ExecReload=/bin/kill -USR1 $MAINPID ExecReload=/bin/kill -USR2 $MAINPID [Install] diff --git a/docs/reference/endpoints/smtp.md b/docs/reference/endpoints/smtp.md index 4dfa723a..58d3d9b1 100644 --- a/docs/reference/endpoints/smtp.md +++ b/docs/reference/endpoints/smtp.md @@ -14,6 +14,7 @@ smtp tcp://0.0.0.0:25 { sasl_login no read_timeout 10m write_timeout 1m + shutdown_timeout 3m max_message_size 32M max_header_size 1M auth pam @@ -125,6 +126,14 @@ I/O write timeout. --- +### shutdown_timeout _duration_ +Default: `3m` + +Time to wait until forcibly closing connections on server shutdown +or configuration reload. + +--- + ### max_message_size _size_ Default: `32M` diff --git a/framework/module/lifetime.go b/framework/module/lifetime.go index ede5d2b4..5338b01f 100644 --- a/framework/module/lifetime.go +++ b/framework/module/lifetime.go @@ -96,7 +96,9 @@ func (lt *LifetimeTracker) ReloadAll() error { // StopAll calls Stop for all registered LifetimeModule instances. func (lt *LifetimeTracker) StopAll() error { - for _, entry := range lt.instances { + for i := len(lt.instances) - 1; i>= 0; i-- { + entry := lt.instances[i] + if !entry.started { continue } diff --git a/framework/resource/netresource/listen.go b/framework/resource/netresource/listen.go index e2328002..6d164fd7 100644 --- a/framework/resource/netresource/listen.go +++ b/framework/resource/netresource/listen.go @@ -13,7 +13,11 @@ var ( ) func CloseUnusedListeners() error { - return tracker.Close() + return tracker.CloseUnused() +} + +func CloseAllListeners() { + tracker.Close() } func ResetListenersUsage() { diff --git a/framework/resource/netresource/tracker.go b/framework/resource/netresource/tracker.go index 97989c62..a1827aa2 100644 --- a/framework/resource/netresource/tracker.go +++ b/framework/resource/netresource/tracker.go @@ -67,12 +67,8 @@ func (lt *ListenerTracker) ResetUsage() { } func (lt *ListenerTracker) CloseUnused() error { - lt.tcp.CloseUnused(func(key string) bool { - return false - }) - lt.unix.CloseUnused(func(key string) bool { - return false - }) + lt.tcp.CloseUnused(func(key string) bool { return true }) + lt.unix.CloseUnused(func(key string) bool { return true }) return nil } @@ -83,9 +79,11 @@ func (lt *ListenerTracker) Close() error { } func NewListenerTracker(log *log.Logger) *ListenerTracker { - return &ListenerTracker{ + lt := &ListenerTracker{ logger: log, - tcp: resource.NewTracker[*net.TCPListener](resource.NewSingleton[*net.TCPListener]()), - unix: resource.NewTracker[*net.UnixListener](resource.NewSingleton[*net.UnixListener]()), + tcp: resource.NewTracker[*net.TCPListener](resource.NewSingleton[*net.TCPListener](log.Sublogger("tcp"))), + unix: resource.NewTracker[*net.UnixListener](resource.NewSingleton[*net.UnixListener](log.Sublogger("unix"))), } + + return lt } diff --git a/framework/resource/singleton.go b/framework/resource/singleton.go index 2614cee2..45f5b903 100644 --- a/framework/resource/singleton.go +++ b/framework/resource/singleton.go @@ -2,16 +2,20 @@ package resource import ( "sync" + + "github.com/foxcpp/maddy/framework/log" ) // Singleton represents a set of resources identified by an unique key. type Singleton[T Resource] struct { + log *log.Logger lock sync.RWMutex resources map[string]T } -func NewSingleton[T Resource]() *Singleton[T] { +func NewSingleton[T Resource](log *log.Logger) *Singleton[T] { return &Singleton[T]{ + log: log, resources: make(map[string]T), } } @@ -22,6 +26,7 @@ func (s *Singleton[T]) GetOpen(key string, open func() (T, error)) (T, error) { existing, ok := s.resources[key] if ok { + s.log.DebugMsg("resource reused", "key", key) return existing, nil } @@ -31,6 +36,7 @@ func (s *Singleton[T]) GetOpen(key string, open func() (T, error)) (T, error) { return empty, err } + s.log.DebugMsg("new resource", "key", key) s.resources[key] = res return res, nil @@ -44,6 +50,7 @@ func (s *Singleton[T]) CloseUnused(isUsed func(key string) bool) error { if isUsed(key) { continue } + s.log.DebugMsg("resource released", "key", key) res.Close() delete(s.resources, key) } @@ -56,6 +63,7 @@ func (s *Singleton[T]) Close() error { defer s.lock.Unlock() for key, res := range s.resources { + s.log.DebugMsg("resource released", "key", key) res.Close() delete(s.resources, key) } diff --git a/framework/resource/tracker.go b/framework/resource/tracker.go index 318cf298..ae2d9796 100644 --- a/framework/resource/tracker.go +++ b/framework/resource/tracker.go @@ -42,6 +42,10 @@ func (t *Tracker[T]) CloseUnused(isUsed func(key string) bool) error { return t.C.CloseUnused(func(key string) bool { used := t.used[key] - return used && isUsed(key) + used = used && isUsed(key) + if !used { + delete(t.used, key) + } + return used }) } diff --git a/internal/endpoint/smtp/smtp.go b/internal/endpoint/smtp/smtp.go index ee433e86..8c1b9616 100644 --- a/internal/endpoint/smtp/smtp.go +++ b/internal/endpoint/smtp/smtp.go @@ -72,7 +72,8 @@ type Endpoint struct { maxReceived int maxHeaderBytes int64 - sessionCnt atomic.Int32 + sessionCnt atomic.Int32 + shutdownTimeout time.Duration authNormalize authz.NormalizeFunc authMap module.Table @@ -251,6 +252,7 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { modconfig.Table(cfg, "auth_map", true, false, nil, &endp.saslAuth.AuthMap) cfg.Duration("write_timeout", false, false, 1*time.Minute, &endp.serv.WriteTimeout) cfg.Duration("read_timeout", false, false, 10*time.Minute, &endp.serv.ReadTimeout) + cfg.Duration("shutdown_timeout", false, false, 3*time.Minute, &endp.shutdownTimeout) cfg.DataSize("max_message_size", false, false, 32*1024*1024, &endp.serv.MaxMessageBytes) cfg.DataSize("max_header_size", false, false, 1*1024*1024, &endp.maxHeaderBytes) cfg.Int("max_recipients", false, false, 20000, &endp.serv.MaxRecipients) @@ -424,8 +426,13 @@ func (endp *Endpoint) ConnectionCount() int { } func (endp *Endpoint) Stop() error { - endp.serv.Close() + ctx, cancel := context.WithTimeout(context.Background(), endp.shutdownTimeout) + defer cancel() + + endp.serv.Shutdown(ctx) + endp.listenersWg.Wait() + return nil } diff --git a/maddy.go b/maddy.go index e0a33e1d..c60360e2 100644 --- a/maddy.go +++ b/maddy.go @@ -26,6 +26,7 @@ import ( "path/filepath" "runtime" "runtime/debug" + "sync" "github.com/caddyserver/certmagic" parser "github.com/foxcpp/maddy/framework/cfgparser" @@ -206,6 +207,8 @@ func Run(c *cli.Context) error { defer log.DefaultLogger.Out.Close() defer hooks.RunHooks(hooks.EventShutdown) + hooks.AddHook(hooks.EventShutdown, netresource.CloseAllListeners) + if err := moduleMain(c.Path("config")); err != nil { systemdStatusErr(err) return cli.Exit(err.Error(), 1) @@ -395,24 +398,29 @@ func moduleMain(configPath string) error { } c.DefaultLogger.Msg("server started", "version", Version) - systemdStatus(SDReady, "Listening for incoming connections...") + systemdStatus(SDReady, "Configuration running.") + asyncStopWg := sync.WaitGroup{} // Some containers might still be waiting on moduleStop for handleSignals() { - systemdStatus(SDReloading, "Reloading state...") hooks.RunHooks(hooks.EventReload) - c = moduleReload(c, configPath) + c = moduleReload(c, configPath, &asyncStopWg) } c.DefaultLogger.Msg("server stopping...") - systemdStatus(SDStopping, "Waiting for running transactions to complete...") + systemdStatus(SDStopping, "Waiting for old configuration to stop...") + asyncStopWg.Wait() + + systemdStatus(SDStopping, "Waiting for current configuration to stop...") moduleStop(c) c.DefaultLogger.Msg("server stopped") + return nil } -func moduleReload(oldContainer *container.C, configPath string) *container.C { +func moduleReload(oldContainer *container.C, configPath string, asyncStopWg *sync.WaitGroup) *container.C { oldContainer.DefaultLogger.Msg("reloading server...") + systemdStatus(SDReloading, "Reloading server...") oldContainer.DefaultLogger.Msg("loading new configuration...") newContainer, err := moduleConfigure(configPath) @@ -430,12 +438,22 @@ func moduleReload(oldContainer *container.C, configPath string) *container.C { container.Global = oldContainer return oldContainer } - netresource.CloseUnusedListeners() newContainer.DefaultLogger.Msg("server started", "version", Version) - oldContainer.DefaultLogger.Msg("stopping server") - moduleStop(oldContainer) - oldContainer.DefaultLogger.Msg("server stopped") + + systemdStatus(SDReloading, "New configuration running. Waiting for old connections and transactions to finish...") + + asyncStopWg.Add(1) + go func() { + defer asyncStopWg.Done() + defer netresource.CloseUnusedListeners() + + oldContainer.DefaultLogger.Msg("stopping old server") + moduleStop(oldContainer) + oldContainer.DefaultLogger.Msg("old server stopped") + + systemdStatus(SDReloading, "Configuration running.") + }() return newContainer } From 95ba6bf1fc71d12c2fed0f1eadcb5fc237d2a5c8 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月30日 03:52:41 +0300 Subject: [PATCH 044/111] cli/cli: Fix panic on module access --- internal/cli/ctl/moduleinit.go | 1 + internal/table/sql_query.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/cli/ctl/moduleinit.go b/internal/cli/ctl/moduleinit.go index ef58d7b4..1129a516 100644 --- a/internal/cli/ctl/moduleinit.go +++ b/internal/cli/ctl/moduleinit.go @@ -44,6 +44,7 @@ func getCfgBlockModule(ctx *cli.Context) (*container.C, module.Module, error) { } c := container.New() + container.Global = c cfg, err := maddy.ReadConfig(cfgPath) if err != nil { diff --git a/internal/table/sql_query.go b/internal/table/sql_query.go index 96bce8f0..0fe5c64f 100644 --- a/internal/table/sql_query.go +++ b/internal/table/sql_query.go @@ -133,11 +133,11 @@ func (s *SQL) Configure(inlineArgs []string, cfg *config.Map) error { return nil } - return nil + return s.prepare() } func (s *SQL) Start() error { - return s.prepare() + return nil } func (s *SQL) Stop() error { From b4e8716bba8e9c067cd66ea28e85884b73bf788a Mon Sep 17 00:00:00 2001 From: James Mills Date: 2025年1月27日 09:17:01 +1000 Subject: [PATCH 045/111] Add support for GCore DNS --- .gitignore | 2 ++ go.mod | 6 +++++- go.sum | 4 ++++ internal/libdns/gcore.go | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 internal/libdns/gcore.go diff --git a/.gitignore b/.gitignore index 5bf94b90..790b848a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ _testmain.go *.exe~ *.test *.prof +**/.envrc +**/.DS_Store # Tests coverage *.out diff --git a/go.mod b/go.mod index bfa5673c..668bf27b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/foxcpp/maddy -go 1.23 +go 1.23.1 + +toolchain go1.23.5 require ( blitiri.com.ar/go/spf v1.5.1 @@ -34,6 +36,7 @@ require ( github.com/libdns/cloudflare v0.1.1 github.com/libdns/digitalocean v0.0.0-20230728223659-4f9064657aea github.com/libdns/gandi v1.0.3 + github.com/libdns/gcore v0.0.0-20250127070537-4a9d185c9d20 github.com/libdns/googleclouddns v1.1.0 github.com/libdns/hetzner v0.0.1 github.com/libdns/leaseweb v0.4.0 @@ -64,6 +67,7 @@ require ( cloud.google.com/go/compute/metadata v0.6.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/G-Core/gcore-dns-sdk-go v0.2.9 // indirect github.com/aws/aws-sdk-go v1.44.40 // indirect github.com/aws/aws-sdk-go-v2 v1.33.0 // indirect github.com/aws/aws-sdk-go-v2/config v1.29.1 // indirect diff --git a/go.sum b/go.sum index 2f99728a..4d3eaea7 100644 --- a/go.sum +++ b/go.sum @@ -190,6 +190,8 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzS github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/G-Core/gcore-dns-sdk-go v0.2.9 h1:LMMZIRX8y3aJJuAviNSpFmLbovZUw+6Om+8VElp1F90= +github.com/G-Core/gcore-dns-sdk-go v0.2.9/go.mod h1:35t795gOfzfVanhzkFyUXEzaBuMXwETmJldPpP28MN4= github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI= github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -521,6 +523,8 @@ github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn0 github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU= github.com/libdns/digitalocean v0.0.0-20230728223659-4f9064657aea h1:IGlMNZCUp8Ho7NYYorpP5ZJgg2mFXARs6eHs/pSqFkA= github.com/libdns/digitalocean v0.0.0-20230728223659-4f9064657aea/go.mod h1:B2TChhOTxvBflpRTHlguXWtwa1Ha5WI6JkB6aCViM+0= +github.com/libdns/gcore v0.0.0-20250127070537-4a9d185c9d20 h1:bQwFw+C9sX/zYZlV53ey0KnNkxrfWYIFpvptuAVhJ1Y= +github.com/libdns/gcore v0.0.0-20250127070537-4a9d185c9d20/go.mod h1:JGoT1mbmqQwtYQqN5F/vGc9j4TTTMKw/hDm5vXADHUI= github.com/libdns/googleclouddns v1.1.0 h1:murPR1LfTZZObLV2OLxUVmymWH25glkMFKpDjkk2m0E= github.com/libdns/googleclouddns v1.1.0/go.mod h1:3tzd056dfqKlf71V8Oy19En4WjJ3ybyuWx6P9bQSCIw= github.com/libdns/hetzner v0.0.1 h1:WsmcsOKnfpKmzwhfyqhGQEIlEeEaEUvb7ezoJgBKaqU= diff --git a/internal/libdns/gcore.go b/internal/libdns/gcore.go new file mode 100644 index 00000000..01d7afb6 --- /dev/null +++ b/internal/libdns/gcore.go @@ -0,0 +1,34 @@ +//go:build libdns_gcore || !libdns_separate +// +build libdns_gcore !libdns_separate + +package libdns + +import ( + "fmt" + + "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/module" + "github.com/libdns/gcore" +) + +func init() { + module.Register("libdns.gcore", func(modName, instName string, _, _ []string) (module.Module, error) { + p := gcore.Provider{} + return &ProviderModule{ + RecordDeleter: &p, + RecordAppender: &p, + setConfig: func(c *config.Map) { + c.String("api_key", false, false, "", &p.APIKey) + }, + afterConfig: func() error { + if p.APIKey == "" { + return fmt.Errorf("libdns.gcore: api_key should be specified") + } + return nil + }, + + instName: instName, + modName: modName, + }, nil + }) +} From 91c1617698b661d1135a8da5b832b56e397de3c1 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2025年1月30日 21:50:28 +0300 Subject: [PATCH 046/111] tests: Fix-up linter nit --- tests/t.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/t.go b/tests/t.go index 3ae27c65..c1c04bc3 100644 --- a/tests/t.go +++ b/tests/t.go @@ -242,7 +242,8 @@ func (t *T) MustRunCLIGroup(args ...[]string) { _, err := t.RunCLI(arg...) if err != nil { - t.Fatalf("maddy %v: %v", arg, err) + t.Printf("maddy %v: %v", arg, err) + t.Fail() } }() } From 1d044249c2400a6585e79723e125ca270898747e Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Sat, 1 Feb 2025 19:14:45 +0300 Subject: [PATCH 047/111] endpoint/smtp: Fix auth_map ignored --- internal/endpoint/smtp/smtp.go | 4 +- tests/multiple_domains_test.go | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/internal/endpoint/smtp/smtp.go b/internal/endpoint/smtp/smtp.go index 004d92e5..1d38c57b 100644 --- a/internal/endpoint/smtp/smtp.go +++ b/internal/endpoint/smtp/smtp.go @@ -289,6 +289,8 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { return err } + endp.saslAuth.Log.Debug = endp.Log.Debug + // INTERNATIONALIZATION: See RFC 6531 Section 3.3. endp.serv.Domain, err = idna.ToASCII(hostname) if err != nil { @@ -310,8 +312,6 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { return fmt.Errorf("%s: auth. provider must be set for submission endpoint", endp.name) } } - endp.saslAuth.AuthNormalize = endp.authNormalize - endp.saslAuth.AuthMap = endp.authMap if ioDebug { endp.serv.Debug = endp.Log.DebugWriter() diff --git a/tests/multiple_domains_test.go b/tests/multiple_domains_test.go index 971ca91b..df00cf2d 100644 --- a/tests/multiple_domains_test.go +++ b/tests/multiple_domains_test.go @@ -71,26 +71,44 @@ func TestMultipleDomains_SeparateNamespace(tt *testing.T) { t.Run(2) user1 := t.Conn("imap") + defer user1.Close() user1.ExpectPattern(`\* OK *`) user1.Writeln(`. LOGIN user1@test1.maddy.email user1`) user1.ExpectPattern(`. OK *`) user1.Writeln(`. CREATE user1`) user1.ExpectPattern(`. OK *`) + user1SMTP := t.Conn("submission") + defer user1SMTP.Close() + user1SMTP.SMTPNegotation("localhost", []string{"AUTH PLAIN"}, nil) + user1SMTP.SMTPPlainAuth("user1@test1.maddy.email", "user1", true) + user2 := t.Conn("imap") + defer user2.Close() user2.ExpectPattern(`\* OK *`) user2.Writeln(`. LOGIN user2@test1.maddy.email user2`) user2.ExpectPattern(`. OK *`) user2.Writeln(`. CREATE user2`) user2.ExpectPattern(`. OK *`) + user2SMTP := t.Conn("submission") + defer user2SMTP.Close() + user2SMTP.SMTPNegotation("localhost", []string{"AUTH PLAIN"}, nil) + user2SMTP.SMTPPlainAuth("user2@test1.maddy.email", "user2", true) + user3 := t.Conn("imap") + defer user3.Close() user3.ExpectPattern(`\* OK *`) user3.Writeln(`. LOGIN user1@test2.maddy.email user3`) user3.ExpectPattern(`. OK *`) user3.Writeln(`. CREATE user3`) user3.ExpectPattern(`. OK *`) + user3SMTP := t.Conn("submission") + defer user3SMTP.Close() + user3SMTP.SMTPNegotation("localhost", []string{"AUTH PLAIN"}, nil) + user3SMTP.SMTPPlainAuth("user3@test2.maddy.email", "user3", true) + user1.Writeln(`. LIST "" "*"`) user1.Expect(`* LIST (\HasNoChildren) "." INBOX`) user1.Expect(`* LIST (\HasNoChildren) "." "user1"`) @@ -150,26 +168,44 @@ func TestMultipleDomains_SharedCredentials_DistinctMailboxes(tt *testing.T) { t.Run(2) user1 := t.Conn("imap") + defer user1.Close() user1.ExpectPattern(`\* OK *`) user1.Writeln(`. LOGIN user1@test1.maddy.email user1`) user1.ExpectPattern(`. OK *`) user1.Writeln(`. CREATE user1`) user1.ExpectPattern(`. OK *`) + user1SMTP := t.Conn("submission") + defer user1SMTP.Close() + user1SMTP.SMTPNegotation("localhost", []string{"AUTH PLAIN"}, nil) + user1SMTP.SMTPPlainAuth("user1@test1.maddy.email", "user1", true) + user2 := t.Conn("imap") + defer user2.Close() user2.ExpectPattern(`\* OK *`) user2.Writeln(`. LOGIN user2@test1.maddy.email user2`) user2.ExpectPattern(`. OK *`) user2.Writeln(`. CREATE user2`) user2.ExpectPattern(`. OK *`) + user2SMTP := t.Conn("submission") + defer user2SMTP.Close() + user2SMTP.SMTPNegotation("localhost", []string{"AUTH PLAIN"}, nil) + user2SMTP.SMTPPlainAuth("user2@test1.maddy.email", "user2", true) + user3 := t.Conn("imap") + defer user3.Close() user3.ExpectPattern(`\* OK *`) user3.Writeln(`. LOGIN user1@test2.maddy.email user1`) user3.ExpectPattern(`. OK *`) user3.Writeln(`. CREATE user3`) user3.ExpectPattern(`. OK *`) + user3SMTP := t.Conn("submission") + defer user3SMTP.Close() + user3SMTP.SMTPNegotation("localhost", []string{"AUTH PLAIN"}, nil) + user3SMTP.SMTPPlainAuth("user1@test2.maddy.email", "user1", true) + user1.Writeln(`. LIST "" "*"`) user1.Expect(`* LIST (\HasNoChildren) "." INBOX`) user1.Expect(`* LIST (\HasNoChildren) "." "user1"`) @@ -232,30 +268,62 @@ func TestMultipleDomains_SharedCredentials_SharedMailboxes(tt *testing.T) { t.Run(2) user1 := t.Conn("imap") + defer user1.Close() user1.ExpectPattern(`\* OK *`) user1.Writeln(`. LOGIN user1 user1`) user1.ExpectPattern(`. OK *`) user1.Writeln(`. CREATE user1`) user1.ExpectPattern(`. OK *`) + user1SMTP := t.Conn("submission") + defer user1SMTP.Close() + user1SMTP.SMTPNegotation("localhost", []string{"AUTH PLAIN"}, nil) + user1SMTP.SMTPPlainAuth("user1", "user1", true) + user2 := t.Conn("imap") + defer user2.Close() user2.ExpectPattern(`\* OK *`) user2.Writeln(`. LOGIN user2@test1.maddy.email user2`) user2.ExpectPattern(`. OK *`) user2.Writeln(`. CREATE user2`) user2.ExpectPattern(`. OK *`) + user2SMTP := t.Conn("submission") + defer user2SMTP.Close() + user2SMTP.SMTPNegotation("localhost", []string{"AUTH PLAIN"}, nil) + user2SMTP.SMTPPlainAuth("user2", "user2", true) + user12 := t.Conn("imap") + defer user12.Close() user12.ExpectPattern(`\* OK *`) user12.Writeln(`. LOGIN user1@test2.maddy.email user1`) user12.ExpectPattern(`. OK *`) user12.Writeln(`. CREATE user12`) user12.ExpectPattern(`. OK *`) + user13 := t.Conn("imap") + defer user13.Close() + user13.ExpectPattern(`\* OK *`) + user13.Writeln(`. LOGIN user1@test.maddy.email user1`) + user13.ExpectPattern(`. OK *`) + user13.Writeln(`. CREATE user13`) + user13.ExpectPattern(`. OK *`) + + user12SMTP := t.Conn("submission") + defer user12SMTP.Close() + user12SMTP.SMTPNegotation("localhost", []string{"AUTH PLAIN"}, nil) + user12SMTP.SMTPPlainAuth("user1", "user1", true) + + user13SMTP := t.Conn("submission") + defer user13SMTP.Close() + user13SMTP.SMTPNegotation("localhost", []string{"AUTH PLAIN"}, nil) + user13SMTP.SMTPPlainAuth("user1@test.maddy.email", "user1", true) + user1.Writeln(`. LIST "" "*"`) user1.Expect(`* LIST (\HasNoChildren) "." INBOX`) user1.Expect(`* LIST (\HasNoChildren) "." "user1"`) user1.Expect(`* LIST (\HasNoChildren) "." "user12"`) + user1.Expect(`* LIST (\HasNoChildren) "." "user13"`) user1.ExpectPattern(". OK *") user2.Writeln(`. LIST "" "*"`) @@ -267,5 +335,6 @@ func TestMultipleDomains_SharedCredentials_SharedMailboxes(tt *testing.T) { user12.Expect(`* LIST (\HasNoChildren) "." INBOX`) user12.Expect(`* LIST (\HasNoChildren) "." "user1"`) user12.Expect(`* LIST (\HasNoChildren) "." "user12"`) + user12.Expect(`* LIST (\HasNoChildren) "." "user13"`) user12.ExpectPattern(". OK *") } From ef7fa210dca00c2a883f9475c63f9aed4eef579f Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Sat, 1 Feb 2025 19:24:53 +0300 Subject: [PATCH 048/111] Slightly improve debug logging for complex authentication pipelines --- framework/log/orderedjson.go | 7 +++++++ internal/auth/sasl.go | 15 +++++++-------- internal/endpoint/dovecot_sasld/dovecot_sasl.go | 1 + internal/endpoint/imap/imap.go | 2 ++ maddy.go | 10 +++++----- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/framework/log/orderedjson.go b/framework/log/orderedjson.go index 834f3a6c..bbe01c29 100644 --- a/framework/log/orderedjson.go +++ b/framework/log/orderedjson.go @@ -31,6 +31,11 @@ import ( // human-readable when values from multiple messages are lined up to each // other. +type module interface { + Name() string + InstanceName() string +} + func marshalOrderedJSON(output *strings.Builder, m map[string]interface{}) error { order := make([]string, 0, len(m)) for k := range m { @@ -62,6 +67,8 @@ func marshalOrderedJSON(output *strings.Builder, m map[string]interface{}) error val = casted.FormatLog() case fmt.Stringer: val = casted.String() + case module: + val = casted.Name() + "/" + casted.InstanceName() case error: val = casted.Error() } diff --git a/internal/auth/sasl.go b/internal/auth/sasl.go index 21a3d897..545c523e 100644 --- a/internal/auth/sasl.go +++ b/internal/auth/sasl.go @@ -105,12 +105,16 @@ func (s *SASLAuth) AuthPlain(username, password string) error { var lastErr error for _, p := range s.Plain { - username, err := s.usernameForAuth(context.TODO(), username) + mappedUsername, err := s.usernameForAuth(context.TODO(), username) if err != nil { return err } - lastErr = p.AuthPlain(username, password) + s.Log.DebugMsg("attempting authentication", + "mapped_username", mappedUsername, "original_username", username, + "module", p) + + lastErr = p.AuthPlain(mappedUsername, password) if lastErr == nil { return nil } @@ -139,12 +143,7 @@ func (s *SASLAuth) CreateSASL(mech string, remoteAddr net.Addr, successCb func(i return ErrInvalidAuthCred } - username, err := s.usernameForAuth(context.Background(), username) - if err != nil { - return err - } - - err = s.AuthPlain(username, password) + err := s.AuthPlain(username, password) if err != nil { s.Log.Error("authentication failed", err, "username", username, "src_ip", remoteAddr) return ErrInvalidAuthCred diff --git a/internal/endpoint/dovecot_sasld/dovecot_sasl.go b/internal/endpoint/dovecot_sasld/dovecot_sasl.go index 77eedd0e..26796962 100644 --- a/internal/endpoint/dovecot_sasld/dovecot_sasl.go +++ b/internal/endpoint/dovecot_sasld/dovecot_sasl.go @@ -79,6 +79,7 @@ func (endp *Endpoint) Init(cfg *config.Map) error { endp.srv = dovecotsasl.NewServer() endp.srv.Log = stdlog.New(endp.log, "", 0) + endp.saslAuth.Log.Debug = endp.log.Debug for _, mech := range endp.saslAuth.SASLMechanisms() { endp.srv.AddMechanism(mech, mechInfo[mech], func(req *dovecotsasl.AuthReq) sasl.Server { diff --git a/internal/endpoint/imap/imap.go b/internal/endpoint/imap/imap.go index 191d93d2..cece7972 100644 --- a/internal/endpoint/imap/imap.go +++ b/internal/endpoint/imap/imap.go @@ -112,6 +112,8 @@ func (endp *Endpoint) Init(cfg *config.Map) error { } } + endp.saslAuth.Log.Debug = endp.Log.Debug + addresses := make([]config.Endpoint, 0, len(endp.addrs)) for _, addr := range endp.addrs { saddr, err := config.ParseEndpoint(addr) diff --git a/maddy.go b/maddy.go index f838e96e..5439fba1 100644 --- a/maddy.go +++ b/maddy.go @@ -110,15 +110,15 @@ func init() { Value: filepath.Join(ConfigDirectory, "maddy.conf"), }, ) + maddycli.AddGlobalFlag(&cli.BoolFlag{ + Name: "debug", + Usage: "enable debug logging early", + Destination: &log.DefaultLogger.Debug, + }) maddycli.AddSubcommand(&cli.Command{ Name: "run", Usage: "Start the server", Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "debug", - Usage: "enable debug logging early", - Destination: &log.DefaultLogger.Debug, - }, &cli.StringFlag{ Name: "libexec", Value: DefaultLibexecDirectory, From 01c65cfb0e40d3c5898ce831fc45a24f0f261ab9 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Sun, 2 Feb 2025 13:14:11 +0300 Subject: [PATCH 049/111] Finally fix tests Took way too long. --- internal/endpoint/smtp/smtp.go | 3 --- tests/multiple_domains_test.go | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/endpoint/smtp/smtp.go b/internal/endpoint/smtp/smtp.go index 1d38c57b..8f800e52 100644 --- a/internal/endpoint/smtp/smtp.go +++ b/internal/endpoint/smtp/smtp.go @@ -72,9 +72,6 @@ type Endpoint struct { sessionCnt atomic.Int32 - authNormalize authz.NormalizeFunc - authMap module.Table - listenersWg sync.WaitGroup Log log.Logger diff --git a/tests/multiple_domains_test.go b/tests/multiple_domains_test.go index df00cf2d..6b29c3fa 100644 --- a/tests/multiple_domains_test.go +++ b/tests/multiple_domains_test.go @@ -107,7 +107,7 @@ func TestMultipleDomains_SeparateNamespace(tt *testing.T) { user3SMTP := t.Conn("submission") defer user3SMTP.Close() user3SMTP.SMTPNegotation("localhost", []string{"AUTH PLAIN"}, nil) - user3SMTP.SMTPPlainAuth("user3@test2.maddy.email", "user3", true) + user3SMTP.SMTPPlainAuth("user1@test2.maddy.email", "user3", true) user1.Writeln(`. LIST "" "*"`) user1.Expect(`* LIST (\HasNoChildren) "." INBOX`) From 96790bd82fcccaaaf59f1564105df77943c6fcb0 Mon Sep 17 00:00:00 2001 From: Alexey Nurmukhametov Date: Sat, 8 Feb 2025 19:19:49 +0000 Subject: [PATCH 050/111] Build aarch64 binary in release workflow Building in a Docker container is a workaround for the issue of JavaScript-based GitHub Actions not being supported in Alpine containers on the Arm64 platform. Otherwise, we could completely reuse artifact-builder-x86 as a matrix job by running it on an Arm runner. This could be done later when upload-artifact works in an Alpine Arm64 container. --- .github/workflows/release.yml | 47 +++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0b5dfab0..e7155b68 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,8 +11,8 @@ permissions: packages: write jobs: - artifact-builder: - name: "Prepare release artifacts" + artifact-builder-x86: + name: "Prepare release artifacts (x86)" if: github.ref_type == 'tag' runs-on: ubuntu-latest container: @@ -55,6 +55,49 @@ jobs: uses: actions/attest-build-provenance@v2 with: subject-path: '~/maddy-x86_64-linux-musl.tar.zst' + artifact-builder-arm: + name: "Prepare release artifacts (aarch64)" + if: github.ref_type == 'tag' + runs-on: ubuntu-22.04-arm + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + # Building in a Docker container is a workaround for the issue of + # JavaScript-based GitHub Actions not being supported in Alpine + # containers on the Arm64 platform. Otherwise, we could completely reuse + # artifact-builder-x86 as a matrix job by running it on an Arm runner. + - name: Build in Docker container + run: | + # Create Dockerfile for the build + cat> Dockerfile << 'EOF' + FROM alpine:edge + RUN apk add --no-cache gcc go zstd musl-dev scdoc + WORKDIR /build + COPY . . + RUN ./build.sh --builddir /package-output/ --static build && \ + ver=$(cat .version) && \ + if [ "v$ver" != "${{github.ref_name}}" ]; then echo ".version does not match the Git tag"; exit 1; fi && \ + mv /package-output/ /maddy-$ver-aarch64-linux-musl && \ + cd / && \ + tar c ./maddy-$ver-aarch64-linux-musl | zstd> /maddy-aarch64-linux-musl.tar.zst + EOF + # Build the image, create a temporary container and copy the artifact. + docker build -t maddy-builder . + container_id=$(docker create maddy-builder) + docker cp $container_id:/maddy-aarch64-linux-musl.tar.zst . + docker rm $container_id + - name: Upload binary tree + uses: actions/upload-artifact@v4 + with: + name: maddy-binary-aarch64.tar.zst + path: maddy-aarch64-linux-musl.tar.zst + if-no-files-found: error + - name: "Generate artifact attestation" + uses: actions/attest-build-provenance@v2 + with: + subject-path: 'maddy-aarch64-linux-musl.tar.zst' docker-builder: name: "Build & push Docker image" if: github.ref_type == 'tag' From 0e953a824bfc942e4344b2112750840155b2d44c Mon Sep 17 00:00:00 2001 From: Mark Lipscombe Date: 2025年2月10日 08:41:48 -0500 Subject: [PATCH 051/111] fix: make `tls_client` configuration work in `target.smtp` block --- internal/target/smtp/smtp_downstream.go | 6 +++--- internal/target/smtp/smtp_downstream_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/target/smtp/smtp_downstream.go b/internal/target/smtp/smtp_downstream.go index 8880f6cb..294e283e 100644 --- a/internal/target/smtp/smtp_downstream.go +++ b/internal/target/smtp/smtp_downstream.go @@ -57,7 +57,7 @@ type Downstream struct { hostname string endpoints []config.Endpoint saslFactory saslClientFactory - tlsConfig tls.Config + tlsConfig *tls.Config connectTimeout time.Duration commandTimeout time.Duration @@ -229,9 +229,9 @@ func (d *delivery) connect(ctx context.Context) error { for _, endp := range d.u.endpoints { var err error if d.u.lmtp { - _, err = conn.ConnectLMTP(ctx, endp, d.u.starttls, &d.u.tlsConfig) + _, err = conn.ConnectLMTP(ctx, endp, d.u.starttls, d.u.tlsConfig) } else { - _, err = conn.Connect(ctx, endp, d.u.starttls, &d.u.tlsConfig) + _, err = conn.Connect(ctx, endp, d.u.starttls, d.u.tlsConfig) } if err != nil { if len(d.u.endpoints) != 1 { diff --git a/internal/target/smtp/smtp_downstream_test.go b/internal/target/smtp/smtp_downstream_test.go index 31ef2954..f0f58c66 100644 --- a/internal/target/smtp/smtp_downstream_test.go +++ b/internal/target/smtp/smtp_downstream_test.go @@ -221,7 +221,7 @@ func TestDownstreamDelivery_StartTLS(t *testing.T) { Port: testPort, }, }, - tlsConfig: *clientCfg.Clone(), + tlsConfig: clientCfg.Clone(), starttls: true, log: testutils.Logger(t, "target.smtp"), } From 68e3065663620d389ecbf7e936733c3048fbe65b Mon Sep 17 00:00:00 2001 From: Mark Lipscombe Date: 2025年2月10日 08:48:08 -0500 Subject: [PATCH 052/111] fix: correctly announce SASL LOGIN capabilty --- internal/auth/sasl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/auth/sasl.go b/internal/auth/sasl.go index 545c523e..8510052e 100644 --- a/internal/auth/sasl.go +++ b/internal/auth/sasl.go @@ -62,7 +62,7 @@ func (s *SASLAuth) SASLMechanisms() []string { if len(s.Plain) != 0 { mechs = append(mechs, sasl.Plain) - if s.OnlyFirstID { + if s.EnableLogin { mechs = append(mechs, sasl.Login) } } From 4a5f379832d8c659ac6f4749e6df0400603a1315 Mon Sep 17 00:00:00 2001 From: John Weldon Date: 2025年2月22日 13:47:14 -0700 Subject: [PATCH 053/111] framework/log: Fix Logger usages in tests --- internal/storage/blob/test_blob.go | 3 ++- internal/target/remote/dane_delivery_test.go | 3 ++- internal/target/remote/mxauth_test.go | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/storage/blob/test_blob.go b/internal/storage/blob/test_blob.go index 9672efea..8f3d0bcb 100644 --- a/internal/storage/blob/test_blob.go +++ b/internal/storage/blob/test_blob.go @@ -39,10 +39,11 @@ func TestStore(t *testing.T, newStore func() module.BlobStore, cleanStore func(m prng := rand.New(randSrc) store := newStore() + l := testutils.Logger(t, "imapsql") b, err := imapsql.New("sqlite3", ":memory:", imapsql2.ExtBlobStore{Base: store}, imapsql.Opts{ PRNG: prng, - Log: testutils.Logger(t, "imapsql"), + Log: &l, }, ) if err != nil { diff --git a/internal/target/remote/dane_delivery_test.go b/internal/target/remote/dane_delivery_test.go index 2b921c7d..377f7cfc 100644 --- a/internal/target/remote/dane_delivery_test.go +++ b/internal/target/remote/dane_delivery_test.go @@ -32,7 +32,8 @@ import ( ) func targetWithExtResolver(t *testing.T, zones map[string]mockdns.Zone) (*mockdns.Server, *Target) { - dnsSrv, err := mockdns.NewServerWithLogger(zones, testutils.Logger(t, "mockdns"), false) + l := testutils.Logger(t, "mockdns") + dnsSrv, err := mockdns.NewServerWithLogger(zones, &l, false) if err != nil { t.Fatal(err) } diff --git a/internal/target/remote/mxauth_test.go b/internal/target/remote/mxauth_test.go index e332bdb4..978567c5 100644 --- a/internal/target/remote/mxauth_test.go +++ b/internal/target/remote/mxauth_test.go @@ -303,7 +303,8 @@ func TestRemoteDelivery_AuthMX_DNSSEC(t *testing.T) { }, } - dnsSrv, err := mockdns.NewServerWithLogger(zones, testutils.Logger(t, "mockdns"), false) + l := testutils.Logger(t, "mockdns") + dnsSrv, err := mockdns.NewServerWithLogger(zones, &l, false) if err != nil { t.Fatal(err) } @@ -342,7 +343,8 @@ func TestRemoteDelivery_AuthMX_DNSSEC_Fail(t *testing.T) { }, } - dnsSrv, err := mockdns.NewServerWithLogger(zones, testutils.Logger(t, "mockdns"), false) + l := testutils.Logger(t, "mockdns") + dnsSrv, err := mockdns.NewServerWithLogger(zones, &l, false) if err != nil { t.Fatal(err) } From 63420d1bef3a8902d8902b8a23c47bd96881f260 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Sun, 9 Mar 2025 15:39:22 +0300 Subject: [PATCH 054/111] target/smtp: Fix-up default value for tls_client --- internal/target/smtp/smtp_downstream.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/target/smtp/smtp_downstream.go b/internal/target/smtp/smtp_downstream.go index 294e283e..b85c4ba9 100644 --- a/internal/target/smtp/smtp_downstream.go +++ b/internal/target/smtp/smtp_downstream.go @@ -121,7 +121,7 @@ func (u *Downstream) Init(cfg *config.Map) error { return nil, nil }, saslAuthDirective, &u.saslFactory) cfg.Custom("tls_client", true, false, func() (interface{}, error) { - return tls.Config{}, nil + return &tls.Config{}, nil }, tls2.TLSClientBlock, &u.tlsConfig) cfg.Duration("connect_timeout", false, false, 5*time.Minute, &u.connectTimeout) cfg.Duration("command_timeout", false, false, 5*time.Minute, &u.commandTimeout) From 7cfee986906dac85144b7312913087ab4706ff72 Mon Sep 17 00:00:00 2001 From: spiarh Date: 2025年4月15日 15:25:03 +0200 Subject: [PATCH 055/111] fix: replace typo trasanactions with transactions Signed-off-by: spiarh --- internal/endpoint/smtp/metrics.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/endpoint/smtp/metrics.go b/internal/endpoint/smtp/metrics.go index 509241bc..8c9a7188 100644 --- a/internal/endpoint/smtp/metrics.go +++ b/internal/endpoint/smtp/metrics.go @@ -26,7 +26,7 @@ var ( Namespace: "maddy", Subsystem: "smtp", Name: "started_transactions", - Help: "Amount of SMTP trasanactions started", + Help: "Amount of SMTP transactions started", }, []string{"module"}, ) @@ -35,7 +35,7 @@ var ( Namespace: "maddy", Subsystem: "smtp", Name: "smtp_completed_transactions", - Help: "Amount of SMTP trasanactions successfully completed", + Help: "Amount of SMTP transactions successfully completed", }, []string{"module"}, ) @@ -44,7 +44,7 @@ var ( Namespace: "maddy", Subsystem: "smtp", Name: "aborted_transactions", - Help: "Amount of SMTP trasanactions aborted", + Help: "Amount of SMTP transactions aborted", }, []string{"module"}, ) From ad98777e45587062bf7f1e3fdb4e1546010f2b3c Mon Sep 17 00:00:00 2001 From: spiarh Date: 2025年4月15日 15:26:07 +0200 Subject: [PATCH 056/111] feat: implement maddy_queue_length metric The maddy_queue_length was already registered but never used. The gauge is updated when actions on disks are performed: - Incremented when a message is written to disk or when the queue is read from disk. - Decremented when a message is removed from disk. Signed-off-by: spiarh --- internal/target/queue/queue.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/target/queue/queue.go b/internal/target/queue/queue.go index 264a70fb..4bea9053 100644 --- a/internal/target/queue/queue.go +++ b/internal/target/queue/queue.go @@ -89,7 +89,6 @@ import ( // partialError describes state of partially successful message delivery. type partialError struct { - // Underlying error objects for each recipient. Errs map[string]error @@ -129,7 +128,6 @@ type Queue struct { // Retry delay is calculated using the following formula: // initialRetryTime * retryTimeScale ^ (TriesCount - 1) - initialRetryTime time.Duration retryTimeScale float64 maxTries int @@ -635,6 +633,9 @@ func (q *Queue) removeFromDisk(msgMeta *module.MsgMetadata) { if err := os.Remove(metaPath); err != nil { dl.Error("failed to remove meta-data from disk", err) } + + queuedMsgs.WithLabelValues(q.name, q.location).Dec() + dl.Debugf("removed message from disk") } @@ -704,6 +705,8 @@ func (q *Queue) readDiskQueue() error { ID: id, }) loadedCount++ + + queuedMsgs.WithLabelValues(q.name, q.location).Inc() } if loadedCount != 0 { @@ -762,6 +765,8 @@ func (q *Queue) storeNewMessage(meta *QueueMetadata, header textproto.Header, bo return nil, err } + queuedMsgs.WithLabelValues(q.name, q.location).Inc() + return buffer.FileBuffer{Path: bodyPath, LenHint: body.Len()}, nil } From 14372d6aeb6b3babdb2a291af97e68cadf500edc Mon Sep 17 00:00:00 2001 From: Carl-Friedrich Braun Date: 2025年5月26日 11:52:43 +0200 Subject: [PATCH 057/111] fix: reduce StaleKeyLifetimeSec to 4 minutes RFC 5321 recommends idle time of 5 minutes. Maddy runs pool cleanup once per minute. When a connection is returned to the pool at time 'last cleanup + 10 seconds', it will be closed at 'last cleanup + StaleKeyLifetimeSec + 60 seconds' . With the original value of 5 minutes, the connection will be closed 50 seconds after the recommended timeout. By reducing the lifetime to 4 minutes, connections might be closed a little early, but never late. --- internal/target/remote/remote.go | 2 +- internal/target/remote/remote_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/target/remote/remote.go b/internal/target/remote/remote.go index d4c42ed6..3f848832 100644 --- a/internal/target/remote/remote.go +++ b/internal/target/remote/remote.go @@ -146,7 +146,7 @@ func (rt *Target) Init(cfg *config.Map) error { MaxKeys: 5000, MaxConnsPerKey: 5, // basically, max. amount of idle connections in cache MaxConnLifetimeSec: 150, // 2.5 mins, half of recommended idle time from RFC 5321 - StaleKeyLifetimeSec: 60 * 5, // should be bigger than MaxConnLifetimeSec + StaleKeyLifetimeSec: 60 * 4, // make sure that cleanup runs before recommended idle time from RFC 5321 } cfg.Int("conn_max_idle_count", false, false, 5, &poolCfg.MaxConnsPerKey) cfg.Int64("conn_max_idle_time", false, false, 150, &poolCfg.MaxConnLifetimeSec) diff --git a/internal/target/remote/remote_test.go b/internal/target/remote/remote_test.go index 4998e0c6..c21bb6eb 100644 --- a/internal/target/remote/remote_test.go +++ b/internal/target/remote/remote_test.go @@ -64,7 +64,7 @@ func testTarget(t *testing.T, zones map[string]mockdns.Zone, extResolver *dns.Ex MaxKeys: 5000, MaxConnsPerKey: 5, // basically, max. amount of idle connections in cache MaxConnLifetimeSec: 150, // 2.5 mins, half of recommended idle time from RFC 5321 - StaleKeyLifetimeSec: 60 * 5, // should be bigger than MaxConnLifetimeSec + StaleKeyLifetimeSec: 60 * 4, // make sure that cleanup runs before recommended idle time from RFC 5321 }), } From 0ecc909dd46c4f418dbde3079168674b8da631fa Mon Sep 17 00:00:00 2001 From: Ruiyang Guan Date: 2025年10月25日 11:14:24 +0000 Subject: [PATCH 058/111] Fix typo in TLS certificates section doc --- docs/tutorials/setting-up.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/setting-up.md b/docs/tutorials/setting-up.md index 2ec83519..240caa80 100644 --- a/docs/tutorials/setting-up.md +++ b/docs/tutorials/setting-up.md @@ -105,7 +105,7 @@ $(local_domains) = $(primary_domain) example.com other.example.com ## TLS certificates -One thing that can't be automagically configured is TLS certs. If you already +One thing that can't be automatically configured is TLS certs. If you already have them somewhere - use them, open /etc/maddy/maddy.conf and put the right paths in. You need to make sure maddy can read them while running as unprivileged user (maddy never runs as root, even during start-up), one way to From acedab39e008f62e081e7dd0377bd05cd40ea3ce Mon Sep 17 00:00:00 2001 From: hcl Date: Thu, 8 Jan 2026 11:40:58 +0800 Subject: [PATCH 059/111] Fix typo in SMTP endpoint section doc --- docs/reference/endpoints/smtp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/endpoints/smtp.md b/docs/reference/endpoints/smtp.md index 4dfa723a..16f7eea0 100644 --- a/docs/reference/endpoints/smtp.md +++ b/docs/reference/endpoints/smtp.md @@ -25,7 +25,7 @@ smtp tcp://0.0.0.0:25 { endpoint concurrency 500 } - # Example pipeline ocnfiguration. + # Example pipeline configuration. destination example.org { deliver_to &local_mailboxes } From c39c921e61f8a3eeb36ee845035252a63b9d950c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+copilot@users.noreply.github.com> Date: 2026年1月10日 12:48:27 +0000 Subject: [PATCH 060/111] Initial plan From b66f114f3c44dbb205799de8f33203e007690bcc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+copilot@users.noreply.github.com> Date: 2026年1月10日 12:53:57 +0000 Subject: [PATCH 061/111] Implement per-response-code DNSBL scoring with custom messages Co-authored-by: thisisjaymehta <31812582+thisisjaymehta@users.noreply.github.com> --- docs/reference/checks/dnsbl.md | 76 ++++++++ internal/check/dnsbl/common.go | 58 +++++- internal/check/dnsbl/dnsbl.go | 87 ++++++++- internal/check/dnsbl/dnsbl_test.go | 280 +++++++++++++++++++++++++++++ 4 files changed, 496 insertions(+), 5 deletions(-) diff --git a/docs/reference/checks/dnsbl.md b/docs/reference/checks/dnsbl.md index a2d27362..be31a67f 100644 --- a/docs/reference/checks/dnsbl.md +++ b/docs/reference/checks/dnsbl.md @@ -29,6 +29,30 @@ check.dnsbl { mailfrom yes score 1 } + + # Example with per-response-code scoring (new in 0.8) + zen.spamhaus.org { + client_ipv4 yes + client_ipv6 yes + + # SBL - Spamhaus Block List (known spam sources) + response 127.0.0.2 127.0.0.3 { + score 10 + message "Listed in Spamhaus SBL. See https://check.spamhaus.org/" + } + + # XBL - Exploits Block List (compromised hosts) + response 127.0.0.4 127.0.0.5 127.0.0.6 127.0.0.7 { + score 10 + message "Listed in Spamhaus XBL. See https://check.spamhaus.org/" + } + + # PBL - Policy Block List (dynamic IPs) + response 127.0.0.10 127.0.0.11 { + score 5 + message "Listed in Spamhaus PBL. See https://check.spamhaus.org/" + } + } } ``` @@ -171,3 +195,55 @@ will be rejected. It is possible to specify a negative value to make list act like a whitelist and override results of other blocklists. + +**Note:** When using `response` blocks (see below), the score from matching response +rules is used instead of this flat score value. + +--- + +### response _ip..._ + +**New in 0.8** + +Defines per-response-code rules for scoring and custom messages. This is useful +for combined DNSBLs like Spamhaus ZEN that return different codes for different +listing types. + +Each `response` block takes one or more IP addresses or CIDR ranges as arguments +and contains the following directives: + +#### score _integer_ +**Required** + +Score to add when this response code is returned. If multiple response codes +are returned by the DNSBL, scores are summed together. + +#### message _string_ +**Optional** + +Custom rejection or quarantine message to include when this response code +matches. This message is shown to the client or logged when the threshold +is reached. + +**Example:** + +``` +zen.spamhaus.org { + client_ipv4 yes + + # High severity - known spam sources + response 127.0.0.2 127.0.0.3 { + score 10 + message "Listed in Spamhaus SBL" + } + + # Lower severity - dynamic IPs + response 127.0.0.10 127.0.0.11 { + score 5 + message "Listed in Spamhaus PBL" + } +} +``` + +**Backwards compatibility:** When `response` blocks are not used, the legacy +`responses` and `score` directives work as before. diff --git a/internal/check/dnsbl/common.go b/internal/check/dnsbl/common.go index 7b874cb9..2f317097 100644 --- a/internal/check/dnsbl/common.go +++ b/internal/check/dnsbl/common.go @@ -32,9 +32,15 @@ type ListedErr struct { Identity string List string Reason string + Score int + Message string } func (le ListedErr) Fields() map[string]interface{} { + msg := "Client identity listed in the used DNSBL" + if le.Message != "" { + msg = le.Message + } return map[string]interface{}{ "check": "dnsbl", "list": le.List, @@ -42,7 +48,7 @@ func (le ListedErr) Fields() map[string]interface{} { "reason": le.Reason, "smtp_code": 554, "smtp_enchcode": exterrors.EnhancedCode{5, 7, 0}, - "smtp_msg": "Client identity listed in the used DNSBL", + "smtp_msg": msg, } } @@ -113,6 +119,56 @@ func checkIP(ctx context.Context, resolver dns.Resolver, cfg List, ip net.IP) er return err } + // If ResponseRules is configured, use new behavior + if len(cfg.ResponseRules)> 0 { + totalScore := 0 + var matchedMessages []string + var matchedReasons []string + + for _, addr := range addrs { + for _, rule := range cfg.ResponseRules { + for _, respNet := range rule.Networks { + if respNet.Contains(addr.IP) { + totalScore += rule.Score + if rule.Message != "" { + matchedMessages = append(matchedMessages, rule.Message) + } + matchedReasons = append(matchedReasons, addr.IP.String()) + break // Only match once per rule + } + } + } + } + + if totalScore == 0 { + return nil + } + + // Attempt to extract explanation string from TXT records + txts, err := resolver.LookupTXT(ctx, query) + var reason string + if err == nil && len(txts)> 0 { + reason = strings.Join(txts, "; ") + } else { + reason = strings.Join(matchedReasons, "; ") + } + + // Use first matched message if available + message := "" + if len(matchedMessages)> 0 { + message = matchedMessages[0] + } + + return ListedErr{ + Identity: ip.String(), + List: cfg.Zone, + Reason: reason, + Score: totalScore, + Message: message, + } + } + + // Legacy behavior: use flat Responses filter filteredAddrs := make([]net.IPAddr, 0, len(addrs)) addrsLoop: for _, addr := range addrs { diff --git a/internal/check/dnsbl/dnsbl.go b/internal/check/dnsbl/dnsbl.go index 2c91c838..fff87c37 100644 --- a/internal/check/dnsbl/dnsbl.go +++ b/internal/check/dnsbl/dnsbl.go @@ -38,6 +38,12 @@ import ( "golang.org/x/sync/errgroup" ) +type ResponseRule struct { + Networks []net.IPNet + Score int + Message string +} + type List struct { Zone string @@ -49,6 +55,8 @@ type List struct { ScoreAdj int Responses []net.IPNet + + ResponseRules []ResponseRule } var defaultBL = List{ @@ -126,7 +134,9 @@ func (bl *DNSBL) readListCfg(node config.Node) error { cfg.Bool("mailfrom", false, defaultBL.EHLO, &listCfg.MAILFROM) cfg.Int("score", false, false, 1, &listCfg.ScoreAdj) cfg.StringList("responses", false, false, []string{"127.0.0.1/24"}, &responseNets) - if _, err := cfg.Process(); err != nil { + cfg.AllowUnknown() + unknown, err := cfg.Process() + if err != nil { return err } @@ -144,6 +154,19 @@ func (bl *DNSBL) readListCfg(node config.Node) error { listCfg.Responses = append(listCfg.Responses, *ipNet) } + // Parse response blocks + for _, child := range unknown { + if child.Name == "response" { + rule, err := parseResponseRule(child) + if err != nil { + return err + } + listCfg.ResponseRules = append(listCfg.ResponseRules, rule) + } else { + return config.NodeErr(child, "unknown directive: %s", child.Name) + } + } + for _, zone := range append([]string{node.Name}, node.Args...) { zoneCfg := listCfg zoneCfg.Zone = zone @@ -173,6 +196,44 @@ func (bl *DNSBL) readListCfg(node config.Node) error { return nil } +func parseResponseRule(node config.Node) (ResponseRule, error) { + var rule ResponseRule + + if len(node.Args) == 0 { + return rule, config.NodeErr(node, "response block requires at least one IP address or CIDR as argument") + } + + // Parse IP addresses/CIDRs from arguments + for _, arg := range node.Args { + // If there is no / - it is a plain IP address, append '/32' or '/128' + resp := arg + if !strings.Contains(resp, "/") { + // Check if it's IPv6 to determine the mask + if strings.Contains(resp, ":") { + resp += "/128" + } else { + resp += "/32" + } + } + + _, ipNet, err := net.ParseCIDR(resp) + if err != nil { + return rule, config.NodeErr(node, "invalid IP address or CIDR: %s: %v", arg, err) + } + rule.Networks = append(rule.Networks, *ipNet) + } + + // Parse directives within the response block + cfg := config.NewMap(nil, node) + cfg.Int("score", false, true, 0, &rule.Score) + cfg.String("message", false, false, "", &rule.Message) + if _, err := cfg.Process(); err != nil { + return rule, err + } + + return rule, nil +} + func (bl *DNSBL) testList(listCfg List) { // Check RFC 5782 Section 5 requirements. @@ -298,6 +359,7 @@ func (bl *DNSBL) checkLists(ctx context.Context, ip net.IP, ehlo, mailFrom strin score int listedOn []string reasons []string + messages []string ) for _, list := range bl.bls { @@ -313,7 +375,18 @@ func (bl *DNSBL) checkLists(ctx context.Context, ip net.IP, ehlo, mailFrom strin defer lck.Unlock() listedOn = append(listedOn, listErr.List) reasons = append(reasons, listErr.Reason) - score += list.ScoreAdj + + // Use score from ListedErr if set (new behavior), otherwise use legacy ScoreAdj + if listErr.Score != 0 { + score += listErr.Score + } else { + score += list.ScoreAdj + } + + // Collect custom messages if available + if listErr.Message != "" { + messages = append(messages, listErr.Message) + } } return nil }) @@ -334,13 +407,19 @@ func (bl *DNSBL) checkLists(ctx context.Context, ip net.IP, ehlo, mailFrom strin } } + // Use custom message if available, otherwise use default + message := "Client identity is listed in the used DNSBL" + if len(messages)> 0 { + message = strings.Join(messages, "; ") + } + if score>= bl.rejectThres { return module.CheckResult{ Reject: true, Reason: &exterrors.SMTPError{ Code: 554, EnhancedCode: exterrors.EnhancedCode{5, 7, 0}, - Message: "Client identity is listed in the used DNSBL", + Message: message, Err: err, CheckName: "dnsbl", }, @@ -352,7 +431,7 @@ func (bl *DNSBL) checkLists(ctx context.Context, ip net.IP, ehlo, mailFrom strin Reason: &exterrors.SMTPError{ Code: 554, EnhancedCode: exterrors.EnhancedCode{5, 7, 0}, - Message: "Client identity is listed in the used DNSBL", + Message: message, Err: err, CheckName: "dnsbl", }, diff --git a/internal/check/dnsbl/dnsbl_test.go b/internal/check/dnsbl/dnsbl_test.go index 1845aeb7..4dce6c9e 100644 --- a/internal/check/dnsbl/dnsbl_test.go +++ b/internal/check/dnsbl/dnsbl_test.go @@ -211,3 +211,283 @@ func TestCheckLists(t *testing.T) { true, false, ) } + +func TestCheckIPWithResponseRules(t *testing.T) { + test := func(zones map[string]mockdns.Zone, cfg List, ip net.IP, expectedErr error) { + t.Helper() + resolver := mockdns.Resolver{Zones: zones} + err := checkIP(context.Background(), &resolver, cfg, ip) + if expectedErr == nil { + if err != nil { + t.Errorf("expected no error, got '%#v'", err) + } + } else { + if err == nil { + t.Errorf("expected err to be '%#v', got nil", expectedErr) + } else { + expectedLE, okExpected := expectedErr.(ListedErr) + actualLE, okActual := err.(ListedErr) + if !okExpected || !okActual { + t.Errorf("expected err to be '%#v', got '%#v'", expectedErr, err) + } else { + if expectedLE.Identity != actualLE.Identity || + expectedLE.List != actualLE.List || + expectedLE.Score != actualLE.Score || + expectedLE.Message != actualLE.Message { + t.Errorf("expected err to be '%#v', got '%#v'", expectedErr, err) + } + } + } + } + } + + // Test single response code with score and message + test(map[string]mockdns.Zone{ + "4.3.2.1.example.org.": { + A: []string{"127.0.0.2"}, + }, + }, List{ + Zone: "example.org", + ClientIPv4: true, + ResponseRules: []ResponseRule{ + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 2), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 10, + Message: "Listed in SBL", + }, + }, + }, net.IPv4(1, 2, 3, 4), ListedErr{ + Identity: "1.2.3.4", + List: "example.org", + Score: 10, + Message: "Listed in SBL", + }) + + // Test multiple response codes with different scores - scores should sum + test(map[string]mockdns.Zone{ + "4.3.2.1.example.org.": { + A: []string{"127.0.0.2", "127.0.0.11"}, + }, + }, List{ + Zone: "example.org", + ClientIPv4: true, + ResponseRules: []ResponseRule{ + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 2), Mask: net.IPv4Mask(255, 255, 255, 255)}, + {IP: net.IPv4(127, 0, 0, 3), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 10, + Message: "Listed in SBL", + }, + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 10), Mask: net.IPv4Mask(255, 255, 255, 255)}, + {IP: net.IPv4(127, 0, 0, 11), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 5, + Message: "Listed in PBL", + }, + }, + }, net.IPv4(1, 2, 3, 4), ListedErr{ + Identity: "1.2.3.4", + List: "example.org", + Score: 15, // 10 + 5 + Message: "Listed in SBL", + }) + + // Test response code that doesn't match any rule - should return nil + test(map[string]mockdns.Zone{ + "4.3.2.1.example.org.": { + A: []string{"127.0.0.99"}, + }, + }, List{ + Zone: "example.org", + ClientIPv4: true, + ResponseRules: []ResponseRule{ + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 2), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 10, + Message: "Listed in SBL", + }, + }, + }, net.IPv4(1, 2, 3, 4), nil) + + // Test low severity only - should get score 5 + test(map[string]mockdns.Zone{ + "4.3.2.1.example.org.": { + A: []string{"127.0.0.10"}, + }, + }, List{ + Zone: "example.org", + ClientIPv4: true, + ResponseRules: []ResponseRule{ + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 2), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 10, + Message: "Listed in SBL", + }, + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 10), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 5, + Message: "Listed in PBL", + }, + }, + }, net.IPv4(1, 2, 3, 4), ListedErr{ + Identity: "1.2.3.4", + List: "example.org", + Score: 5, + Message: "Listed in PBL", + }) + + // Test high severity - should get score 10 + test(map[string]mockdns.Zone{ + "4.3.2.1.example.org.": { + A: []string{"127.0.0.2"}, + }, + }, List{ + Zone: "example.org", + ClientIPv4: true, + ResponseRules: []ResponseRule{ + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 2), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 10, + Message: "Listed in SBL", + }, + }, + }, net.IPv4(1, 2, 3, 4), ListedErr{ + Identity: "1.2.3.4", + List: "example.org", + Score: 10, + Message: "Listed in SBL", + }) +} + +func TestCheckListsWithResponseRules(t *testing.T) { + test := func(zones map[string]mockdns.Zone, bls []List, ip net.IP, ehlo, mailFrom string, reject, quarantine bool) { + mod := &DNSBL{ + bls: bls, + resolver: &mockdns.Resolver{Zones: zones}, + log: testutils.Logger(t, "dnsbl"), + quarantineThres: 5, + rejectThres: 10, + } + result := mod.checkLists(context.Background(), ip, ehlo, mailFrom) + + if result.Reject && !reject { + t.Errorf("Expected message to not be rejected") + } + if !result.Reject && reject { + t.Errorf("Expected message to be rejected") + } + if result.Quarantine && !quarantine { + t.Errorf("Expected message to not be quarantined") + } + if !result.Quarantine && quarantine { + t.Errorf("Expected message to be quarantined") + } + } + + // Test: Only low-severity code returned -> quarantine but not reject + test(map[string]mockdns.Zone{ + "4.3.2.1.zen.example.org.": { + A: []string{"127.0.0.11"}, + }, + }, []List{ + { + Zone: "zen.example.org", + ClientIPv4: true, + ResponseRules: []ResponseRule{ + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 2), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 10, + Message: "Listed in SBL", + }, + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 10), Mask: net.IPv4Mask(255, 255, 255, 255)}, + {IP: net.IPv4(127, 0, 0, 11), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 5, + Message: "Listed in PBL", + }, + }, + }, + }, net.IPv4(1, 2, 3, 4), "mx.example.com", "foo@example.com", false, true) + + // Test: High-severity code returned -> reject + test(map[string]mockdns.Zone{ + "4.3.2.1.zen.example.org.": { + A: []string{"127.0.0.2"}, + }, + }, []List{ + { + Zone: "zen.example.org", + ClientIPv4: true, + ResponseRules: []ResponseRule{ + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 2), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 10, + Message: "Listed in SBL", + }, + }, + }, + }, net.IPv4(1, 2, 3, 4), "mx.example.com", "foo@example.com", true, false) + + // Test: Legacy configuration without response blocks -> existing behavior preserved + test(map[string]mockdns.Zone{ + "4.3.2.1.example.org.": { + A: []string{"127.0.0.1"}, + }, + }, []List{ + { + Zone: "example.org", + ClientIPv4: true, + ScoreAdj: 10, + }, + }, net.IPv4(1, 2, 3, 4), "mx.example.com", "foo@example.com", true, false) + + // Test: Mixed configuration (some lists with response blocks, some without) -> both work correctly + test(map[string]mockdns.Zone{ + "4.3.2.1.zen.example.org.": { + A: []string{"127.0.0.11"}, + }, + "4.3.2.1.legacy.example.org.": { + A: []string{"127.0.0.1"}, + }, + }, []List{ + { + Zone: "zen.example.org", + ClientIPv4: true, + ResponseRules: []ResponseRule{ + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 11), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 5, + Message: "Listed in PBL", + }, + }, + }, + { + Zone: "legacy.example.org", + ClientIPv4: true, + ScoreAdj: 3, + }, + }, net.IPv4(1, 2, 3, 4), "mx.example.com", "foo@example.com", false, true) // 5 + 3 = 8, quarantine but not reject +} + From 3404ef66e6522249e9bb12528fcedcff62419e83 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+copilot@users.noreply.github.com> Date: 2026年1月11日 04:50:36 +0000 Subject: [PATCH 062/111] Fix config parsing to use node.Children instead of AllowUnknown Co-authored-by: thisisjaymehta <31812582+thisisjaymehta@users.noreply.github.com> --- internal/check/dnsbl/dnsbl.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/check/dnsbl/dnsbl.go b/internal/check/dnsbl/dnsbl.go index fff87c37..37b5530a 100644 --- a/internal/check/dnsbl/dnsbl.go +++ b/internal/check/dnsbl/dnsbl.go @@ -134,9 +134,7 @@ func (bl *DNSBL) readListCfg(node config.Node) error { cfg.Bool("mailfrom", false, defaultBL.EHLO, &listCfg.MAILFROM) cfg.Int("score", false, false, 1, &listCfg.ScoreAdj) cfg.StringList("responses", false, false, []string{"127.0.0.1/24"}, &responseNets) - cfg.AllowUnknown() - unknown, err := cfg.Process() - if err != nil { + if _, err := cfg.Process(); err != nil { return err } @@ -154,8 +152,8 @@ func (bl *DNSBL) readListCfg(node config.Node) error { listCfg.Responses = append(listCfg.Responses, *ipNet) } - // Parse response blocks - for _, child := range unknown { + // Parse response blocks from node children + for _, child := range node.Children { if child.Name == "response" { rule, err := parseResponseRule(child) if err != nil { From fe91076f597d496ecceab067b88c6199acb35779 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+copilot@users.noreply.github.com> Date: 2026年1月11日 04:51:43 +0000 Subject: [PATCH 063/111] Fix rule matching to count each rule only once Co-authored-by: thisisjaymehta <31812582+thisisjaymehta@users.noreply.github.com> --- internal/check/dnsbl/common.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/check/dnsbl/common.go b/internal/check/dnsbl/common.go index 2f317097..d11626d4 100644 --- a/internal/check/dnsbl/common.go +++ b/internal/check/dnsbl/common.go @@ -125,8 +125,16 @@ func checkIP(ctx context.Context, resolver dns.Resolver, cfg List, ip net.IP) er var matchedMessages []string var matchedReasons []string + // Track which rules have been matched to avoid counting the same rule multiple times + matchedRules := make(map[int]bool) + for _, addr := range addrs { - for _, rule := range cfg.ResponseRules { + for ruleIdx, rule := range cfg.ResponseRules { + // Skip if this rule has already been matched + if matchedRules[ruleIdx] { + continue + } + for _, respNet := range rule.Networks { if respNet.Contains(addr.IP) { totalScore += rule.Score @@ -134,7 +142,8 @@ func checkIP(ctx context.Context, resolver dns.Resolver, cfg List, ip net.IP) er matchedMessages = append(matchedMessages, rule.Message) } matchedReasons = append(matchedReasons, addr.IP.String()) - break // Only match once per rule + matchedRules[ruleIdx] = true + break // Move to next rule } } } From 11be6679520b23ec1fba38bf0ef37a39cec9e904 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+copilot@users.noreply.github.com> Date: 2026年1月11日 04:52:26 +0000 Subject: [PATCH 064/111] Improve documentation with clearer scoring behavior examples Co-authored-by: thisisjaymehta <31812582+thisisjaymehta@users.noreply.github.com> --- docs/reference/checks/dnsbl.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/reference/checks/dnsbl.md b/docs/reference/checks/dnsbl.md index be31a67f..5766de1a 100644 --- a/docs/reference/checks/dnsbl.md +++ b/docs/reference/checks/dnsbl.md @@ -216,7 +216,9 @@ and contains the following directives: **Required** Score to add when this response code is returned. If multiple response codes -are returned by the DNSBL, scores are summed together. +are returned by the DNSBL, and they match different rules, the scores from +all matched rules are summed together. Each rule is counted only once, even +if multiple returned IPs match networks within that rule. #### message _string_ **Optional** @@ -245,5 +247,11 @@ zen.spamhaus.org { } ``` +**Scoring behavior:** +- If DNSBL returns `127.0.0.2` only → Score: 10 (matches first rule) +- If DNSBL returns `127.0.0.11` only → Score: 5 (matches second rule) +- If DNSBL returns both `127.0.0.2` and `127.0.0.11` → Score: 15 (both rules match, scores sum) +- If DNSBL returns both `127.0.0.2` and `127.0.0.3` → Score: 10 (same rule matches, counted once) + **Backwards compatibility:** When `response` blocks are not used, the legacy `responses` and `score` directives work as before. From 66339ddefb8f13303af3377039f2d48c4cd84153 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+copilot@users.noreply.github.com> Date: 2026年1月11日 05:02:18 +0000 Subject: [PATCH 065/111] Address upstream PR feedback: use cfg.Callback, extract function, remove version annotation Co-authored-by: thisisjaymehta <31812582+thisisjaymehta@users.noreply.github.com> --- docs/reference/checks/dnsbl.md | 2 - internal/check/dnsbl/common.go | 147 +++++++++++++++------------------ internal/check/dnsbl/dnsbl.go | 22 ++--- 3 files changed, 79 insertions(+), 92 deletions(-) diff --git a/docs/reference/checks/dnsbl.md b/docs/reference/checks/dnsbl.md index 5766de1a..0dd62e57 100644 --- a/docs/reference/checks/dnsbl.md +++ b/docs/reference/checks/dnsbl.md @@ -203,8 +203,6 @@ rules is used instead of this flat score value. ### response _ip..._ -**New in 0.8** - Defines per-response-code rules for scoring and custom messages. This is useful for combined DNSBLs like Spamhaus ZEN that return different codes for different listing types. diff --git a/internal/check/dnsbl/common.go b/internal/check/dnsbl/common.go index d11626d4..7541d24f 100644 --- a/internal/check/dnsbl/common.go +++ b/internal/check/dnsbl/common.go @@ -94,6 +94,34 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, cfg List, domain st } } +func matchResponseRules(addrs []net.IPAddr, rules []ResponseRule) (score int, messages []string, reasons []string, matched bool) { + // Track which rules have been matched to avoid counting the same rule multiple times + matchedRules := make(map[int]bool) + + for _, addr := range addrs { + for ruleIdx, rule := range rules { + // Skip if this rule has already been matched + if matchedRules[ruleIdx] { + continue + } + + for _, respNet := range rule.Networks { + if respNet.Contains(addr.IP) { + score += rule.Score + if rule.Message != "" { + messages = append(messages, rule.Message) + } + reasons = append(reasons, addr.IP.String()) + matchedRules[ruleIdx] = true + matched = true + break // Move to next rule + } + } + } + } + return +} + func checkIP(ctx context.Context, resolver dns.Resolver, cfg List, ip net.IP) error { ipv6 := true if ipv4 := ip.To4(); ipv4 != nil { @@ -119,111 +147,72 @@ func checkIP(ctx context.Context, resolver dns.Resolver, cfg List, ip net.IP) er return err } + var filteredAddrs []net.IPAddr + var score int + var customMessage string + // If ResponseRules is configured, use new behavior if len(cfg.ResponseRules)> 0 { - totalScore := 0 - var matchedMessages []string - var matchedReasons []string - - // Track which rules have been matched to avoid counting the same rule multiple times - matchedRules := make(map[int]bool) - - for _, addr := range addrs { - for ruleIdx, rule := range cfg.ResponseRules { - // Skip if this rule has already been matched - if matchedRules[ruleIdx] { - continue - } - - for _, respNet := range rule.Networks { - if respNet.Contains(addr.IP) { - totalScore += rule.Score - if rule.Message != "" { - matchedMessages = append(matchedMessages, rule.Message) - } - matchedReasons = append(matchedReasons, addr.IP.String()) - matchedRules[ruleIdx] = true - break // Move to next rule - } - } - } - } - - if totalScore == 0 { + matchedScore, matchedMessages, matchedReasons, matched := matchResponseRules(addrs, cfg.ResponseRules) + if !matched { return nil } - - // Attempt to extract explanation string from TXT records - txts, err := resolver.LookupTXT(ctx, query) - var reason string - if err == nil && len(txts)> 0 { - reason = strings.Join(txts, "; ") - } else { - reason = strings.Join(matchedReasons, "; ") - } - + score = matchedScore + // Use first matched message if available - message := "" if len(matchedMessages)> 0 { - message = matchedMessages[0] + customMessage = matchedMessages[0] } - - return ListedErr{ - Identity: ip.String(), - List: cfg.Zone, - Reason: reason, - Score: totalScore, - Message: message, + + // Build filteredAddrs from matched reasons for TXT lookup fallback + for _, reason := range matchedReasons { + filteredAddrs = append(filteredAddrs, net.IPAddr{IP: net.ParseIP(reason)}) } - } - - // Legacy behavior: use flat Responses filter - filteredAddrs := make([]net.IPAddr, 0, len(addrs)) -addrsLoop: - for _, addr := range addrs { - // No responses whitelist configured - permit all. - if len(cfg.Responses) == 0 { - filteredAddrs = append(filteredAddrs, addr) - continue - } - - for _, respNet := range cfg.Responses { - if respNet.Contains(addr.IP) { + } else { + // Legacy behavior: use flat Responses filter + filteredAddrs = make([]net.IPAddr, 0, len(addrs)) + addrsLoop: + for _, addr := range addrs { + // No responses whitelist configured - permit all. + if len(cfg.Responses) == 0 { filteredAddrs = append(filteredAddrs, addr) - continue addrsLoop + continue + } + + for _, respNet := range cfg.Responses { + if respNet.Contains(addr.IP) { + filteredAddrs = append(filteredAddrs, addr) + continue addrsLoop + } } } - } - if len(filteredAddrs) == 0 { - return nil + if len(filteredAddrs) == 0 { + return nil + } } - // Attempt to extract explanation string. + // Attempt to extract explanation string from TXT records (shared by both paths) txts, err := resolver.LookupTXT(ctx, query) - if err != nil || len(txts) == 0 { + var reason string + if err == nil && len(txts)> 0 { + reason = strings.Join(txts, "; ") + } else { // Not significant, include addresses as reason. Usually they are // mapped to some predefined 'reasons' by BL. - reasonParts := make([]string, 0, len(filteredAddrs)) for _, addr := range filteredAddrs { reasonParts = append(reasonParts, addr.IP.String()) } - - return ListedErr{ - Identity: ip.String(), - List: cfg.Zone, - Reason: strings.Join(reasonParts, "; "), - } + reason = strings.Join(reasonParts, "; ") } - // Some BLs provide multiple reasons (meta-BLs such as Spamhaus Zen) so - // don't mangle them by joining with "", instead join with "; ". - return ListedErr{ Identity: ip.String(), List: cfg.Zone, - Reason: strings.Join(txts, "; "), + Reason: reason, + Score: score, + Message: customMessage, } } diff --git a/internal/check/dnsbl/dnsbl.go b/internal/check/dnsbl/dnsbl.go index 37b5530a..94cbfe80 100644 --- a/internal/check/dnsbl/dnsbl.go +++ b/internal/check/dnsbl/dnsbl.go @@ -134,6 +134,14 @@ func (bl *DNSBL) readListCfg(node config.Node) error { cfg.Bool("mailfrom", false, defaultBL.EHLO, &listCfg.MAILFROM) cfg.Int("score", false, false, 1, &listCfg.ScoreAdj) cfg.StringList("responses", false, false, []string{"127.0.0.1/24"}, &responseNets) + cfg.Callback("response", func(_ *config.Map, node config.Node) error { + rule, err := parseResponseRule(node) + if err != nil { + return err + } + listCfg.ResponseRules = append(listCfg.ResponseRules, rule) + return nil + }) if _, err := cfg.Process(); err != nil { return err } @@ -152,17 +160,9 @@ func (bl *DNSBL) readListCfg(node config.Node) error { listCfg.Responses = append(listCfg.Responses, *ipNet) } - // Parse response blocks from node children - for _, child := range node.Children { - if child.Name == "response" { - rule, err := parseResponseRule(child) - if err != nil { - return err - } - listCfg.ResponseRules = append(listCfg.ResponseRules, rule) - } else { - return config.NodeErr(child, "unknown directive: %s", child.Name) - } + // Warn if both response and responses are configured + if len(listCfg.ResponseRules)> 0 && len(responseNets)> 0 { + bl.log.Msg("both 'response' blocks and 'responses' directive are specified, 'response' blocks take precedence", "list", node.Name) } for _, zone := range append([]string{node.Name}, node.Args...) { From c29208c95d75e813d4c3bab57a7cf3273a9437c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+copilot@users.noreply.github.com> Date: 2026年1月11日 05:21:16 +0000 Subject: [PATCH 066/111] Add ResponseRules support for checkDomain (domain-based lookups) Co-authored-by: thisisjaymehta <31812582+thisisjaymehta@users.noreply.github.com> --- docs/reference/checks/dnsbl.md | 3 + internal/check/dnsbl/common.go | 53 +++++++++++---- internal/check/dnsbl/common_test.go | 101 ++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 12 deletions(-) diff --git a/docs/reference/checks/dnsbl.md b/docs/reference/checks/dnsbl.md index 0dd62e57..d7bb74cf 100644 --- a/docs/reference/checks/dnsbl.md +++ b/docs/reference/checks/dnsbl.md @@ -207,6 +207,9 @@ Defines per-response-code rules for scoring and custom messages. This is useful for combined DNSBLs like Spamhaus ZEN that return different codes for different listing types. +This works for both IP-based lookups (client_ipv4, client_ipv6) and domain-based +lookups (ehlo, mailfrom). + Each `response` block takes one or more IP addresses or CIDR ranges as arguments and contains the following directives: diff --git a/internal/check/dnsbl/common.go b/internal/check/dnsbl/common.go index 7541d24f..4e90e7e2 100644 --- a/internal/check/dnsbl/common.go +++ b/internal/check/dnsbl/common.go @@ -72,25 +72,54 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, cfg List, domain st return nil } - // Attempt to extract explanation string. - txts, err := resolver.LookupTXT(context.Background(), query) - if err != nil || len(txts) == 0 { - // Not significant, include addresses as reason. Usually they are - // mapped to some predefined 'reasons' by BL. - return ListedErr{ - Identity: domain, - List: cfg.Zone, - Reason: strings.Join(addrs, "; "), + var score int + var customMessage string + var filteredAddrs []string + + // If ResponseRules is configured, use new behavior + if len(cfg.ResponseRules)> 0 { + // Convert string addresses to IPAddr for matching + ipAddrs := make([]net.IPAddr, 0, len(addrs)) + for _, addr := range addrs { + if ip := net.ParseIP(addr); ip != nil { + ipAddrs = append(ipAddrs, net.IPAddr{IP: ip}) + } } + + matchedScore, matchedMessages, matchedReasons, matched := matchResponseRules(ipAddrs, cfg.ResponseRules) + if !matched { + return nil + } + score = matchedScore + + // Use first matched message if available + if len(matchedMessages)> 0 { + customMessage = matchedMessages[0] + } + + filteredAddrs = matchedReasons + } else { + // Legacy behavior: accept all addresses + filteredAddrs = addrs } - // Some BLs provide multiple reasons (meta-BLs such as Spamhaus Zen) so - // don't mangle them by joining with "", instead join with "; ". + // Attempt to extract explanation string from TXT records (shared by both paths) + txts, err := resolver.LookupTXT(ctx, query) + var reason string + if err == nil && len(txts)> 0 { + reason = strings.Join(txts, "; ") + } else { + // Not significant, include addresses as reason. Usually they are + // mapped to some predefined 'reasons' by BL. + reason = strings.Join(filteredAddrs, "; ") + } return ListedErr{ Identity: domain, List: cfg.Zone, - Reason: strings.Join(txts, "; "), + Reason: reason, + Score: score, + Message: customMessage, } } diff --git a/internal/check/dnsbl/common_test.go b/internal/check/dnsbl/common_test.go index caa06575..8d2b24e3 100644 --- a/internal/check/dnsbl/common_test.go +++ b/internal/check/dnsbl/common_test.go @@ -236,3 +236,104 @@ func TestCheckIP(t *testing.T) { Reason: "127.0.0.1", }) } + +func TestCheckDomainWithResponseRules(t *testing.T) { + test := func(zones map[string]mockdns.Zone, cfg List, domain string, expectedErr error) { + t.Helper() + resolver := mockdns.Resolver{Zones: zones} + err := checkDomain(context.Background(), &resolver, cfg, domain) + if expectedErr == nil { + if err != nil { + t.Errorf("expected no error, got '%#v'", err) + } + } else { + if err == nil { + t.Errorf("expected err to be '%#v', got nil", expectedErr) + } else { + expectedLE, okExpected := expectedErr.(ListedErr) + actualLE, okActual := err.(ListedErr) + if !okExpected || !okActual { + t.Errorf("expected err to be '%#v', got '%#v'", expectedErr, err) + } else { + if expectedLE.Identity != actualLE.Identity || + expectedLE.List != actualLE.List || + expectedLE.Score != actualLE.Score || + expectedLE.Message != actualLE.Message { + t.Errorf("expected err to be '%#v', got '%#v'", expectedErr, err) + } + } + } + } + } + + // Test domain with single response code and custom message + test(map[string]mockdns.Zone{ + "spam.example.com.dnsbl.example.org.": { + A: []string{"127.0.0.2"}, + }, + }, List{ + Zone: "dnsbl.example.org", + ResponseRules: []ResponseRule{ + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 2), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 10, + Message: "Domain listed as spam source", + }, + }, + }, "spam.example.com", ListedErr{ + Identity: "spam.example.com", + List: "dnsbl.example.org", + Score: 10, + Message: "Domain listed as spam source", + }) + + // Test domain with multiple response codes - scores should sum + test(map[string]mockdns.Zone{ + "multi.example.com.dnsbl.example.org.": { + A: []string{"127.0.0.2", "127.0.0.11"}, + }, + }, List{ + Zone: "dnsbl.example.org", + ResponseRules: []ResponseRule{ + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 2), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 10, + Message: "High severity", + }, + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 11), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 5, + Message: "Low severity", + }, + }, + }, "multi.example.com", ListedErr{ + Identity: "multi.example.com", + List: "dnsbl.example.org", + Score: 15, // 10 + 5 + Message: "High severity", + }) + + // Test domain with no matching response codes + test(map[string]mockdns.Zone{ + "unknown.example.com.dnsbl.example.org.": { + A: []string{"127.0.0.99"}, + }, + }, List{ + Zone: "dnsbl.example.org", + ResponseRules: []ResponseRule{ + { + Networks: []net.IPNet{ + {IP: net.IPv4(127, 0, 0, 2), Mask: net.IPv4Mask(255, 255, 255, 255)}, + }, + Score: 10, + Message: "Listed", + }, + }, + }, "unknown.example.com", nil) +} From f76fa74e4717494003b0a07b523a9ec031f238ff Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Sun, 9 Mar 2025 15:52:38 +0300 Subject: [PATCH 067/111] docs: Sync docs/index.md with main README See #759. --- docs/index.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index 901f7230..a9b7ee68 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,10 +13,11 @@ daemon with uniform configuration and minimal maintenance cost. feature-packed implementation you may want to use Dovecot instead. maddy still can handle message delivery business. -[![builds.sr.ht status](https://builds.sr.ht/~emersion/maddy.svg)](https://builds.sr.ht/~emersion/maddy?) -[![License text](https://img.shields.io/github/license/foxcpp/maddy)](https://github.com/foxcpp/maddy/blob/master/LICENSE) -[![Issues tracker](https://img.shields.io/github/issues/foxcpp/maddy)](https://github.com/foxcpp/maddy) +[![CI status](https://img.shields.io/github/actions/workflow/status/foxcpp/maddy/cicd.yml?style=flat-square)](https://github.com/foxcpp/maddy/actions/workflows/cicd.yml) +[![Issues tracker](https://img.shields.io/github/issues/foxcpp/maddy?style=flat-square)](https://github.com/foxcpp/maddy) * [Setup tutorial](https://maddy.email/tutorials/setting-up/) +* [Documentation](https://maddy.email/) + * [IRC channel](https://webchat.oftc.net/?channels=maddy&uio=MT11bmRlZmluZWQb1) * [Mailing list](https://lists.sr.ht/~foxcpp/maddy) From 15b398991eeb0d638538e5e278afa71a4a1ab235 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年1月14日 17:47:13 +0300 Subject: [PATCH 068/111] maddy 0.8.2 --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index 6f4eebdf..100435be 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -0.8.1 +0.8.2 From 0d68e634dcc77028839744d4720c6721726b8166 Mon Sep 17 00:00:00 2001 From: Jay Mehta Date: 2026年1月17日 18:06:35 +0530 Subject: [PATCH 069/111] Formatted files with goimports --- internal/check/dnsbl/common.go | 6 +++--- internal/check/dnsbl/dnsbl.go | 4 ++-- internal/check/dnsbl/dnsbl_test.go | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/check/dnsbl/common.go b/internal/check/dnsbl/common.go index 4e90e7e2..ae43b23a 100644 --- a/internal/check/dnsbl/common.go +++ b/internal/check/dnsbl/common.go @@ -133,7 +133,7 @@ func matchResponseRules(addrs []net.IPAddr, rules []ResponseRule) (score int, me if matchedRules[ruleIdx] { continue } - + for _, respNet := range rule.Networks { if respNet.Contains(addr.IP) { score += rule.Score @@ -187,12 +187,12 @@ func checkIP(ctx context.Context, resolver dns.Resolver, cfg List, ip net.IP) er return nil } score = matchedScore - + // Use first matched message if available if len(matchedMessages)> 0 { customMessage = matchedMessages[0] } - + // Build filteredAddrs from matched reasons for TXT lookup fallback for _, reason := range matchedReasons { filteredAddrs = append(filteredAddrs, net.IPAddr{IP: net.ParseIP(reason)}) diff --git a/internal/check/dnsbl/dnsbl.go b/internal/check/dnsbl/dnsbl.go index 94cbfe80..800c6ed9 100644 --- a/internal/check/dnsbl/dnsbl.go +++ b/internal/check/dnsbl/dnsbl.go @@ -373,14 +373,14 @@ func (bl *DNSBL) checkLists(ctx context.Context, ip net.IP, ehlo, mailFrom strin defer lck.Unlock() listedOn = append(listedOn, listErr.List) reasons = append(reasons, listErr.Reason) - + // Use score from ListedErr if set (new behavior), otherwise use legacy ScoreAdj if listErr.Score != 0 { score += listErr.Score } else { score += list.ScoreAdj } - + // Collect custom messages if available if listErr.Message != "" { messages = append(messages, listErr.Message) diff --git a/internal/check/dnsbl/dnsbl_test.go b/internal/check/dnsbl/dnsbl_test.go index 4dce6c9e..d4cd7238 100644 --- a/internal/check/dnsbl/dnsbl_test.go +++ b/internal/check/dnsbl/dnsbl_test.go @@ -490,4 +490,3 @@ func TestCheckListsWithResponseRules(t *testing.T) { }, }, net.IPv4(1, 2, 3, 4), "mx.example.com", "foo@example.com", false, true) // 5 + 3 = 8, quarantine but not reject } - From b1dc3ffed23a473c5c80921b4b1675f3b17dc8c6 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年1月20日 23:03:26 +0300 Subject: [PATCH 070/111] maddy 0.9.0 --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index 100435be..ac39a106 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -0.8.2 +0.9.0 From 1e5e01e742ec60b72b6e8145c516ef9ed3dfbb66 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年1月20日 23:26:17 +0300 Subject: [PATCH 071/111] Fix-up dev merge --- internal/libdns/gcore.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/libdns/gcore.go b/internal/libdns/gcore.go index 01d7afb6..69d0da5e 100644 --- a/internal/libdns/gcore.go +++ b/internal/libdns/gcore.go @@ -1,5 +1,4 @@ //go:build libdns_gcore || !libdns_separate -// +build libdns_gcore !libdns_separate package libdns @@ -12,7 +11,7 @@ import ( ) func init() { - module.Register("libdns.gcore", func(modName, instName string, _, _ []string) (module.Module, error) { + module.Register("libdns.gcore", func(modName, instName string) (module.Module, error) { p := gcore.Provider{} return &ProviderModule{ RecordDeleter: &p, From b2804378c2575fe05cab5002731b0e6fc81f6519 Mon Sep 17 00:00:00 2001 From: Pierre Alexandre SCHEMBRI Date: 2026年2月11日 10:51:39 +0100 Subject: [PATCH 072/111] cli: start/stop storage modules in ctl commands Start LifetimeModule instances when running CLI subcommands that operate on storage/auth DBs, and stop them on Close(). This fixes imap-acct crashes due to uninitialized imapsql backends while preserving ManageableStorage support. --- internal/cli/ctl/moduleinit.go | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/internal/cli/ctl/moduleinit.go b/internal/cli/ctl/moduleinit.go index 1129a516..778c3949 100644 --- a/internal/cli/ctl/moduleinit.go +++ b/internal/cli/ctl/moduleinit.go @@ -37,6 +37,36 @@ func closeIfNeeded(i interface{}) { } } +type managedStorage struct { + module.ManageableStorage + started bool +} + +func (m *managedStorage) Close() error { + if !m.started { + return nil + } + if lm, ok := m.ManageableStorage.(module.LifetimeModule); ok { + return lm.Stop() + } + return nil +} + +type managedUserDB struct { + module.PlainUserDB + started bool +} + +func (m *managedUserDB) Close() error { + if !m.started { + return nil + } + if lm, ok := m.PlainUserDB.(module.LifetimeModule); ok { + return lm.Stop() + } + return nil +} + func getCfgBlockModule(ctx *cli.Context) (*container.C, module.Module, error) { cfgPath := ctx.String("config") if cfgPath == "" { @@ -92,6 +122,14 @@ func openStorage(ctx *cli.Context) (module.Storage, error) { return nil, cli.Exit(fmt.Sprintf("Error: configuration block %s is not an IMAP storage", ctx.String("cfg-block")), 2) } + started := false + if lt, ok := storage.(module.LifetimeModule); ok { + if err := lt.Start(); err != nil { + return nil, err + } + started = true + } + if updStore, ok := mod.(updatepipe.Backend); ok { if err := updStore.EnableUpdatePipe(updatepipe.ModePush); err != nil && !errors.Is(err, os.ErrNotExist) { fmt.Fprintf(os.Stderr, "Failed to initialize update pipe, do not remove messages from mailboxes open by clients: %v\n", err) @@ -100,6 +138,11 @@ func openStorage(ctx *cli.Context) (module.Storage, error) { fmt.Fprintf(os.Stderr, "No update pipe support, do not remove messages from mailboxes open by clients\n") } + if started { + if ms, ok := storage.(module.ManageableStorage); ok { + return &managedStorage{ManageableStorage: ms, started: started}, nil + } + } return storage, nil } @@ -114,5 +157,16 @@ func openUserDB(ctx *cli.Context) (module.PlainUserDB, error) { return nil, cli.Exit(fmt.Sprintf("Error: configuration block %s is not a local credentials store", ctx.String("cfg-block")), 2) } + started := false + if lt, ok := userDB.(module.LifetimeModule); ok { + if err := lt.Start(); err != nil { + return nil, err + } + started = true + } + + if started { + return &managedUserDB{PlainUserDB: userDB, started: started}, nil + } return userDB, nil } From 7d94d7772fecfa198242e763b424644e78f295a2 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Tue, 3 Mar 2026 17:45:56 +0300 Subject: [PATCH 073/111] Linters clean-up, fix DNS mock leak in integration tests --- framework/module/lifetime.go | 4 +++- framework/resource/netresource/listen.go | 4 ++-- framework/resource/netresource/tracker.go | 16 +++++++++++---- go.mod | 3 +++ internal/cli/ctl/moduleinit.go | 10 ++++++---- internal/endpoint/imap/imap.go | 4 +++- internal/endpoint/openmetrics/om.go | 4 +++- internal/endpoint/smtp/smtp.go | 8 ++++++-- internal/endpoint/smtp/smtp_test.go | 5 ++++- internal/table/file_test.go | 9 ++++++--- internal/target/queue/queue_test.go | 5 +++-- internal/target/remote/mxauth_test.go | 13 +++++++++--- maddy.go | 24 +++++++++++++++++------ tests/t.go | 24 +++++++++++++---------- 14 files changed, 93 insertions(+), 40 deletions(-) diff --git a/framework/module/lifetime.go b/framework/module/lifetime.go index 5338b01f..c24f19f3 100644 --- a/framework/module/lifetime.go +++ b/framework/module/lifetime.go @@ -60,7 +60,9 @@ func (lt *LifetimeTracker) StartAll() error { } if err := entry.mod.Start(); err != nil { - lt.StopAll() + if err := lt.StopAll(); err != nil { + lt.logger.Error("StopAll failed after Start fail", err) + } return fmt.Errorf("failed to start module %v: %w", entry.mod.InstanceName(), err) } diff --git a/framework/resource/netresource/listen.go b/framework/resource/netresource/listen.go index 6d164fd7..23fea4a5 100644 --- a/framework/resource/netresource/listen.go +++ b/framework/resource/netresource/listen.go @@ -16,8 +16,8 @@ func CloseUnusedListeners() error { return tracker.CloseUnused() } -func CloseAllListeners() { - tracker.Close() +func CloseAllListeners() error { + return tracker.Close() } func ResetListenersUsage() { diff --git a/framework/resource/netresource/tracker.go b/framework/resource/netresource/tracker.go index a1827aa2..2099f7a7 100644 --- a/framework/resource/netresource/tracker.go +++ b/framework/resource/netresource/tracker.go @@ -67,14 +67,22 @@ func (lt *ListenerTracker) ResetUsage() { } func (lt *ListenerTracker) CloseUnused() error { - lt.tcp.CloseUnused(func(key string) bool { return true }) - lt.unix.CloseUnused(func(key string) bool { return true }) + if err := lt.tcp.CloseUnused(func(key string) bool { return true }); err != nil { + lt.logger.Error("CloseUnused for TCP failed", err) + } + if err := lt.unix.CloseUnused(func(key string) bool { return true }); err != nil { + lt.logger.Error("CloseUnused for Unix failed", err) + } return nil } func (lt *ListenerTracker) Close() error { - lt.tcp.Close() - lt.unix.Close() + if err := lt.tcp.Close(); err != nil { + lt.logger.Error("Close for TCP failed", err) + } + if err := lt.unix.Close(); err != nil { + lt.logger.Error("Close for Unix failed", err) + } return nil } diff --git a/go.mod b/go.mod index 668bf27b..cfa00afd 100644 --- a/go.mod +++ b/go.mod @@ -52,6 +52,7 @@ require ( github.com/minio/minio-go/v7 v7.0.84 github.com/netauth/netauth v0.6.2 github.com/prometheus/client_golang v1.20.5 + github.com/stretchr/testify v1.10.0 github.com/urfave/cli/v2 v2.27.5 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.32.0 @@ -87,6 +88,7 @@ require ( github.com/caddyserver/zerossl v0.1.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/digitalocean/godo v1.134.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/color v1.18.0 // indirect @@ -122,6 +124,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect diff --git a/internal/cli/ctl/moduleinit.go b/internal/cli/ctl/moduleinit.go index 778c3949..5519d0e3 100644 --- a/internal/cli/ctl/moduleinit.go +++ b/internal/cli/ctl/moduleinit.go @@ -21,19 +21,21 @@ package ctl import ( "errors" "fmt" - "io" "os" "github.com/foxcpp/maddy" "github.com/foxcpp/maddy/framework/container" + "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/updatepipe" "github.com/urfave/cli/v2" ) -func closeIfNeeded(i interface{}) { - if c, ok := i.(io.Closer); ok { - c.Close() +func closeIfNeeded(i any) { + if c, ok := i.(module.LifetimeModule); ok { + if err := c.Stop(); err != nil { + log.DefaultLogger.Error("failed to stop module", err) + } } } diff --git a/internal/endpoint/imap/imap.go b/internal/endpoint/imap/imap.go index cf345514..b73908d8 100644 --- a/internal/endpoint/imap/imap.go +++ b/internal/endpoint/imap/imap.go @@ -167,7 +167,9 @@ func (endp *Endpoint) Start() error { } if err := endp.setupListeners(endp.endpoints); err != nil { - endp.Stop() + if err := endp.Stop(); err != nil { + endp.Log.Error("failed to stop after setupListeners error", err) + } return err } return nil diff --git a/internal/endpoint/openmetrics/om.go b/internal/endpoint/openmetrics/om.go index 44ac64db..0cff5c1e 100644 --- a/internal/endpoint/openmetrics/om.go +++ b/internal/endpoint/openmetrics/om.go @@ -85,7 +85,9 @@ func (e *Endpoint) Start() error { for _, endp := range e.endpoints { l, err := netresource.Listen(endp.Network(), endp.Address()) if err != nil { - e.Stop() + if err := e.Stop(); err != nil { + + } return fmt.Errorf("%s: %v", modName, err) } diff --git a/internal/endpoint/smtp/smtp.go b/internal/endpoint/smtp/smtp.go index 5f0d5210..eeb29922 100644 --- a/internal/endpoint/smtp/smtp.go +++ b/internal/endpoint/smtp/smtp.go @@ -318,7 +318,9 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { func (endp *Endpoint) Start() error { if err := endp.setupListeners(endp.endpoints); err != nil { - endp.Stop() + if err := endp.Stop(); err != nil { + endp.Log.Error("failed to Stop after setupListeners fail", err) + } return err } return nil @@ -426,7 +428,9 @@ func (endp *Endpoint) Stop() error { ctx, cancel := context.WithTimeout(context.Background(), endp.shutdownTimeout) defer cancel() - endp.serv.Shutdown(ctx) + if err := endp.serv.Shutdown(ctx); err != nil { + return err + } endp.listenersWg.Wait() diff --git a/internal/endpoint/smtp/smtp_test.go b/internal/endpoint/smtp/smtp_test.go index d6de22dc..b7d51320 100644 --- a/internal/endpoint/smtp/smtp_test.go +++ b/internal/endpoint/smtp/smtp_test.go @@ -37,6 +37,7 @@ import ( "github.com/foxcpp/maddy/internal/auth" "github.com/foxcpp/maddy/internal/msgpipeline" "github.com/foxcpp/maddy/internal/testutils" + "github.com/stretchr/testify/assert" ) var testPort string @@ -146,7 +147,9 @@ func submitMsgOpts(t *testing.T, cl *smtp.Client, from string, rcpts []string, o func TestSMTPDelivery(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { diff --git a/internal/table/file_test.go b/internal/table/file_test.go index 5c7389e3..ae8466fa 100644 --- a/internal/table/file_test.go +++ b/internal/table/file_test.go @@ -26,6 +26,7 @@ import ( "github.com/foxcpp/maddy/framework/config" "github.com/foxcpp/maddy/internal/testutils" + "github.com/stretchr/testify/assert" ) func TestReadFile(t *testing.T) { @@ -107,7 +108,9 @@ func TestFileReload(t *testing.T) { t.Fatal(err) } m.log = testutils.Logger(t, "file_map") - defer m.Stop() + defer func() { + assert.NoError(t, m.Stop()) + }() if err := mod.Configure([]string{f.Name()}, &config.Map{Block: config.Node{}}); err != nil { t.Fatal(err) @@ -147,10 +150,10 @@ func TestFileReload_Broken(t *testing.T) { } defer os.Remove(f.Name()) if _, err := f.WriteString(file); err != nil { - f.Close() + assert.NoError(t, f.Close()) t.Fatal(err) } - f.Close() + assert.NoError(t, f.Close()) mod, err := NewFile("", "") if err != nil { diff --git a/internal/target/queue/queue_test.go b/internal/target/queue/queue_test.go index 205ba891..649fe428 100644 --- a/internal/target/queue/queue_test.go +++ b/internal/target/queue/queue_test.go @@ -39,6 +39,7 @@ import ( "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/testutils" + "github.com/stretchr/testify/assert" ) // newTestQueue returns properly initialized Queue object usable for testing. @@ -269,7 +270,7 @@ func TestQueueDelivery_PermanentFail_NonPartial(t *testing.T) { // Queue will abort a delivery if it fails for all recipients. readMsgChanTimeout(t, dt.aborted, 5*time.Second) - q.Stop() + assert.NoError(t, q.Stop()) // Delivery is failed permanently, hence no retry should be rescheduled. checkQueueDir(t, q, []string{}) @@ -296,7 +297,7 @@ func TestQueueDelivery_PermanentFail_Partial(t *testing.T) { // Here delivery fails for recipients too, but this is reported using PartialDelivery. readMsgChanTimeout(t, dt.aborted, 5*time.Second) - q.Stop() + assert.NoError(t, q.Stop()) checkQueueDir(t, q, []string{}) } diff --git a/internal/target/remote/mxauth_test.go b/internal/target/remote/mxauth_test.go index 978567c5..e7d97f5d 100644 --- a/internal/target/remote/mxauth_test.go +++ b/internal/target/remote/mxauth_test.go @@ -32,6 +32,7 @@ import ( "github.com/foxcpp/maddy/framework/dns" "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/testutils" + "github.com/stretchr/testify/assert" ) func TestRemoteDelivery_AuthMX_MTASTS(t *testing.T) { @@ -63,7 +64,9 @@ func TestRemoteDelivery_AuthMX_MTASTS(t *testing.T) { testSTSPolicy(t, zones, mtastsGet), }) tgt.tlsConfig = clientCfg - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -108,7 +111,9 @@ func TestRemoteDelivery_MTASTS_SkipNonMatching(t *testing.T) { &localPolicy{minMXLevel: module.MX_MTASTS}, }) tgt.tlsConfig = clientCfg - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -148,7 +153,9 @@ func TestRemoteDelivery_AuthMX_MTASTS_Fail(t *testing.T) { &localPolicy{minMXLevel: module.MX_MTASTS}, }) tgt.tlsConfig = clientCfg - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { diff --git a/maddy.go b/maddy.go index cabd6c8e..4f1c642d 100644 --- a/maddy.go +++ b/maddy.go @@ -207,7 +207,11 @@ func Run(c *cli.Context) error { defer log.DefaultLogger.Out.Close() defer hooks.RunHooks(hooks.EventShutdown) - hooks.AddHook(hooks.EventShutdown, netresource.CloseAllListeners) + defer func() { + if err := netresource.CloseAllListeners(); err != nil { + log.DefaultLogger.Error("CloseAllListeners failed", err) + } + }() if err := moduleMain(c.Path("config")); err != nil { systemdStatusErr(err) @@ -373,8 +377,8 @@ func moduleStart(c *container.C) error { return c.Lifetime.StartAll() } -func moduleStop(c *container.C) { - c.Lifetime.StopAll() +func moduleStop(c *container.C) error { + return c.Lifetime.StopAll() } func moduleMain(configPath string) error { @@ -412,7 +416,9 @@ func moduleMain(configPath string) error { asyncStopWg.Wait() systemdStatus(SDStopping, "Waiting for current configuration to stop...") - moduleStop(c) + if err := moduleStop(c); err != nil { + c.DefaultLogger.Msg("moduleStop failed", err) + } c.DefaultLogger.Msg("server stopped") return nil @@ -446,10 +452,16 @@ func moduleReload(oldContainer *container.C, configPath string, asyncStopWg *syn asyncStopWg.Add(1) go func() { defer asyncStopWg.Done() - defer netresource.CloseUnusedListeners() + defer func() { + if err := netresource.CloseUnusedListeners(); err != nil { + oldContainer.DefaultLogger.Error("CloseUnusedListeners failed", err) + } + }() oldContainer.DefaultLogger.Msg("stopping old server") - moduleStop(oldContainer) + if err := moduleStop(oldContainer); err != nil { + oldContainer.DefaultLogger.Error("moduleStop failed", err) + } oldContainer.DefaultLogger.Msg("old server stopped") systemdStatus(SDReloading, "Configuration running.") diff --git a/tests/t.go b/tests/t.go index c1c04bc3..81423610 100644 --- a/tests/t.go +++ b/tests/t.go @@ -101,12 +101,25 @@ func (t *T) DNS(zones map[string]mockdns.Zone) { t.dnsServ.Close() } - dnsServ, err := mockdns.NewServer(zones, false) + dnsServ, err := mockdns.NewServerWithLogger(zones, t, false) if err != nil { t.Fatal("Test configuration failed:", err) } dnsServ.Log = t t.dnsServ = dnsServ + + t.Cleanup(func() { + if t.dnsServ == nil { + return + } + + // Shutdown the DNS server after maddy to make sure it will not spend time + // timing out queries. + if err := t.dnsServ.Close(); err != nil { + t.Log("Unable to stop the DNS server:", err) + } + t.dnsServ = nil + }) } // Port allocates the random TCP port for use by test. It will made accessible @@ -141,15 +154,6 @@ func (t *T) ensureCanRun() { // any DNS queries to the real world. t.Log("NOTE: Explicit DNS(nil) is recommended.") t.DNS(nil) - - t.Cleanup(func() { - // Shutdown the DNS server after maddy to make sure it will not spend time - // timing out queries. - if err := t.dnsServ.Close(); err != nil { - t.Log("Unable to stop the DNS server:", err) - } - t.dnsServ = nil - }) } // Setup file system, create statedir, runtimedir, write out config. From d7551cf7ec8ef4f59c3e42a4860146e7fbbfd3a6 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Tue, 3 Mar 2026 18:25:57 +0300 Subject: [PATCH 074/111] auth/dovecot_sasl: Update go-dovecot-sasl for Dovecot 2.4 compatibility Fixes #808 --- go.mod | 2 +- go.sum | 2 + internal/auth/sasl.go | 19 ++++++- internal/endpoint/smtp/session.go | 14 +---- internal/endpoint/smtp/smtp.go | 18 ++++++ tests/dovecot_sasl_test.go | 91 +++++++++++++++++++++++++++---- 6 files changed, 119 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index cfa00afd..242d0e6d 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/emersion/go-msgauth v0.6.8 github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 github.com/emersion/go-smtp v0.21.3 - github.com/foxcpp/go-dovecot-sasl v0.0.0-20200522223722-c4699d7a24bf + github.com/foxcpp/go-dovecot-sasl v0.0.0-20260303144336-f7632c6ec0ba github.com/foxcpp/go-imap-backend-tests v0.0.0-20220105184719-e80aa29a5e16 github.com/foxcpp/go-imap-i18nlevel v0.0.0-20200208001533-d6ec88553005 github.com/foxcpp/go-imap-mess v0.0.0-20230108134257-b7ec3a649613 diff --git a/go.sum b/go.sum index 4d3eaea7..81c986e0 100644 --- a/go.sum +++ b/go.sum @@ -306,6 +306,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-dovecot-sasl v0.0.0-20200522223722-c4699d7a24bf h1:rmBPY5fryjp9zLQYsUmQqqgsYq7qeVfrjtr96Tf9vD8= github.com/foxcpp/go-dovecot-sasl v0.0.0-20200522223722-c4699d7a24bf/go.mod h1:5yZUmwr851vgjyAfN7OEfnrmKOh/qLA5dbGelXYsu1E= +github.com/foxcpp/go-dovecot-sasl v0.0.0-20260303144336-f7632c6ec0ba h1:yxQhqX9RQCvECZKBtqwCZoKy/6CLaozDZeWH9Lvndy0= +github.com/foxcpp/go-dovecot-sasl v0.0.0-20260303144336-f7632c6ec0ba/go.mod h1:5yZUmwr851vgjyAfN7OEfnrmKOh/qLA5dbGelXYsu1E= github.com/foxcpp/go-imap v1.0.0-beta.1.0.20220623182312-df940c324887 h1:qUoaaHyrRpQw85ru6VQcC6JowdhrWl7lSbI1zRX1FTM= github.com/foxcpp/go-imap v1.0.0-beta.1.0.20220623182312-df940c324887/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY= github.com/foxcpp/go-imap-backend-tests v0.0.0-20220105184719-e80aa29a5e16 h1:qheFPDpteiUy7Ym18R68OYenpk85UyKYGkhYTmddSBg= diff --git a/internal/auth/sasl.go b/internal/auth/sasl.go index 8510052e..b29959b0 100644 --- a/internal/auth/sasl.go +++ b/internal/auth/sasl.go @@ -54,6 +54,8 @@ type SASLAuth struct { AuthMap module.Table AuthNormalize authz.NormalizeFunc + ErrorMap func(err error) error + Plain []module.PlainAuth } @@ -132,7 +134,10 @@ type ContextData struct { } // CreateSASL creates the sasl.Server instance for the corresponding mechanism. -func (s *SASLAuth) CreateSASL(mech string, remoteAddr net.Addr, successCb func(identity string, data ContextData) error) sasl.Server { +func (s *SASLAuth) CreateSASL( + mech string, remoteAddr net.Addr, + successCb func(identity string, data ContextData) error, +) sasl.Server { switch mech { case sasl.Plain: return sasl.NewPlainServer(func(identity, username, password string) error { @@ -140,12 +145,18 @@ func (s *SASLAuth) CreateSASL(mech string, remoteAddr net.Addr, successCb func(i identity = username } if identity != username { + if s.ErrorMap != nil { + return s.ErrorMap(ErrInvalidAuthCred) + } return ErrInvalidAuthCred } err := s.AuthPlain(username, password) if err != nil { s.Log.Error("authentication failed", err, "username", username, "src_ip", remoteAddr) + if s.ErrorMap != nil { + return s.ErrorMap(ErrInvalidAuthCred) + } return ErrInvalidAuthCred } @@ -162,12 +173,18 @@ func (s *SASLAuth) CreateSASL(mech string, remoteAddr net.Addr, successCb func(i return sasllogin.NewLoginServer(func(username, password string) error { username, err := s.usernameForAuth(context.Background(), username) if err != nil { + if s.ErrorMap != nil { + return s.ErrorMap(ErrInvalidAuthCred) + } return err } err = s.AuthPlain(username, password) if err != nil { s.Log.Error("authentication failed", err, "username", username, "src_ip", remoteAddr) + if s.ErrorMap != nil { + return s.ErrorMap(ErrInvalidAuthCred) + } return ErrInvalidAuthCred } diff --git a/internal/endpoint/smtp/session.go b/internal/endpoint/smtp/session.go index 89cf54fc..01e36e73 100644 --- a/internal/endpoint/smtp/session.go +++ b/internal/endpoint/smtp/session.go @@ -171,19 +171,7 @@ func (s *Session) AuthPlain(username, password string) error { failedLogins.WithLabelValues(s.endp.name).Inc() - if exterrors.IsTemporary(err) { - return &smtp.SMTPError{ - Code: 454, - EnhancedCode: smtp.EnhancedCode{4, 7, 0}, - Message: "Temporary authentication failure", - } - } - - return &smtp.SMTPError{ - Code: 535, - EnhancedCode: smtp.EnhancedCode{5, 7, 8}, - Message: "Invalid credentials", - } + return s.endp.authErrorMap(err) } s.connState.AuthUser = username diff --git a/internal/endpoint/smtp/smtp.go b/internal/endpoint/smtp/smtp.go index eeb29922..cabde482 100644 --- a/internal/endpoint/smtp/smtp.go +++ b/internal/endpoint/smtp/smtp.go @@ -38,6 +38,7 @@ import ( modconfig "github.com/foxcpp/maddy/framework/config/module" tls2 "github.com/foxcpp/maddy/framework/config/tls" "github.com/foxcpp/maddy/framework/dns" + "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/future" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" @@ -285,6 +286,7 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { } endp.saslAuth.Log.Debug = endp.Log.Debug + endp.saslAuth.ErrorMap = endp.authErrorMap // INTERNATIONALIZATION: See RFC 6531 Section 3.3. endp.serv.Domain, err = idna.ToASCII(hostname) @@ -326,6 +328,22 @@ func (endp *Endpoint) Start() error { return nil } +func (endp *Endpoint) authErrorMap(err error) error { + if exterrors.IsTemporary(err) { + return &smtp.SMTPError{ + Code: 454, + EnhancedCode: smtp.EnhancedCode{4, 7, 0}, + Message: "Temporary authentication failure", + } + } + + return &smtp.SMTPError{ + Code: 535, + EnhancedCode: smtp.EnhancedCode{5, 7, 8}, + Message: "Invalid credentials", + } +} + func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { for _, addr := range addresses { var l net.Listener diff --git a/tests/dovecot_sasl_test.go b/tests/dovecot_sasl_test.go index af424e81..140b1820 100644 --- a/tests/dovecot_sasl_test.go +++ b/tests/dovecot_sasl_test.go @@ -1,10 +1,8 @@ //go:build integration && (darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris) -// +build integration -// +build darwin dragonfly freebsd linux netbsd openbsd solaris /* Maddy Mail Server - Composable all-in-one email server. -Copyright © 2019-2020 Max Mazurov , Maddy Mail Server contributors +Copyright © 2019-2026 Max Mazurov , Maddy Mail Server contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,9 +24,9 @@ package tests_test import ( "bufio" + "bytes" "errors" "flag" - "io/ioutil" "os" "os/exec" "os/user" @@ -47,7 +45,8 @@ func init() { flag.StringVar(&DovecotExecutable, "integration.dovecot", "dovecot", "path to dovecot executable for interop tests") } -const dovecotConf = `base_dir = $ROOT/run/ +const dovecotConf = ` +base_dir = $ROOT/run/ state_dir = $ROOT/lib/ log_path = /dev/stderr ssl = no @@ -56,12 +55,14 @@ default_internal_user = $USER default_internal_group = $GROUP default_login_user = $USER +auth_failure_delay = 0 + passdb { driver = passwd-file args = $ROOT/passwd } -userdb { +userdb file { driver = passwd-file args = $ROOT/passwd } @@ -78,7 +79,7 @@ protocols = imap service imap-login { chroot = inet_listener imap { - address = 127.0.0.1 + listen = 127.0.0.1 port = 0 } } @@ -95,8 +96,64 @@ auth_verbose_passwords = yes mail_debug = yes ` +const dovecotConf24 = `dovecot_config_version = 2.4.0 +dovecot_storage_version = 2.4.0 + +base_dir = $ROOT/run/ +state_dir = $ROOT/lib/ +mail_plugin_dir = $ROOT/lib/ +login_plugin_dir = $ROOT/lib/ +log_path = /dev/stderr +ssl = no + +default_internal_user = $USER +default_internal_group = $GROUP +default_login_user = $USER + +auth_failure_delay = 0 + +passdb file { + driver = passwd-file + passwd_file_path = $ROOT/passwd +} + +userdb file { + driver = passwd-file + passwd_file_path = $ROOT/passwd +} + +service auth { + unix_listener auth { + mode = 0666 + } +} + +# Turn on debugging information, to help troubleshooting issues. +auth_verbose = yes +auth_debug = yes +auth_debug_passwords = yes +auth_verbose_passwords = yes +mail_debug = yes +` + const dovecotPasswd = `tester:{plain}123456:1000:1000::/home/user` +func isDovecot24(t *testing.T, dovecotExec string) bool { + cmd := exec.Command(dovecotExec, "--version") + var stdout bytes.Buffer + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + t.Fatal(err) + } + + version, _, _ := strings.Cut(stdout.String(), "-") + t.Log("Dovecot version:", stdout.String()) + + parts := strings.SplitN(version, ".", 3) + + return len(parts)>= 2 && parts[0] == "2" && parts[1]>= "4" +} + func runDovecot(t *testing.T) (string, *exec.Cmd) { dovecotExec, err := exec.LookPath(DovecotExecutable) if err != nil { @@ -117,15 +174,20 @@ func runDovecot(t *testing.T) (string, *exec.Cmd) { t.Fatal(err) } + dovecotConfTemplate := dovecotConf + if isDovecot24(t, dovecotExec) { + dovecotConfTemplate = dovecotConf24 + } + dovecotConf := strings.NewReplacer( "$ROOT", tempDir, "$USER", curUser.Username, - "$GROUP", curGroup.Name).Replace(dovecotConf) - err = ioutil.WriteFile(filepath.Join(tempDir, "dovecot.conf"), []byte(dovecotConf), os.ModePerm) + "$GROUP", curGroup.Name).Replace(dovecotConfTemplate) + err = os.WriteFile(filepath.Join(tempDir, "dovecot.conf"), []byte(dovecotConf), os.ModePerm) if err != nil { t.Fatal(err) } - err = ioutil.WriteFile(filepath.Join(tempDir, "passwd"), []byte(dovecotPasswd), os.ModePerm) + err = os.WriteFile(filepath.Join(tempDir, "passwd"), []byte(dovecotPasswd), os.ModePerm) if err != nil { t.Fatal(err) } @@ -147,9 +209,14 @@ func runDovecot(t *testing.T) (string, *exec.Cmd) { for scnr.Scan() { line := scnr.Text() - // One of messages printed near completing initialization. + // One of messages printed near completing initialization (Dovecot 2.3 or older) if strings.Contains(line, "starting up for imap") { - time.Sleep(500*time.Millisecond) + time.Sleep(500 * time.Millisecond) + ready <- struct{}{} + } + // Dovecot 2.4+ + if strings.Contains(line, "starting up without any protocols") { + time.Sleep(500 * time.Millisecond) ready <- struct{}{} } From 813964dfe9191d863edfd3c0530e0f2fb93b703d Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Sun, 8 Mar 2026 22:37:36 +0300 Subject: [PATCH 075/111] sqlite: Generalize sqlite driver selection for use in sql_query/sql_table Fixes #724, #819. --- internal/{table/sqlite3.go => sqlite/is.go} | 11 +++++------ .../imapsql => sqlite}/modernc_sqlite3.go | 17 +++++++++++++---- .../{storage/imapsql => sqlite}/no_sqlite3.go | 14 ++++++++++---- internal/{storage/imapsql => sqlite}/sqlite3.go | 17 +++++++++++++---- internal/storage/imapsql/imapsql.go | 15 ++++++++------- internal/table/sql_query.go | 5 ++++- 6 files changed, 53 insertions(+), 26 deletions(-) rename internal/{table/sqlite3.go => sqlite/is.go} (80%) rename internal/{storage/imapsql => sqlite}/modernc_sqlite3.go (76%) rename internal/{storage/imapsql => sqlite}/no_sqlite3.go (79%) rename internal/{storage/imapsql => sqlite}/sqlite3.go (76%) diff --git a/internal/table/sqlite3.go b/internal/sqlite/is.go similarity index 80% rename from internal/table/sqlite3.go rename to internal/sqlite/is.go index 8b22794b..953179a1 100644 --- a/internal/table/sqlite3.go +++ b/internal/sqlite/is.go @@ -1,9 +1,6 @@ -//go:build !nosqlite3 && cgo -// +build !nosqlite3,cgo - /* Maddy Mail Server - Composable all-in-one email server. -Copyright © 2019-2020 Max Mazurov , Maddy Mail Server contributors +Copyright © 2019-2026 Max Mazurov , Maddy Mail Server contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,6 +16,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package table +package sqliteprovider -import _ "github.com/mattn/go-sqlite3" +func IsSqliteDriver(name string) bool { + return name == "sqlite" || name == "sqlite3" +} diff --git a/internal/storage/imapsql/modernc_sqlite3.go b/internal/sqlite/modernc_sqlite3.go similarity index 76% rename from internal/storage/imapsql/modernc_sqlite3.go rename to internal/sqlite/modernc_sqlite3.go index 696b4c0a..694cb94a 100644 --- a/internal/storage/imapsql/modernc_sqlite3.go +++ b/internal/sqlite/modernc_sqlite3.go @@ -1,9 +1,8 @@ //go:build !nosqlite3 && !cgo -// +build !nosqlite3,!cgo /* Maddy Mail Server - Composable all-in-one email server. -Copyright © 2019-2020 Max Mazurov , Maddy Mail Server contributors +Copyright © 2019-2026 Max Mazurov , Maddy Mail Server contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,8 +18,18 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package imapsql +package sqliteprovider import _ "modernc.org/sqlite" -const sqliteImpl = "modernc" +const ( + IsAvailable = true + IsTranspiled = true +) + +func MapDriverName(n string) string { + if n == "sqlite3" { + return "sqlite" + } + return n +} diff --git a/internal/storage/imapsql/no_sqlite3.go b/internal/sqlite/no_sqlite3.go similarity index 79% rename from internal/storage/imapsql/no_sqlite3.go rename to internal/sqlite/no_sqlite3.go index 525f8e41..17682ae8 100644 --- a/internal/storage/imapsql/no_sqlite3.go +++ b/internal/sqlite/no_sqlite3.go @@ -1,9 +1,8 @@ //go:build nosqlite3 -// +build nosqlite3 /* Maddy Mail Server - Composable all-in-one email server. -Copyright © 2019-2020 Max Mazurov , Maddy Mail Server contributors +Copyright © 2019-2026 Max Mazurov , Maddy Mail Server contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,6 +18,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package imapsql +package sqliteprovider -const sqliteImpl = "missing" +const ( + IsAvailable = false + IsTranspiled = false +) + +func MapDriverName(n string) string { + return n +} diff --git a/internal/storage/imapsql/sqlite3.go b/internal/sqlite/sqlite3.go similarity index 76% rename from internal/storage/imapsql/sqlite3.go rename to internal/sqlite/sqlite3.go index 599f39d7..39aa855b 100644 --- a/internal/storage/imapsql/sqlite3.go +++ b/internal/sqlite/sqlite3.go @@ -1,9 +1,8 @@ //go:build !nosqlite3 && cgo -// +build !nosqlite3,cgo /* Maddy Mail Server - Composable all-in-one email server. -Copyright © 2019-2020 Max Mazurov , Maddy Mail Server contributors +Copyright © 2019-2026 Max Mazurov , Maddy Mail Server contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,8 +18,18 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package imapsql +package sqliteprovider import _ "github.com/mattn/go-sqlite3" -const sqliteImpl = "cgo" +const ( + IsAvailable = true + IsTranspiled = false +) + +func MapDriverName(n string) string { + if n == "sqlite" { + return "sqlite3" + } + return n +} diff --git a/internal/storage/imapsql/imapsql.go b/internal/storage/imapsql/imapsql.go index ddee1358..89657aaf 100644 --- a/internal/storage/imapsql/imapsql.go +++ b/internal/storage/imapsql/imapsql.go @@ -48,6 +48,7 @@ import ( "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/authz" + sqliteprovider "github.com/foxcpp/maddy/internal/sqlite" "github.com/foxcpp/maddy/internal/updatepipe" "github.com/foxcpp/maddy/internal/updatepipe/pubsub" @@ -172,16 +173,16 @@ func (store *Storage) Configure(inlineArgs []string, cfg *config.Map) error { return errors.New("imapsql: driver is required") } - if driver == "sqlite3" { - if sqliteImpl == "modernc" { - store.Log.Println("using transpiled SQLite (modernc.org/sqlite), this is experimental") - driver = "sqlite" - } else if sqliteImpl == "cgo" { + if sqliteprovider.IsSqliteDriver(driver) { + if sqliteprovider.IsTranspiled { + store.Log.Println("using transpiled SQLite (modernc.org/sqlite)") + } else if sqliteprovider.IsAvailable { store.Log.Debugln("using cgo SQLite") - } else if sqliteImpl == "missing" { + } else { return errors.New("imapsql: SQLite is not supported, recompile without no_sqlite3 tag set") } } + driver = sqliteprovider.MapDriverName(driver) deliveryNormFunc, ok := authz.NormalizeFuncs[deliveryNormalize] if !ok { @@ -301,7 +302,7 @@ func (store *Storage) EnableUpdatePipe(mode updatepipe.BackendMode) error { } switch store.driver { - case "sqlite3": + case "sqlite3", "sqlite": dbId := sha1.Sum([]byte(strings.Join(store.dsn, " "))) sockPath := filepath.Join( config.RuntimeDirectory, diff --git a/internal/table/sql_query.go b/internal/table/sql_query.go index 0fe5c64f..ec2c3d23 100644 --- a/internal/table/sql_query.go +++ b/internal/table/sql_query.go @@ -21,11 +21,13 @@ package table import ( "context" "database/sql" + "errors" "fmt" "strings" "github.com/foxcpp/maddy/framework/config" "github.com/foxcpp/maddy/framework/module" + sqliteprovider "github.com/foxcpp/maddy/internal/sqlite" _ "github.com/lib/pq" ) @@ -89,6 +91,7 @@ func (s *SQL) Configure(inlineArgs []string, cfg *config.Map) error { if driver == "postgres" && s.namedArgs { return config.NodeErr(cfg.Block, "PostgreSQL driver does not support named_args") } + driver = sqliteprovider.MapDriverName(driver) db, err := sql.Open(driver, strings.Join(dsnParts, " ")) if err != nil { @@ -156,7 +159,7 @@ func (s *SQL) Lookup(ctx context.Context, val string) (string, bool, error) { row = s.lookup.QueryRowContext(ctx, val) } if err := row.Scan(&repl); err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return "", false, nil } return "", false, fmt.Errorf("%s: lookup %s: %w", s.modName, val, err) From 53fc029196c1f1163ada5ffa96de6ff63e490457 Mon Sep 17 00:00:00 2001 From: denis Date: 2026年3月13日 14:55:11 +0200 Subject: [PATCH 076/111] Make reject and soft reject have configurable action in rspamd module. --- dist/vim/syntax/maddy-conf.vim | 2 ++ docs/reference/checks/rspamd.md | 16 ++++++++++++++++ internal/check/rspamd/rspamd.go | 19 +++++++++++++++---- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/dist/vim/syntax/maddy-conf.vim b/dist/vim/syntax/maddy-conf.vim index d59e7990..9e56cdd8 100644 --- a/dist/vim/syntax/maddy-conf.vim +++ b/dist/vim/syntax/maddy-conf.vim @@ -183,6 +183,7 @@ syn keyword maddyModDir \ quarantine_threshold \ read_timeout \ reject_threshold + \ reject_action \ relaxed_requiretls \ required_fields \ require_sender_match @@ -198,6 +199,7 @@ syn keyword maddyModDir \ sig_expiry \ sign_fields \ sign_subdomains + \ soft_reject_action \ softfail_action \ SOME_action \ source diff --git a/docs/reference/checks/rspamd.md b/docs/reference/checks/rspamd.md index 90063ae9..f37f5dcc 100644 --- a/docs/reference/checks/rspamd.md +++ b/docs/reference/checks/rspamd.md @@ -14,6 +14,8 @@ check.rspamd { error_resp_action ignore add_header_action quarantine rewrite_subj_action quarantine + reject_action reject + soft_reject_action reject flags pass_all } @@ -90,6 +92,20 @@ X-Spam-Flag and X-Spam-Score are added to the header irregardless of value. --- +### reject_action _action_ +Default: `reject` + +Action to take when rspamd requests to "reject". + +--- + +### soft_reject_action _action_ +Default: `reject` + +Action to take when rspamd requests to "soft reject". + +--- + ### flags _string-list..._ Default: `pass_all` diff --git a/internal/check/rspamd/rspamd.go b/internal/check/rspamd/rspamd.go index 559c94b6..ede0c151 100644 --- a/internal/check/rspamd/rspamd.go +++ b/internal/check/rspamd/rspamd.go @@ -57,6 +57,8 @@ type Check struct { errorRespAction modconfig.FailAction addHdrAction modconfig.FailAction rewriteSubjAction modconfig.FailAction + rejectAction modconfig.FailAction + softRejectAction modconfig.FailAction client *http.Client } @@ -117,6 +119,15 @@ func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { func() (interface{}, error) { return modconfig.FailAction{Quarantine: true}, nil }, modconfig.FailActionDirective, &c.rewriteSubjAction) + cfg.Custom("reject_action", false, false, + func() (interface{}, error) { + return modconfig.FailAction{Reject: true}, nil + }, modconfig.FailActionDirective, &c.rejectAction) + cfg.Custom("soft_reject_action", false, false, + func() (interface{}, error) { + return modconfig.FailAction{Reject: true}, nil + }, modconfig.FailActionDirective, &c.softRejectAction) + cfg.StringList("flags", false, false, []string{"pass_all"}, &flags) if _, err := cfg.Process(); err != nil { return err @@ -320,7 +331,7 @@ func (s *state) CheckBody(ctx context.Context, hdr textproto.Header, body buffer Header: hdrAdd, }) case "soft reject": - return module.CheckResult{ + return s.c.softRejectAction.Apply(module.CheckResult{ Reject: true, Reason: &exterrors.SMTPError{ Code: 450, @@ -329,9 +340,9 @@ func (s *state) CheckBody(ctx context.Context, hdr textproto.Header, body buffer CheckName: modName, Misc: map[string]interface{}{"action": "soft reject"}, }, - } + }) case "reject": - return module.CheckResult{ + return s.c.rejectAction.Apply(module.CheckResult{ Reject: true, Reason: &exterrors.SMTPError{ Code: 550, @@ -340,7 +351,7 @@ func (s *state) CheckBody(ctx context.Context, hdr textproto.Header, body buffer CheckName: modName, Misc: map[string]interface{}{"action": "reject"}, }, - } + }) } s.log.Msg("unhandled action", "action", respData.Action) From 1dfec2c0c49211bacecab2a293958863f7151e1a Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年3月14日 01:31:17 +0300 Subject: [PATCH 077/111] Improve config reload consistency, add tests --- framework/dns/debugflags.go | 7 + framework/dns/resolver.go | 4 - framework/module/lifetime.go | 46 ++++- internal/sqlite/modernc_sqlite3.go | 2 +- internal/sqlite/sqlite3.go | 2 +- internal/target/queue/queue.go | 52 ++++- internal/target/queue/timewheel.go | 43 ++-- internal/target/queue/timewheel_test.go | 35 ++-- maddy.go | 20 +- tests/build_cover.sh | 2 +- tests/imapsql_test.go | 3 +- tests/reload_non_unix.go | 25 +++ tests/reload_test.go | 255 ++++++++++++++++++++++++ tests/reload_unix.go | 42 ++++ tests/t.go | 54 +++-- 15 files changed, 510 insertions(+), 82 deletions(-) create mode 100644 tests/reload_non_unix.go create mode 100644 tests/reload_test.go create mode 100644 tests/reload_unix.go diff --git a/framework/dns/debugflags.go b/framework/dns/debugflags.go index fde218be..937edc97 100644 --- a/framework/dns/debugflags.go +++ b/framework/dns/debugflags.go @@ -32,5 +32,12 @@ func init() { Usage: "replace the DNS resolver address", Value: "system-default", Destination: &overrideServ, + Action: func(context *cli.Context, s string) error { + if s != "" && s != "system-default" { + override(s) + } + overrideServ = s + return nil + }, }) } diff --git a/framework/dns/resolver.go b/framework/dns/resolver.go index f1393fe6..41687311 100644 --- a/framework/dns/resolver.go +++ b/framework/dns/resolver.go @@ -53,9 +53,5 @@ func LookupAddr(ctx context.Context, r Resolver, ip net.IP) (string, error) { } func DefaultResolver() Resolver { - if overrideServ != "" && overrideServ != "system-default" { - override(overrideServ) - } - return net.DefaultResolver } diff --git a/framework/module/lifetime.go b/framework/module/lifetime.go index c24f19f3..2ccb08d4 100644 --- a/framework/module/lifetime.go +++ b/framework/module/lifetime.go @@ -37,18 +37,27 @@ type ReloadModule interface { Reload() error } +// EarlyStopModule is a LifetimeModule that needs to do some bookkeeping +// before new server instance starts during reload. +type EarlyStopModule interface { + LifetimeModule + EarlyStop() error +} + type LifetimeTracker struct { logger *log.Logger instances []*struct { - mod LifetimeModule - started bool + mod LifetimeModule + started bool + earlyStopped bool } } func (lt *LifetimeTracker) Add(mod LifetimeModule) { lt.instances = append(lt.instances, &struct { - mod LifetimeModule - started bool + mod LifetimeModule + started bool + earlyStopped bool }{mod: mod, started: false}) } @@ -59,6 +68,9 @@ func (lt *LifetimeTracker) StartAll() error { continue } + lt.logger.DebugMsg("starting module", + "mod_name", entry.mod.Name(), "inst_name", entry.mod.InstanceName()) + if err := entry.mod.Start(); err != nil { if err := lt.StopAll(); err != nil { lt.logger.Error("StopAll failed after Start fail", err) @@ -96,6 +108,32 @@ func (lt *LifetimeTracker) ReloadAll() error { return nil } +func (lt *LifetimeTracker) EarlyStopAll() error { + for i := len(lt.instances) - 1; i>= 0; i-- { + entry := lt.instances[i] + + if !entry.started { + continue + } + + rsm, ok := entry.mod.(EarlyStopModule) + if !ok { + continue + } + + if err := rsm.EarlyStop(); err != nil { + lt.logger.Error("module early stop failed", err, + "mod_name", entry.mod.Name(), "inst_name", entry.mod.InstanceName()) + continue + } + lt.logger.DebugMsg("module early stopped", + "mod_name", entry.mod.Name(), "inst_name", entry.mod.InstanceName()) + + entry.earlyStopped = true + } + return nil +} + // StopAll calls Stop for all registered LifetimeModule instances. func (lt *LifetimeTracker) StopAll() error { for i := len(lt.instances) - 1; i>= 0; i-- { diff --git a/internal/sqlite/modernc_sqlite3.go b/internal/sqlite/modernc_sqlite3.go index 694cb94a..6e31cf73 100644 --- a/internal/sqlite/modernc_sqlite3.go +++ b/internal/sqlite/modernc_sqlite3.go @@ -1,4 +1,4 @@ -//go:build !nosqlite3 && !cgo +//go:build (!nosqlite3 && !cgo) || modernc /* Maddy Mail Server - Composable all-in-one email server. diff --git a/internal/sqlite/sqlite3.go b/internal/sqlite/sqlite3.go index 39aa855b..38fb1dee 100644 --- a/internal/sqlite/sqlite3.go +++ b/internal/sqlite/sqlite3.go @@ -1,4 +1,4 @@ -//go:build !nosqlite3 && cgo +//go:build !nosqlite3 && cgo && !modernc /* Maddy Mail Server - Composable all-in-one email server. diff --git a/internal/target/queue/queue.go b/internal/target/queue/queue.go index 6cff2e22..8657110d 100644 --- a/internal/target/queue/queue.go +++ b/internal/target/queue/queue.go @@ -122,7 +122,7 @@ type Queue struct { location string hostname string autogenMsgDomain string - wheel *TimeWheel + wheel *TimeWheel[queueSlot] dsnPipeline module.DeliveryTarget @@ -211,6 +211,9 @@ func (q *Queue) Configure(inlineArgs []string, cfg *config.Map) error { cfg.Bool("debug", true, false, &q.Log.Debug) cfg.Int("max_tries", false, false, 20, &q.maxTries) cfg.Int("max_parallelism", false, false, 16, &q.maxParallelism) + cfg.Duration("post_init_delay", false, false, q.postInitDelay, &q.postInitDelay) + cfg.Duration("initial_retry_time", false, false, q.initialRetryTime, &q.initialRetryTime) + cfg.Float("retry_time_scale", false, false, q.retryTimeScale, &q.retryTimeScale) cfg.String("location", false, false, q.location, &q.location) cfg.Custom("target", false, true, nil, modconfig.DeliveryDirective, &q.Target) cfg.String("hostname", true, true, "", &q.hostname) @@ -249,7 +252,7 @@ func (q *Queue) Start() error { } func (q *Queue) start(maxParallelism int) error { - q.wheel = NewTimeWheel(q.dispatch) + q.wheel = NewTimeWheel[queueSlot](q.dispatch) q.deliverySemaphore = make(chan struct{}, maxParallelism) if err := q.readDiskQueue(); err != nil { @@ -261,13 +264,18 @@ func (q *Queue) start(maxParallelism int) error { return nil } -func (q *Queue) Stop() error { +func (q *Queue) EarlyStop() error { + // We must ensure queue state is consistent on disk before we proceed + // with configuration reload. q.wheel.Close() q.deliveryWg.Wait() - return nil } +func (q *Queue) Stop() error { + return q.EarlyStop() +} + // discardBroken changes the name of metadata file to have .meta_broken // extension. // @@ -283,8 +291,8 @@ func (q *Queue) discardBroken(id string) { } } -func (q *Queue) dispatch(value TimeSlot) { - slot := value.Value.(queueSlot) +func (q *Queue) dispatch(ctx context.Context, value TimeSlot[queueSlot]) { + slot := value.Value q.Log.Debugln("starting delivery for", slot.ID) @@ -329,7 +337,7 @@ func (q *Queue) dispatch(value TimeSlot) { body = slot.Body } - q.tryDelivery(meta, hdr, body) + q.tryDelivery(ctx, meta, hdr, body) }() } @@ -373,10 +381,10 @@ func toSMTPErr(err error) *smtp.SMTPError { return res } -func (q *Queue) tryDelivery(meta *QueueMetadata, header textproto.Header, body buffer.Buffer) { +func (q *Queue) tryDelivery(ctx context.Context, meta *QueueMetadata, header textproto.Header, body buffer.Buffer) { dl := target.DeliveryLogger(q.Log, meta.MsgMeta) - partialErr := q.deliver(meta, header, body) + partialErr := q.deliver(ctx, meta, header, body) dl.Debugf("errors: %v", partialErr.Errs) // While iterating the list of recipients we also pick the smallest tries count @@ -460,7 +468,7 @@ func (q *Queue) tryDelivery(meta *QueueMetadata, header textproto.Header, body b }) } -func (q *Queue) deliver(meta *QueueMetadata, header textproto.Header, body buffer.Buffer) partialError { +func (q *Queue) deliver(ctx context.Context, meta *QueueMetadata, header textproto.Header, body buffer.Buffer) partialError { dl := target.DeliveryLogger(q.Log, meta.MsgMeta) perr := partialError{ Errs: map[string]error{}, @@ -471,7 +479,7 @@ func (q *Queue) deliver(meta *QueueMetadata, header textproto.Header, body buffe msgMeta.ID = msgMeta.ID + "-" + strconv.FormatInt(time.Now().Unix(), 16) dl.Debugf("using message ID = %s", msgMeta.ID) - msgCtx, msgTask := trace.NewTask(context.Background(), "Queue delivery") + msgCtx, msgTask := trace.NewTask(ctx, "Queue delivery") defer msgTask.End() mailCtx, mailTask := trace.NewTask(msgCtx, "MAIL FROM") @@ -486,6 +494,15 @@ func (q *Queue) deliver(meta *QueueMetadata, header textproto.Header, body buffe } dl.Debugf("target.StartDelivery OK") + // Check in case delivery implementation is actually + // context-unaware. + if err := mailCtx.Err(); err != nil { + for _, rcpt := range meta.To { + perr.Errs[rcpt] = err + } + return perr + } + var acceptedRcpts []string for _, rcpt := range meta.To { rcptCtx, rcptTask := trace.NewTask(msgCtx, "RCPT TO") @@ -497,6 +514,15 @@ func (q *Queue) deliver(meta *QueueMetadata, header textproto.Header, body buffe acceptedRcpts = append(acceptedRcpts, rcpt) } rcptTask.End() + + // Check in case delivery implementation is actually + // context-unaware. + if err := mailCtx.Err(); err != nil { + for _, rcpt := range meta.To { + perr.Errs[rcpt] = err + } + return perr + } } if len(acceptedRcpts) == 0 { @@ -513,6 +539,10 @@ func (q *Queue) deliver(meta *QueueMetadata, header textproto.Header, body buffe } } + // At this point, it is too late to abort delivery. We should complete + // it or fail it consistently. + msgCtx = context.WithoutCancel(msgCtx) + bodyCtx, bodyTask := trace.NewTask(msgCtx, "DATA") defer bodyTask.End() diff --git a/internal/target/queue/timewheel.go b/internal/target/queue/timewheel.go index 060804b2..cc385e3c 100644 --- a/internal/target/queue/timewheel.go +++ b/internal/target/queue/timewheel.go @@ -20,17 +20,18 @@ package queue import ( "container/list" + "context" "sync" "sync/atomic" "time" ) -type TimeSlot struct { +type TimeSlot[Value any] struct { Time time.Time - Value interface{} + Value Value } -type TimeWheel struct { +type TimeWheel[Value any] struct { stopped uint32 slots *list.List @@ -38,39 +39,41 @@ type TimeWheel struct { updateNotify chan time.Time stopNotify chan struct{} + tickerCtx context.Context + tickerCancel context.CancelFunc - dispatch func(TimeSlot) + dispatch func(context.Context, TimeSlot[Value]) } -func NewTimeWheel(dispatch func(TimeSlot)) *TimeWheel { - tw := &TimeWheel{ +func NewTimeWheel[Value any](dispatch func(context.Context, TimeSlot[Value])) *TimeWheel[Value] { + ctx, cancel := context.WithCancel(context.Background()) + + tw := &TimeWheel[Value]{ slots: list.New(), stopNotify: make(chan struct{}), + tickerCtx: ctx, + tickerCancel: cancel, updateNotify: make(chan time.Time), dispatch: dispatch, } - go tw.tick() + go tw.tick(context.Background()) return tw } -func (tw *TimeWheel) Add(target time.Time, value interface{}) { +func (tw *TimeWheel[Value]) Add(target time.Time, value Value) { if atomic.LoadUint32(&tw.stopped) == 1 { // Already stopped, ignore. return } - if value == nil { - panic("can't insert nil objects into TimeWheel queue") - } - tw.slotsLock.Lock() - tw.slots.PushBack(TimeSlot{Time: target, Value: value}) + tw.slots.PushBack(TimeSlot[Value]{Time: target, Value: value}) tw.slotsLock.Unlock() tw.updateNotify <- target } -func (tw *TimeWheel) Close() { +func (tw *TimeWheel[Value]) Close() { atomic.StoreUint32(&tw.stopped, 1) // Idempotent Close is convenient sometimes. @@ -78,6 +81,8 @@ func (tw *TimeWheel) Close() { return } + tw.tickerCancel() + tw.stopNotify <- struct{}{} <-tw.stopnotify @@ -86,16 +91,16 @@ func (tw *TimeWheel) Close() { close(tw.updateNotify) } -func (tw *TimeWheel) tick() { +func (tw *TimeWheel[Value]) tick(ctx context.Context) { for { now := time.Now() // Look for list element closest to now. tw.slotsLock.Lock() - var closestSlot TimeSlot + var closestSlot TimeSlot[Value] var closestEl *list.Element for e := tw.slots.Front(); e != nil; e = e.Next() { - slot := e.Value.(TimeSlot) - if slot.Time.Sub(now) < closestSlot.Time.Sub(now) || closestSlot.Value == nil { + slot := e.Value.(TimeSlot[Value]) + if slot.Time.Sub(now) < closestSlot.Time.Sub(now) || closestEl == nil { closestSlot = slot closestEl = e } @@ -124,7 +129,7 @@ func (tw *TimeWheel) tick() { tw.slots.Remove(closestEl) tw.slotsLock.Unlock() - tw.dispatch(closestSlot) + tw.dispatch(ctx, closestSlot) break selectloop case newTarget := <-tw.updatenotify: diff --git a/internal/target/queue/timewheel_test.go b/internal/target/queue/timewheel_test.go index d758b603..9beb171e 100644 --- a/internal/target/queue/timewheel_test.go +++ b/internal/target/queue/timewheel_test.go @@ -19,6 +19,7 @@ along with this program. If not, see . package queue import ( + "context" "testing" "time" ) @@ -26,9 +27,9 @@ import ( func TestTimeWheelAdd(t *testing.T) { t.Parallel() - called := make(chan TimeSlot) + called := make(chan TimeSlot[int]) - w := NewTimeWheel(func(slot TimeSlot) { + w := NewTimeWheel[int](func(ctx context.Context, slot TimeSlot[int]) { called <- slot }) defer w.Close() @@ -36,7 +37,7 @@ func TestTimeWheelAdd(t *testing.T) { w.Add(time.Now().Add(1*time.Second), 1) slot := <-called - if val, _ := slot.Value.(int); val != 1 { + if slot.Value != 1 { t.Errorf("Wrong slot value: %v", slot.Value) } } @@ -44,9 +45,9 @@ func TestTimeWheelAdd(t *testing.T) { func TestTimeWheelAdd_Ordering(t *testing.T) { t.Parallel() - called := make(chan TimeSlot) + called := make(chan TimeSlot[int]) - w := NewTimeWheel(func(slot TimeSlot) { + w := NewTimeWheel[int](func(ctx context.Context, slot TimeSlot[int]) { called <- slot }) defer w.Close() @@ -55,11 +56,11 @@ func TestTimeWheelAdd_Ordering(t *testing.T) { w.Add(time.Now().Add(1250*time.Millisecond), 2) slot := <-called - if val, _ := slot.Value.(int); val != 1 { + if slot.Value != 1 { t.Errorf("Wrong first slot value: %v", slot.Value) } slot = <-called - if val, _ := slot.Value.(int); val != 2 { + if slot.Value != 2 { t.Errorf("Wrong second slot value: %v", slot.Value) } } @@ -67,9 +68,9 @@ func TestTimeWheelAdd_Ordering(t *testing.T) { func TestTimeWheelAdd_Restart(t *testing.T) { t.Parallel() - called := make(chan TimeSlot) + called := make(chan TimeSlot[int]) - w := NewTimeWheel(func(slot TimeSlot) { + w := NewTimeWheel[int](func(ctx context.Context, slot TimeSlot[int]) { called <- slot }) defer w.Close() @@ -78,11 +79,11 @@ func TestTimeWheelAdd_Restart(t *testing.T) { w.Add(time.Now().Add(500*time.Millisecond), 2) slot := <-called - if val, _ := slot.Value.(int); val != 2 { + if slot.Value != 2 { t.Errorf("Wrong first slot value: %v", slot.Value) } slot = <-called - if val, _ := slot.Value.(int); val != 1 { + if slot.Value != 1 { t.Errorf("Wrong second slot value: %v", slot.Value) } } @@ -90,9 +91,9 @@ func TestTimeWheelAdd_Restart(t *testing.T) { func TestTimeWheelAdd_MissingGotoBug(t *testing.T) { t.Parallel() - called := make(chan TimeSlot) + called := make(chan TimeSlot[int]) - w := NewTimeWheel(func(slot TimeSlot) { + w := NewTimeWheel[int](func(ctx context.Context, slot TimeSlot[int]) { called <- slot }) defer w.Close() @@ -101,7 +102,7 @@ func TestTimeWheelAdd_MissingGotoBug(t *testing.T) { w.Add(time.Now().Add(500*time.Millisecond), 2) // should correctly restart slot := <-called - if val, _ := slot.Value.(int); val != 2 { + if slot.Value != 2 { t.Errorf("Wrong first slot value: %v", slot.Value) } } @@ -109,9 +110,9 @@ func TestTimeWheelAdd_MissingGotoBug(t *testing.T) { func TestTimeWheelAdd_EmptyUpdWait(t *testing.T) { t.Parallel() - called := make(chan TimeSlot) + called := make(chan TimeSlot[int]) - w := NewTimeWheel(func(slot TimeSlot) { + w := NewTimeWheel[int](func(ctx context.Context, slot TimeSlot[int]) { called <- slot }) defer w.Close() @@ -121,7 +122,7 @@ func TestTimeWheelAdd_EmptyUpdWait(t *testing.T) { w.Add(time.Now().Add(1*time.Second), 1) slot := <-called - if val, _ := slot.Value.(int); val != 1 { + if slot.Value != 1 { t.Errorf("Wrong slot value: %v", slot.Value) } } diff --git a/maddy.go b/maddy.go index 4f1c642d..23eea317 100644 --- a/maddy.go +++ b/maddy.go @@ -377,7 +377,13 @@ func moduleStart(c *container.C) error { return c.Lifetime.StartAll() } -func moduleStop(c *container.C) error { +func moduleStop(c *container.C, earlyStop bool) error { + if earlyStop { + if err := c.Lifetime.EarlyStopAll(); err != nil { + log.DefaultLogger.Error("early stop failed", err) + } + } + return c.Lifetime.StopAll() } @@ -416,7 +422,7 @@ func moduleMain(configPath string) error { asyncStopWg.Wait() systemdStatus(SDStopping, "Waiting for current configuration to stop...") - if err := moduleStop(c); err != nil { + if err := moduleStop(c, true); err != nil { c.DefaultLogger.Msg("moduleStop failed", err) } c.DefaultLogger.Msg("server stopped") @@ -437,6 +443,12 @@ func moduleReload(oldContainer *container.C, configPath string, asyncStopWg *syn oldContainer.DefaultLogger.Msg("configuration loaded") + if err := oldContainer.Lifetime.EarlyStopAll(); err != nil { + oldContainer.DefaultLogger.Error("failed to early-stop old server", err) + container.Global = oldContainer + return oldContainer + } + netresource.ResetListenersUsage() oldContainer.DefaultLogger.Msg("starting new server") if err := moduleStart(newContainer); err != nil { @@ -445,7 +457,7 @@ func moduleReload(oldContainer *container.C, configPath string, asyncStopWg *syn return oldContainer } - newContainer.DefaultLogger.Msg("server started", "version", Version) + newContainer.DefaultLogger.Msg("new server started", "version", Version) systemdStatus(SDReloading, "New configuration running. Waiting for old connections and transactions to finish...") @@ -459,7 +471,7 @@ func moduleReload(oldContainer *container.C, configPath string, asyncStopWg *syn }() oldContainer.DefaultLogger.Msg("stopping old server") - if err := moduleStop(oldContainer); err != nil { + if err := moduleStop(oldContainer, false); err != nil { oldContainer.DefaultLogger.Error("moduleStop failed", err) } oldContainer.DefaultLogger.Msg("old server stopped") diff --git a/tests/build_cover.sh b/tests/build_cover.sh index 929511c2..b724fd5a 100755 --- a/tests/build_cover.sh +++ b/tests/build_cover.sh @@ -2,4 +2,4 @@ if [ -z "$GO" ]; then GO=go fi -exec $GO test -tags 'cover_main debugflags' -coverpkg 'github.com/foxcpp/maddy,github.com/foxcpp/maddy/pkg/...,github.com/foxcpp/maddy/internal/...' -cover -covermode atomic -c cover_test.go -o maddy.cover +exec $GO test -race -tags 'cover_main debugflags' -coverpkg 'github.com/foxcpp/maddy,github.com/foxcpp/maddy/pkg/...,github.com/foxcpp/maddy/internal/...' -cover -covermode atomic -c cover_test.go -o maddy.cover diff --git a/tests/imapsql_test.go b/tests/imapsql_test.go index 69f9e7ed..776d7423 100644 --- a/tests/imapsql_test.go +++ b/tests/imapsql_test.go @@ -1,5 +1,4 @@ -//go:build integration && cgo && !nosqlite3 -// +build integration,cgo,!nosqlite3 +//go:build integration /* Maddy Mail Server - Composable all-in-one email server. diff --git a/tests/reload_non_unix.go b/tests/reload_non_unix.go new file mode 100644 index 00000000..a7511634 --- /dev/null +++ b/tests/reload_non_unix.go @@ -0,0 +1,25 @@ +//go:build !unix + +/* +Maddy Mail Server - Composable all-in-one email server. +Copyright © 2019-2026 Max Mazurov , Maddy Mail Server contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package tests + +func (t *T) reloadConfig() { + t.Skip("Tests for config reload are not available") +} diff --git a/tests/reload_test.go b/tests/reload_test.go new file mode 100644 index 00000000..be3a479b --- /dev/null +++ b/tests/reload_test.go @@ -0,0 +1,255 @@ +//go:build unix && integration + +// Can't reload on Windows, yet + +/* +Maddy Mail Server - Composable all-in-one email server. +Copyright © 2019-2026 Max Mazurov , Maddy Mail Server contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package tests_test + +import ( + "testing" + "time" + + sqliteprovider "github.com/foxcpp/maddy/internal/sqlite" + "github.com/foxcpp/maddy/tests" +) + +func TestSmtpPipelineSwitch(tt *testing.T) { + if !sqliteprovider.IsTranspiled { + tt.Skip("Test is unstable with original SQLite") + } + + tt.Parallel() + t := tests.NewT(tt) + + t.DNS(nil) + t.Port("smtp") + t.Config(` + smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} { + hostname maddy.test + tls off + + reject + } + `) + t.Run(1) + defer t.Close() + + conn1 := t.Conn("smtp") + defer conn1.Close() + conn1.SMTPNegotation("localhost", nil, nil) + conn1.Writeln("MAIL FROM:") + conn1.ExpectPattern("2*") + conn1.Writeln("RCPT TO:") + conn1.ExpectPattern("5*") // REJECTED + conn1.Writeln("RSET") + conn1.ExpectPattern("2*") + + t.Config(` + smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} { + hostname maddy.test + tls off + + deliver_to dummy + } + `) + + conn2 := t.Conn("smtp") + defer conn2.Close() + conn2.SMTPNegotation("localhost", nil, nil) + conn2.Writeln("MAIL FROM:") + conn2.ExpectPattern("2*") + conn2.Writeln("RCPT TO:") + conn2.ExpectPattern("2*") + conn2.Writeln("DATA") + conn2.ExpectPattern("354 *") + conn2.Writeln("From: ") + conn2.Writeln("To: ") + conn2.Writeln("Subject: Hi!") + conn2.Writeln("") + conn2.Writeln("Hi!") + conn2.Writeln(".") + conn2.ExpectPattern("2*") // DISCARDED + + conn1.Writeln("MAIL FROM:") + conn1.ExpectPattern("2*") + conn1.Writeln("RCPT TO:") + conn1.ExpectPattern("5*") // Still REJECTED (running on old server). + conn1.Writeln("RSET") + conn1.ExpectPattern("2*") +} + +func TestImapStorageSwitch(tt *testing.T) { + if !sqliteprovider.IsTranspiled { + tt.Skip("Test is unstable with original SQLite") + } + + tt.Parallel() + t := tests.NewT(tt) + + t.DNS(nil) + t.Port("smtp") + t.Port("imap") + t.Config(` + storage.imapsql test_store { + driver sqlite3 + dsn imapsql.db + } + + imap tcp://127.0.0.1:{env:TEST_PORT_imap} { + tls off + + auth dummy + storage &test_store + } + + smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} { + hostname maddy.test + tls off + + deliver_to &test_store + } + `) + t.Run(1) + defer t.Close() + + imapConn := t.Conn("imap") + defer imapConn.Close() + imapConn.ExpectPattern(`\* OK *`) + imapConn.Writeln(". LOGIN testusr@maddy.test 1234") + imapConn.ExpectPattern(". OK *") + imapConn.Writeln(". SELECT INBOX") + imapConn.ExpectPattern(`\* *`) + imapConn.ExpectPattern(`\* *`) + imapConn.ExpectPattern(`\* *`) + imapConn.ExpectPattern(`\* *`) + imapConn.ExpectPattern(`\* *`) + imapConn.ExpectPattern(`\* *`) + imapConn.ExpectPattern(`. OK *`) + + conn1 := t.Conn("smtp") + defer conn1.Close() + conn1.SMTPNegotation("localhost", nil, nil) + conn1.Writeln("MAIL FROM:") + conn1.ExpectPattern("2*") + conn1.Writeln("RCPT TO:") + conn1.ExpectPattern("2*") + conn1.Writeln("DATA") + conn1.ExpectPattern("354 *") + conn1.Writeln("From: ") + conn1.Writeln("To: ") + conn1.Writeln("Subject: Store 1") + conn1.Writeln("") + conn1.Writeln("Hi!") + conn1.Writeln(".") + conn1.ExpectPattern("2*") // Goes to storage 1 + + t.Config(` + storage.imapsql test_store { + driver sqlite3 + dsn imapsql2.db + } + + imap tcp://127.0.0.1:{env:TEST_PORT_imap} { + tls off + + auth dummy + storage &test_store + } + + smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} { + hostname maddy.test + tls off + + deliver_to &test_store + } + `) + + imapConn2 := t.Conn("imap") + defer imapConn2.Close() + imapConn2.ExpectPattern(`\* OK *`) + imapConn2.Writeln(". LOGIN testusr2@maddy.test 1234") + imapConn2.ExpectPattern(". OK *") + + time.Sleep(500 * time.Millisecond) + + conn2 := t.Conn("smtp") + defer conn2.Close() + conn2.SMTPNegotation("localhost", nil, nil) + conn2.Writeln("MAIL FROM:") + conn2.ExpectPattern("2*") + conn2.Writeln("RCPT TO:") + conn2.ExpectPattern("2*") + conn2.Writeln("DATA") + conn2.ExpectPattern("354 *") + conn2.Writeln("From: ") + conn2.Writeln("To: ") + conn2.Writeln("Subject: Store 2") + conn2.Writeln("") + conn2.Writeln("Hi!") + conn2.Writeln(".") + conn2.ExpectPattern("2*") // Goes to storage 2 + + imapConn.Writeln(". NOOP") + imapConn.ExpectPattern(`\* 1 EXISTS`) + imapConn.ExpectPattern(`\* 1 RECENT`) + imapConn.ExpectPattern(". OK *") + + // Old connection sees message in store 1. + imapConn.Writeln(". FETCH 1 (BODY.PEEK[])") + imapConn.ExpectPattern(`\* 1 FETCH (BODY\[\] {*}*`) + imapConn.Expect(`Delivered-To: testusr@maddy.test`) + imapConn.Expect(`Return-Path: `) + imapConn.ExpectPattern(`Received: from localhost (client.maddy.test \[` + tests.DefaultSourceIP.String() + `\]) by maddy.test`) + imapConn.ExpectPattern(` (envelope-sender ) with ESMTP id *; *`) + imapConn.ExpectPattern(` *`) + imapConn.Expect("From: ") + imapConn.Expect("To: ") + imapConn.Expect("Subject: Store 1") + imapConn.Expect("") + imapConn.Expect("Hi!") + imapConn.Expect(")") + imapConn.ExpectPattern(`. OK *`) + + // New connection sees message in store 2. + imapConn2.Writeln(". SELECT INBOX") + imapConn2.ExpectPattern(`\* *`) + imapConn2.ExpectPattern(`\* *`) + imapConn2.ExpectPattern(`\* *`) + imapConn2.ExpectPattern(`\* *`) + imapConn2.ExpectPattern(`\* *`) + imapConn2.ExpectPattern(`\* *`) + imapConn2.ExpectPattern(`\* *`) + imapConn2.ExpectPattern(`. OK *`) + imapConn2.Writeln(". FETCH 1 (BODY.PEEK[])") + imapConn2.ExpectPattern(`\* 1 FETCH (BODY\[\] {*}*`) + imapConn2.Expect(`Delivered-To: testusr2@maddy.test`) + imapConn2.Expect(`Return-Path: `) + imapConn2.ExpectPattern(`Received: from localhost (client.maddy.test \[` + tests.DefaultSourceIP.String() + `\]) by maddy.test`) + imapConn2.ExpectPattern(` (envelope-sender ) with ESMTP id *; *`) + imapConn2.ExpectPattern(` *`) + imapConn2.Expect("From: ") + imapConn2.Expect("To: ") + imapConn2.Expect("Subject: Store 2") + imapConn2.Expect("") + imapConn2.Expect("Hi!") + imapConn2.Expect(")") + imapConn2.ExpectPattern(`. OK *`) + +} diff --git a/tests/reload_unix.go b/tests/reload_unix.go new file mode 100644 index 00000000..303f4e48 --- /dev/null +++ b/tests/reload_unix.go @@ -0,0 +1,42 @@ +//go:build unix + +/* +Maddy Mail Server - Composable all-in-one email server. +Copyright © 2019-2026 Max Mazurov , Maddy Mail Server contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package tests + +import ( + "syscall" + "time" +) + +func (t *T) reloadConfig() { + err := t.servProc.Process.Signal(syscall.SIGUSR2) + if err != nil { + t.Fatal("Failed to send SIGUSR2:", err) + } + + t.Log("waiting for server to reload...") + + select { + case <-t.reloadedchan: + case <-time.after(5 * time.Second): + t.killServer() + t.Fatal("Server reload is taking too long, killed") + } +} diff --git a/tests/t.go b/tests/t.go index 81423610..4e8d4fc1 100644 --- a/tests/t.go +++ b/tests/t.go @@ -60,13 +60,16 @@ type T struct { portsRev map[uint16]string servProc *exec.Cmd + + reloadedChan chan struct{} } func NewT(t *testing.T) *T { return &T{ - T: t, - ports: map[string]uint16{}, - portsRev: map[uint16]string{}, + T: t, + ports: map[string]uint16{}, + portsRev: map[uint16]string{}, + reloadedChan: make(chan struct{}, 1), } } @@ -75,11 +78,21 @@ func NewT(t *testing.T) *T { func (t *T) Config(cfg string) { t.Helper() + t.cfg = cfg + if t.servProc != nil { - panic("tests: Config called after Run") - } + t.Log("Reloading configuration for running server...") - t.cfg = cfg + configPreable := "state_dir " + filepath.Join(t.testDir, "statedir") + "\n" + + "runtime_dir " + filepath.Join(t.testDir, "runtimedir") + "\n\n" + + err := os.WriteFile(filepath.Join(t.testDir, "maddy.conf"), []byte(configPreable+t.cfg), os.ModePerm) + if err != nil { + t.Fatal("Test configuration failed:", err) + } + + t.reloadConfig() + } } // DNS sets the DNS zones to emulate for the tested server instance. @@ -303,33 +316,38 @@ func (t *T) Run(waitListeners int) { t.Fatal("Test configuration failed:", err) } - // Log scanning goroutine checks for the "listening" messages and sends 'true' - // on the channel each time. - listeningMsg := make(chan bool) + serverStarted := make(chan bool) go func() { defer logOut.Close() - defer close(listeningMsg) + defer close(serverStarted) scnr := bufio.NewScanner(logOut) for scnr.Scan() { line := scnr.Text() - if strings.Contains(line, "listening on") { - listeningMsg <- true - line += " (test runner>listener wait trigger<)" + t.Log("maddy:", line) + + if strings.HasPrefix(line, "server started") { + serverStarted <- true } - t.Log("maddy:", line) + if strings.HasPrefix(line, "new server started") { + select { + case t.reloadedChan <- struct{}{}: + t.Log("server reload confirmed, continuing test") + default: + t.Log("unexpected reloads detected") + t.Fail() + } + } } if err := scnr.Err(); err != nil { t.Log("stderr I/O error:", err) } }() - for i := 0; i < waitListeners; i++ { - if !<-listeningmsg { - t.Fatal("Log ended before all expected listeners are up. Start-up error?") - } + if !<-serverstarted { + t.Fatal("Log ended before all expected listeners are up. Start-up error?") } t.servProc = cmd From 0300e401938150171ffe93aba2a3b6dee39e585b Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年3月14日 01:50:15 +0300 Subject: [PATCH 078/111] Linter clean-up --- internal/endpoint/openmetrics/om.go | 2 +- internal/endpoint/smtp/smtp_test.go | 96 ++++++++++++++++++++------- internal/table/file_test.go | 68 +++++++++++++++---- internal/target/queue/queue_test.go | 18 ++--- internal/target/remote/mxauth_test.go | 19 ++++-- 5 files changed, 152 insertions(+), 51 deletions(-) diff --git a/internal/endpoint/openmetrics/om.go b/internal/endpoint/openmetrics/om.go index 0cff5c1e..e7e4a791 100644 --- a/internal/endpoint/openmetrics/om.go +++ b/internal/endpoint/openmetrics/om.go @@ -86,7 +86,7 @@ func (e *Endpoint) Start() error { l, err := netresource.Listen(endp.Network(), endp.Address()) if err != nil { if err := e.Stop(); err != nil { - + e.logger.Error("failed to stop after failed listen", err) } return fmt.Errorf("%s: %v", modName, err) } diff --git a/internal/endpoint/smtp/smtp_test.go b/internal/endpoint/smtp/smtp_test.go index b7d51320..5fc79703 100644 --- a/internal/endpoint/smtp/smtp_test.go +++ b/internal/endpoint/smtp/smtp_test.go @@ -155,7 +155,9 @@ func TestSMTPDelivery(t *testing.T) { if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() err = submitMsg(t, cl, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"}, testMsg) if err != nil { @@ -187,7 +189,9 @@ func TestSMTPDelivery(t *testing.T) { func TestSMTPDelivery_rDNSError(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() endp.resolver.(*mockdns.Resolver).Zones["1.0.0.127.in-addr.arpa."] = mockdns.Zone{ Err: &net.DNSError{ @@ -202,7 +206,9 @@ func TestSMTPDelivery_rDNSError(t *testing.T) { if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() err = submitMsg(t, cl, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"}, testMsg) if err != nil { @@ -231,13 +237,17 @@ func TestSMTPDelivery_EarlyCheck_Fail(t *testing.T) { }, }, }, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() err = cl.Mail("sender@example.org", nil) if err == nil { @@ -271,13 +281,17 @@ func TestSMTPDeliver_CheckError(t *testing.T) { }, }, nil) endp.deferServerReject = false - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() err = cl.Mail("sender@example.org", nil) if err == nil { @@ -310,13 +324,17 @@ func TestSMTPDeliver_CheckError_Deferred(t *testing.T) { }, }, nil) endp.deferServerReject = true - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() err = cl.Mail("sender@example.org", nil) if err != nil { @@ -349,13 +367,17 @@ func TestSMTPDeliver_CheckError_Deferred(t *testing.T) { func TestSMTPDelivery_Multi(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() err = submitMsg(t, cl, "sender1@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"}, testMsg) if err != nil { @@ -387,13 +409,17 @@ func TestSMTPDelivery_Multi(t *testing.T) { func TestSMTPDelivery_AbortData(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + _ = cl.Close() + }() if err := cl.Hello("mx.example.org"); err != nil { t.Fatal(err) @@ -413,7 +439,7 @@ func TestSMTPDelivery_AbortData(t *testing.T) { } // Then.. Suddenly, close the connection without sending the final dot. - cl.Close() + assert.NoError(t, cl.Close()) time.Sleep(250 * time.Millisecond) @@ -425,13 +451,17 @@ func TestSMTPDelivery_AbortData(t *testing.T) { func TestSMTPDelivery_EmptyMessage(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() if err := cl.Hello("mx.example.org"); err != nil { t.Fatal(err) @@ -464,13 +494,17 @@ func TestSMTPDelivery_EmptyMessage(t *testing.T) { func TestSMTPDelivery_AbortLogout(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + _ = cl.Close() + }() if err := cl.Hello("mx.example.org"); err != nil { t.Fatal(err) @@ -483,7 +517,7 @@ func TestSMTPDelivery_AbortLogout(t *testing.T) { } // Then.. Suddenly, close the connection. - cl.Close() + assert.NoError(t, cl.Close()) time.Sleep(250 * time.Millisecond) @@ -495,13 +529,17 @@ func TestSMTPDelivery_AbortLogout(t *testing.T) { func TestSMTPDelivery_Reset(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() if err := cl.Mail("from-garbage@example.org", nil); err != nil { t.Fatal(err) @@ -530,13 +568,17 @@ func TestSMTPDelivery_Reset(t *testing.T) { func TestSMTPDelivery_SubmissionAuthRequire(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "submission", &module.Dummy{}, &tgt, nil, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() if err := cl.Mail("from-garbage@example.org", nil); err == nil { t.Fatal("Expected an error, got none") @@ -546,13 +588,17 @@ func TestSMTPDelivery_SubmissionAuthRequire(t *testing.T) { func TestSMTPDelivery_SubmissionAuthOK(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "submission", &module.Dummy{}, &tgt, nil, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() if err := cl.Auth(sasl.NewPlainClient("", "user", "password")); err != nil { t.Fatal(err) diff --git a/internal/table/file_test.go b/internal/table/file_test.go index ae8466fa..50d2e1cc 100644 --- a/internal/table/file_test.go +++ b/internal/table/file_test.go @@ -37,8 +37,18 @@ func TestReadFile(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.Remove(f.Name()) - defer f.Close() + defer func(name string) { + err := os.Remove(name) + if err != nil { + t.Log(err) + } + }(f.Name()) + defer func(f *os.File) { + err := f.Close() + if err != nil { + t.Log(err) + } + }(f) if _, err := f.WriteString(file); err != nil { t.Fatal(err) } @@ -92,12 +102,20 @@ func TestFileReload(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.Remove(f.Name()) + defer func(name string) { + err := os.Remove(name) + if err != nil { + t.Log(err) + } + }(f.Name()) if _, err := f.WriteString(file); err != nil { - f.Close() + _ = f.Close() + t.Fatal(err) + } + err = f.Close() + if err != nil { t.Fatal(err) } - f.Close() mod, err := NewFile("", "") if err != nil { @@ -148,7 +166,12 @@ func TestFileReload_Broken(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.Remove(f.Name()) + defer func(name string) { + err := os.Remove(name) + if err != nil { + t.Fatal(err) + } + }(f.Name()) if _, err := f.WriteString(file); err != nil { assert.NoError(t, f.Close()) t.Fatal(err) @@ -164,7 +187,12 @@ func TestFileReload_Broken(t *testing.T) { t.Fatal(err) } m.log = testutils.Logger(t, FileModName) - defer m.Stop() + defer func(m *File) { + err := m.Stop() + if err != nil { + t.Fatal(err) + } + }(m) if err := mod.Configure([]string{f.Name()}, &config.Map{Block: config.Node{}}); err != nil { t.Fatal(err) @@ -177,7 +205,12 @@ func TestFileReload_Broken(t *testing.T) { if _, err := f2.WriteString(":"); err != nil { t.Fatal(err) } - defer f2.Close() + defer func(f2 *os.File) { + err := f2.Close() + if err != nil { + t.Fatal(err) + } + }(f2) time.Sleep(3 * reloadInterval) @@ -198,10 +231,13 @@ func TestFileReload_Removed(t *testing.T) { t.Fatal(err) } if _, err := f.WriteString(file); err != nil { - f.Close() + _ = f.Close() + t.Fatal(err) + } + err = f.Close() + if err != nil { t.Fatal(err) } - f.Close() mod, err := NewFile("", "") if err != nil { @@ -212,13 +248,21 @@ func TestFileReload_Removed(t *testing.T) { t.Fatal(err) } m.log = testutils.Logger(t, FileModName) - defer m.Stop() + defer func(m *File) { + err := m.Stop() + if err != nil { + t.Fatal(err) + } + }(m) if err := mod.Configure([]string{f.Name()}, &config.Map{Block: config.Node{}}); err != nil { t.Fatal(err) } - os.Remove(f.Name()) + err = os.Remove(f.Name()) + if err != nil { + t.Fatal(err) + } time.Sleep(3 * reloadInterval) diff --git a/internal/target/queue/queue_test.go b/internal/target/queue/queue_test.go index 649fe428..8c5f5269 100644 --- a/internal/target/queue/queue_test.go +++ b/internal/target/queue/queue_test.go @@ -246,7 +246,7 @@ func TestQueueDelivery(t *testing.T) { // Wait for the delivery to complete and stop processing. msg := readMsgChanTimeout(t, dt.committed, 5*time.Second) - q.Stop() + assert.NoError(t, q.Stop()) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester1@example.org", "tester2@example.org"}, "") @@ -356,7 +356,7 @@ func TestQueueDelivery_TemporaryFail_Partial(t *testing.T) { msg = readMsgChanTimeout(t, dt.committed, 5000*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester2@example.org"}, "") - q.Stop() + assert.NoError(t, q.Stop()) // No more retries scheduled, queue storage is clear. checkQueueDir(t, q, []string{}) } @@ -396,7 +396,7 @@ func TestQueueDelivery_MultipleAttempts(t *testing.T) { msg = readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester2@example.org"}, "") - q.Stop() + assert.NoError(t, q.Stop()) // No more retries should be scheduled. checkQueueDir(t, q, []string{}) } @@ -421,7 +421,7 @@ func TestQueueDelivery_PermanentRcptReject(t *testing.T) { msg := readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.org", []string{"tester2@example.org"}, "") - q.Stop() + assert.NoError(t, q.Stop()) // No more retries should be scheduled. checkQueueDir(t, q, []string{}) } @@ -455,7 +455,7 @@ func TestQueueDelivery_TemporaryRcptReject(t *testing.T) { msg = readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester1@example.org"}, "") - q.Stop() + assert.NoError(t, q.Stop()) // No more retries should be scheduled. checkQueueDir(t, q, []string{}) } @@ -489,7 +489,7 @@ func TestQueueDelivery_SerializationRoundtrip(t *testing.T) { testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester2@example.org"}, "") // Then stop it. - q.Stop() + assert.NoError(t, q.Stop()) // Make sure it is saved. checkQueueDir(t, q, []string{deliveryID}) @@ -502,7 +502,7 @@ func TestQueueDelivery_SerializationRoundtrip(t *testing.T) { testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester1@example.org"}, "") // Close it again. - q.Stop() + assert.NoError(t, q.Stop()) // No more retries should be scheduled. checkQueueDir(t, q, []string{}) } @@ -536,7 +536,7 @@ func TestQueueDelivery_DeserlizationCleanUp(t *testing.T) { msg := readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester2@example.org"}, "") - q.Stop() + assert.NoError(t, q.Stop()) if err := os.Remove(filepath.Join(q.location, deliveryID+fileSuffix)); err != nil { t.Fatal(err) @@ -544,7 +544,7 @@ func TestQueueDelivery_DeserlizationCleanUp(t *testing.T) { // Dangling files should be removed during load. q = newTestQueueDir(t, &dt, q.location) - q.Stop() + assert.NoError(t, q.Stop()) // Nothing should be left. checkQueueDir(t, q, []string{}) diff --git a/internal/target/remote/mxauth_test.go b/internal/target/remote/mxauth_test.go index e7d97f5d..ac47375a 100644 --- a/internal/target/remote/mxauth_test.go +++ b/internal/target/remote/mxauth_test.go @@ -196,7 +196,9 @@ func TestRemoteDelivery_AuthMX_MTASTS_NoTLS(t *testing.T) { testSTSPolicy(t, zones, mtastsGet), &localPolicy{minMXLevel: module.MX_MTASTS}, }) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -237,7 +239,12 @@ func TestRemoteDelivery_AuthMX_MTASTS_RequirePKIX(t *testing.T) { testSTSPolicy(t, zones, mtastsGet), &localPolicy{minMXLevel: module.MX_MTASTS}, }) - defer tgt.Stop() + defer func(tgt *Target) { + err := tgt.Stop() + if err != nil { + t.Fatal(err) + } + }(tgt) _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -287,7 +294,9 @@ func TestRemoteDelivery_AuthMX_MTASTS_NoPolicy(t *testing.T) { testSTSPolicy(t, zones, mtastsGet), &localPolicy{minMXLevel: module.MX_MTASTS}, }) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -330,7 +339,9 @@ func TestRemoteDelivery_AuthMX_DNSSEC(t *testing.T) { extResolver.Cfg.Port = strconv.Itoa(addr.Port) tgt := testTarget(t, zones, extResolver, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) From 7922d24baf4431890bccb3ad5d15aa2fec4bed7d Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年3月14日 02:08:40 +0300 Subject: [PATCH 079/111] Linter clean-up 2 --- framework/dns/override.go | 2 +- internal/endpoint/smtp/smtputf8_test.go | 77 +++++++++++---- internal/endpoint/smtp/submission_test.go | 5 +- internal/target/queue/queue_test.go | 2 +- internal/target/remote/mxauth_test.go | 28 ++++-- internal/target/remote/remote_test.go | 113 ++++++++++++++++------ 6 files changed, 169 insertions(+), 58 deletions(-) diff --git a/framework/dns/override.go b/framework/dns/override.go index 25f0da08..0f073afd 100644 --- a/framework/dns/override.go +++ b/framework/dns/override.go @@ -32,7 +32,7 @@ var overrideServ string // // The server argument is in form of "IP:PORT". It is expected that the server // will be available both using TCP and UDP on the same port. -func override(server string) { +func override(server string) { // nolint: unused // used in debugflags.go net.DefaultResolver.PreferGo = true net.DefaultResolver.Dial = func(ctx context.Context, network, _ string) (net.Conn, error) { dialer := net.Dialer{ diff --git a/internal/endpoint/smtp/smtputf8_test.go b/internal/endpoint/smtp/smtputf8_test.go index 2f157aba..ff16be6c 100644 --- a/internal/endpoint/smtp/smtputf8_test.go +++ b/internal/endpoint/smtp/smtputf8_test.go @@ -27,6 +27,7 @@ import ( "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/testutils" + "github.com/stretchr/testify/assert" ) func TestSMTPUTF8_MangleStatusMessage(t *testing.T) { @@ -43,14 +44,18 @@ func TestSMTPUTF8_MangleStatusMessage(t *testing.T) { }, }, nil) endp.deferServerReject = false - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() err = cl.Mail("sender@example.org", nil) if err == nil { @@ -73,7 +78,9 @@ func TestSMTP_RejectNonASCIIFrom(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) endp.deferServerReject = false - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) @@ -100,14 +107,18 @@ func TestSMTPUTF8_NormalizeCaseFoldFrom(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) endp.deferServerReject = false - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() err = submitMsgOpts(t, cl, "foo@E\u0301.example.org", []string{"rcpt@example.com"}, &smtp.MailOptions{ UTF8: true, @@ -127,14 +138,18 @@ func TestSMTP_RejectNonASCIIRcpt(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) endp.deferServerReject = false - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() err = submitMsg(t, cl, "x@example.org", []string{"ѣ@example.org"}, testMsg) @@ -154,14 +169,18 @@ func TestSMTPUTF8_NormalizeCaseFoldRcpt(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) endp.deferServerReject = false - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() err = submitMsgOpts(t, cl, "x@example.org", []string{"foo@E\u0301.example.org"}, &smtp.MailOptions{ UTF8: true, @@ -191,14 +210,18 @@ func TestSMTPUTF8_NoMangleStatusMessage(t *testing.T) { }, }, nil) endp.deferServerReject = false - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() err = cl.Mail("sender@example.org", &smtp.MailOptions{ UTF8: true, @@ -222,14 +245,18 @@ func TestSMTPUTF8_NoMangleStatusMessage(t *testing.T) { func TestSMTPUTF8_Received_EHLO_ALabel(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() if err := cl.Hello("凱凱.invalid"); err != nil { t.Fatal(err) @@ -256,7 +283,9 @@ func TestSMTPUTF8_Received_EHLO_ALabel(t *testing.T) { func TestSMTPUTF8_Received_rDNS_ALabel(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() defer testutils.WaitForConnsClose(t, endp.serv) endp.resolver.(*mockdns.Resolver).Zones["1.0.0.127.in-addr.arpa."] = mockdns.Zone{ @@ -267,7 +296,9 @@ func TestSMTPUTF8_Received_rDNS_ALabel(t *testing.T) { if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() err = submitMsg(t, cl, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"}, testMsg) if err != nil { @@ -290,7 +321,9 @@ func TestSMTPUTF8_Received_rDNS_ALabel(t *testing.T) { func TestSMTPUTF8_Received_rDNS_ULabel(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() defer testutils.WaitForConnsClose(t, endp.serv) endp.resolver.(*mockdns.Resolver).Zones["1.0.0.127.in-addr.arpa."] = mockdns.Zone{ @@ -301,7 +334,9 @@ func TestSMTPUTF8_Received_rDNS_ULabel(t *testing.T) { if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() err = submitMsgOpts(t, cl, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"}, &smtp.MailOptions{ UTF8: true, @@ -326,14 +361,18 @@ func TestSMTPUTF8_Received_rDNS_ULabel(t *testing.T) { func TestSMTPUTF8_Received_EHLO_ULabel(t *testing.T) { tgt := testutils.Target{} endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil) - defer endp.Stop() + defer func() { + assert.NoError(t, endp.Stop()) + }() defer testutils.WaitForConnsClose(t, endp.serv) cl, err := smtp.Dial("127.0.0.1:" + testPort) if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + assert.NoError(t, cl.Close()) + }() if err := cl.Hello("凱凱.invalid"); err != nil { t.Fatal(err) diff --git a/internal/endpoint/smtp/submission_test.go b/internal/endpoint/smtp/submission_test.go index beb4cf5e..91f030cf 100644 --- a/internal/endpoint/smtp/submission_test.go +++ b/internal/endpoint/smtp/submission_test.go @@ -26,6 +26,7 @@ import ( "github.com/emersion/go-message/textproto" "github.com/emersion/go-smtp" "github.com/foxcpp/maddy/framework/module" + "github.com/stretchr/testify/assert" ) func init() { @@ -54,9 +55,9 @@ func TestSubmissionPrepare(t *testing.T) { // Synchronize the endpoint initialization. // Otherwise Close will race with Serve called by setupListeners. cl, _ := smtp.Dial("127.0.0.1:" + testPort) - cl.Close() + assert.NoError(t, cl.Close()) - endp.Stop() + assert.NoError(t, endp.Stop()) }() session, err := endp.NewSession(nil) diff --git a/internal/target/queue/queue_test.go b/internal/target/queue/queue_test.go index 8c5f5269..6eb7a101 100644 --- a/internal/target/queue/queue_test.go +++ b/internal/target/queue/queue_test.go @@ -323,7 +323,7 @@ func TestQueueDelivery_TemporaryFail(t *testing.T) { msg := readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester1@example.org", "tester2@example.org"}, "") - q.Stop() + assert.NoError(t, q.Stop()) // No more retries scheduled, queue storage is clear. defer checkQueueDir(t, q, []string{}) } diff --git a/internal/target/remote/mxauth_test.go b/internal/target/remote/mxauth_test.go index ac47375a..f979a53d 100644 --- a/internal/target/remote/mxauth_test.go +++ b/internal/target/remote/mxauth_test.go @@ -383,7 +383,9 @@ func TestRemoteDelivery_AuthMX_DNSSEC_Fail(t *testing.T) { tgt := testTarget(t, zones, extResolver, []module.MXAuthPolicy{ &localPolicy{minMXLevel: module.MX_DNSSEC}, }) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() _, err = testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -425,7 +427,9 @@ func TestRemoteDelivery_REQUIRETLS(t *testing.T) { testSTSPolicy(t, zones, mtastsGet), }) tgt.tlsConfig = clientCfg - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDeliveryMeta(t, tgt, "test@example.com", []string{"test@example.invalid"}, &module.MsgMetadata{ OriginalFrom: "test@example.com", @@ -466,7 +470,9 @@ func TestRemoteDelivery_REQUIRETLS_Fail(t *testing.T) { testSTSPolicy(t, zones, mtastsGet), }) tgt.tlsConfig = clientCfg - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() if _, err := testutils.DoTestDeliveryErrMeta(t, tgt, "test@example.com", []string{"test@example.invalid"}, &module.MsgMetadata{ OriginalFrom: "test@example.com", @@ -512,7 +518,9 @@ func TestRemoteDelivery_REQUIRETLS_Relaxed(t *testing.T) { }) tgt.relaxedREQUIRETLS = true tgt.tlsConfig = clientCfg - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDeliveryMeta(t, tgt, "test@example.com", []string{"test@example.invalid"}, &module.MsgMetadata{ OriginalFrom: "test@example.com", @@ -549,7 +557,9 @@ func TestRemoteDelivery_REQUIRETLS_Relaxed_NoMXAuth(t *testing.T) { }) tgt.relaxedREQUIRETLS = true tgt.tlsConfig = clientCfg - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() if _, err := testutils.DoTestDeliveryErrMeta(t, tgt, "test@example.com", []string{"test@example.invalid"}, &module.MsgMetadata{ OriginalFrom: "test@example.com", @@ -595,7 +605,9 @@ func TestRemoteDelivery_REQUIRETLS_Relaxed_NoTLS(t *testing.T) { }) tgt.relaxedREQUIRETLS = true tgt.tlsConfig = nil - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() if _, err := testutils.DoTestDeliveryErrMeta(t, tgt, "test@example.com", []string{"test@example.invalid"}, &module.MsgMetadata{ OriginalFrom: "test@example.com", @@ -646,7 +658,9 @@ func TestRemoteDelivery_REQUIRETLS_Relaxed_TLSFail(t *testing.T) { srv.TLSConfig.MinVersion = tls.VersionTLS11 srv.TLSConfig.MaxVersion = tls.VersionTLS11 tgt.tlsConfig = clientCfg - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() if _, err := testutils.DoTestDeliveryErrMeta(t, tgt, "test@example.com", []string{"test@example.invalid"}, &module.MsgMetadata{ OriginalFrom: "test@example.com", diff --git a/internal/target/remote/remote_test.go b/internal/target/remote/remote_test.go index 795e1634..b21f97e0 100644 --- a/internal/target/remote/remote_test.go +++ b/internal/target/remote/remote_test.go @@ -40,6 +40,7 @@ import ( "github.com/foxcpp/maddy/internal/limits" "github.com/foxcpp/maddy/internal/smtpconn/pool" "github.com/foxcpp/maddy/internal/testutils" + "github.com/stretchr/testify/assert" ) // .invalid TLD is used here to make sure if there is something wrong about @@ -129,7 +130,9 @@ func TestRemoteDelivery(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -146,7 +149,9 @@ func TestRemoteDelivery_NoMXFallback(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { @@ -176,7 +181,9 @@ func TestRemoteDelivery_EmptySender(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "", []string{"test@example.invalid"}) @@ -202,7 +209,9 @@ func TestRemoteDelivery_IPLiteral(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@[127.0.0.1]"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@[127.0.0.1]"}) @@ -219,7 +228,9 @@ func TestRemoteDelivery_FallbackMX(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -239,7 +250,9 @@ func TestRemoteDelivery_BodyNonAtomic(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() c := multipleErrs{ errs: map[string]error{}, @@ -267,7 +280,9 @@ func TestRemoteDelivery_Abort(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { @@ -297,7 +312,9 @@ func TestRemoteDelivery_CommitWithoutBody(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { @@ -334,7 +351,9 @@ func TestRemoteDelivery_MAILFROMErr(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { @@ -360,7 +379,9 @@ func TestRemoteDelivery_NoMX(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { @@ -390,7 +411,9 @@ func TestRemoteDelivery_NullMX(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { @@ -419,7 +442,9 @@ func TestRemoteDelivery_Quarantined(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() meta := module.MsgMetadata{ID: "test..."} @@ -467,7 +492,9 @@ func TestRemoteDelivery_MAILFROMErr_Repeated(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { @@ -507,7 +534,9 @@ func TestRemoteDelivery_RcptErr(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { @@ -558,7 +587,9 @@ func TestRemoteDelivery_DownMX(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -581,7 +612,9 @@ func TestRemoteDelivery_AllMXDown(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -612,7 +645,9 @@ func TestRemoteDelivery_Split(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid", "test@example2.invalid"}) @@ -651,7 +686,9 @@ func TestRemoteDelivery_Split_Fail(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { @@ -704,7 +741,9 @@ func TestRemoteDelivery_BodyErr(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { @@ -758,7 +797,9 @@ func TestRemoteDelivery_Split_BodyErr(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { @@ -814,7 +855,9 @@ func TestRemoteDelivery_Split_BodyErr_NonAtomic(t *testing.T) { } tgt := testTarget(t, zones, nil, nil) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() delivery, err := tgt.StartDelivery(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com") if err != nil { @@ -874,7 +917,9 @@ func TestRemoteDelivery_TLSErrFallback(t *testing.T) { tgt := testTarget(t, zones, nil, nil) tgt.tlsConfig = clientCfg - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -896,7 +941,9 @@ func TestRemoteDelivery_RequireTLS_Missing(t *testing.T) { tgt := testTarget(t, zones, nil, []module.MXAuthPolicy{ &localPolicy{minTLSLevel: module.TLSEncrypted}, }) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -921,7 +968,9 @@ func TestRemoteDelivery_RequireTLS_Present(t *testing.T) { &localPolicy{minTLSLevel: module.TLSEncrypted}, }) tgt.tlsConfig = clientCfg - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -950,7 +999,9 @@ func TestRemoteDelivery_RequireTLS_NoErrFallback(t *testing.T) { &localPolicy{minTLSLevel: module.TLSEncrypted}, }) tgt.tlsConfig = clientCfg - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -975,7 +1026,9 @@ func TestRemoteDelivery_TLS_FallbackNoVerify(t *testing.T) { tgt := testTarget(t, zones, nil, []module.MXAuthPolicy{ &localPolicy{minTLSLevel: module.TLSEncrypted}, }) - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -1008,7 +1061,9 @@ func TestRemoteDelivery_TLS_FallbackPlaintext(t *testing.T) { tgt := testTarget(t, zones, nil, nil) tgt.tlsConfig = clientCfg - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -1041,7 +1096,9 @@ func TestRemoteDelivery_ConnReuse(t *testing.T) { tgt := testTarget(t, zones, nil, nil) tgt.connReuseLimit = 5 - defer tgt.Stop() + defer func() { + assert.NoError(t, tgt.Stop()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) From b2067816f1f1b1a0228a0c6b972cf013a5714ff4 Mon Sep 17 00:00:00 2001 From: denis Date: 2026年3月14日 10:40:44 +0200 Subject: [PATCH 080/111] Append openmetrics endpoints. --- internal/endpoint/openmetrics/om.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/endpoint/openmetrics/om.go b/internal/endpoint/openmetrics/om.go index e7e4a791..4c917835 100644 --- a/internal/endpoint/openmetrics/om.go +++ b/internal/endpoint/openmetrics/om.go @@ -68,6 +68,7 @@ func (e *Endpoint) Configure(inlineArgs []string, cfg *config.Map) error { if endp.IsTLS() { return fmt.Errorf("%s: TLS is not supported yet", modName) } + e.endpoints = append(e.endpoints, endp) } return nil From 77dbb50022d1f9f9fd3c1ffbb28db8665d75f768 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年3月25日 01:34:57 +0300 Subject: [PATCH 081/111] Upgrade to golangci-lint v2, massive linter clean-up --- .github/workflows/test.yml | 25 ++-- .golangci.yml | 14 +- config.go | 4 +- framework/cfgparser/imports.go | 2 +- framework/cfgparser/parse_test.go | 6 +- framework/dns/dnssec_test.go | 9 +- framework/resource/netresource/fd.go | 12 +- framework/resource/singleton.go | 8 +- internal/auth/dovecot_sasl/dovecot_sasl.go | 10 +- internal/auth/ldap/ldap.go | 15 ++- internal/auth/sasl.go | 2 +- internal/auth/shadow/module.go | 4 +- internal/check/rspamd/rspamd.go | 6 +- internal/cli/app.go | 18 ++- internal/cli/ctl/hash.go | 2 +- internal/cli/ctl/users.go | 12 +- internal/dmarc/verifier_test.go | 5 +- internal/dsn/dsn.go | 7 +- .../endpoint/dovecot_sasld/dovecot_sasl.go | 5 +- internal/endpoint/imap/imap.go | 4 +- internal/endpoint/smtp/smtp_test.go | 5 +- internal/endpoint/smtp/smtputf8_test.go | 5 +- internal/modify/dkim/dkim.go | 10 +- internal/modify/dkim/keys.go | 6 +- internal/modify/group.go | 5 +- internal/msgpipeline/check_runner.go | 12 +- internal/msgpipeline/module.go | 2 +- internal/msgpipeline/msgpipeline.go | 24 +++- internal/smtpconn/pool/pool.go | 26 +++- internal/smtpconn/smtpconn.go | 18 ++- internal/smtpconn/smtputf8_test.go | 9 +- internal/storage/blob/fs/fs_test.go | 3 +- internal/storage/blob/s3/s3.go | 4 +- internal/storage/imapsql/imapsql.go | 8 +- internal/table/file_test.go | 5 +- internal/table/sql_query.go | 12 +- internal/target/queue/queue.go | 32 ++++- internal/target/queue/queue_test.go | 26 ++-- internal/target/received.go | 2 +- internal/target/remote/connect.go | 30 +++-- internal/target/remote/dane_delivery_test.go | 98 ++++++++++---- internal/target/remote/mxauth_test.go | 69 +++++++--- internal/target/remote/remote.go | 12 +- internal/target/remote/remote_test.go | 125 +++++++++++++----- internal/target/remote/security.go | 8 +- internal/target/smtp/sasl_test.go | 17 ++- internal/target/smtp/smtp_downstream.go | 31 +++-- internal/target/smtp/smtp_downstream_test.go | 33 +++-- internal/target/smtp/smtputf8_test.go | 5 +- internal/testutils/bench_delivery.go | 2 +- internal/testutils/smtp_server.go | 15 +-- internal/testutils/target.go | 18 ++- internal/updatepipe/pubsub/pq.go | 8 +- internal/updatepipe/unix_pipe.go | 12 +- maddy.go | 27 +++- systemd.go | 12 +- tests/t.go | 12 +- 57 files changed, 656 insertions(+), 262 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 32c02e54..4cf136f1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,24 +17,23 @@ jobs: name: Lint runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 with: go-version-file: 'go.mod' - name: "Install libpam" run: | sudo apt-get update sudo apt-get install -y libpam-dev - - uses: golangci/golangci-lint-action@v6 + - uses: golangci/golangci-lint-action@v9 with: - version: v1.60 - args: "--timeout=30m" + version: v2.11 buildsh: name: "Verify build.sh" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 with: go-version-file: 'go.mod' - name: "Install libpam" @@ -50,8 +49,8 @@ jobs: name: "Build and test" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 with: go-version-file: 'go.mod' - name: "Install libpam" @@ -65,11 +64,3 @@ jobs: run: | cd tests/ ./run.sh - - uses: codecov/codecov-action@v2 - with: - files: ./coverage.out - flags: unit - - uses: codecov/codecov-action@v2 - with: - files: ./tests/coverage.out - flags: integration diff --git a/.golangci.yml b/.golangci.yml index 8617544c..9200934a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,13 +1,11 @@ +version: "2" linters: enable: - - gosimple - errcheck - staticcheck - ineffassign - - typecheck - govet - unused - - goimports - prealloc - unconvert - misspell @@ -15,3 +13,13 @@ linters: - nakedret - dogsled - copyloopvar + - sqlclosecheck + - testifylint + - rowserrcheck + - recvcheck + settings: + errcheck: + disable-default-exclusions: false +formatters: + enable: + - goimports diff --git a/config.go b/config.go index 2f57b472..92053f94 100644 --- a/config.go +++ b/config.go @@ -114,7 +114,9 @@ func reinitLogging() { return } - out.Close() + if err := out.Close(); err != nil { + log.Println("Can't close logger:", err) + } log.DefaultLogger.Out = newOut } diff --git a/framework/cfgparser/imports.go b/framework/cfgparser/imports.go index 8078dd8d..1f9dccd1 100644 --- a/framework/cfgparser/imports.go +++ b/framework/cfgparser/imports.go @@ -169,7 +169,7 @@ func (ctx *parseContext) expandSingleValueMacro(arg string) (string, error) { value = ctx.macros[macroName][0] } - arg = strings.Replace(arg, "$("+macroName+")", value, -1) + arg = strings.ReplaceAll(arg, "$("+macroName+")", value) } return arg, nil diff --git a/framework/cfgparser/parse_test.go b/framework/cfgparser/parse_test.go index 32929b9f..3cf20bae 100644 --- a/framework/cfgparser/parse_test.go +++ b/framework/cfgparser/parse_test.go @@ -23,6 +23,8 @@ import ( "reflect" "strings" "testing" + + "github.com/stretchr/testify/require" ) var cases = []struct { @@ -579,8 +581,8 @@ func printTree(t *testing.T, root Node, indent int) { } func TestRead(t *testing.T) { - os.Setenv("TESTING_VARIABLE", "ABCDEF") - os.Setenv("TESTING_VARIABLE2", "ABC2 DEF2") + require.NoError(t, os.Setenv("TESTING_VARIABLE", "ABCDEF")) + require.NoError(t, os.Setenv("TESTING_VARIABLE2", "ABC2 DEF2")) for _, case_ := range cases { t.Run(case_.name, func(t *testing.T) { diff --git a/framework/dns/dnssec_test.go b/framework/dns/dnssec_test.go index 774897bf..325b14fe 100644 --- a/framework/dns/dnssec_test.go +++ b/framework/dns/dnssec_test.go @@ -11,6 +11,7 @@ import ( "github.com/foxcpp/maddy/framework/log" "github.com/miekg/dns" + "github.com/stretchr/testify/require" ) type TestSrvAction int @@ -55,8 +56,8 @@ func (s *IPAddrTestServer) Run() { go s.udpServ.ActivateAndServe() //nolint:errcheck } -func (s *IPAddrTestServer) Close() { - s.udpServ.PacketConn.Close() +func (s *IPAddrTestServer) Close() error { + return s.udpServ.PacketConn.Close() } func (s *IPAddrTestServer) Addr() *net.UDPAddr { @@ -141,7 +142,9 @@ func TestExtResolver_AuthLookupIPAddr(t *testing.T) { s.aAD = aAD s.aaaaAD = aaaaAD s.Run() - defer s.Close() + defer func() { + require.NoError(t, s.Close()) + }() res := ExtResolver{ cl: new(dns.Client), Cfg: &dns.ClientConfig{ diff --git a/framework/resource/netresource/fd.go b/framework/resource/netresource/fd.go index 395ddbcc..8c2d881a 100644 --- a/framework/resource/netresource/fd.go +++ b/framework/resource/netresource/fd.go @@ -11,7 +11,11 @@ import ( func ListenFD(fd uint) (net.Listener, error) { file := os.NewFile(uintptr(fd), strconv.FormatUint(uint64(fd), 10)) - defer file.Close() + defer func() { + if err := file.Close(); err != nil { + panic(err) + } + }() return net.FileListener(file) } @@ -42,6 +46,10 @@ func ListenFDName(name string) (net.Listener, error) { } file := os.NewFile(3+fd, name) - defer file.Close() + defer func() { + if err := file.Close(); err != nil { + panic(err) + } + }() return net.FileListener(file) } diff --git a/framework/resource/singleton.go b/framework/resource/singleton.go index 45f5b903..33b71db5 100644 --- a/framework/resource/singleton.go +++ b/framework/resource/singleton.go @@ -50,8 +50,10 @@ func (s *Singleton[T]) CloseUnused(isUsed func(key string) bool) error { if isUsed(key) { continue } + if err := res.Close(); err != nil { + s.log.Error("resource close failed", err, "key", key) + } s.log.DebugMsg("resource released", "key", key) - res.Close() delete(s.resources, key) } @@ -63,8 +65,10 @@ func (s *Singleton[T]) Close() error { defer s.lock.Unlock() for key, res := range s.resources { + if err := res.Close(); err != nil { + s.log.Error("resource close failed", err, "key", key) + } s.log.DebugMsg("resource released", "key", key) - res.Close() delete(s.resources, key) } diff --git a/internal/auth/dovecot_sasl/dovecot_sasl.go b/internal/auth/dovecot_sasl/dovecot_sasl.go index cc5dd370..fc0585e6 100644 --- a/internal/auth/dovecot_sasl/dovecot_sasl.go +++ b/internal/auth/dovecot_sasl/dovecot_sasl.go @@ -77,7 +77,9 @@ func (a *Auth) getConn() (*dovecotsasl.Client, error) { } func (a *Auth) returnConn(cl *dovecotsasl.Client) { - cl.Close() + if err := cl.Close(); err != nil { + a.log.Error("connection close failed", err) + } } func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { @@ -113,7 +115,11 @@ func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { return fmt.Errorf("%s: unable to contact server: %v", modName, err) } - defer cl.Close() + defer func() { + if err := cl.Close(); err != nil { + a.log.Error("connection close failed", err) + } + }() a.mechanisms = make(map[string]dovecotsasl.Mechanism, len(cl.ConnInfo().Mechs)) for name, mech := range cl.ConnInfo().Mechs { if mech.Private { diff --git a/internal/auth/ldap/ldap.go b/internal/auth/ldap/ldap.go index a2392d56..88cc0b4d 100644 --- a/internal/auth/ldap/ldap.go +++ b/internal/auth/ldap/ldap.go @@ -181,7 +181,9 @@ func (a *Auth) getConn() (*ldap.Conn, error) { a.conn = conn } if a.conn.IsClosing() { - a.conn.Close() + if err := a.conn.Close(); err != nil { + a.log.Error("Connection close failed", err) + } conn, err := a.newConn() if err != nil { a.connLock.Unlock() @@ -196,11 +198,15 @@ func (a *Auth) returnConn(conn *ldap.Conn) { defer a.connLock.Unlock() if err := a.readBind(conn); err != nil { a.log.Error("failed to rebind for reading", err) - conn.Close() + if err := a.conn.Close(); err != nil { + a.log.Error("Connection close failed", err) + } a.conn = nil } if a.conn != conn { - a.conn.Close() + if err := a.conn.Close(); err != nil { + a.log.Error("Connection close failed", err) + } } a.conn = conn } @@ -285,8 +291,7 @@ func (a *Auth) Start() error { func (a *Auth) Stop() error { a.connLock.Lock() defer a.connLock.Unlock() - a.conn.Close() - return nil + return a.conn.Close() } func init() { diff --git a/internal/auth/sasl.go b/internal/auth/sasl.go index b29959b0..616ad618 100644 --- a/internal/auth/sasl.go +++ b/internal/auth/sasl.go @@ -34,7 +34,7 @@ import ( ) var ( - ErrUnsupportedMech = errors.New("Unsupported SASL mechanism") + ErrUnsupportedMech = errors.New("unsupported SASL mechanism") ErrInvalidAuthCred = errors.New("auth: invalid credentials") ) diff --git a/internal/auth/shadow/module.go b/internal/auth/shadow/module.go index 290d826e..90a65a23 100644 --- a/internal/auth/shadow/module.go +++ b/internal/auth/shadow/module.go @@ -80,7 +80,9 @@ func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { } return fmt.Errorf("shadow: can't read /etc/shadow: %v", err) } - f.Close() + if err := f.Close(); err != nil { + a.Log.Error("can't close /etc/shadow file", err) + } } return nil diff --git a/internal/check/rspamd/rspamd.go b/internal/check/rspamd/rspamd.go index ede0c151..0a737b69 100644 --- a/internal/check/rspamd/rspamd.go +++ b/internal/check/rspamd/rspamd.go @@ -277,7 +277,11 @@ func (s *state) CheckBody(ctx context.Context, hdr textproto.Header, body buffer }, }) } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + s.log.Error("failed to close response body", err) + } + }() var respData response if err := json.NewDecoder(resp.Body).Decode(&respData); err != nil { diff --git a/internal/cli/app.go b/internal/cli/app.go index 59108965..55ab1862 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -1,6 +1,7 @@ package maddycli import ( + "errors" "fmt" "os" "strings" @@ -28,7 +29,22 @@ databases used by it (all other subcommands). }, } app.ExitErrHandler = func(c *cli.Context, err error) { - cli.HandleExitCoder(err) + if err == nil { + return + } + + var exitErr cli.ExitCoder + if errors.As(err, &exitErr) { + if err.Error() != "" { + if _, ok := exitErr.(cli.ErrorFormatter); ok { + _, _ = fmt.Fprintf(os.Stderr, "Error: %+v\n", err) + } else { + _, _ = fmt.Fprintln(os.Stderr, "Error:", err) + } + } + cli.OsExiter(exitErr.ExitCode()) + return + } } app.EnableBashCompletion = true app.Commands = []*cli.Command{ diff --git a/internal/cli/ctl/hash.go b/internal/cli/ctl/hash.go index 5effdf7e..c4371780 100644 --- a/internal/cli/ctl/hash.go +++ b/internal/cli/ctl/hash.go @@ -79,7 +79,7 @@ func hashCommand(ctx *cli.Context) error { hashCompute := pass_table.HashCompute[hashFunc] if hashCompute == nil { - var funcs []string + funcs := make([]string, 0, len(pass_table.HashCompute)) for k := range pass_table.HashCompute { funcs = append(funcs, k) } diff --git a/internal/cli/ctl/users.go b/internal/cli/ctl/users.go index 09dc909c..13bccd79 100644 --- a/internal/cli/ctl/users.go +++ b/internal/cli/ctl/users.go @@ -37,14 +37,14 @@ func init() { &cli.Command{ Name: "creds", Usage: "Local credentials management", - Description: `These commands manipulate credential databases used by + Description: `These commands manipulate credential databases used by maddy mail server. Corresponding credential database should be defined in maddy.conf as a top-level config block. By default the block name should be local_authdb ( can be changed using --cfg-block argument for subcommands). -Note that it is not enough to create user credentials in order to grant +Note that it is not enough to create user credentials in order to grant IMAP access - IMAP account should be also created using 'imap-acct create' subcommand. `, Subcommands: []*cli.Command{ @@ -74,7 +74,7 @@ IMAP access - IMAP account should be also created using 'imap-acct create' subco Description: `Reads password from stdin. If configuration block uses auth.pass_table, then hash algorithm can be configured -using command flags. Otherwise, these options cannot be used. +using command flags. Otherwise, these options cannot be used. `, ArgsUsage: "USERNAME", Flags: []cli.Flag{ @@ -213,12 +213,12 @@ func usersCreate(be module.PlainUserDB, ctx *cli.Context) error { func usersRemove(be module.PlainUserDB, ctx *cli.Context) error { username := ctx.Args().First() if username == "" { - return errors.New("Error: USERNAME is required") + return errors.New("error: USERNAME is required") } if !ctx.Bool("yes") { if !clitools2.Confirmation("Are you sure you want to delete this user account?", false) { - return errors.New("Cancelled") + return errors.New("cancelled") } } @@ -228,7 +228,7 @@ func usersRemove(be module.PlainUserDB, ctx *cli.Context) error { func usersPassword(be module.PlainUserDB, ctx *cli.Context) error { username := ctx.Args().First() if username == "" { - return errors.New("Error: USERNAME is required") + return errors.New("error: USERNAME is required") } var pass string diff --git a/internal/dmarc/verifier_test.go b/internal/dmarc/verifier_test.go index 1adfe996..911cdae0 100644 --- a/internal/dmarc/verifier_test.go +++ b/internal/dmarc/verifier_test.go @@ -29,13 +29,16 @@ import ( "github.com/emersion/go-message/textproto" "github.com/emersion/go-msgauth/authres" "github.com/foxcpp/go-mockdns" + "github.com/stretchr/testify/require" ) func TestDMARC(t *testing.T) { test := func(zones map[string]mockdns.Zone, hdr string, authres []authres.Result, policyApplied Policy, dmarcRes authres.ResultValue) { t.Helper() v := NewVerifier(&mockdns.Resolver{Zones: zones}) - defer v.Close() + defer func() { + require.NoError(t, v.Close()) + }() hdrParsed, err := textproto.ReadHeader(bufio.NewReader(strings.NewReader(hdr))) if err != nil { diff --git a/internal/dsn/dsn.go b/internal/dsn/dsn.go index 59707a7f..db2e9c3d 100644 --- a/internal/dsn/dsn.go +++ b/internal/dsn/dsn.go @@ -202,15 +202,16 @@ func GenerateDSN(utf8 bool, envelope Envelope, mtaInfo ReportingMTAInfo, rcptsIn reportHeader.Add("From", envelope.From) reportHeader.Add("Subject", "Undelivered Mail Returned to Sender") - defer partWriter.Close() - if err := writeHumanReadablePart(partWriter, mtaInfo, rcptsInfo); err != nil { return textproto.Header{}, err } if err := writeMachineReadablePart(utf8, partWriter, mtaInfo, rcptsInfo); err != nil { return textproto.Header{}, err } - return reportHeader, writeHeader(utf8, partWriter, failedHeader) + if err := writeHeader(utf8, partWriter, failedHeader); err != nil { + return textproto.Header{}, err + } + return reportHeader, partWriter.Close() } func writeHeader(utf8 bool, w *textproto.MultipartWriter, header textproto.Header) error { diff --git a/internal/endpoint/dovecot_sasld/dovecot_sasl.go b/internal/endpoint/dovecot_sasld/dovecot_sasl.go index 74a452b2..4bb7a942 100644 --- a/internal/endpoint/dovecot_sasld/dovecot_sasl.go +++ b/internal/endpoint/dovecot_sasld/dovecot_sasl.go @@ -128,9 +128,8 @@ func (endp *Endpoint) Start() error { } func (endp *Endpoint) Stop() error { - endp.srv.Close() - endp.listenersWg.Wait() - return nil + defer endp.listenersWg.Wait() + return endp.srv.Close() } func init() { diff --git a/internal/endpoint/imap/imap.go b/internal/endpoint/imap/imap.go index b73908d8..1a64ca3d 100644 --- a/internal/endpoint/imap/imap.go +++ b/internal/endpoint/imap/imap.go @@ -219,7 +219,9 @@ func (endp *Endpoint) InstanceName() string { func (endp *Endpoint) Stop() error { for _, l := range endp.listeners { - l.Close() + if err := l.Close(); err != nil { + endp.Log.Error("failed to close listener", err) + } } if err := endp.serv.Close(); err != nil { return err diff --git a/internal/endpoint/smtp/smtp_test.go b/internal/endpoint/smtp/smtp_test.go index 5fc79703..ee87efee 100644 --- a/internal/endpoint/smtp/smtp_test.go +++ b/internal/endpoint/smtp/smtp_test.go @@ -38,6 +38,7 @@ import ( "github.com/foxcpp/maddy/internal/msgpipeline" "github.com/foxcpp/maddy/internal/testutils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var testPort string @@ -439,7 +440,7 @@ func TestSMTPDelivery_AbortData(t *testing.T) { } // Then.. Suddenly, close the connection without sending the final dot. - assert.NoError(t, cl.Close()) + require.NoError(t, cl.Close()) time.Sleep(250 * time.Millisecond) @@ -517,7 +518,7 @@ func TestSMTPDelivery_AbortLogout(t *testing.T) { } // Then.. Suddenly, close the connection. - assert.NoError(t, cl.Close()) + require.NoError(t, cl.Close()) time.Sleep(250 * time.Millisecond) diff --git a/internal/endpoint/smtp/smtputf8_test.go b/internal/endpoint/smtp/smtputf8_test.go index ff16be6c..684f07c4 100644 --- a/internal/endpoint/smtp/smtputf8_test.go +++ b/internal/endpoint/smtp/smtputf8_test.go @@ -28,6 +28,7 @@ import ( "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/testutils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestSMTPUTF8_MangleStatusMessage(t *testing.T) { @@ -87,7 +88,9 @@ func TestSMTP_RejectNonASCIIFrom(t *testing.T) { if err != nil { t.Fatal(err) } - defer cl.Close() + defer func() { + require.NoError(t, cl.Close()) + }() err = submitMsg(t, cl, "ѣ@example.org", []string{"rcpt@example.com"}, testMsg) diff --git a/internal/modify/dkim/dkim.go b/internal/modify/dkim/dkim.go index f0894931..b7864cb2 100644 --- a/internal/modify/dkim/dkim.go +++ b/internal/modify/dkim/dkim.go @@ -270,7 +270,7 @@ func (s *state) RewriteSender(ctx context.Context, mailFrom string) (string, err return mailFrom, nil } -func (s state) RewriteRcpt(ctx context.Context, rcptTo string) ([]string, error) { +func (s *state) RewriteRcpt(ctx context.Context, rcptTo string) ([]string, error) { return []string{rcptTo}, nil } @@ -341,16 +341,16 @@ func (s *state) RewriteBody(ctx context.Context, h *textproto.Header, body buffe return exterrors.WithFields(err, map[string]interface{}{"modifier": "modify.dkim"}) } if err := textproto.WriteHeader(signer, *h); err != nil { - signer.Close() + _ = signer.Close() return exterrors.WithFields(err, map[string]interface{}{"modifier": "modify.dkim"}) } r, err := body.Open() if err != nil { - signer.Close() + _ = signer.Close() return exterrors.WithFields(err, map[string]interface{}{"modifier": "modify.dkim"}) } if _, err := io.Copy(signer, r); err != nil { - signer.Close() + _ = signer.Close() return exterrors.WithFields(err, map[string]interface{}{"modifier": "modify.dkim"}) } @@ -365,7 +365,7 @@ func (s *state) RewriteBody(ctx context.Context, h *textproto.Header, body buffe return nil } -func (s state) Close() error { +func (s *state) Close() error { return nil } diff --git a/internal/modify/dkim/keys.go b/internal/modify/dkim/keys.go index 7c39b76e..2a9b7514 100644 --- a/internal/modify/dkim/keys.go +++ b/internal/modify/dkim/keys.go @@ -42,7 +42,11 @@ func (m *Modifier) loadOrGenerateKey(keyPath, newKeyAlgo string) (pkey crypto.Si } return nil, false, err } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + m.log.Error("failed to close key file", err) + } + }() pemBlob, err := io.ReadAll(f) if err != nil { diff --git a/internal/modify/group.go b/internal/modify/group.go index 00fbe124..26e80408 100644 --- a/internal/modify/group.go +++ b/internal/modify/group.go @@ -25,6 +25,7 @@ import ( "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" ) @@ -71,7 +72,9 @@ func (g *Group) ModStateForMsg(ctx context.Context, msgMeta *module.MsgMetadata) if err != nil { // Free state objects we initialized already. for _, state := range gs.states { - state.Close() + if err := state.Close(); err != nil { + log.DefaultLogger.Error("failed to close modifier state", err) + } } return nil, err } diff --git a/internal/msgpipeline/check_runner.go b/internal/msgpipeline/check_runner.go index f6df9525..7f3a2cb7 100644 --- a/internal/msgpipeline/check_runner.go +++ b/internal/msgpipeline/check_runner.go @@ -73,7 +73,9 @@ func (cr *checkRunner) checkStates(ctx context.Context, checks []module.Check) ( newStatesMap := make(map[module.Check]module.CheckState, len(checks)) closeStates := func() { for _, state := range states { - state.Close() + if err := state.Close(); err != nil { + cr.log.Error("failed to close check state", err) + } } } @@ -342,8 +344,12 @@ func (cr *checkRunner) applyResults(hostname string, header *textproto.Header) e } func (cr *checkRunner) close() { - cr.dmarcVerify.Close() + if err := cr.dmarcVerify.Close(); err != nil { + cr.log.Error("failed to close dmarc verify state", err) + } for _, state := range cr.states { - state.Close() + if err := state.Close(); err != nil { + cr.log.Error("failed to close check state", err) + } } } diff --git a/internal/msgpipeline/module.go b/internal/msgpipeline/module.go index a951ec88..29cd4a8f 100644 --- a/internal/msgpipeline/module.go +++ b/internal/msgpipeline/module.go @@ -52,7 +52,7 @@ func (m *Module) Configure(inlineArgs []string, cfg *config.Map) error { return err } m.MsgPipeline = p - m.MsgPipeline.Log = m.log + m.Log = m.log return nil } diff --git a/internal/msgpipeline/msgpipeline.go b/internal/msgpipeline/msgpipeline.go index 5caa0899..e15c49d5 100644 --- a/internal/msgpipeline/msgpipeline.go +++ b/internal/msgpipeline/msgpipeline.go @@ -186,7 +186,9 @@ func (dd *msgpipelineDelivery) initRunGlobalModifiers(ctx context.Context, msgMe } mailFrom, err = globalModifiersState.RewriteSender(ctx, mailFrom) if err != nil { - globalModifiersState.Close() + if err := globalModifiersState.Close(); err != nil { + dd.log.Error("failed to close global modifiers state", err) + } return "", err } dd.globalModifiersState = globalModifiersState @@ -333,7 +335,9 @@ func (dd *msgpipelineDelivery) AddRcpt(ctx context.Context, to string, opts smtp newTo, err = rcptModifiersState.RewriteRcpt(ctx, to) if err != nil { - rcptModifiersState.Close() + if err := rcptModifiersState.Close(); err != nil { + dd.log.Error("failed to close rcpt modifiers state", err) + } return wrapErr(err) } dd.log.Debugln("per-rcpt modifiers:", to, "=>", newTo) @@ -501,7 +505,7 @@ func (dd *msgpipelineDelivery) BodyNonAtomic(ctx context.Context, c module.Statu } } -func (dd msgpipelineDelivery) Commit(ctx context.Context) error { +func (dd *msgpipelineDelivery) Commit(ctx context.Context) error { dd.close() for _, delivery := range dd.deliveries { @@ -517,17 +521,23 @@ func (dd *msgpipelineDelivery) close() { dd.checkRunner.close() if dd.globalModifiersState != nil { - dd.globalModifiersState.Close() + if err := dd.globalModifiersState.Close(); err != nil { + dd.log.Error("failed to close global modifiers state", err) + } } if dd.sourceModifiersState != nil { - dd.sourceModifiersState.Close() + if err := dd.sourceModifiersState.Close(); err != nil { + dd.log.Error("failed to close source modifiers state", err) + } } for _, modifiers := range dd.rcptModifiersState { - modifiers.Close() + if err := modifiers.Close(); err != nil { + dd.log.Error("failed to close rcpt modifiers state", err) + } } } -func (dd msgpipelineDelivery) Abort(ctx context.Context) error { +func (dd *msgpipelineDelivery) Abort(ctx context.Context) error { dd.close() var lastErr error diff --git a/internal/smtpconn/pool/pool.go b/internal/smtpconn/pool/pool.go index 4b700ee4..aee82fd7 100644 --- a/internal/smtpconn/pool/pool.go +++ b/internal/smtpconn/pool/pool.go @@ -22,6 +22,8 @@ import ( "context" "sync" "time" + + "github.com/foxcpp/maddy/framework/log" ) type Conn interface { @@ -96,12 +98,18 @@ func (p *P) CleanUp(ctx context.Context) { close(v.c) for conn := range v.c { - go conn.Close() + go p.close(conn) } delete(p.keys, k) } } +func (p *P) close(c Conn) { + if err := c.Close(); err != nil { + log.DefaultLogger.Error("failed to close pooled connection", err) + } +} + func (p *P) Get(ctx context.Context, key string) (Conn, error) { p.keysLock.Lock() @@ -120,7 +128,7 @@ func (p *P) Get(ctx context.Context, key string) (Conn, error) { p.keysLock.Unlock() for conn := range bucket.c { - conn.Close() + p.close(conn) } return p.cfg.New(ctx, key) @@ -141,11 +149,15 @@ func (p *P) Get(ctx context.Context, key string) (Conn, error) { if !conn.Usable() { // Close might take some time, run in parallel. - go conn.Close() + go p.close(conn) continue } if conn.LastUseAt().Add(time.Duration(p.cfg.MaxConnLifetimeSec) * time.Second).Before(time.Now()) { - go conn.Close() + go func() { + if err := conn.Close(); err != nil { + log.DefaultLogger.Error("failed to close pooled connection", err) + } + }() continue } @@ -173,7 +185,7 @@ func (p *P) Return(key string, c Conn) { close(v.c) for conn := range v.c { - conn.Close() + p.close(conn) } } } @@ -190,7 +202,7 @@ func (p *P) Return(key string, c Conn) { bucket.lastUse = time.Now().Unix() default: // Let it go, let it go... - go c.Close() + go p.close(c) } } @@ -203,7 +215,7 @@ func (p *P) Close() { for k, v := range p.keys { close(v.c) for conn := range v.c { - conn.Close() + p.close(conn) } delete(p.keys, k) } diff --git a/internal/smtpconn/smtpconn.go b/internal/smtpconn/smtpconn.go index 3c4b4224..25fd49a5 100644 --- a/internal/smtpconn/smtpconn.go +++ b/internal/smtpconn/smtpconn.go @@ -228,6 +228,12 @@ func (c *C) RemoteAddr() net.Addr { return c.conn.RemoteAddr() } +func (c *C) closeClient(cl *smtp.Client) { + if err := cl.Close(); err != nil { + c.Log.Error("client connection close failed", err) + } +} + func (c *C) attemptConnect(ctx context.Context, lmtp bool, endp config.Endpoint, starttls bool, tlsConfig *tls.Config) (didTLS bool, cl *smtp.Client, conn net.Conn, err error) { dialCtx, cancel := context.WithTimeout(ctx, c.ConnectTimeout) conn, err = c.Dialer(dialCtx, endp.Network(), endp.Address()) @@ -255,7 +261,7 @@ func (c *C) attemptConnect(ctx context.Context, lmtp bool, endp config.Endpoint, // i18n: hostname is already expected to be in A-labels form. if err := cl.Hello(c.Hostname); err != nil { - cl.Close() + c.closeClient(cl) return false, nil, nil, err } @@ -265,7 +271,7 @@ func (c *C) attemptConnect(ctx context.Context, lmtp bool, endp config.Endpoint, if ok, _ := cl.Extension("STARTTLS"); !ok { if err := cl.Quit(); err != nil { - cl.Close() + c.closeClient(cl) } return false, nil, nil, fmt.Errorf("TLS required but unsupported by downstream") } @@ -278,7 +284,7 @@ func (c *C) attemptConnect(ctx context.Context, lmtp bool, endp config.Endpoint, // *after* the handshake (e.g. PKI verification fail), we don't log the error in // this case though. if err := cl.Quit(); err != nil { - cl.Close() + c.closeClient(cl) } return false, nil, nil, TLSError{err} @@ -286,7 +292,7 @@ func (c *C) attemptConnect(ctx context.Context, lmtp bool, endp config.Endpoint, // Re-do HELO using our hostname instead of localhost. if err := cl.Hello(c.Hostname); err != nil { - cl.Close() + c.closeClient(cl) var tlsErr *tls.CertificateVerificationError if errors.As(err, &tlsErr) { @@ -551,8 +557,8 @@ func (c *C) Close() error { // DirectClose closes the underlying connection without sending the QUIT // command. func (c *C) DirectClose() error { - c.cl.Close() + cl := c.cl c.cl = nil c.serverName = "" - return nil + return cl.Close() } diff --git a/internal/smtpconn/smtputf8_test.go b/internal/smtpconn/smtputf8_test.go index dc580d9c..44efe4fd 100644 --- a/internal/smtpconn/smtputf8_test.go +++ b/internal/smtpconn/smtputf8_test.go @@ -28,6 +28,7 @@ import ( "github.com/foxcpp/maddy/framework/config" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/internal/testutils" + "github.com/stretchr/testify/require" ) func doTestDelivery(t *testing.T, conn *C, from string, to []string, opts smtp.MailOptions) error { @@ -65,7 +66,9 @@ func TestSMTPUTF8(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort) srv.EnableSMTPUTF8 = case_.serverUTF8 - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) c := New() @@ -77,7 +80,9 @@ func TestSMTPUTF8(t *testing.T) { }, false, nil); err != nil { t.Fatal(err) } - defer c.Close() + defer func() { + require.NoError(t, c.Close()) + }() err := doTestDelivery(t, c, case_.clientSender, []string{case_.clientRcpt}, smtp.MailOptions{UTF8: true}) diff --git a/internal/storage/blob/fs/fs_test.go b/internal/storage/blob/fs/fs_test.go index 2c8f7664..ec4635e4 100644 --- a/internal/storage/blob/fs/fs_test.go +++ b/internal/storage/blob/fs/fs_test.go @@ -7,6 +7,7 @@ import ( "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/storage/blob" "github.com/foxcpp/maddy/internal/testutils" + "github.com/stretchr/testify/require" ) func TestFS(t *testing.T) { @@ -14,6 +15,6 @@ func TestFS(t *testing.T) { dir := testutils.Dir(t) return &FSStore{instName: "test", root: dir} }, func(store module.BlobStore) { - os.RemoveAll(store.(*FSStore).root) + require.NoError(t, os.RemoveAll(store.(*FSStore).root)) }) } diff --git a/internal/storage/blob/s3/s3.go b/internal/storage/blob/s3/s3.go index 075c8368..b470e2d2 100644 --- a/internal/storage/blob/s3/s3.go +++ b/internal/storage/blob/s3/s3.go @@ -120,7 +120,9 @@ func (b *s3blob) Sync() error { panic("storage.blob.s3: Sync called twice for a blob object") } - b.pw.Close() + if err := b.pw.Close(); err != nil { + return err + } b.didSync = true return <-b.errch } diff --git a/internal/storage/imapsql/imapsql.go b/internal/storage/imapsql/imapsql.go index 89657aaf..2a335b6a 100644 --- a/internal/storage/imapsql/imapsql.go +++ b/internal/storage/imapsql/imapsql.go @@ -428,7 +428,9 @@ func (store *Storage) Lookup(ctx context.Context, key string) (string, bool, err func (store *Storage) Stop() error { // Stop backend from generating new updates. - store.Back.Close() + if err := store.Back.Close(); err != nil { + store.Log.Error("close backend failed", err) + } // Wait for 'updates replicate' goroutine to actually stop so we will send // all updates before shutting down (this is especially important for @@ -437,7 +439,9 @@ func (store *Storage) Stop() error { close(store.outboundUpds) <-store.updpushstop - store.updPipe.Close() + if err := store.updPipe.Close(); err != nil { + store.Log.Error("updatepipe close failed", err) + } } return nil diff --git a/internal/table/file_test.go b/internal/table/file_test.go index 50d2e1cc..abfc81f8 100644 --- a/internal/table/file_test.go +++ b/internal/table/file_test.go @@ -27,6 +27,7 @@ import ( "github.com/foxcpp/maddy/framework/config" "github.com/foxcpp/maddy/internal/testutils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestReadFile(t *testing.T) { @@ -173,10 +174,10 @@ func TestFileReload_Broken(t *testing.T) { } }(f.Name()) if _, err := f.WriteString(file); err != nil { - assert.NoError(t, f.Close()) + require.NoError(t, f.Close()) t.Fatal(err) } - assert.NoError(t, f.Close()) + require.NoError(t, f.Close()) mod, err := NewFile("", "") if err != nil { diff --git a/internal/table/sql_query.go b/internal/table/sql_query.go index ec2c3d23..6fa0f730 100644 --- a/internal/table/sql_query.go +++ b/internal/table/sql_query.go @@ -26,6 +26,7 @@ import ( "strings" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" sqliteprovider "github.com/foxcpp/maddy/internal/sqlite" _ "github.com/lib/pq" @@ -144,7 +145,9 @@ func (s *SQL) Start() error { } func (s *SQL) Stop() error { - s.lookup.Close() + if err := s.lookup.Close(); err != nil { + log.DefaultLogger.Error("lookup query close failed", err) + } return s.db.Close() } @@ -203,7 +206,9 @@ func (s *SQL) Keys() ([]string, error) { if err != nil { return nil, fmt.Errorf("%s: list: %w", s.modName, err) } - defer rows.Close() + defer func() { + _ = rows.Close() + }() var list []string for rows.Next() { var key string @@ -212,6 +217,9 @@ func (s *SQL) Keys() ([]string, error) { } list = append(list, key) } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("%s: list: %w", s.modName, err) + } return list, nil } diff --git a/internal/target/queue/queue.go b/internal/target/queue/queue.go index 8657110d..11b79683 100644 --- a/internal/target/queue/queue.go +++ b/internal/target/queue/queue.go @@ -109,7 +109,7 @@ func (pe *partialError) SetStatus(rcptTo string, err error) { pe.Errs[rcptTo] = err } -func (pe partialError) Error() string { +func (pe *partialError) Error() string { return fmt.Sprintf("delivery failed for some recipients: %v", pe.Errs) } @@ -758,7 +758,11 @@ func (q *Queue) storeNewMessage(meta *QueueMetadata, header textproto.Header, bo if err != nil { return nil, err } - defer headerFile.Close() + defer func() { + if err := headerFile.Close(); err != nil { + q.Log.Error("header file close failed", err) + } + }() if err := textproto.WriteHeader(headerFile, header); err != nil { q.tryRemoveDanglingFile(id + ".header") @@ -770,14 +774,22 @@ func (q *Queue) storeNewMessage(meta *QueueMetadata, header textproto.Header, bo q.tryRemoveDanglingFile(id + ".header") return nil, err } - defer bodyReader.Close() + defer func() { + if err := bodyReader.Close(); err != nil { + q.Log.Error("bodyReader close failed", err) + } + }() bodyPath := filepath.Join(q.location, id+".body") bodyFile, err := os.Create(bodyPath) if err != nil { return nil, err } - defer bodyFile.Close() + defer func() { + if err := bodyFile.Close(); err != nil { + q.Log.Error("body file close failed", err) + } + }() if _, err := io.Copy(bodyFile, bodyReader); err != nil { q.tryRemoveDanglingFile(id + ".body") @@ -820,7 +832,11 @@ func (q *Queue) updateMetadataOnDisk(meta *QueueMetadata) error { return err } } - defer file.Close() + defer func() { + if err := file.Close(); err != nil { + q.Log.Error("metadata file close failed", err) + } + }() metaCopy := *meta metaCopy.MsgMeta = meta.MsgMeta.DeepCopy() @@ -849,7 +865,11 @@ func (q *Queue) readMessageMeta(id string) (*QueueMetadata, error) { if err != nil { return nil, err } - defer file.Close() + defer func() { + if err := file.Close(); err != nil { + q.Log.Error("metadata file close failed", err) + } + }() meta := &QueueMetadata{} diff --git a/internal/target/queue/queue_test.go b/internal/target/queue/queue_test.go index 6eb7a101..659770b6 100644 --- a/internal/target/queue/queue_test.go +++ b/internal/target/queue/queue_test.go @@ -39,7 +39,7 @@ import ( "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/testutils" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // newTestQueue returns properly initialized Queue object usable for testing. @@ -246,7 +246,7 @@ func TestQueueDelivery(t *testing.T) { // Wait for the delivery to complete and stop processing. msg := readMsgChanTimeout(t, dt.committed, 5*time.Second) - assert.NoError(t, q.Stop()) + require.NoError(t, q.Stop()) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester1@example.org", "tester2@example.org"}, "") @@ -270,7 +270,7 @@ func TestQueueDelivery_PermanentFail_NonPartial(t *testing.T) { // Queue will abort a delivery if it fails for all recipients. readMsgChanTimeout(t, dt.aborted, 5*time.Second) - assert.NoError(t, q.Stop()) + require.NoError(t, q.Stop()) // Delivery is failed permanently, hence no retry should be rescheduled. checkQueueDir(t, q, []string{}) @@ -297,7 +297,7 @@ func TestQueueDelivery_PermanentFail_Partial(t *testing.T) { // Here delivery fails for recipients too, but this is reported using PartialDelivery. readMsgChanTimeout(t, dt.aborted, 5*time.Second) - assert.NoError(t, q.Stop()) + require.NoError(t, q.Stop()) checkQueueDir(t, q, []string{}) } @@ -323,7 +323,7 @@ func TestQueueDelivery_TemporaryFail(t *testing.T) { msg := readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester1@example.org", "tester2@example.org"}, "") - assert.NoError(t, q.Stop()) + require.NoError(t, q.Stop()) // No more retries scheduled, queue storage is clear. defer checkQueueDir(t, q, []string{}) } @@ -356,7 +356,7 @@ func TestQueueDelivery_TemporaryFail_Partial(t *testing.T) { msg = readMsgChanTimeout(t, dt.committed, 5000*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester2@example.org"}, "") - assert.NoError(t, q.Stop()) + require.NoError(t, q.Stop()) // No more retries scheduled, queue storage is clear. checkQueueDir(t, q, []string{}) } @@ -396,7 +396,7 @@ func TestQueueDelivery_MultipleAttempts(t *testing.T) { msg = readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester2@example.org"}, "") - assert.NoError(t, q.Stop()) + require.NoError(t, q.Stop()) // No more retries should be scheduled. checkQueueDir(t, q, []string{}) } @@ -421,7 +421,7 @@ func TestQueueDelivery_PermanentRcptReject(t *testing.T) { msg := readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.org", []string{"tester2@example.org"}, "") - assert.NoError(t, q.Stop()) + require.NoError(t, q.Stop()) // No more retries should be scheduled. checkQueueDir(t, q, []string{}) } @@ -455,7 +455,7 @@ func TestQueueDelivery_TemporaryRcptReject(t *testing.T) { msg = readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester1@example.org"}, "") - assert.NoError(t, q.Stop()) + require.NoError(t, q.Stop()) // No more retries should be scheduled. checkQueueDir(t, q, []string{}) } @@ -489,7 +489,7 @@ func TestQueueDelivery_SerializationRoundtrip(t *testing.T) { testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester2@example.org"}, "") // Then stop it. - assert.NoError(t, q.Stop()) + require.NoError(t, q.Stop()) // Make sure it is saved. checkQueueDir(t, q, []string{deliveryID}) @@ -502,7 +502,7 @@ func TestQueueDelivery_SerializationRoundtrip(t *testing.T) { testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester1@example.org"}, "") // Close it again. - assert.NoError(t, q.Stop()) + require.NoError(t, q.Stop()) // No more retries should be scheduled. checkQueueDir(t, q, []string{}) } @@ -536,7 +536,7 @@ func TestQueueDelivery_DeserlizationCleanUp(t *testing.T) { msg := readMsgChanTimeout(t, dt.committed, 5*time.Second) testutils.CheckMsgID(t, msg, "tester@example.com", []string{"tester2@example.org"}, "") - assert.NoError(t, q.Stop()) + require.NoError(t, q.Stop()) if err := os.Remove(filepath.Join(q.location, deliveryID+fileSuffix)); err != nil { t.Fatal(err) @@ -544,7 +544,7 @@ func TestQueueDelivery_DeserlizationCleanUp(t *testing.T) { // Dangling files should be removed during load. q = newTestQueueDir(t, &dt, q.location) - assert.NoError(t, q.Stop()) + require.NoError(t, q.Stop()) // Nothing should be left. checkQueueDir(t, q, []string{}) diff --git a/internal/target/received.go b/internal/target/received.go index 051e5a6d..b60bbd14 100644 --- a/internal/target/received.go +++ b/internal/target/received.go @@ -31,7 +31,7 @@ import ( ) func SanitizeForHeader(raw string) string { - return strings.Replace(raw, "\n", "", -1) + return strings.ReplaceAll(raw, "\n", "") } func GenerateReceived(ctx context.Context, msgMeta *module.MsgMetadata, ourHostname, mailFrom string) (string, error) { diff --git a/internal/target/remote/connect.go b/internal/target/remote/connect.go index f9d317ef..a8c5cb97 100644 --- a/internal/target/remote/connect.go +++ b/internal/target/remote/connect.go @@ -56,7 +56,7 @@ type mxConn struct { } func (c *mxConn) Usable() bool { - if c.C == nil || c.transactions> c.reuseLimit || c.C.Client() == nil || c.errored { + if c.C == nil || c.transactions> c.reuseLimit || c.Client() == nil || c.errored { return false } return c.C.Client().Reset() == nil @@ -109,7 +109,9 @@ retry: // reason - this is either a connection problem or server actively // rejecting STARTTLS (despite advertising STARTTLS). // We err on the caution side here and do not perform any fallbacks. - conn.DirectClose() + if err := conn.DirectClose(); err != nil { + rd.Log.Error("conn.DirectClose failed", err) + } return module.TLSNone, nil, err } @@ -131,7 +133,9 @@ retry: // TODO: Check go-smtp code to make TLS verification errors // non-sticky so we can properly send QUIT in this case. - conn.DirectClose() + if err := conn.DirectClose(); err != nil { + rd.Log.Error("conn.DirectClose failed", err) + } goto retry } @@ -139,7 +143,9 @@ retry: rd.Log.Error("TLS error, trying plaintext", err, "remote_server", host, "domain", conn.domain) tlsCfg = nil tlsLevel = module.TLSNone - conn.DirectClose() + if err := conn.DirectClose(); err != nil { + rd.Log.Error("conn.DirectClose failed", err) + } goto retry } @@ -183,7 +189,7 @@ func (rd *remoteDelivery) attemptMX(ctx context.Context, conn *mxConn, record *n for _, p := range rd.policies { policyLevel, err := p.CheckConn(connCtx, mxLevel, tlsLevel, conn.domain, record.Host, tlsState) if err != nil { - conn.Close() + rd.closeConn(conn) return exterrors.WithFields(err, map[string]interface{}{"tls_err": tlsErr}) } if policyLevel> tlsLevel { @@ -200,6 +206,12 @@ func (rd *remoteDelivery) attemptMX(ctx context.Context, conn *mxConn, record *n return nil } +func (rd *remoteDelivery) closeConn(c *mxConn) { + if err := c.Close(); err != nil { + rd.Log.Error("client connection close failed", err) + } +} + func (rd *remoteDelivery) connectionForDomain(ctx context.Context, domain string) (*mxConn, error) { if c, ok := rd.connections[domain]; ok { return c, nil @@ -228,7 +240,7 @@ func (rd *remoteDelivery) connectionForDomain(ctx context.Context, domain string if rd.msgMeta.SMTPOpts.RequireTLS { if conn.tlsLevel < module.TLSAuthenticated { - conn.Close() + rd.closeConn(conn) return nil, &exterrors.SMTPError{ Code: 550, EnhancedCode: exterrors.EnhancedCode{5, 7, 30}, @@ -239,7 +251,7 @@ func (rd *remoteDelivery) connectionForDomain(ctx context.Context, domain string } } if conn.mxLevel < module.MX_MTASTS { - conn.Close() + rd.closeConn(conn) return nil, &exterrors.SMTPError{ Code: 550, EnhancedCode: exterrors.EnhancedCode{5, 7, 30}, @@ -254,7 +266,7 @@ func (rd *remoteDelivery) connectionForDomain(ctx context.Context, domain string region := trace.StartRegion(ctx, "remote/limits.TakeDest") if err := rd.rt.limits.TakeDest(ctx, domain); err != nil { region.End() - conn.Close() + rd.closeConn(conn) return nil, err } region.End() @@ -272,7 +284,7 @@ func (rd *remoteDelivery) connectionForDomain(ctx context.Context, domain string } if err := conn.Mail(ctx, rd.mailFrom, rd.msgMeta.SMTPOpts); err != nil { - conn.Close() + rd.closeConn(conn) return nil, err } conn.lastUseAt = time.Now() diff --git a/internal/target/remote/dane_delivery_test.go b/internal/target/remote/dane_delivery_test.go index 377f7cfc..8d1cec22 100644 --- a/internal/target/remote/dane_delivery_test.go +++ b/internal/target/remote/dane_delivery_test.go @@ -29,6 +29,8 @@ import ( "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/testutils" miekgdns "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func targetWithExtResolver(t *testing.T, zones map[string]mockdns.Zone) (*mockdns.Server, *Target) { @@ -77,7 +79,9 @@ func tlsaRecord(name string, usage, matchType, selector uint8, cert string) map[ func TestRemoteDelivery_DANE_Ok(t *testing.T) { _, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) // RFC 7672, Section 2.2.2. "Non-CNAME" case. @@ -98,7 +102,9 @@ func TestRemoteDelivery_DANE_Ok(t *testing.T) { } dnsSrv, tgt := targetWithExtResolver(t, zones) - defer dnsSrv.Close() + defer func() { + assert.NoError(t, dnsSrv.Close()) + }() tgt.policies = append(tgt.policies, &localPolicy{ minTLSLevel: module.TLSAuthenticated, // Established via DANE instead of PKIX. @@ -111,7 +117,9 @@ func TestRemoteDelivery_DANE_Ok(t *testing.T) { func TestRemoteDelivery_DANE_CNAMEd_1(t *testing.T) { _, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) // RFC 7672, Section 2.2.2. "Secure CNAME" case - TLSA at CNAME matches. @@ -135,7 +143,9 @@ func TestRemoteDelivery_DANE_CNAMEd_1(t *testing.T) { } dnsSrv, tgt := targetWithExtResolver(t, zones) - defer dnsSrv.Close() + defer func() { + assert.NoError(t, dnsSrv.Close()) + }() tgt.policies = append(tgt.policies, &localPolicy{ minTLSLevel: module.TLSAuthenticated, // Established via DANE instead of PKIX. @@ -148,7 +158,9 @@ func TestRemoteDelivery_DANE_CNAMEd_1(t *testing.T) { func TestRemoteDelivery_DANE_CNAMEd_2(t *testing.T) { _, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) // RFC 7672, Section 2.2.2. "Secure CNAME" case - TLSA at initial name matches. @@ -173,7 +185,9 @@ func TestRemoteDelivery_DANE_CNAMEd_2(t *testing.T) { } dnsSrv, tgt := targetWithExtResolver(t, zones) - defer dnsSrv.Close() + defer func() { + assert.NoError(t, dnsSrv.Close()) + }() tgt.policies = append(tgt.policies, &localPolicy{ minTLSLevel: module.TLSAuthenticated, // Established via DANE instead of PKIX. @@ -186,7 +200,9 @@ func TestRemoteDelivery_DANE_CNAMEd_2(t *testing.T) { func TestRemoteDelivery_DANE_InsecureCNAMEDest(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) // RFC 7672, Section 2.2.2. "Insecure CNAME" case - initial name is secure. @@ -217,7 +233,9 @@ func TestRemoteDelivery_DANE_InsecureCNAMEDest(t *testing.T) { } dnsSrv, tgt := targetWithExtResolver(t, zones) - defer dnsSrv.Close() + defer func() { + require.NoError(t, dnsSrv.Close()) + }() tgt.tlsConfig = clientCfg _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) @@ -231,7 +249,9 @@ func TestRemoteDelivery_DANE_InsecureCNAMEDest(t *testing.T) { func TestRemoteDelivery_DANE_NonAD_TLSA_Ignore(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) // RFC 7672, Section 2.2.2. "Non-CNAME" case - initial name is insecure. @@ -250,7 +270,9 @@ func TestRemoteDelivery_DANE_NonAD_TLSA_Ignore(t *testing.T) { } dnsSrv, tgt := targetWithExtResolver(t, zones) - defer dnsSrv.Close() + defer func() { + require.NoError(t, dnsSrv.Close()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -258,7 +280,9 @@ func TestRemoteDelivery_DANE_NonAD_TLSA_Ignore(t *testing.T) { func TestRemoteDelivery_DANE_NonADIgnore_CNAME(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) // RFC 7672, Section 2.2.2. "Insecure CNAME" case - initial name is insecure. @@ -281,7 +305,9 @@ func TestRemoteDelivery_DANE_NonADIgnore_CNAME(t *testing.T) { } dnsSrv, tgt := targetWithExtResolver(t, zones) - defer dnsSrv.Close() + defer func() { + require.NoError(t, dnsSrv.Close()) + }() testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"}) @@ -289,7 +315,9 @@ func TestRemoteDelivery_DANE_NonADIgnore_CNAME(t *testing.T) { func TestRemoteDelivery_DANE_SkipAUnauth(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ @@ -308,7 +336,9 @@ func TestRemoteDelivery_DANE_SkipAUnauth(t *testing.T) { } dnsSrv, tgt := targetWithExtResolver(t, zones) - defer dnsSrv.Close() + defer func() { + require.NoError(t, dnsSrv.Close()) + }() tgt.tlsConfig = clientCfg testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) @@ -317,7 +347,9 @@ func TestRemoteDelivery_DANE_SkipAUnauth(t *testing.T) { func TestRemoteDelivery_DANE_Mismatch(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ @@ -337,7 +369,9 @@ func TestRemoteDelivery_DANE_Mismatch(t *testing.T) { } dnsSrv, tgt := targetWithExtResolver(t, zones) - defer dnsSrv.Close() + defer func() { + require.NoError(t, dnsSrv.Close()) + }() tgt.tlsConfig = clientCfg _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) @@ -351,7 +385,9 @@ func TestRemoteDelivery_DANE_Mismatch(t *testing.T) { func TestRemoteDelivery_DANE_NoRecord(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ @@ -365,7 +401,9 @@ func TestRemoteDelivery_DANE_NoRecord(t *testing.T) { } dnsSrv, tgt := targetWithExtResolver(t, zones) - defer dnsSrv.Close() + defer func() { + require.NoError(t, dnsSrv.Close()) + }() tgt.tlsConfig = clientCfg testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"}) @@ -374,7 +412,9 @@ func TestRemoteDelivery_DANE_NoRecord(t *testing.T) { func TestRemoteDelivery_DANE_LookupErr(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ @@ -391,7 +431,9 @@ func TestRemoteDelivery_DANE_LookupErr(t *testing.T) { } dnsSrv, tgt := targetWithExtResolver(t, zones) - defer dnsSrv.Close() + defer func() { + require.NoError(t, dnsSrv.Close()) + }() tgt.tlsConfig = clientCfg _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) @@ -405,7 +447,9 @@ func TestRemoteDelivery_DANE_LookupErr(t *testing.T) { func TestRemoteDelivery_DANE_NoTLS(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ @@ -424,7 +468,9 @@ func TestRemoteDelivery_DANE_NoTLS(t *testing.T) { }, } dnsSrv, tgt := targetWithExtResolver(t, zones) - defer dnsSrv.Close() + defer func() { + require.NoError(t, dnsSrv.Close()) + }() _, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"}) if err == nil { @@ -437,7 +483,9 @@ func TestRemoteDelivery_DANE_NoTLS(t *testing.T) { func TestRemoteDelivery_DANE_TLSError(t *testing.T) { _, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ @@ -457,7 +505,9 @@ func TestRemoteDelivery_DANE_TLSError(t *testing.T) { }, } dnsSrv, tgt := targetWithExtResolver(t, zones) - defer dnsSrv.Close() + defer func() { + require.NoError(t, dnsSrv.Close()) + }() // Cause failure through version incompatibility. tgt.tlsConfig = &tls.Config{ diff --git a/internal/target/remote/mxauth_test.go b/internal/target/remote/mxauth_test.go index f979a53d..5f698554 100644 --- a/internal/target/remote/mxauth_test.go +++ b/internal/target/remote/mxauth_test.go @@ -33,11 +33,14 @@ import ( "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/testutils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestRemoteDelivery_AuthMX_MTASTS(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -74,11 +77,15 @@ func TestRemoteDelivery_AuthMX_MTASTS(t *testing.T) { func TestRemoteDelivery_MTASTS_SkipNonMatching(t *testing.T) { _, be1, srv1 := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv1.Close() + defer func() { + require.NoError(t, srv1.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv1) clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.2:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -125,7 +132,9 @@ func TestRemoteDelivery_MTASTS_SkipNonMatching(t *testing.T) { func TestRemoteDelivery_AuthMX_MTASTS_Fail(t *testing.T) { clientCfg, be1, srv1 := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv1.Close() + defer func() { + assert.NoError(t, srv1.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv1) zones := map[string]mockdns.Zone{ @@ -169,7 +178,9 @@ func TestRemoteDelivery_AuthMX_MTASTS_Fail(t *testing.T) { func TestRemoteDelivery_AuthMX_MTASTS_NoTLS(t *testing.T) { be1, srv1 := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv1.Close() + defer func() { + assert.NoError(t, srv1.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv1) zones := map[string]mockdns.Zone{ @@ -212,7 +223,9 @@ func TestRemoteDelivery_AuthMX_MTASTS_NoTLS(t *testing.T) { func TestRemoteDelivery_AuthMX_MTASTS_RequirePKIX(t *testing.T) { _, be1, srv1 := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv1.Close() + defer func() { + require.NoError(t, srv1.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv1) zones := map[string]mockdns.Zone{ @@ -271,7 +284,9 @@ func TestRemoteDelivery_AuthMX_MTASTS_NoPolicy(t *testing.T) { // // https://builds.sr.ht/~emersion/job/147975 tarpit := testutils.FailOnConn(t, "127.0.0.1:"+smtpPort) - defer tarpit.Close() + defer func() { + require.NoError(t, tarpit.Close()) + }() zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -306,7 +321,9 @@ func TestRemoteDelivery_AuthMX_MTASTS_NoPolicy(t *testing.T) { func TestRemoteDelivery_AuthMX_DNSSEC(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ @@ -324,7 +341,9 @@ func TestRemoteDelivery_AuthMX_DNSSEC(t *testing.T) { if err != nil { t.Fatal(err) } - defer dnsSrv.Close() + defer func() { + require.NoError(t, dnsSrv.Close()) + }() dialer := net.Dialer{} dialer.Resolver = &net.Resolver{} @@ -349,7 +368,9 @@ func TestRemoteDelivery_AuthMX_DNSSEC(t *testing.T) { func TestRemoteDelivery_AuthMX_DNSSEC_Fail(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ @@ -366,7 +387,9 @@ func TestRemoteDelivery_AuthMX_DNSSEC_Fail(t *testing.T) { if err != nil { t.Fatal(err) } - defer dnsSrv.Close() + defer func() { + require.NoError(t, dnsSrv.Close()) + }() dialer := net.Dialer{} dialer.Resolver = &net.Resolver{} @@ -400,7 +423,9 @@ func TestRemoteDelivery_AuthMX_DNSSEC_Fail(t *testing.T) { func TestRemoteDelivery_REQUIRETLS(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) srv.EnableREQUIRETLS = true - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -443,7 +468,9 @@ func TestRemoteDelivery_REQUIRETLS(t *testing.T) { func TestRemoteDelivery_REQUIRETLS_Fail(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) srv.EnableREQUIRETLS = false /* no REQUIRETLS */ - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -490,7 +517,9 @@ func TestRemoteDelivery_REQUIRETLS_Fail(t *testing.T) { func TestRemoteDelivery_REQUIRETLS_Relaxed(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) srv.EnableREQUIRETLS = false /* no REQUIRETLS */ - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -534,7 +563,9 @@ func TestRemoteDelivery_REQUIRETLS_Relaxed(t *testing.T) { func TestRemoteDelivery_REQUIRETLS_Relaxed_NoMXAuth(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) srv.EnableREQUIRETLS = false /* no REQUIRETLS */ - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -577,7 +608,9 @@ func TestRemoteDelivery_REQUIRETLS_Relaxed_NoMXAuth(t *testing.T) { func TestRemoteDelivery_REQUIRETLS_Relaxed_NoTLS(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) srv.EnableREQUIRETLS = false /* no REQUIRETLS */ - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -625,7 +658,9 @@ func TestRemoteDelivery_REQUIRETLS_Relaxed_NoTLS(t *testing.T) { func TestRemoteDelivery_REQUIRETLS_Relaxed_TLSFail(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) srv.EnableREQUIRETLS = false /* no REQUIRETLS */ - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { diff --git a/internal/target/remote/remote.go b/internal/target/remote/remote.go index e300edd8..b382a88b 100644 --- a/internal/target/remote/remote.go +++ b/internal/target/remote/remote.go @@ -217,7 +217,7 @@ type remoteDelivery struct { func (rt *Target) StartDelivery(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { policies := make([]module.DeliveryMXAuthPolicy, 0, len(rt.policies)) - if !(msgMeta.TLSRequireOverride && rt.allowSecOverride) { + if !msgMeta.TLSRequireOverride || !rt.allowSecOverride { for _, p := range rt.policies { policies = append(policies, p.StartDelivery(msgMeta)) } @@ -422,7 +422,11 @@ func (rd *remoteDelivery) BodyNonAtomic(ctx context.Context, c module.StatusColl } return } - defer bodyR.Close() + defer func() { + if err := bodyR.Close(); err != nil { + rd.Log.Error("failed to close message buffer", err) + } + }() err = conn.Data(ctx, header, bodyR) for _, rcpt := range conn.Rcpts() { @@ -453,8 +457,8 @@ func (rd *remoteDelivery) Close() error { if !conn.Usable() { rd.Log.Debugf("disconnected %v from %s (errored=%v,transactions=%v,disconnected before=%v)", - conn.LocalAddr(), conn.ServerName(), conn.errored, conn.transactions, conn.C.Client() == nil) - conn.Close() + conn.LocalAddr(), conn.ServerName(), conn.errored, conn.transactions, conn.Client() == nil) + rd.closeConn(conn) } else { rd.Log.Debugf("returning connection %v for %s to pool", conn.LocalAddr(), conn.ServerName()) rd.rt.pool.Return(conn.domain, conn) diff --git a/internal/target/remote/remote_test.go b/internal/target/remote/remote_test.go index b21f97e0..1578303b 100644 --- a/internal/target/remote/remote_test.go +++ b/internal/target/remote/remote_test.go @@ -41,6 +41,7 @@ import ( "github.com/foxcpp/maddy/internal/smtpconn/pool" "github.com/foxcpp/maddy/internal/testutils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // .invalid TLD is used here to make sure if there is something wrong about @@ -118,7 +119,9 @@ func testDANEPolicy(t *testing.T, extR *dns.ExtResolver) *danePolicy { func TestRemoteDelivery(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -140,7 +143,9 @@ func TestRemoteDelivery(t *testing.T) { func TestRemoteDelivery_NoMXFallback(t *testing.T) { tarpit := testutils.FailOnConn(t, "127.0.0.1:"+smtpPort) - defer tarpit.Close() + defer func() { + assert.NoError(t, tarpit.Close()) + }() zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -169,7 +174,9 @@ func TestRemoteDelivery_NoMXFallback(t *testing.T) { func TestRemoteDelivery_EmptySender(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -193,7 +200,9 @@ func TestRemoteDelivery_IPLiteral(t *testing.T) { t.Skip("Support disabled") be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ @@ -219,7 +228,9 @@ func TestRemoteDelivery_IPLiteral(t *testing.T) { func TestRemoteDelivery_FallbackMX(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -238,7 +249,9 @@ func TestRemoteDelivery_FallbackMX(t *testing.T) { func TestRemoteDelivery_BodyNonAtomic(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -268,7 +281,9 @@ func TestRemoteDelivery_BodyNonAtomic(t *testing.T) { func TestRemoteDelivery_Abort(t *testing.T) { _, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -300,7 +315,9 @@ func TestRemoteDelivery_Abort(t *testing.T) { func TestRemoteDelivery_CommitWithoutBody(t *testing.T) { _, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -333,7 +350,9 @@ func TestRemoteDelivery_CommitWithoutBody(t *testing.T) { func TestRemoteDelivery_MAILFROMErr(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -370,7 +389,9 @@ func TestRemoteDelivery_MAILFROMErr(t *testing.T) { func TestRemoteDelivery_NoMX(t *testing.T) { tarpit := testutils.FailOnConn(t, "127.0.0.1:"+smtpPort) - defer tarpit.Close() + defer func() { + require.NoError(t, tarpit.Close()) + }() zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -402,7 +423,9 @@ func TestRemoteDelivery_NullMX(t *testing.T) { // deliver the message. Use of testutils.SMTPServer here // causes weird race conditions. tarpit := testutils.FailOnConn(t, "127.0.0.1:"+smtpPort) - defer tarpit.Close() + defer func() { + require.NoError(t, tarpit.Close()) + }() zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -430,7 +453,9 @@ func TestRemoteDelivery_NullMX(t *testing.T) { func TestRemoteDelivery_Quarantined(t *testing.T) { _, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -474,7 +499,9 @@ func TestRemoteDelivery_Quarantined(t *testing.T) { func TestRemoteDelivery_MAILFROMErr_Repeated(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -514,7 +541,9 @@ func TestRemoteDelivery_MAILFROMErr_Repeated(t *testing.T) { func TestRemoteDelivery_RcptErr(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -569,7 +598,9 @@ func TestRemoteDelivery_RcptErr(t *testing.T) { func TestRemoteDelivery_DownMX(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -624,10 +655,14 @@ func TestRemoteDelivery_AllMXDown(t *testing.T) { func TestRemoteDelivery_Split(t *testing.T) { be1, srv1 := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv1.Close() + defer func() { + assert.NoError(t, srv1.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv1) be2, srv2 := testutils.SMTPServer(t, "127.0.0.2:"+smtpPort) - defer srv2.Close() + defer func() { + assert.NoError(t, srv2.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv2) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -657,10 +692,14 @@ func TestRemoteDelivery_Split(t *testing.T) { func TestRemoteDelivery_Split_Fail(t *testing.T) { be1, srv1 := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv1.Close() + defer func() { + require.NoError(t, srv1.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv1) be2, srv2 := testutils.SMTPServer(t, "127.0.0.2:"+smtpPort) - defer srv2.Close() + defer func() { + require.NoError(t, srv2.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv2) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -723,7 +762,9 @@ func TestRemoteDelivery_Split_Fail(t *testing.T) { func TestRemoteDelivery_BodyErr(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -770,10 +811,14 @@ func TestRemoteDelivery_BodyErr(t *testing.T) { func TestRemoteDelivery_Split_BodyErr(t *testing.T) { be1, srv1 := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv1.Close() + defer func() { + require.NoError(t, srv1.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv1) _, srv2 := testutils.SMTPServer(t, "127.0.0.2:"+smtpPort) - defer srv2.Close() + defer func() { + require.NoError(t, srv2.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv2) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -828,10 +873,14 @@ func TestRemoteDelivery_Split_BodyErr(t *testing.T) { func TestRemoteDelivery_Split_BodyErr_NonAtomic(t *testing.T) { be1, srv1 := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv1.Close() + defer func() { + require.NoError(t, srv1.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv1) _, srv2 := testutils.SMTPServer(t, "127.0.0.2:"+smtpPort) - defer srv2.Close() + defer func() { + require.NoError(t, srv2.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv2) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -898,7 +947,9 @@ func TestRemoteDelivery_Split_BodyErr_NonAtomic(t *testing.T) { func TestRemoteDelivery_TLSErrFallback(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -927,7 +978,9 @@ func TestRemoteDelivery_TLSErrFallback(t *testing.T) { func TestRemoteDelivery_RequireTLS_Missing(t *testing.T) { _, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -953,7 +1006,9 @@ func TestRemoteDelivery_RequireTLS_Missing(t *testing.T) { func TestRemoteDelivery_RequireTLS_Present(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -978,7 +1033,9 @@ func TestRemoteDelivery_RequireTLS_Present(t *testing.T) { func TestRemoteDelivery_RequireTLS_NoErrFallback(t *testing.T) { clientCfg, _, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -1011,7 +1068,9 @@ func TestRemoteDelivery_RequireTLS_NoErrFallback(t *testing.T) { func TestRemoteDelivery_TLS_FallbackNoVerify(t *testing.T) { _, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -1042,7 +1101,9 @@ func TestRemoteDelivery_TLS_FallbackNoVerify(t *testing.T) { func TestRemoteDelivery_TLS_FallbackPlaintext(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { @@ -1083,7 +1144,9 @@ func TestMain(m *testing.M) { func TestRemoteDelivery_ConnReuse(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) zones := map[string]mockdns.Zone{ "example.invalid.": { diff --git a/internal/target/remote/security.go b/internal/target/remote/security.go index 51572b89..efbbb1d2 100644 --- a/internal/target/remote/security.go +++ b/internal/target/remote/security.go @@ -306,19 +306,19 @@ func NewDNSSECPolicy(_, instName string) (module.Module, error) { }, nil } -func (c *dnssecPolicy) Name() string { +func (dnssecPolicy) Name() string { return "mx_auth.dnssec" } -func (c *dnssecPolicy) InstanceName() string { +func (c dnssecPolicy) InstanceName() string { return c.instName } -func (c *dnssecPolicy) Weight() int { +func (dnssecPolicy) Weight() int { return 1 } -func (c *dnssecPolicy) Configure(inlineArgs []string, cfg *config.Map) error { +func (dnssecPolicy) Configure(inlineArgs []string, cfg *config.Map) error { _, err := cfg.Process() // will fail if there is any directive return err } diff --git a/internal/target/smtp/sasl_test.go b/internal/target/smtp/sasl_test.go index 63b52a5e..a1054845 100644 --- a/internal/target/smtp/sasl_test.go +++ b/internal/target/smtp/sasl_test.go @@ -25,6 +25,7 @@ import ( "github.com/foxcpp/maddy/framework/config" "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/testutils" + "github.com/stretchr/testify/require" ) func testSaslFactory(t *testing.T, args ...string) saslClientFactory { @@ -40,7 +41,9 @@ func testSaslFactory(t *testing.T, args ...string) saslClientFactory { func TestSASL_Plain(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) mod := &Downstream{ @@ -68,7 +71,9 @@ func TestSASL_Plain(t *testing.T) { func TestSASL_Plain_AuthFail(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) be.AuthErr = &smtp.SMTPError{ @@ -98,7 +103,9 @@ func TestSASL_Plain_AuthFail(t *testing.T) { func TestSASL_Forward(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) mod := &Downstream{ @@ -131,7 +138,9 @@ func TestSASL_Forward(t *testing.T) { func TestSASL_Forward_NoCreds(t *testing.T) { _, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) mod := &Downstream{ diff --git a/internal/target/smtp/smtp_downstream.go b/internal/target/smtp/smtp_downstream.go index d48b56ad..6c678f0a 100644 --- a/internal/target/smtp/smtp_downstream.go +++ b/internal/target/smtp/smtp_downstream.go @@ -87,7 +87,7 @@ func NewDownstream(modName, instName string) (module.Module, error) { func (u *Downstream) Configure(inlineArgs []string, cfg *config.Map) error { var attemptTLS *bool - var targetsArg []string + targetsArg := make([]string, 0, len(inlineArgs)) cfg.Bool("debug", true, false, &u.log.Debug) cfg.Callback("require_tls", func(m *config.Map, node config.Node) error { u.log.Msg("require_tls directive is deprecated and ignored") @@ -195,7 +195,9 @@ func (u *Downstream) StartDelivery(ctx context.Context, msgMeta *module.MsgMetad } if err := d.conn.Mail(ctx, mailFrom, msgMeta.SMTPOpts); err != nil { - d.conn.Close() + if err := d.conn.Close(); err != nil { + u.log.Error("failed to close smtp connection", err) + } return nil, err } @@ -206,6 +208,12 @@ func (u *Downstream) StartDelivery(ctx context.Context, msgMeta *module.MsgMetad return d, nil } +func (d *delivery) closeConn(c *smtpconn.C) { + if err := c.Close(); err != nil { + d.log.Error("failed to close SMTP connection", err) + } +} + func (d *delivery) connect(ctx context.Context) error { // TODO: Review possibility of connection pooling here. var lastErr error @@ -251,12 +259,12 @@ func (d *delivery) connect(ctx context.Context) error { if d.u.saslFactory != nil { saslClient, err := d.u.saslFactory(d.msgMeta) if err != nil { - conn.Close() + d.closeConn(conn) return err } if err := conn.Client().Auth(saslClient); err != nil { - conn.Close() + d.closeConn(conn) return err } } @@ -282,7 +290,11 @@ func (d *delivery) Body(ctx context.Context, header textproto.Header, body buffe return exterrors.WithFields(err, map[string]interface{}{"target": d.u.modName}) } - defer r.Close() + defer func() { + if err := r.Close(); err != nil { + d.log.Msg("failed to close body buffer", err) + } + }() return d.u.moduleError(d.conn.Data(ctx, header, r)) } @@ -294,7 +306,11 @@ func (d *lmtpDelivery) BodyNonAtomic(ctx context.Context, sc module.StatusCollec sc.SetStatus(rcpt, modErr) } } - defer r.Close() + defer func() { + if err := r.Close(); err != nil { + d.log.Msg("failed to close body buffer", err) + } + }() rcptIndx := 0 err = d.conn.LMTPData(ctx, header, r, func(rcpt string, err *smtp.SMTPError) { @@ -320,8 +336,7 @@ func (d *lmtpDelivery) BodyNonAtomic(ctx context.Context, sc module.StatusCollec } func (d *delivery) Abort(ctx context.Context) error { - d.conn.Close() - return nil + return d.conn.Close() } func (d *delivery) Commit(ctx context.Context) error { diff --git a/internal/target/smtp/smtp_downstream_test.go b/internal/target/smtp/smtp_downstream_test.go index f0f58c66..e292f5e3 100644 --- a/internal/target/smtp/smtp_downstream_test.go +++ b/internal/target/smtp/smtp_downstream_test.go @@ -30,17 +30,22 @@ import ( "github.com/foxcpp/maddy/framework/config" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/internal/testutils" + "github.com/stretchr/testify/require" ) var testPort string func TestDownstreamDelivery(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) tarpit := testutils.FailOnConn(t, "127.0.0.2:"+testPort) - defer tarpit.Close() + defer func() { + require.NoError(t, tarpit.Close()) + }() mod := &Downstream{ hostname: "mx.example.invalid", @@ -74,7 +79,9 @@ func TestDownstreamDelivery_LMTP(t *testing.T) { Message: "nop", }, } - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) mod := &Downstream{ @@ -125,7 +132,9 @@ func TestDownstreamDelivery_LMTP_ErrorCoerce(t *testing.T) { Message: "nop", }, } - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) mod := &Downstream{ @@ -156,7 +165,9 @@ func (sc *statusCollector) SetStatus(rcptTo string, err error) { func TestDownstreamDelivery_Fallback(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.2:"+testPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) mod := &Downstream{ @@ -182,7 +193,9 @@ func TestDownstreamDelivery_Fallback(t *testing.T) { func TestDownstreamDelivery_MAILErr(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) be.MailErr = &smtp.SMTPError{ @@ -209,7 +222,9 @@ func TestDownstreamDelivery_MAILErr(t *testing.T) { func TestDownstreamDelivery_StartTLS(t *testing.T) { clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+testPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) mod := &Downstream{ @@ -237,7 +252,9 @@ func TestDownstreamDelivery_StartTLS(t *testing.T) { func TestDownstreamDelivery_StartTLS_NoFallback(t *testing.T) { _, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) mod := &Downstream{ diff --git a/internal/target/smtp/smtputf8_test.go b/internal/target/smtp/smtputf8_test.go index f47fc778..1f469e8a 100644 --- a/internal/target/smtp/smtputf8_test.go +++ b/internal/target/smtp/smtputf8_test.go @@ -23,11 +23,14 @@ import ( "github.com/foxcpp/maddy/framework/config" "github.com/foxcpp/maddy/internal/testutils" + "github.com/stretchr/testify/require" ) func TestDownstreamDelivery_EHLO_ALabel(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort) - defer srv.Close() + defer func() { + require.NoError(t, srv.Close()) + }() defer testutils.CheckSMTPConnLeak(t, srv) mod, err := NewDownstream("", "") diff --git a/internal/testutils/bench_delivery.go b/internal/testutils/bench_delivery.go index eedd0d2a..85fb80f0 100644 --- a/internal/testutils/bench_delivery.go +++ b/internal/testutils/bench_delivery.go @@ -123,7 +123,7 @@ func BenchDelivery(b *testing.B, target module.DeliveryTarget, sender string, re } for i, rcptTemplate := range recipientTemplates { - rcpt := strings.Replace(rcptTemplate, "X", strconv.Itoa(i), -1) + rcpt := strings.ReplaceAll(rcptTemplate, "X", strconv.Itoa(i)) if err := delivery.AddRcpt(benchCtx, rcpt, smtp.RcptOptions{}); err != nil { b.Fatal(err) diff --git a/internal/testutils/smtp_server.go b/internal/testutils/smtp_server.go index 9af52c4f..214b72db 100644 --- a/internal/testutils/smtp_server.go +++ b/internal/testutils/smtp_server.go @@ -33,6 +33,7 @@ import ( "github.com/emersion/go-sasl" "github.com/emersion/go-smtp" "github.com/foxcpp/maddy/framework/exterrors" + "github.com/stretchr/testify/require" ) type SMTPMessage struct { @@ -227,10 +228,8 @@ func SMTPServer(t *testing.T, addr string, fn ...SMTPServerConfigureFunc) (*SMTP // nil Server.listener (Serve sets it to a non-nil value, so it is racy and // happens only sometimes). testConn, err := net.Dial("tcp", addr) - if err != nil { - t.Fatal(err) - } - testConn.Close() + require.NoError(t, err) + require.NoError(t, testConn.Close()) return be, s } @@ -321,10 +320,8 @@ func SMTPServerSTARTTLS(t *testing.T, addr string, fn ...SMTPServerConfigureFunc // nil Server.listener (Serve sets it to a non-nil value, so it is racy and // happens only sometimes). testConn, err := net.Dial("tcp", addr) - if err != nil { - t.Fatal(err) - } - testConn.Close() + require.NoError(t, err) + require.NoError(t, testConn.Close()) return clientCfg, be, s } @@ -379,7 +376,7 @@ func SMTPServerTLS(t *testing.T, addr string, fn ...SMTPServerConfigureFunc) (*t if err != nil { t.Fatal(err) } - testConn.Close() + require.NoError(t, testConn.Close()) return clientCfg, be, s } diff --git a/internal/testutils/target.go b/internal/testutils/target.go index 19585215..221834ba 100644 --- a/internal/testutils/target.go +++ b/internal/testutils/target.go @@ -62,18 +62,18 @@ type Target struct { module.Module is implemented with dummy functions for logging done by MsgPipeline code. */ -func (dt Target) Configure([]string, *config.Map) error { +func (dt *Target) Configure([]string, *config.Map) error { return nil } -func (dt Target) InstanceName() string { +func (dt *Target) InstanceName() string { if dt.InstName != "" { return dt.InstName } return "test_instance" } -func (dt Target) Name() string { +func (dt *Target) Name() string { return "test_target" } @@ -129,7 +129,11 @@ func (dtd *testTargetDeliveryPartial) BodyNonAtomic(ctx context.Context, c modul } return } - defer body.Close() + defer func() { + if err := body.Close(); err != nil { + panic(err) + } + }() dtd.msg.Body, err = io.ReadAll(body) if err != nil { @@ -153,7 +157,11 @@ func (dtd *testTargetDelivery) Body(ctx context.Context, header textproto.Header if err != nil { return err } - defer body.Close() + defer func() { + if err := body.Close(); err != nil { + panic(err) + } + }() if dtd.tgt.DiscardMessages { // Don't bother. diff --git a/internal/updatepipe/pubsub/pq.go b/internal/updatepipe/pubsub/pq.go index 29f9c525..c084e600 100644 --- a/internal/updatepipe/pubsub/pq.go +++ b/internal/updatepipe/pubsub/pq.go @@ -50,8 +50,12 @@ func NewPQ(dsn string) (*PqPubSub, error) { } func (l *PqPubSub) Close() error { - l.sender.Close() - l.L.Close() + if err := l.sender.Close(); err != nil { + l.Log.Error("failed to close sender socket", err) + } + if err := l.L.Close(); err != nil { + l.Log.Error("failed to close listener", err) + } return nil } diff --git a/internal/updatepipe/unix_pipe.go b/internal/updatepipe/unix_pipe.go index 945c4f95..a2042d62 100644 --- a/internal/updatepipe/unix_pipe.go +++ b/internal/updatepipe/unix_pipe.go @@ -120,11 +120,17 @@ func (usp *UnixSockPipe) Push(upd mess.Update) error { func (usp *UnixSockPipe) Close() error { if usp.sender != nil { - usp.sender.Close() + if err := usp.sender.Close(); err != nil { + usp.Log.Error("failed to close sender socket", err) + } } if usp.listener != nil { - usp.listener.Close() - os.Remove(usp.SockPath) + if err := usp.listener.Close(); err != nil { + usp.Log.Error("failed to close listener", err) + } + if err := os.Remove(usp.SockPath); err != nil { + usp.Log.Error("failed to remove socket", err) + } } return nil } diff --git a/maddy.go b/maddy.go index 23eea317..5c9381d2 100644 --- a/maddy.go +++ b/maddy.go @@ -201,10 +201,18 @@ func Run(c *cli.Context) error { initDebug(c) - os.Setenv("PATH", config.LibexecDirectory+string(filepath.ListSeparator)+os.Getenv("PATH")) + err = os.Setenv("PATH", config.LibexecDirectory+string(filepath.ListSeparator)+os.Getenv("PATH")) + if err != nil { + systemdStatusErr(err) + return cli.Exit(err.Error(), 1) + } hooks.AddHook(hooks.EventLogRotate, reinitLogging) - defer log.DefaultLogger.Out.Close() + defer func() { + if err := log.DefaultLogger.Out.Close(); err != nil { + log.Println("failed to close default logger output:", err) + } + }() defer hooks.RunHooks(hooks.EventShutdown) defer func() { @@ -222,7 +230,10 @@ func Run(c *cli.Context) error { } func VerifyConfig(c *cli.Context) error { - os.Setenv("PATH", config.LibexecDirectory+string(filepath.ListSeparator)+os.Getenv("PATH")) + err := os.Setenv("PATH", config.LibexecDirectory+string(filepath.ListSeparator)+os.Getenv("PATH")) + if err != nil { + return cli.Exit(err.Error(), 1) + } if _, err := moduleConfigure(c.Path("config")); err != nil { return cli.Exit(err.Error(), 2) @@ -308,7 +319,9 @@ func ensureDirectoryWritable(path string) error { if err != nil { return err } - testFile.Close() + if err := testFile.Close(); err != nil { + log.Println("failed to close writeable-test file:", err) + } return os.RemoveAll(testFile.Name()) } @@ -337,7 +350,11 @@ func ReadConfig(path string) ([]config.Node, error) { if err != nil { return nil, err } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + log.Println("failed to close config file:", err) + } + }() return parser.Read(f, path) } diff --git a/systemd.go b/systemd.go index 649740d3..2eea54e0 100644 --- a/systemd.go +++ b/systemd.go @@ -84,7 +84,11 @@ func systemdStatus(status SDStatus, desc string) { } return } - defer sock.Close() + defer func() { + if err := sock.Close(); err != nil { + log.Println("systemd: failed to close systemd socket:", err) + } + }() if err := setScmPassCred(sock); err != nil { log.Println("systemd: failed to set SCM_PASSCRED on the socket:", err) @@ -111,7 +115,11 @@ func systemdStatusErr(reportedErr error) { } return } - defer sock.Close() + defer func() { + if err := sock.Close(); err != nil { + log.Println("systemd: failed to close systemd socket:", err) + } + }() if err := setScmPassCred(sock); err != nil { log.Println("systemd: failed to set SCM_PASSCRED on the socket:", err) diff --git a/tests/t.go b/tests/t.go index 4e8d4fc1..84592d0a 100644 --- a/tests/t.go +++ b/tests/t.go @@ -40,6 +40,8 @@ import ( "time" "github.com/foxcpp/go-mockdns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var ( @@ -111,7 +113,7 @@ func (t *T) DNS(zones map[string]mockdns.Zone) { if t.dnsServ != nil { t.Log("NOTE: Multiple DNS calls, replacing the server instance...") - t.dnsServ.Close() + require.NoError(t, t.dnsServ.Close()) } dnsServ, err := mockdns.NewServerWithLogger(zones, t, false) @@ -191,7 +193,7 @@ func (t *T) ensureCanRun() { } t.Log("removing", t.testDir) - os.RemoveAll(t.testDir) + assert.NoError(t, os.RemoveAll(t.testDir)) t.testDir = "" }) } @@ -319,7 +321,9 @@ func (t *T) Run(waitListeners int) { serverStarted := make(chan bool) go func() { - defer logOut.Close() + defer func() { + assert.NoError(t, logOut.Close()) + }() defer close(serverStarted) scnr := bufio.NewScanner(logOut) for scnr.Scan() { @@ -366,7 +370,7 @@ func (t *T) RuntimeDir() string { func (t *T) killServer() { if err := t.servProc.Process.Signal(os.Interrupt); err != nil { t.Log("Unable to kill the server process:", err) - os.RemoveAll(t.testDir) + assert.NoError(t, os.RemoveAll(t.testDir)) return // Return, as now it is pointless to wait for it. } From a9a0ef5a381f8b97875448073d56839d68aef2aa Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年3月25日 01:44:46 +0300 Subject: [PATCH 082/111] tests: No need to close integration test stderr twice --- tests/t.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/t.go b/tests/t.go index 84592d0a..58d021d0 100644 --- a/tests/t.go +++ b/tests/t.go @@ -321,9 +321,6 @@ func (t *T) Run(waitListeners int) { serverStarted := make(chan bool) go func() { - defer func() { - assert.NoError(t, logOut.Close()) - }() defer close(serverStarted) scnr := bufio.NewScanner(logOut) for scnr.Scan() { From 8e834effa480315551ca2a921fd72f062565acbb Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年3月25日 01:53:16 +0300 Subject: [PATCH 083/111] Add AGENTS.md for LLM coding agents Some people use these. File itself is generated using Opus 4.6 and hand-edited later. Possibly incomplete and could be merged with HACKING.md. --- AGENTS.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..ea3ec1f7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,56 @@ +# AGENTS.md — Maddy Mail Server + +## Architecture + +Maddy is a composable all-in-one mail server (MTA/MX/IMAP) written in Go. The core abstraction is the **module system**: every functional component (auth, storage, checks, targets, endpoints) implements `module.Module` from `framework/module/module.go` and registers itself via `module.Register(name, factory)` in an `init()` function. + +- **`framework/`** — Stable, reusable packages (config parsing, module interfaces, address handling, error types, logging). Interfaces live here to avoid circular imports. +- **`internal/`** — All module implementations. Subdirectories map to module roles: `endpoint/` (protocol listeners), `target/` (delivery destinations), `auth/`, `check/` (message inspectors), `modify/` (header modifiers), `storage/`, `table/` (string→string lookups). +- **`maddy.go`** — Side-effect imports that pull all `internal/` modules into the binary, plus the `Run`/`moduleConfigure`/`RegisterModules` startup sequence. +- **`cmd/maddy/main.go`** — Thin entrypoint; imports root package for module registration, then calls `maddycli.Run()`. + +Modules are wired together at runtime via `maddy.conf` configuration. Top-level blocks are lazily initialized through `module.Registry`. The **message pipeline** (`internal/msgpipeline/`) routes messages from endpoints through checks, modifiers, and to delivery targets based on sender/recipient matching rules. + +## Build & Test + +```sh +# Build (produces ./build/maddy by default): +./build.sh build + +# Build with specific tags (e.g. for Docker): +./build.sh --tags "docker" build + +# Unit tests (standard Go): +go test ./... + +# Integration tests +cd tests && ./run.sh +``` + +The build embeds version via `-ldflags -X github.com/foxcpp/maddy.Version=...`. A C compiler is needed for SQLite support (`mattn/go-sqlite3`). + +## Adding a New Module + +1. Create a package under the appropriate `internal/` subdirectory (e.g. `internal/check/mycheck/`). +2. Implement `module.Module` plus the relevant role interface (`module.Check`, `module.DeliveryTarget`, `module.PlainAuth`, `module.Table`, etc.) from `framework/module/`. +3. Register in `init()`: `module.Register("check.mycheck", NewMyCheck)`. Use naming convention: `check.`, `target.`, `auth.`, `table.`, `modify.` prefixes. +4. Add a blank import `_ "github.com/foxcpp/maddy/internal/check/mycheck"` in `maddy.go`. +5. For checks: use the skeleton at `internal/check/skeleton.go` or `check.RegisterStatelessCheck` (see `internal/check/dns/` for a stateless example). + +## Error Handling + +Use `framework/exterrors` — not bare `fmt.Errorf`. Errors crossing module boundaries must carry: +- SMTP status info via `exterrors.SMTPError{Code, EnhancedCode, Message, CheckName/TargetName}` +- Temporary flag via `exterrors.WithTemporary` +- Module name field + +Keep SMTP error messages generic (no server config details). Use `exterrors.WithFields` for unexpected errors. See `HACKING.md` for full guidelines. + +## Key Conventions + +- **No shared state between messages** — check/modifier code runs in parallel across messages. +- **Panic recovery** — any goroutine you spawn must recover panics to avoid crashing the server. +- **Address normalization** — domain parts must be U-labels with NFC normalization and case-folding. Use `framework/address.CleanDomain`. +- **Configuration parsing** — modules receive config via `config.Map` in their `Configure` method. See `framework/config/` and existing modules for the pattern. +- **Logging** — use `framework/log.Logger`, not `log` stdlib. Per-delivery loggers via `target.DeliveryLogger(...)`. + From a90af91150296e9d29c991a2bbb1de0b6e6e51b5 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年3月25日 15:06:23 +0300 Subject: [PATCH 084/111] auth/ldap, check/rspamd: Fix tls_client directive definition See #824. --- internal/auth/ldap/ldap.go | 2 +- internal/check/rspamd/rspamd.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/auth/ldap/ldap.go b/internal/auth/ldap/ldap.go index 88cc0b4d..d1dac8b2 100644 --- a/internal/auth/ldap/ldap.go +++ b/internal/auth/ldap/ldap.go @@ -25,7 +25,7 @@ type Auth struct { urls []string readBind func(*ldap.Conn) error startls bool - tlsCfg tls.Config + tlsCfg *tls.Config dialer *net.Dialer requestTimeout time.Duration diff --git a/internal/check/rspamd/rspamd.go b/internal/check/rspamd/rspamd.go index 0a737b69..f487a103 100644 --- a/internal/check/rspamd/rspamd.go +++ b/internal/check/rspamd/rspamd.go @@ -92,7 +92,7 @@ func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { } var ( - tlsConfig tls.Config + tlsConfig *tls.Config flags []string ) @@ -135,7 +135,7 @@ func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { c.client = &http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tlsConfig, + TLSClientConfig: tlsConfig, }, } c.flags = strings.Join(flags, ",") From 46ebd46cc8a658966742a5c9b0357e337372c8a0 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年3月25日 15:17:38 +0300 Subject: [PATCH 085/111] proxy_protocol: Fix compatibility with go-imap v1 See #783. --- internal/proxy_protocol/proxy_protocol.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/proxy_protocol/proxy_protocol.go b/internal/proxy_protocol/proxy_protocol.go index 1a3a7873..e48c2fa3 100644 --- a/internal/proxy_protocol/proxy_protocol.go +++ b/internal/proxy_protocol/proxy_protocol.go @@ -72,11 +72,12 @@ func NewListener(inner net.Listener, p *ProxyProtocol, logger log.Logger) net.Li return false, nil } - listener = proxyprotocol.NewDefaultListener(inner). + proxyListener := proxyprotocol.NewDefaultListener(inner). WithLogger(proxyprotocol.LoggerFunc(func(format string, v ...interface{}) { logger.Debugf("proxy_protocol: "+format, v...) })). WithSourceChecker(sourceChecker) + listener = &proxyListener if p.tlsConfig != nil { listener = tls.NewListener(listener, p.tlsConfig) From 1712a7abb72688ffcf67f8256d7017784cbf1da1 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年3月25日 21:24:59 +0300 Subject: [PATCH 086/111] libdns: Deprecate libdns providers not updated for libdns 1.x --- internal/libdns/hetzner.go | 2 ++ internal/libdns/leaseweb.go | 2 ++ internal/libdns/namedotcom.go | 2 ++ internal/libdns/vultr.go | 2 ++ 4 files changed, 8 insertions(+) diff --git a/internal/libdns/hetzner.go b/internal/libdns/hetzner.go index 06620c46..d9a39def 100644 --- a/internal/libdns/hetzner.go +++ b/internal/libdns/hetzner.go @@ -5,6 +5,7 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" "github.com/libdns/hetzner" ) @@ -16,6 +17,7 @@ func init() { RecordDeleter: &p, RecordAppender: &p, setConfig: func(c *config.Map) { + log.DefaultLogger.Println("WARNING: maddy 0.10.0 will require new DNS API, see https://github.com/foxcpp/maddy/issues/807 for details") c.String("api_token", false, false, "", &p.AuthAPIToken) }, instName: instName, diff --git a/internal/libdns/leaseweb.go b/internal/libdns/leaseweb.go index 888d0870..013e8b6e 100644 --- a/internal/libdns/leaseweb.go +++ b/internal/libdns/leaseweb.go @@ -5,6 +5,7 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" "github.com/libdns/leaseweb" ) @@ -16,6 +17,7 @@ func init() { RecordDeleter: &p, RecordAppender: &p, setConfig: func(c *config.Map) { + log.DefaultLogger.Println("WARNING: maddy 0.10.0 will drop libdns.leaseweb, see https://github.com/foxcpp/maddy/issues/807 for details") c.String("api_key", false, false, "", &p.APIKey) }, instName: instName, diff --git a/internal/libdns/namedotcom.go b/internal/libdns/namedotcom.go index 0a5c9934..0b4ab608 100644 --- a/internal/libdns/namedotcom.go +++ b/internal/libdns/namedotcom.go @@ -5,6 +5,7 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" "github.com/libdns/namedotcom" ) @@ -18,6 +19,7 @@ func init() { RecordDeleter: &p, RecordAppender: &p, setConfig: func(c *config.Map) { + log.DefaultLogger.Println("WARNING: maddy 0.10.0 will drop libdns.namedotcom, see https://github.com/foxcpp/maddy/issues/807 for details") c.String("user", false, false, "", &p.User) c.String("token", false, false, "", &p.Token) }, diff --git a/internal/libdns/vultr.go b/internal/libdns/vultr.go index e94a2869..35ba38c2 100644 --- a/internal/libdns/vultr.go +++ b/internal/libdns/vultr.go @@ -5,6 +5,7 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" "github.com/libdns/vultr" ) @@ -16,6 +17,7 @@ func init() { RecordDeleter: &p, RecordAppender: &p, setConfig: func(c *config.Map) { + log.DefaultLogger.Println("WARNING: maddy 0.10.0 will drop libdns.vultr, see https://github.com/foxcpp/maddy/issues/807 for details") c.String("api_token", false, false, "", &p.APIToken) }, instName: instName, From 9c5e85dd66bed55e737662c8360f79789dd882b0 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年3月26日 22:14:38 +0300 Subject: [PATCH 087/111] maddy 0.9.1 --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index ac39a106..f374f666 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -0.9.0 +0.9.1 From 2ab067c8b2b42bd2d07f7c56d9c2a430ea30df7c Mon Sep 17 00:00:00 2001 From: oidq Date: 2026年3月26日 20:56:40 +0100 Subject: [PATCH 088/111] rspamd: fix panic on unspecified tls_client --- internal/check/rspamd/rspamd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/check/rspamd/rspamd.go b/internal/check/rspamd/rspamd.go index f487a103..4f32edf7 100644 --- a/internal/check/rspamd/rspamd.go +++ b/internal/check/rspamd/rspamd.go @@ -97,7 +97,7 @@ func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { ) cfg.Custom("tls_client", true, false, func() (interface{}, error) { - return tls.Config{}, nil + return &tls.Config{}, nil }, tls2.TLSClientBlock, &tlsConfig) cfg.String("api_path", false, false, c.apiPath, &c.apiPath) cfg.String("settings_id", false, false, "", &c.settingsID) From 979ab37ad6eab91a29909a91c6aaef666ca1acfb Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年3月27日 02:06:54 +0300 Subject: [PATCH 089/111] maddy 0.9.2 --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index f374f666..2003b639 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -0.9.1 +0.9.2 From da6bcb1751c5ab3f07bf9468633107da2879a3c7 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年3月27日 02:14:24 +0300 Subject: [PATCH 090/111] auth/ldap: Also fix-up LDAP --- cmd/maddy/maddy.conf | 13 +++++++++++++ internal/auth/ldap/ldap.go | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 cmd/maddy/maddy.conf diff --git a/cmd/maddy/maddy.conf b/cmd/maddy/maddy.conf new file mode 100644 index 00000000..83f93f21 --- /dev/null +++ b/cmd/maddy/maddy.conf @@ -0,0 +1,13 @@ + +state_dir /tmp +runtime_dir /tmp + +smtp tcp://127.0.0.1:1234 { + tls off + auth ldap { + base_dn "1" + filter "2 " + } + hostname test + deliver_to dummy +} diff --git a/internal/auth/ldap/ldap.go b/internal/auth/ldap/ldap.go index d1dac8b2..1ac9e774 100644 --- a/internal/auth/ldap/ldap.go +++ b/internal/auth/ldap/ldap.go @@ -54,7 +54,7 @@ func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { cfg.Bool("debug", true, false, &a.log.Debug) cfg.Custom("tls_client", true, false, func() (interface{}, error) { - return tls.Config{}, nil + return &tls.Config{}, nil }, tls2.TLSClientBlock, &a.tlsCfg) cfg.Callback("urls", func(m *config.Map, node config.Node) error { a.urls = append(a.urls, node.Args...) From 66ec658d85e7e599fa857eddc18028378305c684 Mon Sep 17 00:00:00 2001 From: Karel Balej Date: 2026年3月30日 21:46:05 +0200 Subject: [PATCH 091/111] docs: mention rDNS in the setup tutorial --- docs/tutorials/setting-up.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/tutorials/setting-up.md b/docs/tutorials/setting-up.md index 240caa80..04de75fd 100644 --- a/docs/tutorials/setting-up.md +++ b/docs/tutorials/setting-up.md @@ -103,6 +103,11 @@ one as "primary". Add all other domains to the `local_domains` line: $(local_domains) = $(primary_domain) example.com other.example.com ``` +Do not forget to set a suitable rDNS (PTR) record for your server's IP address +to reduce the chances of outgoing mails getting marked as spam or being +downright rejected. Ideally, the PTR record should match whatever you specified +in `$(hostname)`. + ## TLS certificates One thing that can't be automatically configured is TLS certs. If you already From a52ebc8fa35356d58bd4847b41fb33ae8d512d4b Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Wed, 1 Apr 2026 02:21:44 +0300 Subject: [PATCH 092/111] module: Break dependency cycles when loading config correctly See #832 --- framework/module/registry.go | 2 +- tests/modules_test.go | 63 ++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tests/modules_test.go diff --git a/framework/module/registry.go b/framework/module/registry.go index ce133402..acb0b190 100644 --- a/framework/module/registry.go +++ b/framework/module/registry.go @@ -105,11 +105,11 @@ func (r *Registry) ensureInitialized(name string, entry *registryEntry) error { r.logger.DebugMsg("module configure", "mod_name", entry.Mod.Name(), "inst_name", entry.Mod.InstanceName()) + r.initialized[name] = struct{}{} err := entry.LazyInit() if err != nil { return err } - r.initialized[name] = struct{}{} return nil } diff --git a/tests/modules_test.go b/tests/modules_test.go new file mode 100644 index 00000000..39046d83 --- /dev/null +++ b/tests/modules_test.go @@ -0,0 +1,63 @@ +//go:build integration + +/* +Maddy Mail Server - Composable all-in-one email server. +Copyright © 2019-2020 Max Mazurov , Maddy Mail Server contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package tests_test + +import ( + "testing" + + "github.com/foxcpp/maddy/tests" +) + +func TestConfigCycle(tt *testing.T) { + tt.Parallel() + + t := tests.NewT(tt) + t.DNS(nil) + t.Config(` + hostname mx.maddy.test + + msgpipeline local_routing { + destination maddy.test { + deliver_to dummy + } + default_destination { + deliver_to &outbound_queue + } + } + + target.queue outbound_queue { + target dummy + autogenerated_msg_domain maddy.test + bounce { + deliver_to &local_routing + } + } + + smtp tcp://127.0.0.1:1443 { + tls off + + deliver_to &local_routing + } + `) + t.Run(1) + + t.Close() +} From b7316d9a588d763df58d0098635465dbb5d01511 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Wed, 1 Apr 2026 22:38:06 +0300 Subject: [PATCH 093/111] docs: Update wording for NetAuth configuration (auth_map/storage_map use) --- docs/reference/auth/netauth.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/reference/auth/netauth.md b/docs/reference/auth/netauth.md index 2664d41f..4c268108 100644 --- a/docs/reference/auth/netauth.md +++ b/docs/reference/auth/netauth.md @@ -7,10 +7,12 @@ maddy needs to know the Entity ID to use for authentication. It must match the string the user provides for the Local Atom part of their mail address. -Note that storage backends conventionally use email addresses. Since -NetAuth recommends *nix compatible usernames, you will need to map the -email identifiers to NetAuth Entity IDs using `auth_map` (see -documentation page for used storage backend). +Note that storage backends conventionally use email addresses. Since NetAuth +recommends *nix compatible usernames. You will need to either map email +identifiers specified by user to NetAuth Entity IDs using `auth_map` in +endpoint.smtp/imap configuration (recommended) or you would need to use +`storage_map` in storage backend configuration to map NetAuth Entity ID +specified by user back to appropriate storage backend account names. auth.netauth also can be used as a table module. This way you can check whether the account exists. From 6a06337eb41fa87a35697366bcb71c3c962c44ba Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年4月10日 02:36:39 +0300 Subject: [PATCH 094/111] auth/ldap: Fix GHSA-5835-4gvc-32pc Add proper escaping when building filter expression, add proper escaping when building DN from DN template. --- docs/reference/auth/netauth.md | 10 +- go.mod | 1 + go.sum | 2 + internal/auth/ldap/ldap.go | 6 +- tests/conn.go | 9 +- tests/ghsa_5835_4gvc_32pc_test.go | 178 ++++++++++++++++++++++++++++++ 6 files changed, 198 insertions(+), 8 deletions(-) create mode 100644 tests/ghsa_5835_4gvc_32pc_test.go diff --git a/docs/reference/auth/netauth.md b/docs/reference/auth/netauth.md index 2664d41f..4c268108 100644 --- a/docs/reference/auth/netauth.md +++ b/docs/reference/auth/netauth.md @@ -7,10 +7,12 @@ maddy needs to know the Entity ID to use for authentication. It must match the string the user provides for the Local Atom part of their mail address. -Note that storage backends conventionally use email addresses. Since -NetAuth recommends *nix compatible usernames, you will need to map the -email identifiers to NetAuth Entity IDs using `auth_map` (see -documentation page for used storage backend). +Note that storage backends conventionally use email addresses. Since NetAuth +recommends *nix compatible usernames. You will need to either map email +identifiers specified by user to NetAuth Entity IDs using `auth_map` in +endpoint.smtp/imap configuration (recommended) or you would need to use +`storage_map` in storage backend configuration to map NetAuth Entity ID +specified by user back to appropriate storage backend account names. auth.netauth also can be used as a table module. This way you can check whether the account exists. diff --git a/go.mod b/go.mod index 242d0e6d..37227d4d 100644 --- a/go.mod +++ b/go.mod @@ -107,6 +107,7 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jimlambrt/gldap v0.1.14 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.17.11 // indirect diff --git a/go.sum b/go.sum index 81c986e0..ea270b65 100644 --- a/go.sum +++ b/go.sum @@ -488,6 +488,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jimlambrt/gldap v0.1.14 h1:InG9kldhIu6OoQK0hvfkW1Lqpc5eLJhxiiDTNmRnrDM= +github.com/jimlambrt/gldap v0.1.14/go.mod h1:yobW9JIAmqe23dVNOaMWewPaff6jGaHgYjspPIIgYmg= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= diff --git a/internal/auth/ldap/ldap.go b/internal/auth/ldap/ldap.go index 1ac9e774..06890fd5 100644 --- a/internal/auth/ldap/ldap.go +++ b/internal/auth/ldap/ldap.go @@ -225,7 +225,7 @@ func (a *Auth) Lookup(_ context.Context, username string) (string, bool, error) req := ldap.NewSearchRequest( a.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 2, 0, false, - strings.ReplaceAll(a.filterTemplate, "{username}", username), + strings.ReplaceAll(a.filterTemplate, "{username}", ldap.EscapeFilter(username)), []string{"dn"}, nil) res, err := conn.Search(req) if err != nil { @@ -252,12 +252,12 @@ func (a *Auth) AuthPlain(username, password string) error { var userDN string if a.dnTemplate != "" { - userDN = strings.ReplaceAll(a.dnTemplate, "{username}", username) + userDN = strings.ReplaceAll(a.dnTemplate, "{username}", ldap.EscapeDN(username)) } else { req := ldap.NewSearchRequest( a.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 2, 0, false, - strings.ReplaceAll(a.filterTemplate, "{username}", username), + strings.ReplaceAll(a.filterTemplate, "{username}", ldap.EscapeFilter(username)), []string{"dn"}, nil) res, err := conn.Search(req) if err != nil { diff --git a/tests/conn.go b/tests/conn.go index 9ff86e8b..2d597e86 100644 --- a/tests/conn.go +++ b/tests/conn.go @@ -211,7 +211,7 @@ func (c *Conn) SMTPPlainAuth(username, password string, expectOk bool) { if expectOk { c.ExpectPattern("235 *") } else { - c.ExpectPattern("*") + c.ExpectPattern("5*") } } @@ -282,6 +282,13 @@ func (c *Conn) Close() error { return c.Conn.Close() } +func (c *Conn) MustClose() { + c.T.Helper() + if err := c.Close(); err != nil { + c.fatal("Close: %v", err) + } +} + func (c *Conn) Rebind(subtest *T) *Conn { cpy := *c cpy.T = subtest diff --git a/tests/ghsa_5835_4gvc_32pc_test.go b/tests/ghsa_5835_4gvc_32pc_test.go new file mode 100644 index 00000000..b7e0108f --- /dev/null +++ b/tests/ghsa_5835_4gvc_32pc_test.go @@ -0,0 +1,178 @@ +//go:build integration + +package tests_test + +import ( + "strconv" + "testing" + "time" + + "github.com/foxcpp/maddy/tests" + "github.com/jimlambrt/gldap" + "github.com/stretchr/testify/require" +) + +type searchEntry struct { + dn string + options []gldap.Option +} + +type MockLDAP struct { + T *testing.T + SearchEntries map[string][]searchEntry + AllowedBinds map[string]string +} + +func (ml *MockLDAP) HandleBind(w *gldap.ResponseWriter, r *gldap.Request) { + resp := r.NewBindResponse( + gldap.WithResponseCode(gldap.ResultInvalidCredentials), + ) + + m, err := r.GetSimpleBindMessage() + if err != nil { + require.NoError(ml.T, w.Write(resp)) + return + } + + pass, ok := ml.AllowedBinds[m.UserName] + if ok && pass == string(m.Password) { + resp.SetResultCode(gldap.ResultSuccess) + require.NoError(ml.T, w.Write(resp)) + } + + require.NoError(ml.T, w.Write(resp)) +} + +func (ml *MockLDAP) HandleSearch(w *gldap.ResponseWriter, r *gldap.Request) { + resp := r.NewSearchDoneResponse() + m, err := r.GetSearchMessage() + if err != nil { + ml.T.Logf("not a search message: %s", err) + require.NoError(ml.T, w.Write(resp)) + return + } + ml.T.Logf("search base dn: %s", m.BaseDN) + ml.T.Logf("search scope: %d", m.Scope) + ml.T.Logf("search filter: %s", m.Filter) + + entries := ml.SearchEntries[m.Filter] + for _, entry := range entries { + ldapEntry := r.NewSearchResponseEntry(entry.dn, entry.options...) + require.NoError(ml.T, w.Write(ldapEntry)) + } + + resp.SetResultCode(gldap.ResultSuccess) + require.NoError(ml.T, w.Write(resp)) +} + +func (ml *MockLDAP) Run(address string) { + s, err := gldap.NewServer() + if err != nil { + ml.T.Fatalf("unable to create server: %s", err.Error()) + } + + // create a router and add a bind handler + r, err := gldap.NewMux() + if err != nil { + ml.T.Fatalf("unable to create router: %s", err.Error()) + } + require.NoError(ml.T, r.Bind(ml.HandleBind)) + require.NoError(ml.T, r.Search(ml.HandleSearch)) + require.NoError(ml.T, s.Router(r)) + go func() { + require.NoError(ml.T, s.Run(address)) + }() + ml.T.Cleanup(func() { + require.NoError(ml.T, s.Stop()) + }) + + for !s.Ready() { + ml.T.Log("Waiting for server to start") + time.Sleep(100 * time.Millisecond) + } +} + +func TestLDAPInjectionFilter(tt *testing.T) { + tt.Parallel() + t := tests.NewT(tt) + + ldapPort := t.Port("ldap") + + ldapSrv := &MockLDAP{ + T: tt, + AllowedBinds: map[string]string{ + "DC=com,CN=bob": "bob_pass", + "DC=com,CN=alice": "alice_pass", + }, + SearchEntries: map[string][]searchEntry{ + "(&(objectClass=inetOrgPerson)(uid=alice))": { + { + dn: "DC=com,CN=alice", + options: []gldap.Option{ + gldap.WithAttributes(map[string][]string{ + "objectClass": {"inetOrgPerson"}, + "uid": {"alice"}, + "description": {"prefix_test"}, + }), + }, + }, + }, + "(&(objectClass=inetOrgPerson)(uid=bob))": { + { + dn: "DC=com,CN=bob", + options: []gldap.Option{ + gldap.WithAttributes(map[string][]string{ + "objectClass": {"inetOrgPerson"}, + "uid": {"bob"}, + "description": {"prefix_test"}, + }), + }, + }, + }, + "(&(objectClass=inetOrgPerson)(uid=bob)(description=prefix*))": { + { + dn: "DC=com,CN=bob", + options: []gldap.Option{ + gldap.WithAttributes(map[string][]string{ + "objectClass": {"inetOrgPerson"}, + "uid": {"bob"}, + "description": {"prefix_test"}, + }), + }, + }, + }, + }, + } + ldapSrv.Run(":" + strconv.Itoa(int(ldapPort))) + + t.Port("smtp") + t.DNS(nil) + t.Config(` + hostname mx.maddy.test + tls off + + auth.ldap ldap_auth { + urls ldap://127.0.0.1:{env:TEST_PORT_ldap} + bind plain "DC=com,CN=bob" "bob_pass" + base_dn "DC=com" + filter "(&(objectClass=inetOrgPerson)(uid={username}))" + } + + submission tcp://0.0.0.0:{env:TEST_PORT_smtp} { + auth &ldap_auth + deliver_to dummy + } + `) + t.Run(1) + defer t.Close() + + smtpConn := t.Conn("smtp") + defer smtpConn.MustClose() + smtpConn.SMTPNegotation("clieht.maddy.test", nil, nil) + smtpConn.SMTPPlainAuth("alice", "alice_pass", true) + + smtpConn2 := t.Conn("smtp") + defer smtpConn2.MustClose() + smtpConn2.SMTPNegotation("clieht.maddy.test", nil, nil) + smtpConn2.SMTPPlainAuth("bob)(description=prefix*", "bob_pass", false) +} From ec821b7d1197cc437255c36a159ead1b51099876 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年4月12日 14:25:04 +0300 Subject: [PATCH 095/111] maddy 0.9.3 --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index 2003b639..965065db 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -0.9.2 +0.9.3 From 2f6a4cf83a38b58b50cf0d2a59e7b63bd63ade86 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年4月12日 16:33:25 +0300 Subject: [PATCH 096/111] storage/imapsql: Upgrade go-imap-sql to fix SQLITE_BUSY issues Might fix #786 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 37227d4d..128bb20b 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/foxcpp/go-imap-i18nlevel v0.0.0-20200208001533-d6ec88553005 github.com/foxcpp/go-imap-mess v0.0.0-20230108134257-b7ec3a649613 github.com/foxcpp/go-imap-namespace v0.0.0-20200802091432-08496dd8e0ed - github.com/foxcpp/go-imap-sql v0.5.1-0.20250124140007-8da5567429d5 + github.com/foxcpp/go-imap-sql v0.5.1-0.20260412133145-20097edd35ec github.com/foxcpp/go-mockdns v1.1.0 github.com/foxcpp/go-mtasts v0.0.0-20240130093538-1438da2e5932 github.com/go-ldap/ldap/v3 v3.4.10 diff --git a/go.sum b/go.sum index ea270b65..b843d206 100644 --- a/go.sum +++ b/go.sum @@ -320,6 +320,8 @@ github.com/foxcpp/go-imap-namespace v0.0.0-20200802091432-08496dd8e0ed h1:1Jo7ge github.com/foxcpp/go-imap-namespace v0.0.0-20200802091432-08496dd8e0ed/go.mod h1:Shows1vmkBWO40ChOClaUe6DUnZrsP1UPAuoWzIUdgQ= github.com/foxcpp/go-imap-sql v0.5.1-0.20250124140007-8da5567429d5 h1:jMxhw9qmwqg70qfMDWq0ImRHAduQjkTZOC9vBs5t2ug= github.com/foxcpp/go-imap-sql v0.5.1-0.20250124140007-8da5567429d5/go.mod h1:LMlfyNkVs7v2zE6OVeGe9qWPmKFdXDmLNddPLodPVIw= +github.com/foxcpp/go-imap-sql v0.5.1-0.20260412133145-20097edd35ec h1:Jm71K60qrrnyISeLXMYKzSZe0RVco+aO/RJugJvafIM= +github.com/foxcpp/go-imap-sql v0.5.1-0.20260412133145-20097edd35ec/go.mod h1:LMlfyNkVs7v2zE6OVeGe9qWPmKFdXDmLNddPLodPVIw= github.com/foxcpp/go-mockdns v0.0.0-20191216195825-5eabd8dbfe1f/go.mod h1:tPg4cp4nseejPd+UKxtCVQ2hUxNTZ7qQZJa7CLriIeo= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= From 9db7e6150a39b1cdb2fb16b81fdfd0171b4544c5 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年4月12日 22:09:19 +0300 Subject: [PATCH 097/111] storage/imapsql: Fix handling of serialization errors --- internal/storage/imapsql/delivery.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/internal/storage/imapsql/delivery.go b/internal/storage/imapsql/delivery.go index a9ce32e8..55dc36b3 100644 --- a/internal/storage/imapsql/delivery.go +++ b/internal/storage/imapsql/delivery.go @@ -20,6 +20,7 @@ package imapsql import ( "context" + "errors" "runtime/trace" "github.com/emersion/go-imap" @@ -78,10 +79,11 @@ func (d *delivery) AddRcpt(ctx context.Context, rcptTo string, _ smtp.RcptOption userHeader.Add("Delivered-To", accountName) if err := d.d.AddRcpt(accountName, userHeader); err != nil { - if err == imapsql.ErrUserDoesntExists || err == backend.ErrNoSuchMailbox { + if errors.Is(err, imapsql.ErrUserDoesntExists) || errors.Is(err, backend.ErrNoSuchMailbox) { return userDoesNotExist(err) } - if _, ok := err.(imapsql.SerializationError); ok { + var serializationError imapsql.SerializationError + if errors.As(err, &serializationError) { return &exterrors.SMTPError{ Code: 453, EnhancedCode: exterrors.EnhancedCode{4, 3, 2}, @@ -115,11 +117,12 @@ func (d *delivery) Body(ctx context.Context, header textproto.Header, body buffe if d.msgMeta.Quarantine { if err := d.d.SpecialMailbox(imap.JunkAttr, d.store.junkMbox); err != nil { - if _, ok := err.(imapsql.SerializationError); ok { + var serializationError imapsql.SerializationError + if errors.As(err, &serializationError) { return &exterrors.SMTPError{ Code: 453, EnhancedCode: exterrors.EnhancedCode{4, 3, 2}, - Message: "Storage access serialiation problem, try again later", + Message: "Internal server error, try again later", TargetName: "imapsql", Err: err, } @@ -131,11 +134,12 @@ func (d *delivery) Body(ctx context.Context, header textproto.Header, body buffe header = header.Copy() header.Add("Return-Path", "<"+target.sanitizeforheader(d.mailfrom)+">") err := d.d.BodyParsed(header, body.Len(), body) - if _, ok := err.(imapsql.SerializationError); ok { + var serializationError imapsql.SerializationError + if errors.As(err, &serializationError) { return &exterrors.SMTPError{ Code: 453, EnhancedCode: exterrors.EnhancedCode{4, 3, 2}, - Message: "Storage access serialiation problem, try again later", + Message: "Internal server error, try again later", TargetName: "imapsql", Err: err, } From d74f5119a2a1a46fd4ed70ca66112c7848c0a393 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年4月12日 22:10:02 +0300 Subject: [PATCH 098/111] storage/imapsql: Upgrade go-imap-sql to disable Repeatable Read by default --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 128bb20b..d3d6e997 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/foxcpp/go-imap-i18nlevel v0.0.0-20200208001533-d6ec88553005 github.com/foxcpp/go-imap-mess v0.0.0-20230108134257-b7ec3a649613 github.com/foxcpp/go-imap-namespace v0.0.0-20200802091432-08496dd8e0ed - github.com/foxcpp/go-imap-sql v0.5.1-0.20260412133145-20097edd35ec + github.com/foxcpp/go-imap-sql v0.5.1-0.20260412184517-b5e85e90f14d github.com/foxcpp/go-mockdns v1.1.0 github.com/foxcpp/go-mtasts v0.0.0-20240130093538-1438da2e5932 github.com/go-ldap/ldap/v3 v3.4.10 diff --git a/go.sum b/go.sum index b843d206..e5bf2edb 100644 --- a/go.sum +++ b/go.sum @@ -322,6 +322,8 @@ github.com/foxcpp/go-imap-sql v0.5.1-0.20250124140007-8da5567429d5 h1:jMxhw9qmwq github.com/foxcpp/go-imap-sql v0.5.1-0.20250124140007-8da5567429d5/go.mod h1:LMlfyNkVs7v2zE6OVeGe9qWPmKFdXDmLNddPLodPVIw= github.com/foxcpp/go-imap-sql v0.5.1-0.20260412133145-20097edd35ec h1:Jm71K60qrrnyISeLXMYKzSZe0RVco+aO/RJugJvafIM= github.com/foxcpp/go-imap-sql v0.5.1-0.20260412133145-20097edd35ec/go.mod h1:LMlfyNkVs7v2zE6OVeGe9qWPmKFdXDmLNddPLodPVIw= +github.com/foxcpp/go-imap-sql v0.5.1-0.20260412184517-b5e85e90f14d h1:oiq5MLSSqd3sl4VNHKTlrwszWTHIx8+x8y/olInMJRo= +github.com/foxcpp/go-imap-sql v0.5.1-0.20260412184517-b5e85e90f14d/go.mod h1:LMlfyNkVs7v2zE6OVeGe9qWPmKFdXDmLNddPLodPVIw= github.com/foxcpp/go-mockdns v0.0.0-20191216195825-5eabd8dbfe1f/go.mod h1:tPg4cp4nseejPd+UKxtCVQ2hUxNTZ7qQZJa7CLriIeo= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= From fb2eb3771c2494a4eb50b263658063410828d8fd Mon Sep 17 00:00:00 2001 From: Sean van Osnabrugge <18647660+osnabrugge@users.noreply.github.com> Date: 2026年4月16日 18:24:10 -0400 Subject: [PATCH 099/111] smtp: add LOGIN SASL auth directive --- internal/target/smtp/sasl.go | 10 ++++++++-- internal/target/smtp/sasl_test.go | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/internal/target/smtp/sasl.go b/internal/target/smtp/sasl.go index 75f5d4e5..96861721 100644 --- a/internal/target/smtp/sasl.go +++ b/internal/target/smtp/sasl.go @@ -57,12 +57,18 @@ func saslAuthDirective(_ *config.Map, node config.Node) (interface{}, error) { } return sasl.NewPlainClient("", msgMeta.Conn.AuthUser, msgMeta.Conn.AuthPassword), nil }, nil - case "plain": + case "plain", "login": if len(node.Args) != 3 { return nil, config.NodeErr(node, "two additional arguments are required (username, password)") } return func(*module.MsgMetadata) (sasl.Client, error) { - return sasl.NewPlainClient("", node.Args[1], node.Args[2]), nil + if node.Args[0] == "plain" { + return sasl.NewPlainClient("", node.Args[1], node.Args[2]), nil + } + if node.Args[0] == "login" { + return sasl.NewLoginClient(node.Args[1], node.Args[2]), nil + } + return nil, config.NodeErr(node, "unknown authentication mechanism: %s", node.Args[0]) }, nil case "external": if len(node.Args)> 1 { diff --git a/internal/target/smtp/sasl_test.go b/internal/target/smtp/sasl_test.go index a1054845..c28ec7e4 100644 --- a/internal/target/smtp/sasl_test.go +++ b/internal/target/smtp/sasl_test.go @@ -101,6 +101,23 @@ func TestSASL_Plain_AuthFail(t *testing.T) { } } +func TestSASL_Login_Directive(t *testing.T) { + factory := testSaslFactory(t, "login", "test", "testpass") + client, err := factory(nil) + if err != nil { + t.Fatal(err) + } + + mech, _, err := client.Start() + if err != nil { + t.Fatal(err) + } + + if mech != "LOGIN" { + t.Fatalf("expected LOGIN mechanism, got %q", mech) + } +} + func TestSASL_Forward(t *testing.T) { be, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort) defer func() { From b418bd94bd7fd9007a910f94a32608824627ed54 Mon Sep 17 00:00:00 2001 From: Denis Girko Date: 2026年4月23日 20:27:20 +0300 Subject: [PATCH 100/111] Fixed limiters group configuration Fixed typo in limiters group configuration --- internal/limits/limits.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/limits/limits.go b/internal/limits/limits.go index 7c1a39b0..34161f7a 100644 --- a/internal/limits/limits.go +++ b/internal/limits/limits.go @@ -118,8 +118,8 @@ func (g *Group) Configure(inlineArgs []string, cfg *config.Map) error { } if len(destL) != 0 { g.dest = limiters.NewBucketSet(func() limiters.L { - l := make([]limiters.L, 0, len(sourceL)) - for _, ctor := range sourceL { + l := make([]limiters.L, 0, len(destL)) + for _, ctor := range destL { l = append(l, ctor()) } return &limiters.MultiLimit{Wrapped: l} From 5841e95b47a7d7c607974d66b9016e9861f8965b Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Fri, 1 May 2026 00:41:46 +0300 Subject: [PATCH 101/111] log: Refactor to define proper loggers tree *container.C is also now passed to module constructor allowing access to container logger for sublogger initialization. This in turn required some package movement to break import cycles. --- cmd/maddy/maddy.conf | 13 ---- config.go | 6 +- framework/config/module/modconfig.go | 15 +++-- framework/container/container.go | 14 ++-- framework/{module => container}/lifetime.go | 7 +- framework/{module => container}/registry.go | 13 ++-- framework/log/log.go | 40 ++++++++---- framework/module/module.go | 22 ------- framework/module/{ => modules}/dummy.go | 12 ++-- framework/module/{ => modules}/modules.go | 34 +++++++++- internal/auth/dovecot_sasl/dovecot_sasl.go | 10 +-- internal/auth/external/externalauth.go | 16 +++-- internal/auth/ldap/ldap.go | 12 ++-- internal/auth/netauth/netauth.go | 15 +++-- internal/auth/pam/module.go | 10 +-- internal/auth/pass_table/table.go | 6 +- internal/auth/pass_table/table_test.go | 3 +- .../auth/plain_separate/plain_separate.go | 12 ++-- internal/auth/sasl.go | 2 +- internal/auth/shadow/module.go | 14 ++-- .../authorize_sender/authorize_sender.go | 11 ++-- internal/check/command/command.go | 15 +++-- internal/check/dkim/dkim.go | 12 ++-- internal/check/dkim/dkim_test.go | 3 +- internal/check/dnsbl/dnsbl.go | 12 ++-- internal/check/milter/milter.go | 16 +++-- internal/check/rspamd/rspamd.go | 16 +++-- internal/check/skeleton.go | 3 +- internal/check/spf/spf.go | 12 ++-- internal/check/stateless_check.go | 10 +-- internal/cli/ctl/moduleinit.go | 17 +++-- .../endpoint/dovecot_sasld/dovecot_sasl.go | 16 +++-- internal/endpoint/imap/imap.go | 49 +++++++------- internal/endpoint/openmetrics/om.go | 11 ++-- internal/endpoint/smtp/session.go | 12 ++-- internal/endpoint/smtp/smtp.go | 45 +++++++------ internal/endpoint/smtp/smtp_test.go | 10 +-- internal/endpoint/smtp/submission_test.go | 3 +- internal/imap_filter/command/command.go | 14 ++-- internal/imap_filter/group.go | 10 +-- internal/libdns/acmedns.go | 4 +- internal/libdns/alidns.go | 4 +- internal/libdns/cloudflare.go | 4 +- internal/libdns/digitalocean.go | 4 +- internal/libdns/gandi.go | 4 +- internal/libdns/gcore.go | 4 +- internal/libdns/googleclouddns.go | 4 +- internal/libdns/hetzner.go | 4 +- internal/libdns/leaseweb.go | 4 +- internal/libdns/metaname.go | 4 +- internal/libdns/namecheap.go | 4 +- internal/libdns/namedotcom.go | 4 +- internal/libdns/rfc2136.go | 4 +- internal/libdns/route53.go | 4 +- internal/libdns/vultr.go | 4 +- internal/limits/limits.go | 6 +- internal/modify/dkim/dkim.go | 12 ++-- internal/modify/dkim/dkim_test.go | 3 +- internal/modify/group.go | 4 +- internal/modify/replace_addr.go | 8 ++- internal/modify/replace_addr_test.go | 3 +- internal/msgpipeline/check_group.go | 4 +- internal/msgpipeline/check_runner.go | 4 +- internal/msgpipeline/module.go | 10 +-- internal/msgpipeline/msgpipeline.go | 4 +- internal/proxy_protocol/proxy_protocol.go | 8 +-- internal/smtpconn/smtpconn.go | 2 +- internal/storage/blob/fs/fs.go | 6 +- internal/storage/blob/s3/s3.go | 10 +-- internal/storage/blob/test_blob.go | 2 +- internal/storage/imapsql/delivery.go | 2 +- internal/storage/imapsql/imapsql.go | 56 ++++++++-------- internal/table/chain.go | 6 +- internal/table/email_localpart.go | 8 ++- internal/table/email_with_domain.go | 10 +-- internal/table/file.go | 10 +-- internal/table/file_test.go | 7 +- internal/table/identity.go | 6 +- internal/table/regexp.go | 6 +- internal/table/sql_query.go | 6 +- internal/table/sql_query_test.go | 3 +- internal/table/sql_table.go | 6 +- internal/table/static.go | 6 +- internal/target/delivery.go | 3 +- internal/target/queue/queue.go | 62 +++++++++--------- internal/target/queue/queue_test.go | 7 +- internal/target/remote/connect.go | 22 +++---- internal/target/remote/dane_delivery_test.go | 2 +- internal/target/remote/mxauth_test.go | 4 +- internal/target/remote/policy_group.go | 4 +- internal/target/remote/remote.go | 26 ++++---- internal/target/remote/remote_test.go | 7 +- internal/target/remote/security.go | 36 ++++++----- internal/target/skeleton.go | 3 +- internal/target/smtp/smtp_downstream.go | 16 +++-- internal/target/smtp/smtputf8_test.go | 3 +- internal/testutils/check.go | 4 +- internal/testutils/logger.go | 14 ++-- internal/testutils/modifier.go | 4 +- internal/tls/acme/acme.go | 10 +-- internal/tls/file.go | 10 +-- internal/tls/self_signed.go | 6 +- internal/updatepipe/pubsub/pq.go | 4 +- internal/updatepipe/pubsub_pipe.go | 2 +- internal/updatepipe/unix_pipe.go | 2 +- maddy.go | 64 +++++++++++++------ 106 files changed, 679 insertions(+), 487 deletions(-) delete mode 100644 cmd/maddy/maddy.conf rename framework/{module => container}/lifetime.go (97%) rename framework/{module => container}/registry.go (90%) rename framework/module/{ => modules}/dummy.go (86%) rename framework/module/{ => modules}/modules.go (69%) diff --git a/cmd/maddy/maddy.conf b/cmd/maddy/maddy.conf deleted file mode 100644 index 83f93f21..00000000 --- a/cmd/maddy/maddy.conf +++ /dev/null @@ -1,13 +0,0 @@ - -state_dir /tmp -runtime_dir /tmp - -smtp tcp://127.0.0.1:1234 { - tls off - auth ldap { - base_dn "1" - filter "2 " - } - hostname test - deliver_to dummy -} diff --git a/config.go b/config.go index 92053f94..8b5044f2 100644 --- a/config.go +++ b/config.go @@ -71,7 +71,7 @@ func LogOutputOption(args []string) (log.Output, error) { } return log.NopOutput{}, nil default: - // Log file paths are converted to absolute to make sure + // log file paths are converted to absolute to make sure // we will be able to recreate them in right location // after changing working directory to the state dir. absPath, err := filepath.Abs(arg) @@ -98,7 +98,7 @@ func LogOutputOption(args []string) (log.Output, error) { } func defaultLogOutput() (interface{}, error) { - return log.DefaultLogger.Out, nil + return nil, nil } func reinitLogging() { @@ -115,7 +115,7 @@ func reinitLogging() { } if err := out.Close(); err != nil { - log.Println("Can't close logger:", err) + log.Println("Can't close old logger:", err) } log.DefaultLogger.Out = newOut diff --git a/framework/config/module/modconfig.go b/framework/config/module/modconfig.go index 443bfebd..2b4eab6c 100644 --- a/framework/config/module/modconfig.go +++ b/framework/config/module/modconfig.go @@ -36,23 +36,24 @@ import ( "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) // createInlineModule is a helper function for config matchers that can create inline modules. -func createInlineModule(preferredNamespace, modName string) (module.Module, error) { - var newMod module.FuncNewModule +func createInlineModule(c *container.C, preferredNamespace, modName string) (module.Module, error) { + var newMod modules.FuncNewModule originalModName := modName // First try to extend the name with preferred namespace unless the name // already contains it. if !strings.Contains(modName, ".") && preferredNamespace != "" { modName = preferredNamespace + "." + modName - newMod = module.Get(modName) + newMod = modules.Get(modName) } // Then try global namespace for compatibility and complex modules. if newMod == nil { - newMod = module.Get(originalModName) + newMod = modules.Get(originalModName) } // Bail if both failed. @@ -60,7 +61,7 @@ func createInlineModule(preferredNamespace, modName string) (module.Module, erro return nil, fmt.Errorf("unknown module: %s (namespace: %s)", originalModName, preferredNamespace) } - return newMod(modName, "") + return newMod(c, modName, "") } // configureInlineModule constructs "faked" config tree and passes it to module @@ -73,7 +74,7 @@ func configureInlineModule(modObj module.Module, args []string, globals map[stri return err } - if li, ok := modObj.(module.LifetimeModule); ok { + if li, ok := modObj.(container.LifetimeModule); ok { container.Global.Lifetime.Add(li) } @@ -115,7 +116,7 @@ func ModuleFromNode(preferredNamespace string, args []string, inlineCfg config.N log.Debugf("%s:%d: reference %s", inlineCfg.File, inlineCfg.Line, args[0]) } else { log.Debugf("%s:%d: new module %s %v", inlineCfg.File, inlineCfg.Line, args[0], args[1:]) - modObj, err = createInlineModule(preferredNamespace, args[0]) + modObj, err = createInlineModule(container.Global, preferredNamespace, args[0]) } if err != nil { return err diff --git a/framework/container/container.go b/framework/container/container.go index dc1e0124..25690522 100644 --- a/framework/container/container.go +++ b/framework/container/container.go @@ -20,7 +20,6 @@ package container import ( "github.com/foxcpp/maddy/framework/log" - "github.com/foxcpp/maddy/framework/module" ) type GlobalConfig struct { @@ -53,16 +52,17 @@ type GlobalConfig struct { type C struct { Config GlobalConfig - DefaultLogger log.Logger - Modules *module.Registry - Lifetime *module.LifetimeTracker + DefaultLogger *log.Logger + Modules *Registry + Lifetime *LifetimeTracker } func New() *C { + rootLog := log.DefaultLogger.Sublogger("") return &C{ - DefaultLogger: log.DefaultLogger, - Modules: module.NewRegistry(log.DefaultLogger.Sublogger("registry")), - Lifetime: module.NewLifetime(log.DefaultLogger.Sublogger("lifetime")), + DefaultLogger: rootLog, + Modules: NewRegistry(rootLog.Sublogger("registry")), + Lifetime: NewLifetime(rootLog.Sublogger("lifetime")), } } diff --git a/framework/module/lifetime.go b/framework/container/lifetime.go similarity index 97% rename from framework/module/lifetime.go rename to framework/container/lifetime.go index 2ccb08d4..993f6d94 100644 --- a/framework/module/lifetime.go +++ b/framework/container/lifetime.go @@ -16,24 +16,25 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package module +package container import ( "fmt" "github.com/foxcpp/maddy/framework/log" + "github.com/foxcpp/maddy/framework/module" ) // LifetimeModule is a stateful module that needs to have post-configuration // startup and graceful shutdown functionality. type LifetimeModule interface { - Module + module.Module Start() error Stop() error } type ReloadModule interface { - Module + module.Module Reload() error } diff --git a/framework/module/registry.go b/framework/container/registry.go similarity index 90% rename from framework/module/registry.go rename to framework/container/registry.go index acb0b190..5cdf9f00 100644 --- a/framework/module/registry.go +++ b/framework/container/registry.go @@ -16,12 +16,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package module +package container import ( "errors" "github.com/foxcpp/maddy/framework/log" + "github.com/foxcpp/maddy/framework/module" ) var ( @@ -30,7 +31,7 @@ var ( ) type registryEntry struct { - Mod Module + Mod module.Module LazyInit func() error } @@ -56,7 +57,7 @@ func NewRegistry(log *log.Logger) *Registry { // // lazyInit function will be called on first request to get the module from // registry. -func (r *Registry) Register(mod Module, lazyInit func() error) error { +func (r *Registry) Register(mod module.Module, lazyInit func() error) error { instName := mod.InstanceName() if instName == "" { panic("module with empty instance name cannot be added to the registry") @@ -114,7 +115,7 @@ func (r *Registry) ensureInitialized(name string, entry *registryEntry) error { return nil } -func (r *Registry) Get(name string) (Module, error) { +func (r *Registry) Get(name string) (module.Module, error) { if name == "" { panic("cannot get module with empty name") } @@ -135,8 +136,8 @@ func (r *Registry) Get(name string) (Module, error) { return mod.Mod, nil } -func (r *Registry) NotInitialized() []Module { - notinit := make([]Module, 0, len(r.instances)-len(r.initialized)) +func (r *Registry) NotInitialized() []module.Module { + notinit := make([]module.Module, 0, len(r.instances)-len(r.initialized)) for name, mod := range r.instances { if _, ok := r.initialized[name]; ok { continue diff --git a/framework/log/log.go b/framework/log/log.go index 366cf156..3ad291a6 100644 --- a/framework/log/log.go +++ b/framework/log/log.go @@ -59,10 +59,7 @@ func (l *Logger) Zap() *zap.Logger { } func (l *Logger) IsDebug() bool { - if l.Parent == nil { - return l.Debug - } - return l.Debug || l.Parent.IsDebug() + return l.Debug || (l.Parent != nil && l.Parent.IsDebug()) } func (l *Logger) Debugf(format string, val ...interface{}) { @@ -217,19 +214,30 @@ func (l *Logger) DebugWriter() io.Writer { return l2 } +func (l *Logger) output() Output { + if l.Out != nil { + return l.Out + } + if l.Parent != nil { + return l.Parent.output() + } + + if DefaultLogger.Out == nil { + panic("DefaultLogger.Out is not set") + } + if l.Parent == nil && l != &DefaultLogger { + DefaultLogger.Out.Write(time.Now(), true, "logger "+l.Name+" has no parent, this is a bug") + } + return DefaultLogger.Out +} + func (l *Logger) log(debug bool, s string) { if l.Name != "" { s = l.Name + ": " + s } - if l.Out != nil { - l.Out.Write(time.Now(), debug, s) - return - } - if DefaultLogger.Out != nil { - DefaultLogger.Out.Write(time.Now(), debug, s) - return - } + out := l.output() + out.Write(time.Now(), debug, s) // Logging is disabled - do nothing. } @@ -240,9 +248,7 @@ func (l *Logger) Sublogger(name string) *Logger { } return &Logger{ Parent: l, - Out: l.Out, Name: name, - Debug: l.Debug, } } @@ -253,6 +259,12 @@ func (l *Logger) Sublogger(name string) *Logger { // however underlying log.Output may provide necessary serialization. var DefaultLogger = Logger{Out: WriterOutput(os.Stderr, false)} +// NopLogger is the logger that discards all messages written to it. +var NopLogger = Logger{ + Parent: &DefaultLogger, + Out: NopOutput{}, +} + func Debugf(format string, val ...interface{}) { DefaultLogger.Debugf(format, val...) } func Debugln(val ...interface{}) { DefaultLogger.Debugln(val...) } func Printf(format string, val ...interface{}) { DefaultLogger.Printf(format, val...) } diff --git a/framework/module/module.go b/framework/module/module.go index 2f949c14..2af39990 100644 --- a/framework/module/module.go +++ b/framework/module/module.go @@ -50,25 +50,3 @@ type Module interface { // string if module instance is unnamed. InstanceName() string } - -// FuncNewModule is function that creates new instance of module with specified name. -// -// Module.InstanceName() of the returned module object should return instName. -// If module is defined inline, instName will be empty. -// -// Returned Module may additionally implement LifetimeModule. -type FuncNewModule func(modName, instName string) (Module, error) - -// FuncNewEndpoint is a function that creates new instance of endpoint -// module. -// -// Compared to regular modules, endpoint module instances are: -// - Not registered in the global registry. -// - Can't be defined inline. -// - Don't have an unique name -// - All config arguments are always passed as an 'addrs' slice and not used as -// names. -// -// As a consequence of having no per-instance name, InstanceName of the module -// object always returns the same value as Name. -type FuncNewEndpoint func(modName string, addrs []string) (LifetimeModule, error) diff --git a/framework/module/dummy.go b/framework/module/modules/dummy.go similarity index 86% rename from framework/module/dummy.go rename to framework/module/modules/dummy.go index 0722fb26..7d2051d1 100644 --- a/framework/module/dummy.go +++ b/framework/module/modules/dummy.go @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package module +package modules import ( "context" @@ -25,6 +25,8 @@ import ( "github.com/emersion/go-smtp" "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" + "github.com/foxcpp/maddy/framework/module" ) // Dummy is a struct that implements PlainAuth and DeliveryTarget @@ -58,7 +60,7 @@ func (d *Dummy) Configure(_ []string, _ *config.Map) error { return nil } -func (d *Dummy) StartDelivery(ctx context.Context, msgMeta *MsgMetadata, mailFrom string) (Delivery, error) { +func (d *Dummy) StartDelivery(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) { return dummyDelivery{}, nil } @@ -80,8 +82,6 @@ func (dd dummyDelivery) Commit(ctx context.Context) error { return nil } -func init() { - Register("dummy", func(_, instName string) (Module, error) { - return &Dummy{instName: instName}, nil - }) +func NewDummy(_ *container.C, _, instName string) (module.Module, error) { + return &Dummy{instName: instName}, nil } diff --git a/framework/module/modules.go b/framework/module/modules/modules.go similarity index 69% rename from framework/module/modules.go rename to framework/module/modules/modules.go index d7669808..44604ab9 100644 --- a/framework/module/modules.go +++ b/framework/module/modules/modules.go @@ -16,14 +16,38 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package module +package modules import ( "sync" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" + "github.com/foxcpp/maddy/framework/module" ) +// FuncNewModule is function that creates new instance of module with specified name. +// +// Module.InstanceName() of the returned module object should return instName. +// If module is defined inline, instName will be empty. +// +// Returned Module may additionally implement LifetimeModule. +type FuncNewModule func(c *container.C, modName, instName string) (module.Module, error) + +// FuncNewEndpoint is a function that creates new instance of endpoint +// module. +// +// Compared to regular modules, endpoint module instances are: +// - Not registered in the global registry. +// - Can't be defined inline. +// - Don't have an unique name +// - All config arguments are always passed as an 'addrs' slice and not used as +// names. +// +// As a consequence of having no per-instance name, InstanceName of the module +// object always returns the same value as Name. +type FuncNewEndpoint func(c *container.C, modName string, addrs []string) (container.LifetimeModule, error) + var ( modules = make(map[string]FuncNewModule) endpoints = make(map[string]FuncNewEndpoint) @@ -52,9 +76,9 @@ func Register(name string, factory FuncNewModule) { // It prints warning to the log about name being deprecated and suggests using // a new name. func RegisterDeprecated(name, newName string, factory FuncNewModule) { - Register(name, func(modName, instName string) (Module, error) { + Register(name, func(c *container.C, modName, instName string) (module.Module, error) { log.Printf("module initialized via deprecated name %s, %s should be used instead; deprecated name may be removed in the next version", name, newName) - return factory(modName, instName) + return factory(c, modName, instName) }) } @@ -94,3 +118,7 @@ func RegisterEndpoint(name string, factory FuncNewEndpoint) { endpoints[name] = factory } + +func init() { + Register("dummy", NewDummy) +} diff --git a/internal/auth/dovecot_sasl/dovecot_sasl.go b/internal/auth/dovecot_sasl/dovecot_sasl.go index fc0585e6..9b30f924 100644 --- a/internal/auth/dovecot_sasl/dovecot_sasl.go +++ b/internal/auth/dovecot_sasl/dovecot_sasl.go @@ -25,16 +25,18 @@ import ( "github.com/emersion/go-sasl" dovecotsasl "github.com/foxcpp/go-dovecot-sasl" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/auth" ) type Auth struct { instName string serverEndpoint string - log log.Logger + log *log.Logger network string addr string @@ -44,10 +46,10 @@ type Auth struct { const modName = "dovecot_sasl" -func New(_, instName string) (module.Module, error) { +func New(c *container.C, _, instName string) (module.Module, error) { a := &Auth{ instName: instName, - log: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug}, + log: c.DefaultLogger.Sublogger(modName), } return a, nil @@ -162,5 +164,5 @@ func (a *Auth) AuthPlain(username, password string) error { } func init() { - module.Register(modName, New) + modules.Register(modName, New) } diff --git a/internal/auth/external/externalauth.go b/internal/auth/external/externalauth.go index 144864da..1d3f4ae3 100644 --- a/internal/auth/external/externalauth.go +++ b/internal/auth/external/externalauth.go @@ -25,8 +25,10 @@ import ( "path/filepath" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/auth" ) @@ -38,14 +40,14 @@ type ExternalAuth struct { perDomain bool domains []string - Log log.Logger + log *log.Logger } -func NewExternalAuth(modName, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { ea := &ExternalAuth{ modName: modName, instName: instName, - Log: log.Logger{Name: modName}, + log: c.DefaultLogger.Sublogger(modName), } return ea, nil @@ -64,7 +66,7 @@ func (ea *ExternalAuth) Configure(inlineArgs []string, cfg *config.Map) error { return errors.New("external: inline arguments are not used") } - cfg.Bool("debug", false, false, &ea.Log.Debug) + cfg.Bool("debug", false, false, &ea.log.Debug) cfg.Bool("perdomain", false, false, &ea.perDomain) cfg.StringList("domains", false, false, nil, &ea.domains) cfg.String("helper", false, false, "", &ea.helperPath) @@ -76,7 +78,7 @@ func (ea *ExternalAuth) Configure(inlineArgs []string, cfg *config.Map) error { } if ea.helperPath != "" { - ea.Log.Debugln("using helper:", ea.helperPath) + ea.log.Debugln("using helper:", ea.helperPath) } else { ea.helperPath = filepath.Join(config.LibexecDirectory, "maddy-auth-helper") } @@ -84,7 +86,7 @@ func (ea *ExternalAuth) Configure(inlineArgs []string, cfg *config.Map) error { return fmt.Errorf("%s doesn't exist", ea.helperPath) } - ea.Log.Debugln("using helper:", ea.helperPath) + ea.log.Debugln("using helper:", ea.helperPath) return nil } @@ -99,5 +101,5 @@ func (ea *ExternalAuth) AuthPlain(username, password string) error { } func init() { - module.Register("auth.external", NewExternalAuth) + modules.Register("auth.external", New) } diff --git a/internal/auth/ldap/ldap.go b/internal/auth/ldap/ldap.go index 06890fd5..af8c6304 100644 --- a/internal/auth/ldap/ldap.go +++ b/internal/auth/ldap/ldap.go @@ -12,8 +12,10 @@ import ( "github.com/foxcpp/maddy/framework/config" tls2 "github.com/foxcpp/maddy/framework/config/tls" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/go-ldap/ldap/v3" ) @@ -37,13 +39,13 @@ type Auth struct { conn *ldap.Conn connLock sync.Mutex - log log.Logger + log *log.Logger } -func New(modName, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { return &Auth{ instName: instName, - log: log.Logger{Name: modName}, + log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -297,6 +299,6 @@ func (a *Auth) Stop() error { func init() { var _ module.PlainAuth = &Auth{} var _ module.Table = &Auth{} - module.Register(modName, New) - module.Register("table.ldap", New) + modules.Register(modName, New) + modules.Register("table.ldap", New) } diff --git a/internal/auth/netauth/netauth.go b/internal/auth/netauth/netauth.go index db84cd18..ce6715e6 100644 --- a/internal/auth/netauth/netauth.go +++ b/internal/auth/netauth/netauth.go @@ -5,8 +5,10 @@ import ( "fmt" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/hashicorp/go-hclog" "github.com/netauth/netauth/pkg/netauth" ) @@ -16,8 +18,8 @@ const modName = "auth.netauth" func init() { var _ module.PlainAuth = &Auth{} var _ module.Table = &Auth{} - module.Register(modName, New) - module.Register("table.netauth", New) + modules.Register(modName, New) + modules.Register("table.netauth", New) } // Auth binds all methods related to the NetAuth client library. @@ -27,22 +29,23 @@ type Auth struct { nacl *netauth.Client - log log.Logger + log *log.Logger } // New creates a new instance of the NetAuth module. -func New(modName, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { return &Auth{ instName: instName, - log: log.Logger{Name: modName}, + log: c.DefaultLogger.Sublogger(modName), }, nil } + func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { if len(inlineArgs)> 0 { return fmt.Errorf("%s: inline arguments are not used", modName) } - l := hclog.New(&hclog.LoggerOptions{Output: &a.log}) + l := hclog.New(&hclog.LoggerOptions{Output: a.log}) n, err := netauth.NewWithLog(l) if err != nil { return err diff --git a/internal/auth/pam/module.go b/internal/auth/pam/module.go index da8e59f9..12977a52 100644 --- a/internal/auth/pam/module.go +++ b/internal/auth/pam/module.go @@ -25,8 +25,10 @@ import ( "path/filepath" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/auth/external" ) @@ -35,13 +37,13 @@ type Auth struct { useHelper bool helperPath string - Log log.Logger + Log *log.Logger } -func New(modName, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { return &Auth{ instName: instName, - Log: log.Logger{Name: modName}, + Log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -91,5 +93,5 @@ func (a *Auth) AuthPlain(username, password string) error { } func init() { - module.Register("auth.pam", New) + modules.Register("auth.pam", New) } diff --git a/internal/auth/pass_table/table.go b/internal/auth/pass_table/table.go index 626913b4..8d3cea34 100644 --- a/internal/auth/pass_table/table.go +++ b/internal/auth/pass_table/table.go @@ -25,7 +25,9 @@ import ( "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "golang.org/x/crypto/bcrypt" "golang.org/x/text/secure/precis" ) @@ -37,7 +39,7 @@ type Auth struct { table module.Table } -func New(modName, instName string) (module.Module, error) { +func New(_ *container.C, modName, instName string) (module.Module, error) { return &Auth{ modName: modName, instName: instName, @@ -192,5 +194,5 @@ func (a *Auth) DeleteUser(username string) error { } func init() { - module.Register("auth.pass_table", New) + modules.Register("auth.pass_table", New) } diff --git a/internal/auth/pass_table/table_test.go b/internal/auth/pass_table/table_test.go index 7f688cac..7c8cf614 100644 --- a/internal/auth/pass_table/table_test.go +++ b/internal/auth/pass_table/table_test.go @@ -22,13 +22,14 @@ import ( "testing" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/internal/testutils" ) func TestAuth_AuthPlain(t *testing.T) { addSHA256() - mod, err := New("pass_table", "") + mod, err := New(container.New(), "pass_table", "") if err != nil { t.Fatal(err) } diff --git a/internal/auth/plain_separate/plain_separate.go b/internal/auth/plain_separate/plain_separate.go index b3e06016..ae437143 100644 --- a/internal/auth/plain_separate/plain_separate.go +++ b/internal/auth/plain_separate/plain_separate.go @@ -25,8 +25,10 @@ import ( "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) type Auth struct { @@ -38,15 +40,15 @@ type Auth struct { onlyFirstID bool - Log log.Logger + log *log.Logger } -func NewAuth(modName, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { a := &Auth{ modName: modName, instName: instName, onlyFirstID: false, - Log: log.Logger{Name: modName}, + log: c.DefaultLogger.Sublogger(modName), } return a, nil @@ -65,7 +67,7 @@ func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { return errors.New("plain_separate: inline arguments are not used") } - cfg.Bool("debug", false, false, &a.Log.Debug) + cfg.Bool("debug", false, false, &a.log.Debug) cfg.Callback("user", func(m *config.Map, node config.Node) error { var tbl module.Table err := modconfig.ModuleFromNode("table", node.Args, node, m.Globals, &tbl) @@ -141,5 +143,5 @@ func (a *Auth) AuthPlain(username, password string) error { } func init() { - module.Register("auth.plain_separate", NewAuth) + modules.Register("auth.plain_separate", New) } diff --git a/internal/auth/sasl.go b/internal/auth/sasl.go index 616ad618..591e3764 100644 --- a/internal/auth/sasl.go +++ b/internal/auth/sasl.go @@ -47,7 +47,7 @@ var ( // It supports reporting of multiple authorization identities so multiple // accounts can be associated with a single set of credentials. type SASLAuth struct { - Log log.Logger + Log *log.Logger OnlyFirstID bool EnableLogin bool diff --git a/internal/auth/shadow/module.go b/internal/auth/shadow/module.go index 90a65a23..8223af28 100644 --- a/internal/auth/shadow/module.go +++ b/internal/auth/shadow/module.go @@ -28,8 +28,10 @@ import ( "path/filepath" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/auth/external" ) @@ -38,13 +40,13 @@ type Auth struct { useHelper bool helperPath string - Log log.Logger + log *log.Logger } -func New(modName, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { return &Auth{ instName: instName, - Log: log.Logger{Name: modName}, + log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -61,7 +63,7 @@ func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { return errors.New("shadow: inline arguments are not used") } - cfg.Bool("debug", true, false, &a.Log.Debug) + cfg.Bool("debug", true, false, &a.log.Debug) cfg.Bool("use_helper", false, false, &a.useHelper) if _, err := cfg.Process(); err != nil { return err @@ -81,7 +83,7 @@ func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error { return fmt.Errorf("shadow: can't read /etc/shadow: %v", err) } if err := f.Close(); err != nil { - a.Log.Error("can't close /etc/shadow file", err) + a.log.Error("can't close /etc/shadow file", err) } } @@ -137,5 +139,5 @@ func (a *Auth) AuthPlain(username, password string) error { } func init() { - module.Register("auth.shadow", New) + modules.Register("auth.shadow", New) } diff --git a/internal/check/authorize_sender/authorize_sender.go b/internal/check/authorize_sender/authorize_sender.go index ab091c56..e1785fdb 100644 --- a/internal/check/authorize_sender/authorize_sender.go +++ b/internal/check/authorize_sender/authorize_sender.go @@ -27,9 +27,11 @@ import ( "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/authz" "github.com/foxcpp/maddy/internal/table" "github.com/foxcpp/maddy/internal/target" @@ -39,7 +41,7 @@ const modName = "check.authorize_sender" type Check struct { instName string - log log.Logger + log *log.Logger checkHeader bool emailPrepare module.Table @@ -53,9 +55,10 @@ type Check struct { authNorm authz.NormalizeFunc } -func New(_, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { return &Check{ instName: instName, + log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -108,7 +111,7 @@ func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { type state struct { c *Check msgMeta *module.MsgMetadata - log log.Logger + log *log.Logger } func (c *Check) CheckStateForMsg(_ context.Context, msgMeta *module.MsgMetadata) (module.CheckState, error) { @@ -306,5 +309,5 @@ func (s *state) Close() error { } func init() { - module.Register(modName, New) + modules.Register(modName, New) } diff --git a/internal/check/command/command.go b/internal/check/command/command.go index 512f147a..a69db4fd 100644 --- a/internal/check/command/command.go +++ b/internal/check/command/command.go @@ -37,9 +37,11 @@ import ( "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/target" ) @@ -58,7 +60,7 @@ var placeholderRe = regexp.MustCompile(`{[a-zA-Z0-9_]+?}`) type Check struct { instName string - log log.Logger + log *log.Logger stage Stage actions map[int]modconfig.FailAction @@ -66,9 +68,10 @@ type Check struct { cmdArgs []string } -func New(modName, instName string) (module.Module, error) { - c := &Check{ +func New(c *container.C, modName, instName string) (module.Module, error) { + chk := &Check{ instName: instName, + log: c.DefaultLogger.Sublogger(modName), actions: map[int]modconfig.FailAction{ 1: { Reject: true, @@ -79,7 +82,7 @@ func New(modName, instName string) (module.Module, error) { }, } - return c, nil + return chk, nil } func (c *Check) Name() string { @@ -140,7 +143,7 @@ func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { type state struct { c *Check msgMeta *module.MsgMetadata - log log.Logger + log *log.Logger mailFrom string rcpts []string @@ -397,5 +400,5 @@ func (s *state) Close() error { } func init() { - module.Register(modName, New) + modules.Register(modName, New) } diff --git a/internal/check/dkim/dkim.go b/internal/check/dkim/dkim.go index b6921d40..9178cb98 100644 --- a/internal/check/dkim/dkim.go +++ b/internal/check/dkim/dkim.go @@ -33,16 +33,18 @@ import ( "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/dns" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/target" ) type Check struct { instName string - log log.Logger + log *log.Logger requiredFields map[string]struct{} brokenSigAction modconfig.FailAction @@ -52,10 +54,10 @@ type Check struct { resolver dns.Resolver } -func New(_, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { return &Check{ instName: instName, - log: log.Logger{Name: "check.dkim"}, + log: c.DefaultLogger.Sublogger(modName), resolver: dns.DefaultResolver(), }, nil } @@ -102,7 +104,7 @@ func (c *Check) InstanceName() string { type dkimCheckState struct { c *Check msgMeta *module.MsgMetadata - log log.Logger + log *log.Logger } func (d *dkimCheckState) CheckConnection(ctx context.Context) module.CheckResult { @@ -269,5 +271,5 @@ func (c *Check) CheckStateForMsg(ctx context.Context, msgMeta *module.MsgMetadat } func init() { - module.Register("check.dkim", New) + modules.Register("check.dkim", New) } diff --git a/internal/check/dkim/dkim_test.go b/internal/check/dkim/dkim_test.go index b4054124..70fafa53 100644 --- a/internal/check/dkim/dkim_test.go +++ b/internal/check/dkim/dkim_test.go @@ -28,6 +28,7 @@ import ( "github.com/foxcpp/go-mockdns" "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/testutils" @@ -84,7 +85,7 @@ Joe. func testCheck(t *testing.T, zones map[string]mockdns.Zone, cfg []config.Node) *Check { t.Helper() - mod, err := New("check.dkim", "") + mod, err := New(container.New(), "check.dkim", "") if err != nil { t.Fatal(err) } diff --git a/internal/check/dnsbl/dnsbl.go b/internal/check/dnsbl/dnsbl.go index 77b5e3ea..ae292185 100644 --- a/internal/check/dnsbl/dnsbl.go +++ b/internal/check/dnsbl/dnsbl.go @@ -30,10 +30,12 @@ import ( "github.com/foxcpp/maddy/framework/address" "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/dns" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/target" "golang.org/x/sync/errgroup" ) @@ -72,15 +74,15 @@ type DNSBL struct { rejectThres int resolver dns.Resolver - log log.Logger + log *log.Logger } -func New(_, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { return &DNSBL{ instName: instName, resolver: dns.DefaultResolver(), - log: log.Logger{Name: "dnsbl"}, + log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -462,7 +464,7 @@ func (bl *DNSBL) CheckConnection(ctx context.Context, state *module.ConnState) e type state struct { bl *DNSBL msgMeta *module.MsgMetadata - log log.Logger + log *log.Logger } func (bl *DNSBL) CheckStateForMsg(ctx context.Context, msgMeta *module.MsgMetadata) (module.CheckState, error) { @@ -506,5 +508,5 @@ func (*state) Close() error { } func init() { - module.Register("check.dnsbl", New) + modules.Register("check.dnsbl", New) } diff --git a/internal/check/milter/milter.go b/internal/check/milter/milter.go index 38f81930..2a6d6e9e 100644 --- a/internal/check/milter/milter.go +++ b/internal/check/milter/milter.go @@ -30,9 +30,11 @@ import ( "github.com/emersion/go-milter" "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/target" ) @@ -43,16 +45,16 @@ type Check struct { milterUrl string failOpen bool instName string - log log.Logger + log *log.Logger } -func New(_, instName string) (module.Module, error) { - c := &Check{ +func New(c *container.C, _, instName string) (module.Module, error) { + chk := &Check{ instName: instName, - log: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug}, + log: c.DefaultLogger.Sublogger(modName), } - return c, nil + return chk, nil } func (c *Check) Name() string { @@ -111,7 +113,7 @@ type state struct { session *milter.ClientSession msgMeta *module.MsgMetadata skipChecks bool - log log.Logger + log *log.Logger } func (c *Check) CheckStateForMsg(ctx context.Context, msgMeta *module.MsgMetadata) (module.CheckState, error) { @@ -444,5 +446,5 @@ var ( ) func init() { - module.Register(modName, New) + modules.Register(modName, New) } diff --git a/internal/check/rspamd/rspamd.go b/internal/check/rspamd/rspamd.go index 4f32edf7..f03bdeb3 100644 --- a/internal/check/rspamd/rspamd.go +++ b/internal/check/rspamd/rspamd.go @@ -35,9 +35,11 @@ import ( "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" tls2 "github.com/foxcpp/maddy/framework/config/tls" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/target" ) @@ -45,7 +47,7 @@ const modName = "check.rspamd" type Check struct { instName string - log log.Logger + log *log.Logger apiPath string flags string @@ -63,14 +65,14 @@ type Check struct { client *http.Client } -func New(modName, instName string) (module.Module, error) { - c := &Check{ +func New(c *container.C, modName, instName string) (module.Module, error) { + chk := &Check{ instName: instName, client: http.DefaultClient, - log: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug}, + log: c.DefaultLogger.Sublogger(modName), } - return c, nil + return chk, nil } func (c *Check) Name() string { @@ -146,7 +148,7 @@ func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error { type state struct { c *Check msgMeta *module.MsgMetadata - log log.Logger + log *log.Logger mailFrom string rcpt []string @@ -378,5 +380,5 @@ func (s *state) Close() error { } func init() { - module.Register(modName, New) + modules.Register(modName, New) } diff --git a/internal/check/skeleton.go b/internal/check/skeleton.go index f3743596..3bfc9cde 100644 --- a/internal/check/skeleton.go +++ b/internal/check/skeleton.go @@ -34,6 +34,7 @@ import ( "github.com/foxcpp/maddy/framework/config" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/target" ) @@ -97,5 +98,5 @@ func (s *state) Close() error { } func init() { - module.Register(modName, New) + modules.Register(modName, New) } diff --git a/internal/check/spf/spf.go b/internal/check/spf/spf.go index a799cd85..01154b7d 100644 --- a/internal/check/spf/spf.go +++ b/internal/check/spf/spf.go @@ -34,10 +34,12 @@ import ( "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/dns" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" maddydmarc "github.com/foxcpp/maddy/internal/dmarc" "github.com/foxcpp/maddy/internal/target" "golang.org/x/net/idna" @@ -56,14 +58,14 @@ type Check struct { permerrAction modconfig.FailAction temperrAction modconfig.FailAction - log log.Logger + log *log.Logger resolver dns.Resolver } -func New(_, instName string) (module.Module, error) { +func New(c *container.C, _, instName string) (module.Module, error) { return &Check{ instName: instName, - log: log.Logger{Name: modName}, + log: c.DefaultLogger.Sublogger(modName), resolver: dns.DefaultResolver(), }, nil } @@ -120,7 +122,7 @@ type state struct { c *Check msgMeta *module.MsgMetadata spfFetch chan spfRes - log log.Logger + log *log.Logger skip bool } @@ -416,5 +418,5 @@ func (s *state) Close() error { } func init() { - module.Register(modName, New) + modules.Register(modName, New) } diff --git a/internal/check/stateless_check.go b/internal/check/stateless_check.go index 729c106b..cda7faf6 100644 --- a/internal/check/stateless_check.go +++ b/internal/check/stateless_check.go @@ -27,9 +27,11 @@ import ( "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/dns" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/target" ) @@ -47,7 +49,7 @@ type ( // Logger that should be used by the check for logging, note that it is // already wrapped to append Msg ID to all messages so check code // should not do the same. - Logger log.Logger + Logger *log.Logger } FuncConnCheck func(checkContext StatelessCheckContext) module.CheckResult FuncSenderCheck func(checkContext StatelessCheckContext, mailFrom string) module.CheckResult @@ -59,7 +61,7 @@ type statelessCheck struct { modName string instName string resolver dns.Resolver - logger log.Logger + logger *log.Logger // One used by Init if config option is not passed by a user. defaultFailAction modconfig.FailAction @@ -185,12 +187,12 @@ func (c *statelessCheck) InstanceName() string { // code doesn't need to know about it. It should assume that it is always "Reject" and hence it should // populate Reason field of the result object with the relevant error description. func RegisterStatelessCheck(name string, defaultFailAction modconfig.FailAction, connCheck FuncConnCheck, senderCheck FuncSenderCheck, rcptCheck FuncRcptCheck, bodyCheck FuncBodyCheck) { - module.Register(name, func(modName, instName string) (module.Module, error) { + modules.Register(name, func(c *container.C, modName, instName string) (module.Module, error) { return &statelessCheck{ modName: modName, instName: instName, resolver: dns.DefaultResolver(), - logger: log.Logger{Name: modName}, + logger: c.DefaultLogger.Sublogger(modName), defaultFailAction: defaultFailAction, diff --git a/internal/cli/ctl/moduleinit.go b/internal/cli/ctl/moduleinit.go index 5519d0e3..f280b181 100644 --- a/internal/cli/ctl/moduleinit.go +++ b/internal/cli/ctl/moduleinit.go @@ -32,7 +32,7 @@ import ( ) func closeIfNeeded(i any) { - if c, ok := i.(module.LifetimeModule); ok { + if c, ok := i.(container.LifetimeModule); ok { if err := c.Stop(); err != nil { log.DefaultLogger.Error("failed to stop module", err) } @@ -48,7 +48,7 @@ func (m *managedStorage) Close() error { if !m.started { return nil } - if lm, ok := m.ManageableStorage.(module.LifetimeModule); ok { + if lm, ok := m.ManageableStorage.(container.LifetimeModule); ok { return lm.Stop() } return nil @@ -63,7 +63,7 @@ func (m *managedUserDB) Close() error { if !m.started { return nil } - if lm, ok := m.PlainUserDB.(module.LifetimeModule); ok { + if lm, ok := m.PlainUserDB.(container.LifetimeModule); ok { return lm.Stop() } return nil @@ -88,6 +88,11 @@ func getCfgBlockModule(ctx *cli.Context) (*container.C, module.Module, error) { return nil, nil, err } + // For CLI management we force-rollback configured logger and consider only + // --log so messages relevant to command execution will go where admin would + // see them. + c.DefaultLogger.Out = log.DefaultLogger.Out + if err := maddy.InitDirs(c); err != nil { return nil, nil, err } @@ -104,7 +109,7 @@ func getCfgBlockModule(ctx *cli.Context) (*container.C, module.Module, error) { mod, err := c.Modules.Get(cfgBlock) if err != nil { - if errors.Is(err, module.ErrInstanceUnknown) { + if errors.Is(err, container.ErrInstanceUnknown) { return nil, nil, cli.Exit(fmt.Sprintf("Error: unknown configuration block: %s", cfgBlock), 2) } return nil, nil, err @@ -125,7 +130,7 @@ func openStorage(ctx *cli.Context) (module.Storage, error) { } started := false - if lt, ok := storage.(module.LifetimeModule); ok { + if lt, ok := storage.(container.LifetimeModule); ok { if err := lt.Start(); err != nil { return nil, err } @@ -160,7 +165,7 @@ func openUserDB(ctx *cli.Context) (module.PlainUserDB, error) { } started := false - if lt, ok := userDB.(module.LifetimeModule); ok { + if lt, ok := userDB.(container.LifetimeModule); ok { if err := lt.Start(); err != nil { return nil, err } diff --git a/internal/endpoint/dovecot_sasld/dovecot_sasl.go b/internal/endpoint/dovecot_sasld/dovecot_sasl.go index 4bb7a942..215b8508 100644 --- a/internal/endpoint/dovecot_sasld/dovecot_sasl.go +++ b/internal/endpoint/dovecot_sasld/dovecot_sasl.go @@ -29,8 +29,9 @@ import ( dovecotsasl "github.com/foxcpp/go-dovecot-sasl" "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" - "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/framework/resource/netresource" "github.com/foxcpp/maddy/internal/auth" "github.com/foxcpp/maddy/internal/authz" @@ -40,7 +41,7 @@ const modName = "dovecot_sasld" type Endpoint struct { addrs []string - log log.Logger + log *log.Logger saslAuth auth.SASLAuth endpoints []config.Endpoint @@ -49,13 +50,14 @@ type Endpoint struct { srv *dovecotsasl.Server } -func New(_ string, addrs []string) (module.LifetimeModule, error) { +func New(c *container.C, _ string, addrs []string) (container.LifetimeModule, error) { + logger := c.DefaultLogger.Sublogger(modName) return &Endpoint{ addrs: addrs, saslAuth: auth.SASLAuth{ - Log: log.Logger{Name: modName + "/saslauth"}, + Log: logger.Sublogger("sasl"), }, - log: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug}, + log: logger, }, nil } @@ -81,7 +83,7 @@ func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { endp.srv = dovecotsasl.NewServer() endp.saslAuth.Log.Debug = endp.log.Debug - endp.srv.Log = stdlog.New(&endp.log, "", 0) + endp.srv.Log = stdlog.New(endp.log, "", 0) for _, mech := range endp.saslAuth.SASLMechanisms() { endp.srv.AddMechanism(mech, mechInfo[mech], func(req *dovecotsasl.AuthReq) sasl.Server { @@ -133,5 +135,5 @@ func (endp *Endpoint) Stop() error { } func init() { - module.RegisterEndpoint(modName, New) + modules.RegisterEndpoint(modName, New) } diff --git a/internal/endpoint/imap/imap.go b/internal/endpoint/imap/imap.go index 1a64ca3d..afc68a79 100644 --- a/internal/endpoint/imap/imap.go +++ b/internal/endpoint/imap/imap.go @@ -40,8 +40,10 @@ import ( "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" tls2 "github.com/foxcpp/maddy/framework/config/tls" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/framework/resource/netresource" "github.com/foxcpp/maddy/internal/auth" "github.com/foxcpp/maddy/internal/authz" @@ -65,15 +67,16 @@ type Endpoint struct { storageNormalize authz.NormalizeFunc storageMap module.Table - Log log.Logger + log *log.Logger } -func New(modName string, addrs []string) (module.LifetimeModule, error) { +func New(c *container.C, modName string, addrs []string) (container.LifetimeModule, error) { + logger := c.DefaultLogger.Sublogger(modName) endp := &Endpoint{ addrs: addrs, - Log: log.Logger{Name: modName}, + log: logger, saslAuth: auth.SASLAuth{ - Log: log.Logger{Name: modName + "/sasl"}, + Log: logger.Sublogger("sasl"), }, } @@ -97,7 +100,7 @@ func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { cfg.Bool("insecure_auth", false, false, &insecureAuth) cfg.Bool("io_debug", false, false, &ioDebug) cfg.Bool("io_errors", false, false, &ioErrors) - cfg.Bool("debug", true, false, &endp.Log.Debug) + cfg.Bool("debug", true, false, &endp.log.Debug) config.EnumMapped(cfg, "storage_map_normalize", false, false, authz.NormalizeFuncs, authz.NormalizeAuto, &endp.storageNormalize) modconfig.Table(cfg, "storage_map", false, false, nil, &endp.storageMap) @@ -108,7 +111,7 @@ func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { return err } - endp.saslAuth.Log.Debug = endp.Log.Debug + endp.saslAuth.Log.Debug = endp.log.Debug addresses := make([]config.Endpoint, 0, len(endp.addrs)) for _, addr := range endp.addrs { @@ -127,13 +130,13 @@ func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { endp.serv.AllowInsecureAuth = insecureAuth endp.serv.TLSConfig = endp.tlsConfig if ioErrors { - endp.serv.ErrorLog = &endp.Log + endp.serv.ErrorLog = endp.log } else { - endp.serv.ErrorLog = &log.Logger{Out: log.NopOutput{}} + endp.serv.ErrorLog = &log.NopLogger } if ioDebug { - endp.serv.Debug = endp.Log.DebugWriter() - endp.Log.Println("I/O debugging is on! It may leak passwords in logs, be careful!") + endp.serv.Debug = endp.log.DebugWriter() + endp.log.Println("I/O debugging is on! It may leak passwords in logs, be careful!") } if err := endp.enableExtensions(); err != nil { @@ -149,10 +152,10 @@ func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { } if endp.serv.AllowInsecureAuth { - endp.Log.Println("authentication over unencrypted connections is allowed, this is insecure configuration and should be used only for testing!") + endp.log.Println("authentication over unencrypted connections is allowed, this is insecure configuration and should be used only for testing!") } if endp.serv.TLSConfig == nil { - endp.Log.Println("TLS is disabled, this is insecure configuration and should be used only for testing!") + endp.log.Println("TLS is disabled, this is insecure configuration and should be used only for testing!") endp.serv.AllowInsecureAuth = true } @@ -162,13 +165,13 @@ func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { func (endp *Endpoint) Start() error { if updBe, ok := endp.Store.(updatepipe.Backend); ok { if err := updBe.EnableUpdatePipe(updatepipe.ModeReplicate); err != nil { - endp.Log.Error("failed to initialize updates pipe", err) + endp.log.Error("failed to initialize updates pipe", err) } } if err := endp.setupListeners(endp.endpoints); err != nil { if err := endp.Stop(); err != nil { - endp.Log.Error("failed to stop after setupListeners error", err) + endp.log.Error("failed to stop after setupListeners error", err) } return err } @@ -183,7 +186,7 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { if err != nil { return fmt.Errorf("imap: %v", err) } - endp.Log.Printf("listening on %v", addr) + endp.log.Printf("listening on %v", addr) if addr.IsTLS() { if endp.tlsConfig == nil { @@ -193,7 +196,7 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { } if endp.proxyProtocol != nil { - l = proxy_protocol.NewListener(l, endp.proxyProtocol, endp.Log) + l = proxy_protocol.NewListener(l, endp.proxyProtocol, endp.log) } endp.listeners = append(endp.listeners, l) @@ -201,7 +204,7 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { go func() { defer endp.listenersWg.Done() if err := endp.serv.Serve(l); err != nil && !strings.HasSuffix(err.Error(), "use of closed network connection") { - endp.Log.Printf("imap: failed to serve %s: %s", addr, err) + endp.log.Printf("imap: failed to serve %s: %s", addr, err) } }() } @@ -220,7 +223,7 @@ func (endp *Endpoint) InstanceName() string { func (endp *Endpoint) Stop() error { for _, l := range endp.listeners { if err := l.Close(); err != nil { - endp.Log.Error("failed to close listener", err) + endp.log.Error("failed to close listener", err) } } if err := endp.serv.Close(); err != nil { @@ -249,7 +252,7 @@ func (endp *Endpoint) usernameForStorage(ctx context.Context, saslUsername strin } if saslUsername != mapped { - endp.Log.DebugMsg("using mapped username for storage", "username", saslUsername, "mapped_username", mapped) + endp.log.DebugMsg("using mapped username for storage", "username", saslUsername, "mapped_username", mapped) } return mapped, nil @@ -261,7 +264,7 @@ func (endp *Endpoint) openAccount(c imapserver.Conn, identity string) error { if errors.Is(err, imapbackend.ErrInvalidCredentials) { return err } - endp.Log.Error("failed to determine storage account name", err, "username", username) + endp.log.Error("failed to determine storage account name", err, "username", username) return fmt.Errorf("internal server error") } @@ -279,7 +282,7 @@ func (endp *Endpoint) Login(connInfo *imap.ConnInfo, username, password string) // saslAuth handles AuthMap calling. err := endp.saslAuth.AuthPlain(username, password) if err != nil { - endp.Log.Error("authentication failed", err, "username", username, "src_ip", connInfo.RemoteAddr) + endp.log.Error("authentication failed", err, "username", username, "src_ip", connInfo.RemoteAddr) return nil, imapbackend.ErrInvalidCredentials } @@ -288,7 +291,7 @@ func (endp *Endpoint) Login(connInfo *imap.ConnInfo, username, password string) if errors.Is(err, imapbackend.ErrInvalidCredentials) { return nil, err } - endp.Log.Error("authentication failed due to an internal error", err, "username", username, "src_ip", connInfo.RemoteAddr) + endp.log.Error("authentication failed due to an internal error", err, "username", username, "src_ip", connInfo.RemoteAddr) return nil, fmt.Errorf("internal server error") } @@ -333,7 +336,7 @@ func (endp *Endpoint) SupportedThreadAlgorithms() []sortthread.ThreadAlgorithm { } func init() { - module.RegisterEndpoint("imap", New) + modules.RegisterEndpoint("imap", New) imap.CharsetReader = message.CharsetReader } diff --git a/internal/endpoint/openmetrics/om.go b/internal/endpoint/openmetrics/om.go index 4c917835..7251b85f 100644 --- a/internal/endpoint/openmetrics/om.go +++ b/internal/endpoint/openmetrics/om.go @@ -25,8 +25,9 @@ import ( "sync" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" - "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/framework/resource/netresource" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -36,17 +37,17 @@ const modName = "openmetrics" type Endpoint struct { addrs []string endpoints []config.Endpoint - logger log.Logger + logger *log.Logger listenersWg sync.WaitGroup serv http.Server mux *http.ServeMux } -func New(_ string, args []string) (module.LifetimeModule, error) { +func New(c *container.C, _ string, args []string) (container.LifetimeModule, error) { return &Endpoint{ addrs: args, - logger: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug}, + logger: c.DefaultLogger.Sublogger(modName), }, nil } @@ -114,5 +115,5 @@ func (e *Endpoint) Stop() error { } func init() { - module.RegisterEndpoint(modName, New) + modules.RegisterEndpoint(modName, New) } diff --git a/internal/endpoint/smtp/session.go b/internal/endpoint/smtp/session.go index 01e36e73..c6adfa0c 100644 --- a/internal/endpoint/smtp/session.go +++ b/internal/endpoint/smtp/session.go @@ -95,7 +95,7 @@ type Session struct { delivery module.Delivery deliveryErr error - log log.Logger + log *log.Logger } func (s *Session) AuthMechanisms() []string { @@ -117,7 +117,7 @@ func (s *Session) Reset() { if s.delivery != nil { s.abort(s.msgCtx) } - s.endp.Log.DebugMsg("reset") + s.endp.log.DebugMsg("reset") } func (s *Session) releaseLimits() { @@ -139,7 +139,7 @@ func (s *Session) releaseLimits() { func (s *Session) abort(ctx context.Context) { if err := s.delivery.Abort(ctx); err != nil { - s.endp.Log.Error("delivery abort failed", err) + s.endp.log.Error("delivery abort failed", err) } s.log.Msg("aborted", "msg_id", s.msgMeta.ID) abortedSMTPTransactions.WithLabelValues(s.endp.name).Inc() @@ -167,7 +167,7 @@ func (s *Session) AuthPlain(username, password string) error { // saslAuth will handle AuthMap and AuthNormalize. err := s.endp.saslAuth.AuthPlain(username, password) if err != nil { - s.endp.Log.Error("authentication failed", err, "username", username, "src_ip", s.connState.RemoteAddr) + s.endp.log.Error("authentication failed", err, "username", username, "src_ip", s.connState.RemoteAddr) failedLogins.WithLabelValues(s.endp.name).Inc() @@ -371,7 +371,7 @@ func (s *Session) Rcpt(to string, opts *smtp.RcptOptions) error { } return s.endp.wrapErr(s.msgMeta.ID, !s.opts.UTF8, "RCPT", err) } - s.endp.Log.Msg("RCPT ok", "rcpt", to, "msg_id", s.msgMeta.ID) + s.endp.log.Msg("RCPT ok", "rcpt", to, "msg_id", s.msgMeta.ID) return nil } @@ -610,7 +610,7 @@ func (endp *Endpoint) wrapErr(msgId string, mangleUTF8 bool, command string, err } if smtpErr, ok := err.(*smtp.SMTPError); ok { - endp.Log.Printf("plain SMTP error returned, this is deprecated") + endp.log.Printf("plain SMTP error returned, this is deprecated") res.Code = smtpErr.Code res.EnhancedCode = smtpErr.EnhancedCode res.Message = smtpErr.Message diff --git a/internal/endpoint/smtp/smtp.go b/internal/endpoint/smtp/smtp.go index cabde482..eb334556 100644 --- a/internal/endpoint/smtp/smtp.go +++ b/internal/endpoint/smtp/smtp.go @@ -37,11 +37,13 @@ import ( "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" tls2 "github.com/foxcpp/maddy/framework/config/tls" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/dns" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/future" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/framework/resource/netresource" "github.com/foxcpp/maddy/internal/auth" "github.com/foxcpp/maddy/internal/authz" @@ -78,7 +80,7 @@ type Endpoint struct { listenersWg sync.WaitGroup - Log log.Logger + log *log.Logger } func (endp *Endpoint) Name() string { @@ -89,7 +91,8 @@ func (endp *Endpoint) InstanceName() string { return endp.name } -func New(modName string, addrs []string) (module.LifetimeModule, error) { +func New(c *container.C, modName string, addrs []string) (container.LifetimeModule, error) { + logger := c.DefaultLogger.Sublogger(modName) endp := &Endpoint{ name: modName, addrs: addrs, @@ -97,9 +100,9 @@ func New(modName string, addrs []string) (module.LifetimeModule, error) { lmtp: modName == "lmtp", resolver: dns.DefaultResolver(), buffer: buffer.BufferInMemory, - Log: log.Logger{Name: modName}, + log: logger, saslAuth: auth.SASLAuth{ - Log: log.Logger{Name: modName + "/sasl"}, + Log: logger.Sublogger("sasl"), }, } return endp, nil @@ -107,7 +110,7 @@ func New(modName string, addrs []string) (module.LifetimeModule, error) { func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { endp.serv = smtp.NewServer(endp) - endp.serv.ErrorLog = &endp.Log + endp.serv.ErrorLog = endp.log endp.serv.LMTP = endp.lmtp endp.serv.EnableSMTPUTF8 = true endp.serv.EnableREQUIRETLS = true @@ -134,11 +137,11 @@ func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error { } if endp.serv.AllowInsecureAuth && !allLocal { - endp.Log.Println("authentication over unencrypted connections is allowed, this is insecure configuration and should be used only for testing!") + endp.log.Println("authentication over unencrypted connections is allowed, this is insecure configuration and should be used only for testing!") } if endp.serv.TLSConfig == nil { if !allLocal { - endp.Log.Println("TLS is disabled, this is insecure configuration and should be used only for testing!") + endp.log.Println("TLS is disabled, this is insecure configuration and should be used only for testing!") } endp.serv.AllowInsecureAuth = true @@ -267,7 +270,7 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { cfg.Bool("insecure_auth", endp.name == "lmtp", false, &endp.serv.AllowInsecureAuth) cfg.Int("smtp_max_line_length", false, false, 4000, &endp.serv.MaxLineLength) cfg.Bool("io_debug", false, false, &ioDebug) - cfg.Bool("debug", true, false, &endp.Log.Debug) + cfg.Bool("debug", true, false, &endp.log.Debug) cfg.Bool("defer_sender_reject", false, true, &endp.deferServerReject) cfg.Int("max_logged_rcpt_errors", false, false, 5, &endp.maxLoggedRcptErrors) cfg.Custom("limits", false, false, func() (interface{}, error) { @@ -285,7 +288,7 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { return err } - endp.saslAuth.Log.Debug = endp.Log.Debug + endp.saslAuth.Log.Debug = endp.log.Debug endp.saslAuth.ErrorMap = endp.authErrorMap // INTERNATIONALIZATION: See RFC 6531 Section 3.3. @@ -300,7 +303,7 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { } endp.pipeline.Hostname = endp.serv.Domain endp.pipeline.Resolver = endp.resolver - endp.pipeline.Log = log.Logger{Name: "smtp/pipeline", Debug: endp.Log.Debug} + endp.pipeline.Log = endp.log.Sublogger("pipeline") endp.pipeline.FirstPipeline = true if endp.submission { @@ -311,8 +314,8 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { } if ioDebug { - endp.serv.Debug = endp.Log.DebugWriter() - endp.Log.Println("I/O debugging is on! It may leak passwords in logs, be careful!") + endp.serv.Debug = endp.log.DebugWriter() + endp.log.Println("I/O debugging is on! It may leak passwords in logs, be careful!") } return nil @@ -321,7 +324,7 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { func (endp *Endpoint) Start() error { if err := endp.setupListeners(endp.endpoints); err != nil { if err := endp.Stop(); err != nil { - endp.Log.Error("failed to Stop after setupListeners fail", err) + endp.log.Error("failed to Stop after setupListeners fail", err) } return err } @@ -352,7 +355,7 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { if err != nil { return fmt.Errorf("%s: %w", endp.name, err) } - endp.Log.Printf("listening on %v", addr) + endp.log.Printf("listening on %v", addr) if addr.IsTLS() { if endp.serv.TLSConfig == nil { @@ -362,7 +365,7 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { } if endp.proxyProtocol != nil { - l = proxy_protocol.NewListener(l, endp.proxyProtocol, endp.Log) + l = proxy_protocol.NewListener(l, endp.proxyProtocol, endp.log.Sublogger("proxy")) } endp.listeners = append(endp.listeners, l) @@ -370,7 +373,7 @@ func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error { endp.listenersWg.Add(1) go func() { if err := endp.serv.Serve(l); err != nil { - endp.Log.Printf("failed to serve %s: %s", addr, err) + endp.log.Printf("failed to serve %s: %s", addr, err) } endp.listenersWg.Done() }() @@ -385,7 +388,7 @@ func (endp *Endpoint) NewSession(conn *smtp.Conn) (smtp.Session, error) { // Executed before authentication and session initialization. if err := endp.pipeline.RunEarlyChecks(context.TODO(), &sess.connState); err != nil { if err := sess.Logout(); err != nil { - endp.Log.Error("early checks logout failed", err) + endp.log.Error("early checks logout failed", err) } return nil, endp.wrapErr("", true, "EHLO", err) } @@ -398,7 +401,7 @@ func (endp *Endpoint) NewSession(conn *smtp.Conn) (smtp.Session, error) { func (endp *Endpoint) newSession(conn *smtp.Conn) *Session { s := &Session{ endp: endp, - log: endp.Log, + log: endp.log, sessionCtx: context.Background(), } @@ -456,7 +459,7 @@ func (endp *Endpoint) Stop() error { } func init() { - module.RegisterEndpoint("smtp", New) - module.RegisterEndpoint("submission", New) - module.RegisterEndpoint("lmtp", New) + modules.RegisterEndpoint("smtp", New) + modules.RegisterEndpoint("submission", New) + modules.RegisterEndpoint("lmtp", New) } diff --git a/internal/endpoint/smtp/smtp_test.go b/internal/endpoint/smtp/smtp_test.go index ee87efee..485c7e9e 100644 --- a/internal/endpoint/smtp/smtp_test.go +++ b/internal/endpoint/smtp/smtp_test.go @@ -32,8 +32,10 @@ import ( "github.com/emersion/go-smtp" "github.com/foxcpp/go-mockdns" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/auth" "github.com/foxcpp/maddy/internal/msgpipeline" "github.com/foxcpp/maddy/internal/testutils" @@ -51,7 +53,7 @@ const testMsg = "From: \r\n" + func testEndpoint(t *testing.T, modName string, authMod module.PlainAuth, tgt module.DeliveryTarget, checks []module.Check, cfg []config.Node) *Endpoint { t.Helper() - mod, err := New(modName, []string{"tcp://127.0.0.1:" + testPort}) + mod, err := New(container.New(), modName, []string{"tcp://127.0.0.1:" + testPort}) if err != nil { t.Fatal(err) } @@ -67,7 +69,7 @@ func testEndpoint(t *testing.T, modName string, authMod module.PlainAuth, tgt mo }, }, } - endp.Log = testutils.Logger(t, "smtp") + endp.log = testutils.Logger(t, "smtp") cfg = append(cfg, config.Node{ @@ -568,7 +570,7 @@ func TestSMTPDelivery_Reset(t *testing.T) { func TestSMTPDelivery_SubmissionAuthRequire(t *testing.T) { tgt := testutils.Target{} - endp := testEndpoint(t, "submission", &module.Dummy{}, &tgt, nil, nil) + endp := testEndpoint(t, "submission", &modules.Dummy{}, &tgt, nil, nil) defer func() { assert.NoError(t, endp.Stop()) }() @@ -588,7 +590,7 @@ func TestSMTPDelivery_SubmissionAuthRequire(t *testing.T) { func TestSMTPDelivery_SubmissionAuthOK(t *testing.T) { tgt := testutils.Target{} - endp := testEndpoint(t, "submission", &module.Dummy{}, &tgt, nil, nil) + endp := testEndpoint(t, "submission", &modules.Dummy{}, &tgt, nil, nil) defer func() { assert.NoError(t, endp.Stop()) }() diff --git a/internal/endpoint/smtp/submission_test.go b/internal/endpoint/smtp/submission_test.go index 91f030cf..911d4328 100644 --- a/internal/endpoint/smtp/submission_test.go +++ b/internal/endpoint/smtp/submission_test.go @@ -26,6 +26,7 @@ import ( "github.com/emersion/go-message/textproto" "github.com/emersion/go-smtp" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/stretchr/testify/assert" ) @@ -50,7 +51,7 @@ func TestSubmissionPrepare(t *testing.T) { } } - endp := testEndpoint(t, "submission", &module.Dummy{}, &module.Dummy{}, nil, nil) + endp := testEndpoint(t, "submission", &modules.Dummy{}, &modules.Dummy{}, nil, nil) defer func() { // Synchronize the endpoint initialization. // Otherwise Close will race with Serve called by setupListeners. diff --git a/internal/imap_filter/command/command.go b/internal/imap_filter/command/command.go index 32f05c03..9961c4ea 100644 --- a/internal/imap_filter/command/command.go +++ b/internal/imap_filter/command/command.go @@ -32,8 +32,10 @@ import ( "github.com/emersion/go-message/textproto" "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) const modName = "imap.filter.command" @@ -42,7 +44,7 @@ var placeholderRe = regexp.MustCompile(`{[a-zA-Z0-9_]+?}`) type Check struct { instName string - log log.Logger + log *log.Logger cmd string cmdArgs []string @@ -61,13 +63,13 @@ func (c *Check) IMAPFilter(accountName string, rcptTo string, msgMeta *module.Ms return c.run(cmd, args, io.MultiReader(bytes.NewReader(buf.Bytes()), bR)) } -func New(_, instName string) (module.Module, error) { - c := &Check{ +func New(c *container.C, _, instName string) (module.Module, error) { + chk := &Check{ instName: instName, - log: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug}, + log: c.DefaultLogger.Sublogger(modName), } - return c, nil + return chk, nil } func (c *Check) Name() string { @@ -203,5 +205,5 @@ func (c *Check) run(cmdName string, args []string, stdin io.Reader) (string, []s } func init() { - module.Register(modName, New) + modules.Register(modName, New) } diff --git a/internal/imap_filter/group.go b/internal/imap_filter/group.go index f86ea866..fd818f86 100644 --- a/internal/imap_filter/group.go +++ b/internal/imap_filter/group.go @@ -23,8 +23,10 @@ import ( "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) // Group wraps multiple modifiers and runs them serially. @@ -34,13 +36,13 @@ import ( type Group struct { instName string Filters []module.IMAPFilter - log log.Logger + log *log.Logger } -func NewGroup(_, instName string) (module.Module, error) { +func NewGroup(c *container.C, modName, instName string) (module.Module, error) { return &Group{ instName: instName, - log: log.Logger{Name: "imap_filters", Debug: log.DefaultLogger.Debug}, + log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -88,5 +90,5 @@ func (g *Group) InstanceName() string { } func init() { - module.Register("imap_filters", NewGroup) + modules.Register("imap_filters", NewGroup) } diff --git a/internal/libdns/acmedns.go b/internal/libdns/acmedns.go index d088811d..ec829f88 100644 --- a/internal/libdns/acmedns.go +++ b/internal/libdns/acmedns.go @@ -5,12 +5,14 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/acmedns" ) func init() { - module.Register("libdns.acmedns", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.acmedns", func(c *container.C, modName, instName string) (module.Module, error) { p := acmedns.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/alidns.go b/internal/libdns/alidns.go index 9e32399c..27c89074 100644 --- a/internal/libdns/alidns.go +++ b/internal/libdns/alidns.go @@ -5,12 +5,14 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/alidns" ) func init() { - module.Register("libdns.alidns", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.alidns", func(c *container.C, modName, instName string) (module.Module, error) { p := alidns.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/cloudflare.go b/internal/libdns/cloudflare.go index b69ec75d..cc8cc7db 100644 --- a/internal/libdns/cloudflare.go +++ b/internal/libdns/cloudflare.go @@ -5,12 +5,14 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/cloudflare" ) func init() { - module.Register("libdns.cloudflare", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.cloudflare", func(c *container.C, modName, instName string) (module.Module, error) { p := cloudflare.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/digitalocean.go b/internal/libdns/digitalocean.go index 20369c9a..96cdc728 100644 --- a/internal/libdns/digitalocean.go +++ b/internal/libdns/digitalocean.go @@ -5,12 +5,14 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/digitalocean" ) func init() { - module.Register("libdns.digitalocean", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.digitalocean", func(c *container.C, modName, instName string) (module.Module, error) { p := digitalocean.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/gandi.go b/internal/libdns/gandi.go index 59a493d5..828a48cd 100644 --- a/internal/libdns/gandi.go +++ b/internal/libdns/gandi.go @@ -7,13 +7,15 @@ import ( "fmt" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/gandi" ) func init() { - module.Register("libdns.gandi", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.gandi", func(c *container.C, modName, instName string) (module.Module, error) { p := gandi.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/gcore.go b/internal/libdns/gcore.go index 69d0da5e..98f71e7e 100644 --- a/internal/libdns/gcore.go +++ b/internal/libdns/gcore.go @@ -6,12 +6,14 @@ import ( "fmt" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/gcore" ) func init() { - module.Register("libdns.gcore", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.gcore", func(c *container.C, modName, instName string) (module.Module, error) { p := gcore.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/googleclouddns.go b/internal/libdns/googleclouddns.go index a9bc493e..dc027ddc 100644 --- a/internal/libdns/googleclouddns.go +++ b/internal/libdns/googleclouddns.go @@ -5,12 +5,14 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/googleclouddns" ) func init() { - module.Register("libdns.googleclouddns", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.googleclouddns", func(c *container.C, modName, instName string) (module.Module, error) { p := googleclouddns.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/hetzner.go b/internal/libdns/hetzner.go index d9a39def..cd5c3458 100644 --- a/internal/libdns/hetzner.go +++ b/internal/libdns/hetzner.go @@ -5,13 +5,15 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/hetzner" ) func init() { - module.Register("libdns.hetzner", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.hetzner", func(c *container.C, modName, instName string) (module.Module, error) { p := hetzner.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/leaseweb.go b/internal/libdns/leaseweb.go index 013e8b6e..a76487a6 100644 --- a/internal/libdns/leaseweb.go +++ b/internal/libdns/leaseweb.go @@ -5,13 +5,15 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/leaseweb" ) func init() { - module.Register("libdns.leaseweb", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.leaseweb", func(c *container.C, modName, instName string) (module.Module, error) { p := leaseweb.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/metaname.go b/internal/libdns/metaname.go index 2180509b..0ffc6177 100644 --- a/internal/libdns/metaname.go +++ b/internal/libdns/metaname.go @@ -5,12 +5,14 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/metaname" ) func init() { - module.Register("libdns.metaname", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.metaname", func(c *container.C, modName, instName string) (module.Module, error) { p := metaname.Provider{ Endpoint: "https://metaname.net/api/1.1", } diff --git a/internal/libdns/namecheap.go b/internal/libdns/namecheap.go index cb38461e..a8538c31 100644 --- a/internal/libdns/namecheap.go +++ b/internal/libdns/namecheap.go @@ -5,12 +5,14 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/namecheap" ) func init() { - module.Register("libdns.namecheap", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.namecheap", func(c *container.C, modName, instName string) (module.Module, error) { p := namecheap.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/namedotcom.go b/internal/libdns/namedotcom.go index 0b4ab608..57c31481 100644 --- a/internal/libdns/namedotcom.go +++ b/internal/libdns/namedotcom.go @@ -5,13 +5,15 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/namedotcom" ) func init() { - module.Register("libdns.namedotcom", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.namedotcom", func(c *container.C, modName, instName string) (module.Module, error) { p := namedotcom.Provider{ Server: "https://api.name.com", } diff --git a/internal/libdns/rfc2136.go b/internal/libdns/rfc2136.go index 90eca724..686bb324 100644 --- a/internal/libdns/rfc2136.go +++ b/internal/libdns/rfc2136.go @@ -5,12 +5,14 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/rfc2136" ) func init() { - module.Register("libdns.rfc2136", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.rfc2136", func(c *container.C, modName, instName string) (module.Module, error) { p := rfc2136.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/route53.go b/internal/libdns/route53.go index fbb5edf7..be0a1dab 100644 --- a/internal/libdns/route53.go +++ b/internal/libdns/route53.go @@ -5,12 +5,14 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/route53" ) func init() { - module.Register("libdns.route53", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.route53", func(c *container.C, modName, instName string) (module.Module, error) { p := route53.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/libdns/vultr.go b/internal/libdns/vultr.go index 35ba38c2..097bc442 100644 --- a/internal/libdns/vultr.go +++ b/internal/libdns/vultr.go @@ -5,13 +5,15 @@ package libdns import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/libdns/vultr" ) func init() { - module.Register("libdns.vultr", func(modName, instName string) (module.Module, error) { + modules.Register("libdns.vultr", func(c *container.C, modName, instName string) (module.Module, error) { p := vultr.Provider{} return &ProviderModule{ RecordDeleter: &p, diff --git a/internal/limits/limits.go b/internal/limits/limits.go index 34161f7a..eba335d4 100644 --- a/internal/limits/limits.go +++ b/internal/limits/limits.go @@ -33,7 +33,9 @@ import ( "time" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/limits/limiters" ) @@ -46,7 +48,7 @@ type Group struct { dest *limiters.BucketSet // BucketSet of MultiLimit } -func New(_, instName string) (module.Module, error) { +func New(c *container.C, _, instName string) (module.Module, error) { return &Group{ instName: instName, }, nil @@ -230,5 +232,5 @@ func (g *Group) InstanceName() string { } func init() { - module.Register("limits", New) + modules.Register("limits", New) } diff --git a/internal/modify/dkim/dkim.go b/internal/modify/dkim/dkim.go index b7864cb2..bf413121 100644 --- a/internal/modify/dkim/dkim.go +++ b/internal/modify/dkim/dkim.go @@ -34,10 +34,12 @@ import ( "github.com/foxcpp/maddy/framework/address" "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/dns" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/target" "golang.org/x/net/idna" ) @@ -108,14 +110,14 @@ type Modifier struct { multipleFromOk bool signSubdomains bool - log log.Logger + log *log.Logger } -func New(_, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { m := &Modifier{ instName: instName, signers: map[string]crypto.Signer{}, - log: log.Logger{Name: "modify.dkim"}, + log: c.DefaultLogger.Sublogger(modName), } return m, nil @@ -254,7 +256,7 @@ type state struct { m *Modifier meta *module.MsgMetadata from string - log log.Logger + log *log.Logger } func (m *Modifier) ModStateForMsg(ctx context.Context, msgMeta *module.MsgMetadata) (module.ModifierState, error) { @@ -370,5 +372,5 @@ func (s *state) Close() error { } func init() { - module.Register("modify.dkim", New) + modules.Register("modify.dkim", New) } diff --git a/internal/modify/dkim/dkim_test.go b/internal/modify/dkim/dkim_test.go index 172f0f34..7e189149 100644 --- a/internal/modify/dkim/dkim_test.go +++ b/internal/modify/dkim/dkim_test.go @@ -32,12 +32,13 @@ import ( "github.com/foxcpp/go-mockdns" "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/internal/testutils" ) func newTestModifier(t *testing.T, dir, keyAlgo string, domains []string) *Modifier { - mod, err := New("", "test") + mod, err := New(container.New(), "", "test") if err != nil { t.Fatal(err) } diff --git a/internal/modify/group.go b/internal/modify/group.go index 26e80408..116a32cf 100644 --- a/internal/modify/group.go +++ b/internal/modify/group.go @@ -25,8 +25,10 @@ import ( "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) type ( @@ -135,7 +137,7 @@ func (gs groupState) Close() error { } func init() { - module.Register("modifiers", func(_, instName string) (module.Module, error) { + modules.Register("modifiers", func(c *container.C, _, instName string) (module.Module, error) { return &Group{ instName: instName, }, nil diff --git a/internal/modify/replace_addr.go b/internal/modify/replace_addr.go index f305ac95..7b3e9842 100644 --- a/internal/modify/replace_addr.go +++ b/internal/modify/replace_addr.go @@ -28,7 +28,9 @@ import ( "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) // replaceAddr is a simple module that replaces matching sender (or recipient) address @@ -45,7 +47,7 @@ type replaceAddr struct { table module.MultiTable } -func NewReplaceAddr(modName, instName string) (module.Module, error) { +func NewReplaceAddr(c *container.C, modName, instName string) (module.Module, error) { r := replaceAddr{ modName: modName, instName: instName, @@ -149,6 +151,6 @@ func (r *replaceAddr) rewrite(ctx context.Context, val string) ([]string, error) } func init() { - module.Register("modify.replace_sender", NewReplaceAddr) - module.Register("modify.replace_rcpt", NewReplaceAddr) + modules.Register("modify.replace_sender", NewReplaceAddr) + modules.Register("modify.replace_rcpt", NewReplaceAddr) } diff --git a/internal/modify/replace_addr_test.go b/internal/modify/replace_addr_test.go index 9c3b0b75..35ebbb2e 100644 --- a/internal/modify/replace_addr_test.go +++ b/internal/modify/replace_addr_test.go @@ -24,6 +24,7 @@ import ( "testing" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/internal/testutils" ) @@ -31,7 +32,7 @@ func testReplaceAddr(t *testing.T, modName string) { test := func(addr string, expectedMulti []string, aliases map[string][]string) { t.Helper() - mod, err := NewReplaceAddr(modName, "") + mod, err := NewReplaceAddr(container.New(), modName, "") if err != nil { t.Fatal(err) } diff --git a/internal/msgpipeline/check_group.go b/internal/msgpipeline/check_group.go index 27bcdb6c..98b6f9c5 100644 --- a/internal/msgpipeline/check_group.go +++ b/internal/msgpipeline/check_group.go @@ -21,7 +21,9 @@ package msgpipeline import ( "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) // CheckGroup is a module container for a group of Check implementations. @@ -59,7 +61,7 @@ func (cg *CheckGroup) InstanceName() string { } func init() { - module.Register("checks", func(_, instName string) (module.Module, error) { + modules.Register("checks", func(_ *container.C, _, instName string) (module.Module, error) { return &CheckGroup{ instName: instName, }, nil diff --git a/internal/msgpipeline/check_runner.go b/internal/msgpipeline/check_runner.go index 7f3a2cb7..48ce0049 100644 --- a/internal/msgpipeline/check_runner.go +++ b/internal/msgpipeline/check_runner.go @@ -49,14 +49,14 @@ type checkRunner struct { didDMARCFetch bool dmarcVerify *dmarc.Verifier - log log.Logger + log *log.Logger states map[module.Check]module.CheckState mergedRes module.CheckResult } -func newCheckRunner(msgMeta *module.MsgMetadata, log log.Logger, r dns.Resolver) *checkRunner { +func newCheckRunner(msgMeta *module.MsgMetadata, log *log.Logger, r dns.Resolver) *checkRunner { return &checkRunner{ msgMeta: msgMeta, checkedRcptsPerCheck: map[module.CheckState]map[string]struct{}{}, diff --git a/internal/msgpipeline/module.go b/internal/msgpipeline/module.go index 29cd4a8f..f7c80792 100644 --- a/internal/msgpipeline/module.go +++ b/internal/msgpipeline/module.go @@ -20,19 +20,21 @@ package msgpipeline import ( "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) type Module struct { instName string - log log.Logger + log *log.Logger *MsgPipeline } -func NewModule(modName, instName string) (module.Module, error) { +func NewModule(c *container.C, modName, instName string) (module.Module, error) { return &Module{ - log: log.Logger{Name: "msgpipeline"}, + log: c.DefaultLogger.Sublogger(modName), instName: instName, }, nil } @@ -66,5 +68,5 @@ func (m *Module) InstanceName() string { } func init() { - module.Register("msgpipeline", NewModule) + modules.Register("msgpipeline", NewModule) } diff --git a/internal/msgpipeline/msgpipeline.go b/internal/msgpipeline/msgpipeline.go index e15c49d5..8fc9c54d 100644 --- a/internal/msgpipeline/msgpipeline.go +++ b/internal/msgpipeline/msgpipeline.go @@ -61,7 +61,7 @@ type MsgPipeline struct { // exactly in this place. FirstPipeline bool - Log log.Logger + Log *log.Logger } type rcptIn struct { @@ -269,7 +269,7 @@ type msgpipelineDelivery struct { sourceModifiersState module.ModifierState rcptModifiersState map[*rcptBlock]module.ModifierState - log log.Logger + log *log.Logger sourceAddr string sourceBlock sourceBlock diff --git a/internal/proxy_protocol/proxy_protocol.go b/internal/proxy_protocol/proxy_protocol.go index e48c2fa3..24fe20c0 100644 --- a/internal/proxy_protocol/proxy_protocol.go +++ b/internal/proxy_protocol/proxy_protocol.go @@ -50,7 +50,7 @@ func ProxyProtocolDirective(_ *config.Map, node config.Node) (interface{}, error return &p, nil } -func NewListener(inner net.Listener, p *ProxyProtocol, logger log.Logger) net.Listener { +func NewListener(inner net.Listener, p *ProxyProtocol, logger *log.Logger) net.Listener { var listener net.Listener sourceChecker := func(upstream net.Addr) (bool, error) { @@ -68,14 +68,12 @@ func NewListener(inner net.Listener, p *ProxyProtocol, logger log.Logger) net.Li return true, nil } - logger.Printf("proxy_protocol: connection from untrusted source %s", upstream) + logger.Printf("connection from untrusted source %s", upstream) return false, nil } proxyListener := proxyprotocol.NewDefaultListener(inner). - WithLogger(proxyprotocol.LoggerFunc(func(format string, v ...interface{}) { - logger.Debugf("proxy_protocol: "+format, v...) - })). + WithLogger(proxyprotocol.LoggerFunc(logger.Debugf)). WithSourceChecker(sourceChecker) listener = &proxyListener diff --git a/internal/smtpconn/smtpconn.go b/internal/smtpconn/smtpconn.go index 25fd49a5..22398937 100644 --- a/internal/smtpconn/smtpconn.go +++ b/internal/smtpconn/smtpconn.go @@ -73,7 +73,7 @@ type C struct { TLSConfig *tls.Config // Logger to use for debug log and certain errors. - Log log.Logger + Log *log.Logger // Include the remote server address in SMTP status messages in the form // "ADDRESS said: ..." diff --git a/internal/storage/blob/fs/fs.go b/internal/storage/blob/fs/fs.go index 48e5f21a..0ef45cd0 100644 --- a/internal/storage/blob/fs/fs.go +++ b/internal/storage/blob/fs/fs.go @@ -8,7 +8,9 @@ import ( "path/filepath" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) // FSStore struct represents directory on FS used to store blobs. @@ -17,7 +19,7 @@ type FSStore struct { root string } -func New(_, instName string) (module.Module, error) { +func New(_ *container.C, _, instName string) (module.Module, error) { return &FSStore{instName: instName}, nil } @@ -92,5 +94,5 @@ func (s *FSStore) Delete(_ context.Context, keys []string) error { func init() { var _ module.BlobStore = &FSStore{} - module.Register((&FSStore{}).Name(), New) + modules.Register((&FSStore{}).Name(), New) } diff --git a/internal/storage/blob/s3/s3.go b/internal/storage/blob/s3/s3.go index b470e2d2..4b2c0d63 100644 --- a/internal/storage/blob/s3/s3.go +++ b/internal/storage/blob/s3/s3.go @@ -7,8 +7,10 @@ import ( "net/http" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) @@ -25,7 +27,7 @@ const ( type Store struct { instName string - log log.Logger + log *log.Logger endpoint string cl *minio.Client @@ -34,10 +36,10 @@ type Store struct { objectPrefix string } -func New(_, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { return &Store{ instName: instName, - log: log.Logger{Name: modName}, + log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -194,5 +196,5 @@ func (s *Store) Delete(ctx context.Context, keys []string) error { func init() { var _ module.BlobStore = &Store{} - module.Register(modName, New) + modules.Register(modName, New) } diff --git a/internal/storage/blob/test_blob.go b/internal/storage/blob/test_blob.go index 8f3d0bcb..f0b2e2d9 100644 --- a/internal/storage/blob/test_blob.go +++ b/internal/storage/blob/test_blob.go @@ -43,7 +43,7 @@ func TestStore(t *testing.T, newStore func() module.BlobStore, cleanStore func(m b, err := imapsql.New("sqlite3", ":memory:", imapsql2.ExtBlobStore{Base: store}, imapsql.Opts{ PRNG: prng, - Log: &l, + Log: l, }, ) if err != nil { diff --git a/internal/storage/imapsql/delivery.go b/internal/storage/imapsql/delivery.go index 55dc36b3..a20c6c3b 100644 --- a/internal/storage/imapsql/delivery.go +++ b/internal/storage/imapsql/delivery.go @@ -108,7 +108,7 @@ func (d *delivery) Body(ctx context.Context, header textproto.Header, body buffe for rcpt, rcptData := range d.addedRcpts { folder, flags, err := d.store.filters.IMAPFilter(rcpt, rcptData.rcptTo, d.msgMeta, header, body) if err != nil { - d.store.Log.Error("IMAPFilter failed", err, "rcpt", rcpt) + d.store.log.Error("IMAPFilter failed", err, "rcpt", rcpt) continue } d.d.UserMailbox(rcpt, folder, flags) diff --git a/internal/storage/imapsql/imapsql.go b/internal/storage/imapsql/imapsql.go index 2a335b6a..fe9103de 100644 --- a/internal/storage/imapsql/imapsql.go +++ b/internal/storage/imapsql/imapsql.go @@ -44,9 +44,11 @@ import ( imapsql "github.com/foxcpp/go-imap-sql" "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/dns" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/authz" sqliteprovider "github.com/foxcpp/maddy/internal/sqlite" "github.com/foxcpp/maddy/internal/updatepipe" @@ -56,10 +58,12 @@ import ( _ "github.com/lib/pq" ) +const modName = "storage.imapsql" + type Storage struct { Back *imapsql.Backend instName string - Log log.Logger + log *log.Logger junkMbox string @@ -83,17 +87,17 @@ type Storage struct { } func (store *Storage) Name() string { - return "imapsql" + return modName } func (store *Storage) InstanceName() string { return store.instName } -func New(_, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { store := &Storage{ instName: instName, - Log: log.Logger{Name: "imapsql"}, + log: c.DefaultLogger.Sublogger(modName), resolver: dns.DefaultResolver(), } return store, nil @@ -124,7 +128,7 @@ func (store *Storage) Configure(inlineArgs []string, cfg *config.Map) error { cfg.String("driver", false, false, store.driver, &driver) cfg.StringList("dsn", false, false, store.dsn, &dsn) cfg.Callback("fsstore", func(m *config.Map, node config.Node) error { - store.Log.Msg("'fsstore' directive is deprecated, use 'msg_store fs' instead") + store.log.Msg("'fsstore' directive is deprecated, use 'msg_store fs' instead") return modconfig.ModuleFromNode("storage.blob", append([]string{"fs"}, node.Args...), node, m.Globals, &blobStore) }) @@ -141,7 +145,7 @@ func (store *Storage) Configure(inlineArgs []string, cfg *config.Map) error { }, &blobStore) cfg.StringList("compression", false, false, []string{"off"}, &compression) cfg.DataSize("appendlimit", false, false, 32*1024*1024, &appendlimitVal) - cfg.Bool("debug", true, false, &store.Log.Debug) + cfg.Bool("debug", true, false, &store.log.Debug) cfg.Int("sqlite3_cache_size", false, false, 0, &opts.CacheSize) cfg.Int("sqlite3_busy_timeout", false, false, 5000, &opts.BusyTimeout) cfg.Bool("disable_recent", false, true, &opts.DisableRecent) @@ -175,9 +179,9 @@ func (store *Storage) Configure(inlineArgs []string, cfg *config.Map) error { if sqliteprovider.IsSqliteDriver(driver) { if sqliteprovider.IsTranspiled { - store.Log.Println("using transpiled SQLite (modernc.org/sqlite)") + store.log.Println("using transpiled SQLite (modernc.org/sqlite)") } else if sqliteprovider.IsAvailable { - store.Log.Debugln("using cgo SQLite") + store.log.Debugln("using cgo SQLite") } else { return errors.New("imapsql: SQLite is not supported, recompile without no_sqlite3 tag set") } @@ -206,7 +210,7 @@ func (store *Storage) Configure(inlineArgs []string, cfg *config.Map) error { } if authNormalize != "auto" { - store.Log.Msg("auth_normalize in storage.imapsql is deprecated and will be removed in the next release, use storage_map in imap config instead") + store.log.Msg("auth_normalize in storage.imapsql is deprecated and will be removed in the next release, use storage_map in imap config instead") } authNormFunc, ok := authz.NormalizeFuncs[authNormalize] if !ok { @@ -216,7 +220,7 @@ func (store *Storage) Configure(inlineArgs []string, cfg *config.Map) error { return authNormFunc(s) } if store.authMap != nil { - store.Log.Msg("auth_map in storage.imapsql is deprecated and will be removed in the next release, use storage_map in imap config instead") + store.log.Msg("auth_map in storage.imapsql is deprecated and will be removed in the next release, use storage_map in imap config instead") store.authNormalize = func(ctx context.Context, username string) (string, error) { username, err := authNormFunc(username) if err != nil { @@ -230,7 +234,7 @@ func (store *Storage) Configure(inlineArgs []string, cfg *config.Map) error { } } - opts.Log = &store.Log + opts.Log = store.log if appendlimitVal == -1 { opts.MaxMsgBytes = nil @@ -281,7 +285,7 @@ func (store *Storage) Configure(inlineArgs []string, cfg *config.Map) error { store.dsn = dsn store.blobStore = blobStore store.opts = opts - store.Log.Debugln("go-imap-sql version", imapsql.VersionStr) + store.log.Debugln("go-imap-sql version", imapsql.VersionStr) return nil } @@ -307,21 +311,21 @@ func (store *Storage) EnableUpdatePipe(mode updatepipe.BackendMode) error { sockPath := filepath.Join( config.RuntimeDirectory, fmt.Sprintf("sql-%s.sock", hex.EncodeToString(dbId[:]))) - store.Log.DebugMsg("using unix socket for external updates", "path", sockPath) + store.log.DebugMsg("using unix socket for external updates", "path", sockPath) store.updPipe = &updatepipe.UnixSockPipe{ SockPath: sockPath, - Log: log.Logger{Name: "storage.imapsql/updpipe", Debug: store.Log.Debug}, + Log: store.log.Sublogger("updpipe"), } case "postgres": - store.Log.DebugMsg("using PostgreSQL broker for external updates") + store.log.DebugMsg("using PostgreSQL broker for external updates") ps, err := pubsub.NewPQ(strings.Join(store.dsn, " ")) if err != nil { return fmt.Errorf("enable_update_pipe: %w", err) } - ps.Log = log.Logger{Name: "storage.imapsql/updpipe/pubsub", Debug: store.Log.Debug} + ps.Log = store.log.Sublogger("updpipe/pubsub") pipe := &updatepipe.PubSubPipe{ PubSub: ps, - Log: log.Logger{Name: "storage.imapsql/updpipe", Debug: store.Log.Debug}, + Log: store.log.Sublogger("updpipe"), } store.Back.UpdateManager().ExternalUnsubscribe = pipe.Unsubscribe store.Back.UpdateManager().ExternalSubscribe = pipe.Subscribe @@ -354,7 +358,7 @@ func (store *Storage) EnableUpdatePipe(mode updatepipe.BackendMode) error { // Ensure we sent all outbound updates. for upd := range outbound { if err := store.updPipe.Push(upd); err != nil { - store.Log.Error("IMAP update pipe push failed", err) + store.log.Error("IMAP update pipe push failed", err) } } store.updPushStop <- struct{}{} @@ -368,15 +372,15 @@ func (store *Storage) EnableUpdatePipe(mode updatepipe.BackendMode) error { for { select { case u := <-inbound: - store.Log.DebugMsg("external update received", "type", u.Type, "key", u.Key) + store.log.DebugMsg("external update received", "type", u.Type, "key", u.Key) store.Back.UpdateManager().ExternalUpdate(u) case u, ok := <-outbound: if !ok { return } - store.Log.DebugMsg("sending external update", "type", u.Type, "key", u.Key) + store.log.DebugMsg("sending external update", "type", u.Type, "key", u.Key) if err := store.updPipe.Push(u); err != nil { - store.Log.Error("IMAP update pipe push failed", err) + store.log.Error("IMAP update pipe push failed", err) } } } @@ -420,7 +424,7 @@ func (store *Storage) Lookup(ctx context.Context, key string) (string, bool, err return "", false, err } if err := usr.Logout(); err != nil { - store.Log.Error("logout failed", err, "username", accountName) + store.log.Error("logout failed", err, "username", accountName) } return "", true, nil @@ -429,7 +433,7 @@ func (store *Storage) Lookup(ctx context.Context, key string) (string, bool, err func (store *Storage) Stop() error { // Stop backend from generating new updates. if err := store.Back.Close(); err != nil { - store.Log.Error("close backend failed", err) + store.log.Error("close backend failed", err) } // Wait for 'updates replicate' goroutine to actually stop so we will send @@ -440,7 +444,7 @@ func (store *Storage) Stop() error { <-store.updpushstop if err := store.updPipe.Close(); err != nil { - store.Log.Error("updatepipe close failed", err) + store.log.Error("updatepipe close failed", err) } } @@ -456,6 +460,6 @@ func (store *Storage) SupportedThreadAlgorithms() []sortthread.ThreadAlgorithm { } func init() { - module.Register("storage.imapsql", New) - module.Register("target.imapsql", New) + modules.Register("storage.imapsql", New) + modules.Register("target.imapsql", New) } diff --git a/internal/table/chain.go b/internal/table/chain.go index 3b29a1c0..819002a9 100644 --- a/internal/table/chain.go +++ b/internal/table/chain.go @@ -23,7 +23,9 @@ import ( "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) type Chain struct { @@ -34,7 +36,7 @@ type Chain struct { optional []bool } -func NewChain(modName, instName string) (module.Module, error) { +func NewChain(_ *container.C, modName, instName string) (module.Module, error) { return &Chain{ modName: modName, instName: instName, @@ -127,5 +129,5 @@ STEP: } func init() { - module.Register("table.chain", NewChain) + modules.Register("table.chain", NewChain) } diff --git a/internal/table/email_localpart.go b/internal/table/email_localpart.go index 4371a3e3..b500b31c 100644 --- a/internal/table/email_localpart.go +++ b/internal/table/email_localpart.go @@ -23,7 +23,9 @@ import ( "github.com/foxcpp/maddy/framework/address" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) type EmailLocalpart struct { @@ -32,7 +34,7 @@ type EmailLocalpart struct { allowNonEmail bool } -func NewEmailLocalpart(modName, instName string) (module.Module, error) { +func NewEmailLocalpart(_ *container.C, modName, instName string) (module.Module, error) { return &EmailLocalpart{ modName: modName, instName: instName, @@ -65,6 +67,6 @@ func (s *EmailLocalpart) Lookup(ctx context.Context, key string) (string, bool, } func init() { - module.Register("table.email_localpart", NewEmailLocalpart) - module.Register("table.email_localpart_optional", NewEmailLocalpart) + modules.Register("table.email_localpart", NewEmailLocalpart) + modules.Register("table.email_localpart_optional", NewEmailLocalpart) } diff --git a/internal/table/email_with_domain.go b/internal/table/email_with_domain.go index 62d9c565..6ebb706b 100644 --- a/internal/table/email_with_domain.go +++ b/internal/table/email_with_domain.go @@ -24,22 +24,24 @@ import ( "github.com/foxcpp/maddy/framework/address" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) type EmailWithDomain struct { modName string instName string domains []string - log log.Logger + log *log.Logger } -func NewEmailWithDomain(modName, instName string) (module.Module, error) { +func NewEmailWithDomain(c *container.C, modName, instName string) (module.Module, error) { return &EmailWithDomain{ modName: modName, instName: instName, - log: log.Logger{Name: modName}, + log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -86,5 +88,5 @@ func (s *EmailWithDomain) LookupMulti(ctx context.Context, key string) ([]string } func init() { - module.Register("table.email_with_domain", NewEmailWithDomain) + modules.Register("table.email_with_domain", NewEmailWithDomain) } diff --git a/internal/table/file.go b/internal/table/file.go index 3b7fcc03..47d8f372 100644 --- a/internal/table/file.go +++ b/internal/table/file.go @@ -29,8 +29,10 @@ import ( "time" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) const FileModName = "table.file" @@ -46,16 +48,16 @@ type File struct { stopReloader chan struct{} forceReload chan struct{} - log log.Logger + log *log.Logger } -func NewFile(_, instName string) (module.Module, error) { +func NewFile(c *container.C, modName, instName string) (module.Module, error) { m := &File{ instName: instName, m: make(map[string][]string), stopReloader: make(chan struct{}), forceReload: make(chan struct{}), - log: log.Logger{Name: FileModName}, + log: c.DefaultLogger.Sublogger(modName), } return m, nil @@ -258,5 +260,5 @@ func (f *File) LookupMulti(_ context.Context, val string) ([]string, error) { } func init() { - module.Register(FileModName, NewFile) + modules.Register(FileModName, NewFile) } diff --git a/internal/table/file_test.go b/internal/table/file_test.go index abfc81f8..33cabe05 100644 --- a/internal/table/file_test.go +++ b/internal/table/file_test.go @@ -25,6 +25,7 @@ import ( "time" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/internal/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -118,7 +119,7 @@ func TestFileReload(t *testing.T) { t.Fatal(err) } - mod, err := NewFile("", "") + mod, err := NewFile(container.New(),"", "") if err != nil { t.Fatal(err) } @@ -179,7 +180,7 @@ func TestFileReload_Broken(t *testing.T) { } require.NoError(t, f.Close()) - mod, err := NewFile("", "") + mod, err := NewFile(container.New(), "", "") if err != nil { t.Fatal(err) } @@ -240,7 +241,7 @@ func TestFileReload_Removed(t *testing.T) { t.Fatal(err) } - mod, err := NewFile("", "") + mod, err := NewFile(container.New(), "", "") if err != nil { t.Fatal(err) } diff --git a/internal/table/identity.go b/internal/table/identity.go index 6db17df8..2a179b27 100644 --- a/internal/table/identity.go +++ b/internal/table/identity.go @@ -22,7 +22,9 @@ import ( "context" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) type Identity struct { @@ -30,7 +32,7 @@ type Identity struct { instName string } -func NewIdentity(modName, instName string) (module.Module, error) { +func NewIdentity(_ *container.C, modName, instName string) (module.Module, error) { return &Identity{ modName: modName, instName: instName, @@ -54,5 +56,5 @@ func (s *Identity) Lookup(_ context.Context, key string) (string, bool, error) { } func init() { - module.Register("table.identity", NewIdentity) + modules.Register("table.identity", NewIdentity) } diff --git a/internal/table/regexp.go b/internal/table/regexp.go index 0c22cd8a..1cf214a9 100644 --- a/internal/table/regexp.go +++ b/internal/table/regexp.go @@ -25,7 +25,9 @@ import ( "strings" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) type Regexp struct { @@ -38,7 +40,7 @@ type Regexp struct { expandPlaceholders bool } -func NewRegexp(modName, instName string) (module.Module, error) { +func NewRegexp(_ *container.C, modName, instName string) (module.Module, error) { return &Regexp{ modName: modName, instName: instName, @@ -121,5 +123,5 @@ func (r *Regexp) Lookup(ctx context.Context, key string) (string, bool, error) { } func init() { - module.Register("table.regexp", NewRegexp) + modules.Register("table.regexp", NewRegexp) } diff --git a/internal/table/sql_query.go b/internal/table/sql_query.go index 6fa0f730..248dc874 100644 --- a/internal/table/sql_query.go +++ b/internal/table/sql_query.go @@ -26,8 +26,10 @@ import ( "strings" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" sqliteprovider "github.com/foxcpp/maddy/internal/sqlite" _ "github.com/lib/pq" ) @@ -47,7 +49,7 @@ type SQL struct { del *sql.Stmt } -func NewSQL(modName, instName string) (module.Module, error) { +func NewSQL(_ *container.C, modName, instName string) (module.Module, error) { return &SQL{ modName: modName, instName: instName, @@ -265,5 +267,5 @@ func (s *SQL) SetKey(k, v string) error { } func init() { - module.Register("table.sql_query", NewSQL) + modules.Register("table.sql_query", NewSQL) } diff --git a/internal/table/sql_query_test.go b/internal/table/sql_query_test.go index 99269c62..ac976f6c 100644 --- a/internal/table/sql_query_test.go +++ b/internal/table/sql_query_test.go @@ -28,12 +28,13 @@ import ( "testing" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/internal/testutils" ) func TestSQL(t *testing.T) { path := testutils.Dir(t) - mod, err := NewSQL("sql_table", "") + mod, err := NewSQL(container.New(),"sql_table", "") if err != nil { t.Fatal("Module create failed:", err) } diff --git a/internal/table/sql_table.go b/internal/table/sql_table.go index edadb919..c8f80216 100644 --- a/internal/table/sql_table.go +++ b/internal/table/sql_table.go @@ -23,7 +23,9 @@ import ( "fmt" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" _ "github.com/lib/pq" ) @@ -34,7 +36,7 @@ type SQLTable struct { wrapped *SQL } -func NewSQLTable(modName, instName string) (module.Module, error) { +func NewSQLTable(_ *container.C, modName, instName string) (module.Module, error) { return &SQLTable{ modName: modName, instName: instName, @@ -171,5 +173,5 @@ func (s *SQLTable) SetKey(k, v string) error { } func init() { - module.Register("table.sql_table", NewSQLTable) + modules.Register("table.sql_table", NewSQLTable) } diff --git a/internal/table/static.go b/internal/table/static.go index 4444f991..21b09c1f 100644 --- a/internal/table/static.go +++ b/internal/table/static.go @@ -22,7 +22,9 @@ import ( "context" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) type Static struct { @@ -32,7 +34,7 @@ type Static struct { m map[string][]string } -func NewStatic(modName, instName string) (module.Module, error) { +func NewStatic(_ *container.C, modName, instName string) (module.Module, error) { return &Static{ modName: modName, instName: instName, @@ -73,5 +75,5 @@ func (s *Static) LookupMulti(ctx context.Context, key string) ([]string, error) } func init() { - module.Register("table.static", NewStatic) + modules.Register("table.static", NewStatic) } diff --git a/internal/target/delivery.go b/internal/target/delivery.go index 1c3450fa..75959136 100644 --- a/internal/target/delivery.go +++ b/internal/target/delivery.go @@ -23,7 +23,8 @@ import ( "github.com/foxcpp/maddy/framework/module" ) -func DeliveryLogger(l log.Logger, msgMeta *module.MsgMetadata) log.Logger { +func DeliveryLogger(parent *log.Logger, msgMeta *module.MsgMetadata) *log.Logger { + l := parent.Sublogger("") fields := make(map[string]interface{}, len(l.Fields)+1) for k, v := range l.Fields { fields[k] = v diff --git a/internal/target/queue/queue.go b/internal/target/queue/queue.go index 11b79683..1aec8213 100644 --- a/internal/target/queue/queue.go +++ b/internal/target/queue/queue.go @@ -79,9 +79,11 @@ import ( "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/dsn" "github.com/foxcpp/maddy/internal/msgpipeline" "github.com/foxcpp/maddy/internal/target" @@ -145,7 +147,7 @@ type Queue struct { // after start-up for whatever reason it will not affect the queue. postInitDelay time.Duration - Log log.Logger + log *log.Logger Target module.DeliveryTarget deliveryWg sync.WaitGroup @@ -187,13 +189,13 @@ type queueSlot struct { Body buffer.Buffer } -func NewQueue(_, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { q := &Queue{ name: instName, initialRetryTime: 15 * time.Minute, retryTimeScale: 1.25, postInitDelay: 10 * time.Second, - Log: log.Logger{Name: "queue"}, + log: c.DefaultLogger.Sublogger(modName), } return q, nil } @@ -208,7 +210,7 @@ func (q *Queue) Configure(inlineArgs []string, cfg *config.Map) error { return errors.New("queue: wrong amount of inline arguments") } - cfg.Bool("debug", true, false, &q.Log.Debug) + cfg.Bool("debug", true, false, &q.log.Debug) cfg.Int("max_tries", false, false, 20, &q.maxTries) cfg.Int("max_parallelism", false, false, 16, &q.maxParallelism) cfg.Duration("post_init_delay", false, false, q.postInitDelay, &q.postInitDelay) @@ -231,7 +233,7 @@ func (q *Queue) Configure(inlineArgs []string, cfg *config.Map) error { } q.dsnPipeline.(*msgpipeline.MsgPipeline).Hostname = q.hostname - q.dsnPipeline.(*msgpipeline.MsgPipeline).Log = log.Logger{Name: "queue/pipeline", Debug: q.Log.Debug} + q.dsnPipeline.(*msgpipeline.MsgPipeline).Log = q.log.Sublogger("pipeline") } if q.location == "" && q.name == "" { return errors.New("queue: need explicit location directive or inline argument if defined inline") @@ -259,7 +261,7 @@ func (q *Queue) start(maxParallelism int) error { return err } - q.Log.Debugf("delivery target: %T", q.Target) + q.log.Debugf("delivery target: %T", q.Target) return nil } @@ -294,11 +296,11 @@ func (q *Queue) discardBroken(id string) { func (q *Queue) dispatch(ctx context.Context, value TimeSlot[queueSlot]) { slot := value.Value - q.Log.Debugln("starting delivery for", slot.ID) + q.log.Debugln("starting delivery for", slot.ID) q.deliveryWg.Add(1) go func() { - q.Log.Debugln("waiting on delivery semaphore for", slot.ID) + q.log.Debugln("waiting on delivery semaphore for", slot.ID) q.deliverySemaphore <- struct{}{} defer func() { <-q.deliverysemaphore @@ -315,7 +317,7 @@ func (q *Queue) dispatch(ctx context.Context, value TimeSlot[queueSlot]) { } }() - q.Log.Debugln("delivery semaphore acquired for", slot.ID) + q.log.Debugln("delivery semaphore acquired for", slot.ID) var ( meta *QueueMetadata hdr textproto.Header @@ -325,7 +327,7 @@ func (q *Queue) dispatch(ctx context.Context, value TimeSlot[queueSlot]) { var err error meta, hdr, body, err = q.openMessage(slot.ID) if err != nil { - q.Log.Error("read message", err, slot.ID) + q.log.Error("read message", err, slot.ID) return } if meta == nil { @@ -382,7 +384,7 @@ func toSMTPErr(err error) *smtp.SMTPError { } func (q *Queue) tryDelivery(ctx context.Context, meta *QueueMetadata, header textproto.Header, body buffer.Buffer) { - dl := target.DeliveryLogger(q.Log, meta.MsgMeta) + dl := target.DeliveryLogger(q.log, meta.MsgMeta) partialErr := q.deliver(ctx, meta, header, body) dl.Debugf("errors: %v", partialErr.Errs) @@ -469,7 +471,7 @@ func (q *Queue) tryDelivery(ctx context.Context, meta *QueueMetadata, header tex } func (q *Queue) deliver(ctx context.Context, meta *QueueMetadata, header textproto.Header, body buffer.Buffer) partialError { - dl := target.DeliveryLogger(q.Log, meta.MsgMeta) + dl := target.DeliveryLogger(q.log, meta.MsgMeta) perr := partialError{ Errs: map[string]error{}, statusLock: new(sync.Mutex), @@ -650,7 +652,7 @@ func (q *Queue) StartDelivery(ctx context.Context, msgMeta *module.MsgMetadata, func (q *Queue) removeFromDisk(msgMeta *module.MsgMetadata) { id := msgMeta.ID - dl := target.DeliveryLogger(q.Log, msgMeta) + dl := target.DeliveryLogger(q.log, msgMeta) // Order is important. // If we remove header and body but can't remove meta now - readDiskQueue @@ -692,18 +694,18 @@ func (q *Queue) readDiskQueue() error { meta, err := q.readMessageMeta(id) if err != nil { - q.Log.Printf("failed to read meta-data, skipping: %v (msg ID = %s)", err, id) + q.log.Printf("failed to read meta-data, skipping: %v (msg ID = %s)", err, id) continue } // Check header file existence. if _, err := os.Stat(filepath.Join(q.location, id+".header")); err != nil { if os.IsNotExist(err) { - q.Log.Printf("header file doesn't exist for msg ID = %s", id) + q.log.Printf("header file doesn't exist for msg ID = %s", id) q.tryRemoveDanglingFile(id + ".meta") q.tryRemoveDanglingFile(id + ".body") } else { - q.Log.Printf("skipping nonstat'able header file: %v (msg ID = %s)", err, id) + q.log.Printf("skipping nonstat'able header file: %v (msg ID = %s)", err, id) } continue } @@ -711,11 +713,11 @@ func (q *Queue) readDiskQueue() error { // Check body file existence. if _, err := os.Stat(filepath.Join(q.location, id+".body")); err != nil { if os.IsNotExist(err) { - q.Log.Printf("body file doesn't exist for msg ID = %s", id) + q.log.Printf("body file doesn't exist for msg ID = %s", id) q.tryRemoveDanglingFile(id + ".meta") q.tryRemoveDanglingFile(id + ".header") } else { - q.Log.Printf("skipping nonstat'able body file: %v (msg ID = %s)", err, id) + q.log.Printf("skipping nonstat'able body file: %v (msg ID = %s)", err, id) } continue } @@ -734,7 +736,7 @@ func (q *Queue) readDiskQueue() error { nextTryTime = time.Now().Add(q.postInitDelay) } - q.Log.Debugf("will try to deliver (msg ID = %s) in %v (%v)", id, time.Until(nextTryTime), nextTryTime) + q.log.Debugf("will try to deliver (msg ID = %s) in %v (%v)", id, time.Until(nextTryTime), nextTryTime) q.wheel.Add(nextTryTime, queueSlot{ ID: id, }) @@ -744,7 +746,7 @@ func (q *Queue) readDiskQueue() error { } if loadedCount != 0 { - q.Log.Printf("loaded %d saved queue entries", loadedCount) + q.log.Printf("loaded %d saved queue entries", loadedCount) } return nil @@ -760,7 +762,7 @@ func (q *Queue) storeNewMessage(meta *QueueMetadata, header textproto.Header, bo } defer func() { if err := headerFile.Close(); err != nil { - q.Log.Error("header file close failed", err) + q.log.Error("header file close failed", err) } }() @@ -776,7 +778,7 @@ func (q *Queue) storeNewMessage(meta *QueueMetadata, header textproto.Header, bo } defer func() { if err := bodyReader.Close(); err != nil { - q.Log.Error("bodyReader close failed", err) + q.log.Error("bodyReader close failed", err) } }() @@ -787,7 +789,7 @@ func (q *Queue) storeNewMessage(meta *QueueMetadata, header textproto.Header, bo } defer func() { if err := bodyFile.Close(); err != nil { - q.Log.Error("body file close failed", err) + q.log.Error("body file close failed", err) } }() @@ -834,7 +836,7 @@ func (q *Queue) updateMetadataOnDisk(meta *QueueMetadata) error { } defer func() { if err := file.Close(); err != nil { - q.Log.Error("metadata file close failed", err) + q.log.Error("metadata file close failed", err) } }() @@ -867,7 +869,7 @@ func (q *Queue) readMessageMeta(id string) (*QueueMetadata, error) { } defer func() { if err := file.Close(); err != nil { - q.Log.Error("metadata file close failed", err) + q.log.Error("metadata file close failed", err) } }() @@ -894,10 +896,10 @@ type BufferedReadCloser struct { func (q *Queue) tryRemoveDanglingFile(name string) { if err := os.Remove(filepath.Join(q.location, name)); err != nil { - q.Log.Error("dangling file remove failed", err) + q.log.Error("dangling file remove failed", err) return } - q.Log.Printf("removed dangling file %s", name) + q.log.Printf("removed dangling file %s", name) } func (q *Queue) openMessage(id string) (*QueueMetadata, textproto.Header, buffer.Buffer, error) { @@ -956,7 +958,7 @@ func (q *Queue) emitDSN(meta *QueueMetadata, header textproto.Header, failedRcpt dsnID, err := module.GenerateMsgID() if err != nil { - q.Log.Error("rand.Rand error", err) + q.log.Error("rand.Rand error", err) return } @@ -996,7 +998,7 @@ func (q *Queue) emitDSN(meta *QueueMetadata, header textproto.Header, failedRcpt } var dsnBodyBlob bytes.Buffer - dl := target.DeliveryLogger(q.Log, meta.MsgMeta) + dl := target.DeliveryLogger(q.log, meta.MsgMeta) dsnHeader, err := dsn.GenerateDSN(meta.MsgMeta.SMTPOpts.UTF8, dsnEnvelope, mtaInfo, rcptInfo, header, &dsnBodyBlob) if err != nil { dl.Error("failed to generate fail DSN", err) @@ -1053,5 +1055,5 @@ func (q *Queue) emitDSN(meta *QueueMetadata, header textproto.Header, failedRcpt } func init() { - module.Register("target.queue", NewQueue) + modules.Register("target.queue", New) } diff --git a/internal/target/queue/queue_test.go b/internal/target/queue/queue_test.go index 659770b6..2ad9e821 100644 --- a/internal/target/queue/queue_test.go +++ b/internal/target/queue/queue_test.go @@ -35,6 +35,7 @@ import ( "github.com/emersion/go-message/textproto" "github.com/emersion/go-smtp" "github.com/foxcpp/maddy/framework/buffer" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" @@ -58,7 +59,7 @@ func cleanQueue(t *testing.T, q *Queue) { } func newTestQueueDir(t *testing.T, target module.DeliveryTarget, dir string) *Queue { - mod, _ := NewQueue("", "queue") + mod, _ := New(container.New(), "", "queue") q := mod.(*Queue) q.initialRetryTime = 0 q.retryTimeScale = 1 @@ -68,9 +69,9 @@ func newTestQueueDir(t *testing.T, target module.DeliveryTarget, dir string) *Qu q.Target = target if testing.Verbose() { - q.Log = testutils.Logger(t, "queue") + q.log = testutils.Logger(t, "queue") } else { - q.Log = log.Logger{Out: log.NopOutput{}} + q.log = &log.NopLogger } if err := q.start(1); err != nil { diff --git a/internal/target/remote/connect.go b/internal/target/remote/connect.go index a8c5cb97..4456d927 100644 --- a/internal/target/remote/connect.go +++ b/internal/target/remote/connect.go @@ -89,7 +89,7 @@ func (rd *remoteDelivery) connect(ctx context.Context, conn mxConn, host string, tlsCfg.ServerName = host } - rd.Log.DebugMsg("trying", "remote_server", host, "domain", conn.domain) + rd.log.DebugMsg("trying", "remote_server", host, "domain", conn.domain) retry: // smtpconn.C default TLS behavior is not useful for us, we want to handle @@ -110,7 +110,7 @@ retry: // rejecting STARTTLS (despite advertising STARTTLS). // We err on the caution side here and do not perform any fallbacks. if err := conn.DirectClose(); err != nil { - rd.Log.Error("conn.DirectClose failed", err) + rd.log.Error("conn.DirectClose failed", err) } return module.TLSNone, nil, err } @@ -127,24 +127,24 @@ retry: // error happens with InsecureSkipVerify too (e.g. certificate is // *too* broken). if isVerifyError(err) && tlsLevel == module.TLSAuthenticated { - rd.Log.Error("TLS verify error, trying without authentication", err, "remote_server", host, "domain", conn.domain) + rd.log.Error("TLS verify error, trying without authentication", err, "remote_server", host, "domain", conn.domain) tlsCfg.InsecureSkipVerify = true tlsLevel = module.TLSEncrypted // TODO: Check go-smtp code to make TLS verification errors // non-sticky so we can properly send QUIT in this case. if err := conn.DirectClose(); err != nil { - rd.Log.Error("conn.DirectClose failed", err) + rd.log.Error("conn.DirectClose failed", err) } goto retry } - rd.Log.Error("TLS error, trying plaintext", err, "remote_server", host, "domain", conn.domain) + rd.log.Error("TLS error, trying plaintext", err, "remote_server", host, "domain", conn.domain) tlsCfg = nil tlsLevel = module.TLSNone if err := conn.DirectClose(); err != nil { - rd.Log.Error("conn.DirectClose failed", err) + rd.log.Error("conn.DirectClose failed", err) } goto retry @@ -208,7 +208,7 @@ func (rd *remoteDelivery) attemptMX(ctx context.Context, conn *mxConn, record *n func (rd *remoteDelivery) closeConn(c *mxConn) { if err := c.Close(); err != nil { - rd.Log.Error("client connection close failed", err) + rd.log.Error("client connection close failed", err) } } @@ -228,10 +228,10 @@ func (rd *remoteDelivery) connectionForDomain(ctx context.Context, domain string // connection with weaker security. if pooledConn != nil && !rd.msgMeta.SMTPOpts.RequireTLS { conn = pooledConn.(*mxConn) - rd.Log.Msg("reusing cached connection", "domain", domain, "transactions_counter", conn.transactions, + rd.log.Msg("reusing cached connection", "domain", domain, "transactions_counter", conn.transactions, "local_addr", conn.LocalAddr(), "remote_addr", conn.RemoteAddr()) } else { - rd.Log.DebugMsg("opening new connection", "domain", domain, "cache_ignored", pooledConn != nil) + rd.log.DebugMsg("opening new connection", "domain", domain, "cache_ignored", pooledConn != nil) conn, err = rd.newConn(ctx, domain) if err != nil { return nil, err @@ -302,7 +302,7 @@ func (rd *remoteDelivery) newConn(ctx context.Context, domain string) (*mxConn, } conn.Dialer = rd.rt.dialer - conn.Log = rd.Log + conn.Log = rd.log conn.Hostname = rd.rt.hostname conn.AddrInSMTPMsg = true if rd.rt.connectTimeout != 0 { @@ -340,7 +340,7 @@ func (rd *remoteDelivery) newConn(ctx context.Context, domain string) (*mxConn, if err := rd.attemptMX(ctx, &conn, record); err != nil { if len(records) != 0 { - rd.Log.Error("cannot use MX", err, "remote_server", record.Host, "domain", domain) + rd.log.Error("cannot use MX", err, "remote_server", record.Host, "domain", domain) } lastErr = err continue diff --git a/internal/target/remote/dane_delivery_test.go b/internal/target/remote/dane_delivery_test.go index 8d1cec22..cb441aca 100644 --- a/internal/target/remote/dane_delivery_test.go +++ b/internal/target/remote/dane_delivery_test.go @@ -35,7 +35,7 @@ import ( func targetWithExtResolver(t *testing.T, zones map[string]mockdns.Zone) (*mockdns.Server, *Target) { l := testutils.Logger(t, "mockdns") - dnsSrv, err := mockdns.NewServerWithLogger(zones, &l, false) + dnsSrv, err := mockdns.NewServerWithLogger(zones, l, false) if err != nil { t.Fatal(err) } diff --git a/internal/target/remote/mxauth_test.go b/internal/target/remote/mxauth_test.go index 5f698554..8a9e7e37 100644 --- a/internal/target/remote/mxauth_test.go +++ b/internal/target/remote/mxauth_test.go @@ -337,7 +337,7 @@ func TestRemoteDelivery_AuthMX_DNSSEC(t *testing.T) { } l := testutils.Logger(t, "mockdns") - dnsSrv, err := mockdns.NewServerWithLogger(zones, &l, false) + dnsSrv, err := mockdns.NewServerWithLogger(zones, l, false) if err != nil { t.Fatal(err) } @@ -383,7 +383,7 @@ func TestRemoteDelivery_AuthMX_DNSSEC_Fail(t *testing.T) { } l := testutils.Logger(t, "mockdns") - dnsSrv, err := mockdns.NewServerWithLogger(zones, &l, false) + dnsSrv, err := mockdns.NewServerWithLogger(zones, l, false) if err != nil { t.Fatal(err) } diff --git a/internal/target/remote/policy_group.go b/internal/target/remote/policy_group.go index 992bcfa4..a7d949b6 100644 --- a/internal/target/remote/policy_group.go +++ b/internal/target/remote/policy_group.go @@ -21,7 +21,9 @@ package remote import ( "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) // PolicyGroup is a module container for a group of Policy implementations. @@ -96,7 +98,7 @@ func (pg *PolicyGroup) InstanceName() string { } func init() { - module.Register("mx_auth", func(_, instName string) (module.Module, error) { + modules.Register("mx_auth", func(_ *container.C, _, instName string) (module.Module, error) { return &PolicyGroup{ instName: instName, pols: map[string]module.MXAuthPolicy{}, diff --git a/internal/target/remote/remote.go b/internal/target/remote/remote.go index b382a88b..281b7ce6 100644 --- a/internal/target/remote/remote.go +++ b/internal/target/remote/remote.go @@ -41,10 +41,12 @@ import ( "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" tls2 "github.com/foxcpp/maddy/framework/config/tls" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/dns" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/limits" "github.com/foxcpp/maddy/internal/smtpconn/pool" "github.com/foxcpp/maddy/internal/target" @@ -78,7 +80,7 @@ type Target struct { pool *pool.P connReuseLimit int - Log log.Logger + log *log.Logger connectTimeout time.Duration commandTimeout time.Duration @@ -87,13 +89,13 @@ type Target struct { var _ module.DeliveryTarget = &Target{} -func New(_, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { // Keep this synchronized with testTarget. return &Target{ name: instName, resolver: dns.DefaultResolver(), dialer: (&net.Dialer{}).DialContext, - Log: log.Logger{Name: "remote"}, + log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -105,13 +107,13 @@ func (rt *Target) Configure(inlineArgs []string, cfg *config.Map) error { var err error rt.extResolver, err = dns.NewExtResolver() if err != nil { - rt.Log.Error("cannot initialize DNSSEC-aware resolver, DNSSEC and DANE are not available", err) + rt.log.Error("cannot initialize DNSSEC-aware resolver, DNSSEC and DANE are not available", err) } cfg.String("hostname", true, true, "", &rt.hostname) cfg.String("local_ip", false, false, "", &rt.localIP) cfg.Bool("force_ipv4", false, false, &rt.ipv4) - cfg.Bool("debug", true, false, &rt.Log.Debug) + cfg.Bool("debug", true, false, &rt.log.Debug) cfg.Custom("tls_client", true, false, func() (interface{}, error) { return &tls.Config{}, nil }, tls2.TLSClientBlock, &rt.tlsConfig) @@ -206,8 +208,8 @@ func (rt *Target) InstanceName() string { type remoteDelivery struct { rt *Target mailFrom string - msgMeta *module.MsgMetadata - Log log.Logger + msgMeta *module.MsgMetadata + log *log.Logger recipients []string connections map[string]*mxConn @@ -269,7 +271,7 @@ func (rt *Target) StartDelivery(ctx context.Context, msgMeta *module.MsgMetadata rt: rt, mailFrom: mailFrom, msgMeta: msgMeta, - Log: target.DeliveryLogger(rt.Log, msgMeta), + log: target.DeliveryLogger(rt.log, msgMeta), connections: map[string]*mxConn{}, policies: policies, }, nil @@ -424,7 +426,7 @@ func (rd *remoteDelivery) BodyNonAtomic(ctx context.Context, c module.StatusColl } defer func() { if err := bodyR.Close(); err != nil { - rd.Log.Error("failed to close message buffer", err) + rd.log.Error("failed to close message buffer", err) } }() @@ -456,11 +458,11 @@ func (rd *remoteDelivery) Close() error { conn.transactions++ if !conn.Usable() { - rd.Log.Debugf("disconnected %v from %s (errored=%v,transactions=%v,disconnected before=%v)", + rd.log.Debugf("disconnected %v from %s (errored=%v,transactions=%v,disconnected before=%v)", conn.LocalAddr(), conn.ServerName(), conn.errored, conn.transactions, conn.Client() == nil) rd.closeConn(conn) } else { - rd.Log.Debugf("returning connection %v for %s to pool", conn.LocalAddr(), conn.ServerName()) + rd.log.Debugf("returning connection %v for %s to pool", conn.LocalAddr(), conn.ServerName()) rd.rt.pool.Return(conn.domain, conn) } } @@ -489,5 +491,5 @@ func (rd *remoteDelivery) Close() error { } func init() { - module.Register("target.remote", New) + modules.Register("target.remote", New) } diff --git a/internal/target/remote/remote_test.go b/internal/target/remote/remote_test.go index 1578303b..73bc0231 100644 --- a/internal/target/remote/remote_test.go +++ b/internal/target/remote/remote_test.go @@ -34,6 +34,7 @@ import ( "github.com/foxcpp/go-mtasts" "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/dns" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/module" @@ -59,7 +60,7 @@ func testTarget(t *testing.T, zones map[string]mockdns.Zone, extResolver *dns.Ex dialer: resolver.DialContext, extResolver: extResolver, tlsConfig: &tls.Config{}, - Log: testutils.Logger(t, "remote"), + log: testutils.Logger(t, "remote"), policies: extraPolicies, limits: &limits.Group{}, pool: pool.New(pool.Config{ @@ -74,7 +75,7 @@ func testTarget(t *testing.T, zones map[string]mockdns.Zone, extResolver *dns.Ex } func testSTSPolicy(t *testing.T, zones map[string]mockdns.Zone, mtastsGet func(context.Context, string) (*mtasts.Policy, error)) *mtastsPolicy { - m, err := NewMTASTSPolicy("mx_auth.mtasts", "test") + m, err := NewMTASTSPolicy(container.New(),"mx_auth.mtasts", "test") if err != nil { t.Fatal(err) } @@ -100,7 +101,7 @@ func testSTSPolicy(t *testing.T, zones map[string]mockdns.Zone, mtastsGet func(c } func testDANEPolicy(t *testing.T, extR *dns.ExtResolver) *danePolicy { - m, err := NewDANEPolicy("mx_auth.dane", "test") + m, err := NewDANEPolicy(container.New(),"mx_auth.dane", "test") if err != nil { t.Fatal(err) } diff --git a/internal/target/remote/security.go b/internal/target/remote/security.go index efbbb1d2..c40c5fc1 100644 --- a/internal/target/remote/security.go +++ b/internal/target/remote/security.go @@ -28,11 +28,13 @@ import ( "github.com/foxcpp/go-mtasts" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/dns" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/future" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/target" ) @@ -41,21 +43,21 @@ type ( cache *mtasts.Cache mtastsGet func(context.Context, string) (*mtasts.Policy, error) updaterStop chan struct{} - log log.Logger + log *log.Logger instName string } mtastsDelivery struct { c *mtastsPolicy domain string policyFut *future.Future - log log.Logger + log *log.Logger } ) -func NewMTASTSPolicy(_, instName string) (module.Module, error) { +func NewMTASTSPolicy(c *container.C, modName, instName string) (module.Module, error) { return &mtastsPolicy{ instName: instName, - log: log.Logger{Name: "mx_auth.mtasts", Debug: log.DefaultLogger.Debug}, + log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -238,14 +240,14 @@ func (c *mtastsDelivery) Reset(msgMeta *module.MsgMetadata) { // Stub that will be removed in 0.5. type stsPreloadPolicy struct { - log log.Logger + log *log.Logger instName string } -func NewSTSPreload(_, instName string) (module.Module, error) { +func NewSTSPreload(c *container.C, modName, instName string) (module.Module, error) { return &stsPreloadPolicy{ instName: instName, - log: log.Logger{Name: "mx_auth.sts_preload", Debug: log.DefaultLogger.Debug}, + log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -300,7 +302,7 @@ type dnssecPolicy struct { instName string } -func NewDNSSECPolicy(_, instName string) (module.Module, error) { +func NewDNSSECPolicy(_ *container.C, _, instName string) (module.Module, error) { return &dnssecPolicy{ instName: instName, }, nil @@ -345,7 +347,7 @@ func (dnssecPolicy) CheckConn(ctx context.Context, mxLevel module.MXLevel, tlsLe type ( danePolicy struct { extResolver *dns.ExtResolver - log log.Logger + log *log.Logger instName string } daneDelivery struct { @@ -354,10 +356,10 @@ type ( } ) -func NewDANEPolicy(_, instName string) (module.Module, error) { +func NewDANEPolicy(c *container.C, modName, instName string) (module.Module, error) { return &danePolicy{ instName: instName, - log: log.Logger{Name: "remote/dane", Debug: log.DefaultLogger.Debug}, + log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -529,7 +531,7 @@ type ( } ) -func NewLocalPolicy(_, instName string) (module.Module, error) { +func NewLocalPolicy(_ *container.C, _, instName string) (module.Module, error) { return &localPolicy{ instName: instName, }, nil @@ -623,9 +625,9 @@ func (l *localPolicy) CheckConn(ctx context.Context, mxLevel module.MXLevel, tls } func init() { - module.Register("mx_auth.mtasts", NewMTASTSPolicy) - module.Register("mx_auth.sts_preload", NewSTSPreload) - module.Register("mx_auth.dnssec", NewDNSSECPolicy) - module.Register("mx_auth.dane", NewDANEPolicy) - module.Register("mx_auth.local_policy", NewLocalPolicy) + modules.Register("mx_auth.mtasts", NewMTASTSPolicy) + modules.Register("mx_auth.sts_preload", NewSTSPreload) + modules.Register("mx_auth.dnssec", NewDNSSECPolicy) + modules.Register("mx_auth.dane", NewDANEPolicy) + modules.Register("mx_auth.local_policy", NewLocalPolicy) } diff --git a/internal/target/skeleton.go b/internal/target/skeleton.go index 00d481cc..488ce813 100644 --- a/internal/target/skeleton.go +++ b/internal/target/skeleton.go @@ -31,6 +31,7 @@ import ( "github.com/foxcpp/maddy/framework/config" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) const modName = "target.target_name" @@ -126,5 +127,5 @@ func (d *delivery) Commit(ctx context.Context) error { } func init() { - module.Register(modName, New) + modules.Register(modName, New) } diff --git a/internal/target/smtp/smtp_downstream.go b/internal/target/smtp/smtp_downstream.go index 6c678f0a..5fbbd751 100644 --- a/internal/target/smtp/smtp_downstream.go +++ b/internal/target/smtp/smtp_downstream.go @@ -39,9 +39,11 @@ import ( "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" tls2 "github.com/foxcpp/maddy/framework/config/tls" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/exterrors" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/smtpconn" "github.com/foxcpp/maddy/internal/target" "golang.org/x/net/idna" @@ -62,7 +64,7 @@ type Downstream struct { commandTimeout time.Duration submissionTimeout time.Duration - log log.Logger + log *log.Logger } func (u *Downstream) moduleError(err error) error { @@ -75,12 +77,12 @@ func (u *Downstream) moduleError(err error) error { }) } -func NewDownstream(modName, instName string) (module.Module, error) { +func New(c *container.C, modName, instName string) (module.Module, error) { return &Downstream{ modName: modName, instName: instName, - lmtp: modName == "target.lmtp" || modName == "lmtp_downstream", /* compatibility with 0.3 configs */ - log: log.Logger{Name: modName}, + lmtp: modName == "target.lmtp", + log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -167,7 +169,7 @@ func (u *Downstream) InstanceName() string { type delivery struct { u *Downstream - log log.Logger + log *log.Logger msgMeta *module.MsgMetadata mailFrom string @@ -344,6 +346,6 @@ func (d *delivery) Commit(ctx context.Context) error { } func init() { - module.Register("target.smtp", NewDownstream) - module.Register("target.lmtp", NewDownstream) + modules.Register("target.smtp", New) + modules.Register("target.lmtp", New) } diff --git a/internal/target/smtp/smtputf8_test.go b/internal/target/smtp/smtputf8_test.go index 1f469e8a..7c8962e0 100644 --- a/internal/target/smtp/smtputf8_test.go +++ b/internal/target/smtp/smtputf8_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/internal/testutils" "github.com/stretchr/testify/require" ) @@ -33,7 +34,7 @@ func TestDownstreamDelivery_EHLO_ALabel(t *testing.T) { }() defer testutils.CheckSMTPConnLeak(t, srv) - mod, err := NewDownstream("", "") + mod, err := New(container.New(), "", "") if err != nil { t.Fatal(err) } diff --git a/internal/testutils/check.go b/internal/testutils/check.go index 2c44883d..7c829cd5 100644 --- a/internal/testutils/check.go +++ b/internal/testutils/check.go @@ -24,7 +24,9 @@ import ( "github.com/emersion/go-message/textproto" "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) type Check struct { @@ -104,7 +106,7 @@ func (cs *checkState) Close() error { } func init() { - module.Register("test_check", func(_, _ string) (module.Module, error) { + modules.Register("test_check", func(_ *container.C, _, _ string) (module.Module, error) { return &Check{}, nil }) } diff --git a/internal/testutils/logger.go b/internal/testutils/logger.go index 9fd55061..712d53bb 100644 --- a/internal/testutils/logger.go +++ b/internal/testutils/logger.go @@ -33,16 +33,18 @@ var ( directLog = flag.Bool("test.directlog", false, "(maddy) Log to stderr instead of test log") ) -func Logger(t *testing.T, name string) log.Logger { +func Logger(t *testing.T, name string) *log.Logger { if *directLog { - return log.Logger{ - Out: log.WriterOutput(os.Stderr, true), - Name: name, - Debug: *debugLog, + return &log.Logger{ + Parent: &log.DefaultLogger, // silence "no parent" warning + Out: log.WriterOutput(os.Stderr, true), + Name: name, + Debug: *debugLog, } } - return log.Logger{ + return &log.Logger{ + Parent: &log.DefaultLogger, Out: log.FuncOutput(func(_ time.Time, debug bool, str string) { t.Helper() str = strings.TrimSuffix(str, "\n") diff --git a/internal/testutils/modifier.go b/internal/testutils/modifier.go index 1c6f135a..c276b925 100644 --- a/internal/testutils/modifier.go +++ b/internal/testutils/modifier.go @@ -24,7 +24,9 @@ import ( "github.com/emersion/go-message/textproto" "github.com/foxcpp/maddy/framework/buffer" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) type Modifier struct { @@ -115,7 +117,7 @@ func (ms modifierState) Close() error { } func init() { - module.Register("test_modifier", func(_, _ string) (module.Module, error) { + modules.Register("test_modifier", func(_ *container.C, _, _ string) (module.Module, error) { return &Modifier{}, nil }) } diff --git a/internal/tls/acme/acme.go b/internal/tls/acme/acme.go index 0ebcd241..ca171616 100644 --- a/internal/tls/acme/acme.go +++ b/internal/tls/acme/acme.go @@ -9,9 +9,11 @@ import ( "github.com/caddyserver/certmagic" "github.com/foxcpp/maddy/framework/config" modconfig "github.com/foxcpp/maddy/framework/config/module" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/hooks" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) const modName = "tls.loader.acme" @@ -25,13 +27,13 @@ type Loader struct { cfg *certmagic.Config cancelManage context.CancelFunc - log log.Logger + log *log.Logger } -func New(_, instName string) (module.Module, error) { +func New(c *container.C, _, instName string) (module.Module, error) { return &Loader{ instName: instName, - log: log.Logger{Name: modName}, + log: c.DefaultLogger.Sublogger(modName), }, nil } @@ -163,5 +165,5 @@ func init() { func init() { var _ module.TLSLoader = &Loader{} - module.Register(modName, New) + modules.Register(modName, New) } diff --git a/internal/tls/file.go b/internal/tls/file.go index ae260ec6..ab7b90ed 100644 --- a/internal/tls/file.go +++ b/internal/tls/file.go @@ -27,15 +27,17 @@ import ( "time" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) type FileLoader struct { instName string certPaths []string keyPaths []string - log log.Logger + log *log.Logger certs []tls.Certificate certsLock sync.RWMutex @@ -44,10 +46,10 @@ type FileLoader struct { stopTick chan struct{} } -func NewFileLoader(_, instName string) (module.Module, error) { +func NewFileLoader(c *container.C, modName, instName string) (module.Module, error) { return &FileLoader{ instName: instName, - log: log.Logger{Name: "tls.loader.file", Debug: log.DefaultLogger.Debug}, + log: c.DefaultLogger.Sublogger(modName), stopTick: make(chan struct{}), }, nil } @@ -163,5 +165,5 @@ func (f *FileLoader) ConfigureTLS(c *tls.Config) error { func init() { var _ module.TLSLoader = &FileLoader{} - module.Register("tls.loader.file", NewFileLoader) + modules.Register("tls.loader.file", NewFileLoader) } diff --git a/internal/tls/self_signed.go b/internal/tls/self_signed.go index 269b0c0d..1d6c5134 100644 --- a/internal/tls/self_signed.go +++ b/internal/tls/self_signed.go @@ -30,7 +30,9 @@ import ( "time" "github.com/foxcpp/maddy/framework/config" + "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" ) type SelfSignedLoader struct { @@ -40,7 +42,7 @@ type SelfSignedLoader struct { cert tls.Certificate } -func NewSelfSignedLoader(_, instName string) (module.Module, error) { +func NewSelfSignedLoader(_ *container.C, _, instName string) (module.Module, error) { return &SelfSignedLoader{ instName: instName, }, nil @@ -108,5 +110,5 @@ func (f *SelfSignedLoader) ConfigureTLS(c *tls.Config) error { func init() { var _ module.TLSLoader = &SelfSignedLoader{} - module.Register("tls.loader.self_signed", NewSelfSignedLoader) + modules.Register("tls.loader.self_signed", NewSelfSignedLoader) } diff --git a/internal/updatepipe/pubsub/pq.go b/internal/updatepipe/pubsub/pq.go index c084e600..bee74922 100644 --- a/internal/updatepipe/pubsub/pq.go +++ b/internal/updatepipe/pubsub/pq.go @@ -20,12 +20,12 @@ type PqPubSub struct { L *pq.Listener sender *sql.DB - Log log.Logger + Log *log.Logger } func NewPQ(dsn string) (*PqPubSub, error) { l := &PqPubSub{ - Log: log.Logger{Name: "pgpubsub"}, + Log: log.DefaultLogger.Sublogger("pgpubsub"), Notify: make(chan Msg), } l.L = pq.NewListener(dsn, 10*time.Second, time.Minute, l.eventHandler) diff --git a/internal/updatepipe/pubsub_pipe.go b/internal/updatepipe/pubsub_pipe.go index b1cef67f..4341f4d3 100644 --- a/internal/updatepipe/pubsub_pipe.go +++ b/internal/updatepipe/pubsub_pipe.go @@ -13,7 +13,7 @@ import ( type PubSubPipe struct { PubSub pubsub.PubSub - Log log.Logger + Log *log.Logger } func (p *PubSubPipe) Listen(upds chan<- mess.Update) error { diff --git a/internal/updatepipe/unix_pipe.go b/internal/updatepipe/unix_pipe.go index a2042d62..8cd124d8 100644 --- a/internal/updatepipe/unix_pipe.go +++ b/internal/updatepipe/unix_pipe.go @@ -45,7 +45,7 @@ import ( // is initialized on the first call to Listen or (Init)Push. type UnixSockPipe struct { SockPath string - Log log.Logger + Log *log.Logger listener net.Listener sender net.Conn diff --git a/maddy.go b/maddy.go index 5c9381d2..c3a560bc 100644 --- a/maddy.go +++ b/maddy.go @@ -36,7 +36,7 @@ import ( "github.com/foxcpp/maddy/framework/container" "github.com/foxcpp/maddy/framework/hooks" "github.com/foxcpp/maddy/framework/log" - "github.com/foxcpp/maddy/framework/module" + "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/framework/resource/netresource" "github.com/foxcpp/maddy/internal/authz" maddycli "github.com/foxcpp/maddy/internal/cli" @@ -208,11 +208,11 @@ func Run(c *cli.Context) error { } hooks.AddHook(hooks.EventLogRotate, reinitLogging) - defer func() { - if err := log.DefaultLogger.Out.Close(); err != nil { + defer func(out log.Output) { + if err := out.Close(); err != nil { log.Println("failed to close default logger output:", err) } - }() + }(log.DefaultLogger.Out) defer hooks.RunHooks(hooks.EventShutdown) defer func() { @@ -239,7 +239,7 @@ func VerifyConfig(c *cli.Context) error { return cli.Exit(err.Error(), 2) } - log.DefaultLogger.Msg("No errors detected") + _, _ = fmt.Fprintln(os.Stderr, "No errors detected") return nil } @@ -337,7 +337,7 @@ func ReadGlobals(c *container.C, cfg []config.Node) (map[string]interface{}, []c globals.Bool("auth_perdomain", false, false, nil) globals.StringList("auth_domains", false, false, nil, nil) globals.Custom("log", false, false, defaultLogOutput, logOutput, &c.DefaultLogger.Out) - globals.Bool("debug", false, log.DefaultLogger.Debug, &c.DefaultLogger.Debug) + globals.Bool("debug", false, false, &c.DefaultLogger.Debug) config.EnumMapped(globals, "auth_map_normalize", true, false, authz.NormalizeFuncs, authz.NormalizeAuto, nil) modconfig.Table(globals, "auth_map", true, false, nil, nil) globals.AllowUnknown() @@ -373,6 +373,11 @@ func moduleConfigure(configPath string) (*container.C, error) { return nil, err } + // ReadGlobals will configure c.DefaultLogger. + if c.DefaultLogger.Out != nil { + log.DefaultLogger.Out = c.DefaultLogger.Out + } + if err := InitDirs(c); err != nil { return nil, err } @@ -397,7 +402,7 @@ func moduleStart(c *container.C) error { func moduleStop(c *container.C, earlyStop bool) error { if earlyStop { if err := c.Lifetime.EarlyStopAll(); err != nil { - log.DefaultLogger.Error("early stop failed", err) + c.DefaultLogger.Error("early stop failed", err) } } @@ -444,6 +449,12 @@ func moduleMain(configPath string) error { } c.DefaultLogger.Msg("server stopped") + if c.DefaultLogger.Out != nil { + if err := c.DefaultLogger.Out.Close(); err != nil { + log.DefaultLogger.Error("failed to close output logger", err) + } + } + return nil } @@ -451,26 +462,40 @@ func moduleReload(oldContainer *container.C, configPath string, asyncStopWg *syn oldContainer.DefaultLogger.Msg("reloading server...") systemdStatus(SDReloading, "Reloading server...") + rollbackReload := func() { + // Restore DefaultLogger config that might be set by moduleConfig + log.DefaultLogger.Out = oldContainer.DefaultLogger.Out + } + oldContainer.DefaultLogger.Msg("loading new configuration...") newContainer, err := moduleConfigure(configPath) if err != nil { + rollbackReload() oldContainer.DefaultLogger.Error("failed to load new configuration", err) + return oldContainer } oldContainer.DefaultLogger.Msg("configuration loaded") + rollbackReload = func() { + // Restore DefaultLogger config that might be set by moduleConfig + log.DefaultLogger.Out = oldContainer.DefaultLogger.Out + container.Global = oldContainer + } if err := oldContainer.Lifetime.EarlyStopAll(); err != nil { + rollbackReload() oldContainer.DefaultLogger.Error("failed to early-stop old server", err) - container.Global = oldContainer + return oldContainer } netresource.ResetListenersUsage() oldContainer.DefaultLogger.Msg("starting new server") if err := moduleStart(newContainer); err != nil { + rollbackReload() oldContainer.DefaultLogger.Error("failed to start new server", err) - container.Global = oldContainer + return oldContainer } @@ -492,6 +517,9 @@ func moduleReload(oldContainer *container.C, configPath string, asyncStopWg *syn oldContainer.DefaultLogger.Error("moduleStop failed", err) } oldContainer.DefaultLogger.Msg("old server stopped") + if err := oldContainer.DefaultLogger.Out.Close(); err != nil { + newContainer.DefaultLogger.Error("failed to close old server log", err) + } systemdStatus(SDReloading, "Configuration running.") }() @@ -501,7 +529,7 @@ func moduleReload(oldContainer *container.C, configPath string, asyncStopWg *syn func RegisterModules(c *container.C, globals map[string]interface{}, nodes []config.Node) (err error) { var endpoints []struct { - Endpoint module.LifetimeModule + Endpoint container.LifetimeModule Cfg *config.Map } @@ -517,26 +545,26 @@ func RegisterModules(c *container.C, globals map[string]interface{}, nodes []con modName := block.Name - endpFactory := module.GetEndpoint(modName) + endpFactory := modules.GetEndpoint(modName) if endpFactory != nil { - inst, err := endpFactory(modName, block.Args) + inst, err := endpFactory(c, modName, block.Args) if err != nil { return err } endpoints = append(endpoints, struct { - Endpoint module.LifetimeModule + Endpoint container.LifetimeModule Cfg *config.Map }{Endpoint: inst, Cfg: config.NewMap(globals, block)}) continue } - factory := module.Get(modName) + factory := modules.Get(modName) if factory == nil { return config.NodeErr(block, "unknown module or global directive: %s", modName) } - inst, err := factory(modName, instName) + inst, err := factory(c, modName, instName) if err != nil { return err } @@ -547,13 +575,13 @@ func RegisterModules(c *container.C, globals map[string]interface{}, nodes []con return err } - if lt, ok := inst.(module.LifetimeModule); ok { + if lt, ok := inst.(container.LifetimeModule); ok { c.Lifetime.Add(lt) } return nil }) if err != nil { - if errors.Is(err, module.ErrInstanceNameDuplicate) { + if errors.Is(err, container.ErrInstanceNameDuplicate) { return config.NodeErr(block, "config block named %s already exists", inst.InstanceName()) } return err @@ -561,7 +589,7 @@ func RegisterModules(c *container.C, globals map[string]interface{}, nodes []con for _, alias := range modAliases { if err := c.Modules.AddAlias(instName, alias); err != nil { - if errors.Is(err, module.ErrInstanceNameDuplicate) { + if errors.Is(err, container.ErrInstanceNameDuplicate) { return config.NodeErr(block, "config block named %s already exists", alias) } return err From b5bd761afd3f7324c16051277772faf24c955869 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Fri, 1 May 2026 01:01:45 +0300 Subject: [PATCH 102/111] cli: Remove old-style maddyctl compatibility (pre-0.6 binary split) --- internal/cli/app.go | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/internal/cli/app.go b/internal/cli/app.go index 55ab1862..2e6011e6 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "os" - "strings" "github.com/foxcpp/maddy/framework/log" "github.com/urfave/cli/v2" @@ -81,17 +80,6 @@ func AddGlobalFlag(f cli.Flag) { func AddSubcommand(cmd *cli.Command) { app.Commands = append(app.Commands, cmd) - - if cmd.Name == "run" { - // Backward compatibility hack to start the server as just ./maddy - // Needs to be done here so we will register all known flags with - // stdlib before Run is called. - app.Action = func(c *cli.Context) error { - log.Println("WARNING: Starting server not via 'maddy run' is deprecated and will stop working in the next version") - return cmd.Action(c) - } - app.Flags = append(app.Flags, cmd.Flags...) - } } // RunWithoutExit is like Run but returns exit code instead of calling os.Exit @@ -114,15 +102,6 @@ func Run() { // Actual entry point is registered in maddy.go. - // Print help when called via maddyctl executable. To be removed - // once backward compatibility hack for 'maddy run' is removed too. - if strings.Contains(os.Args[0], "maddyctl") && len(os.Args) == 1 { - if err := app.Run([]string{os.Args[0], "help"}); err != nil { - log.DefaultLogger.Error("app.Run failed", err) - } - return - } - if err := app.Run(os.Args); err != nil { log.DefaultLogger.Error("app.Run failed", err) } From 6885f8531969b81e17330c32d8a7453b669a00c6 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Fri, 1 May 2026 01:03:30 +0300 Subject: [PATCH 103/111] Fix formatting issues, linter warnings and test errors after logger refactor --- framework/dns/dnssec_test.go | 6 +++++- internal/table/file_test.go | 2 +- internal/table/sql_query_test.go | 2 +- internal/target/remote/remote.go | 4 ++-- internal/target/remote/remote_test.go | 4 ++-- tests/t.go | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/framework/dns/dnssec_test.go b/framework/dns/dnssec_test.go index 325b14fe..8a6de426 100644 --- a/framework/dns/dnssec_test.go +++ b/framework/dns/dnssec_test.go @@ -129,7 +129,11 @@ func TestExtResolver_AuthLookupIPAddr(t *testing.T) { // AD flag handling for use in DANE algorithms. // Silence log messages about disregarded I/O errors. - log.DefaultLogger.Out = nil + oldLog := log.DefaultLogger + log.DefaultLogger = log.NopLogger + t.Cleanup(func() { + log.DefaultLogger = oldLog + }) test := func(aAct, aaaaAct TestSrvAction, aAD, aaaaAD, ad bool, addrs []net.IP, err bool) { t.Helper() diff --git a/internal/table/file_test.go b/internal/table/file_test.go index 33cabe05..20335eb1 100644 --- a/internal/table/file_test.go +++ b/internal/table/file_test.go @@ -119,7 +119,7 @@ func TestFileReload(t *testing.T) { t.Fatal(err) } - mod, err := NewFile(container.New(),"", "") + mod, err := NewFile(container.New(), "", "") if err != nil { t.Fatal(err) } diff --git a/internal/table/sql_query_test.go b/internal/table/sql_query_test.go index ac976f6c..a81f1bfe 100644 --- a/internal/table/sql_query_test.go +++ b/internal/table/sql_query_test.go @@ -34,7 +34,7 @@ import ( func TestSQL(t *testing.T) { path := testutils.Dir(t) - mod, err := NewSQL(container.New(),"sql_table", "") + mod, err := NewSQL(container.New(), "sql_table", "") if err != nil { t.Fatal("Module create failed:", err) } diff --git a/internal/target/remote/remote.go b/internal/target/remote/remote.go index 281b7ce6..93b26573 100644 --- a/internal/target/remote/remote.go +++ b/internal/target/remote/remote.go @@ -208,8 +208,8 @@ func (rt *Target) InstanceName() string { type remoteDelivery struct { rt *Target mailFrom string - msgMeta *module.MsgMetadata - log *log.Logger + msgMeta *module.MsgMetadata + log *log.Logger recipients []string connections map[string]*mxConn diff --git a/internal/target/remote/remote_test.go b/internal/target/remote/remote_test.go index 73bc0231..ca4fd656 100644 --- a/internal/target/remote/remote_test.go +++ b/internal/target/remote/remote_test.go @@ -75,7 +75,7 @@ func testTarget(t *testing.T, zones map[string]mockdns.Zone, extResolver *dns.Ex } func testSTSPolicy(t *testing.T, zones map[string]mockdns.Zone, mtastsGet func(context.Context, string) (*mtasts.Policy, error)) *mtastsPolicy { - m, err := NewMTASTSPolicy(container.New(),"mx_auth.mtasts", "test") + m, err := NewMTASTSPolicy(container.New(), "mx_auth.mtasts", "test") if err != nil { t.Fatal(err) } @@ -101,7 +101,7 @@ func testSTSPolicy(t *testing.T, zones map[string]mockdns.Zone, mtastsGet func(c } func testDANEPolicy(t *testing.T, extR *dns.ExtResolver) *danePolicy { - m, err := NewDANEPolicy(container.New(),"mx_auth.dane", "test") + m, err := NewDANEPolicy(container.New(), "mx_auth.dane", "test") if err != nil { t.Fatal(err) } diff --git a/tests/t.go b/tests/t.go index 58d021d0..a2795e7b 100644 --- a/tests/t.go +++ b/tests/t.go @@ -217,7 +217,7 @@ func (t *T) buildCmd(additionalArgs ...string) *exec.Cmd { args := []string{"-config", filepath.Join(t.testDir, "maddy.conf"), "-debug.smtpport", remoteSmtp, "-debug.dnsoverride", t.dnsServ.LocalAddr().String(), - "-log", "/tmp/test.log"} + } if CoverageOut != "" { args = append(args, "-test.coverprofile", CoverageOut+"."+strconv.FormatInt(time.Now().UnixNano(), 16)) From 837b1b8a7761eaaa9ae2b3ff8c9b3a4495e9821e Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Fri, 1 May 2026 01:15:36 +0300 Subject: [PATCH 104/111] maddy 0.9.4 --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index 965065db..a602fc9e 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -0.9.3 +0.9.4 From 1c1a01ae4b7ec6bd3a43f48fdfa00884db92b8e4 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年5月16日 01:24:05 +0300 Subject: [PATCH 105/111] msgpipeline: Ensure logger is always initialized for nested pipelines --- internal/msgpipeline/msgpipeline.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/msgpipeline/msgpipeline.go b/internal/msgpipeline/msgpipeline.go index 8fc9c54d..6025bf06 100644 --- a/internal/msgpipeline/msgpipeline.go +++ b/internal/msgpipeline/msgpipeline.go @@ -90,6 +90,7 @@ func New(globals map[string]interface{}, cfg []config.Node) (*MsgPipeline, error return &MsgPipeline{ msgpipelineCfg: parsedCfg, Resolver: dns.DefaultResolver(), + Log: log.DefaultLogger.Sublogger("msgpipeline"), }, err } From fd6d880141d880251fba84eeb0a171648506393a Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年5月16日 02:06:40 +0300 Subject: [PATCH 106/111] target/remote: Fix dangling destination limiter on first RCPT error, add debug logs Might fix #842. --- internal/endpoint/smtp/smtp.go | 2 +- internal/limits/limits.go | 37 ++++++++++++++++++++++++--- internal/smtpconn/smtpconn.go | 2 +- internal/target/remote/connect.go | 6 +++++ internal/target/remote/remote.go | 3 ++- internal/target/remote/remote_test.go | 2 +- 6 files changed, 44 insertions(+), 8 deletions(-) diff --git a/internal/endpoint/smtp/smtp.go b/internal/endpoint/smtp/smtp.go index eb334556..acfcca3b 100644 --- a/internal/endpoint/smtp/smtp.go +++ b/internal/endpoint/smtp/smtp.go @@ -274,7 +274,7 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error { cfg.Bool("defer_sender_reject", false, true, &endp.deferServerReject) cfg.Int("max_logged_rcpt_errors", false, false, 5, &endp.maxLoggedRcptErrors) cfg.Custom("limits", false, false, func() (interface{}, error) { - return &limits.Group{}, nil + return limits.Empty(endp.log.Sublogger("limits")), nil }, func(cfg *config.Map, n config.Node) (interface{}, error) { var g *limits.Group if err := modconfig.GroupFromNode("limits", n.Args, n, cfg.Globals, &g); err != nil { diff --git a/internal/limits/limits.go b/internal/limits/limits.go index eba335d4..66086f7a 100644 --- a/internal/limits/limits.go +++ b/internal/limits/limits.go @@ -28,18 +28,21 @@ package limits import ( "context" + "fmt" "net" "strconv" "time" "github.com/foxcpp/maddy/framework/config" "github.com/foxcpp/maddy/framework/container" + "github.com/foxcpp/maddy/framework/log" "github.com/foxcpp/maddy/framework/module" "github.com/foxcpp/maddy/framework/module/modules" "github.com/foxcpp/maddy/internal/limits/limiters" ) type Group struct { + log *log.Logger instName string global limiters.MultiLimit @@ -48,8 +51,15 @@ type Group struct { dest *limiters.BucketSet // BucketSet of MultiLimit } +func Empty(log *log.Logger) *Group { + return &Group{ + log: log, + } +} + func New(c *container.C, _, instName string) (module.Module, error) { return &Group{ + log: c.DefaultLogger.Sublogger("limits"), instName: instName, }, nil } @@ -177,22 +187,28 @@ func (g *Group) TakeMsg(ctx context.Context, addr net.IP, sourceDomain string) e ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() + g.log.DebugMsg("global TakeContext") if err := g.global.TakeContext(ctx); err != nil { - return err + return fmt.Errorf("TakeMsg: global: %w", err) } + g.log.DebugMsg("global TakeContext done") if g.ip != nil { + g.log.DebugMsg("ip TakeContext", "ip", addr.String()) if err := g.ip.TakeContext(ctx, addr.String()); err != nil { g.global.Release() - return err + return fmt.Errorf("TakeMsg: ip: %w", err) } + g.log.DebugMsg("ip TakeContext done", "ip", addr.String()) } if g.source != nil { + g.log.DebugMsg("source TakeContext", "domain", sourceDomain) if err := g.source.TakeContext(ctx, sourceDomain); err != nil { g.global.Release() g.ip.Release(addr.String()) - return err + return fmt.Errorf("TakeMSg: source: %w", err) } + g.log.DebugMsg("source TakeContext done", "domain", sourceDomain) } return nil } @@ -201,17 +217,27 @@ func (g *Group) TakeDest(ctx context.Context, domain string) error { if g.dest == nil { return nil } + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() - return g.dest.TakeContext(ctx, domain) + + g.log.DebugMsg("TakeDest", "domain", domain) + if err := g.dest.TakeContext(ctx, domain); err != nil { + return fmt.Errorf("TakeDest: dest: %w", err) + } + g.log.DebugMsg("TakeDest done", "domain", domain) + return nil } func (g *Group) ReleaseMsg(addr net.IP, sourceDomain string) { + g.log.DebugMsg("global ReleaseMsg") g.global.Release() if g.ip != nil { + g.log.DebugMsg("ip ReleaseMsg", "ip", addr.String()) g.ip.Release(addr.String()) } if g.source != nil { + g.log.DebugMsg("source ReleaseMsg", "domain", sourceDomain) g.source.Release(sourceDomain) } } @@ -220,7 +246,10 @@ func (g *Group) ReleaseDest(domain string) { if g.dest == nil { return } + + g.log.DebugMsg("ReleaseDest", "domain", domain) g.dest.Release(domain) + g.log.DebugMsg("ReleaseDest done", "domain", domain) } func (g *Group) Name() string { diff --git a/internal/smtpconn/smtpconn.go b/internal/smtpconn/smtpconn.go index 22398937..7ec93cac 100644 --- a/internal/smtpconn/smtpconn.go +++ b/internal/smtpconn/smtpconn.go @@ -239,7 +239,7 @@ func (c *C) attemptConnect(ctx context.Context, lmtp bool, endp config.Endpoint, conn, err = c.Dialer(dialCtx, endp.Network(), endp.Address()) cancel() if err != nil { - return false, nil, nil, err + return false, nil, nil, fmt.Errorf("dialer: %w", err) } if endp.IsTLS() { diff --git a/internal/target/remote/connect.go b/internal/target/remote/connect.go index 4456d927..3b9692d0 100644 --- a/internal/target/remote/connect.go +++ b/internal/target/remote/connect.go @@ -45,6 +45,7 @@ type mxConn struct { errored bool reuseLimit int + takeDest bool // Amount of times connection was used for an SMTP transaction. transactions int @@ -207,6 +208,10 @@ func (rd *remoteDelivery) attemptMX(ctx context.Context, conn *mxConn, record *n } func (rd *remoteDelivery) closeConn(c *mxConn) { + if c.takeDest { + rd.rt.limits.ReleaseDest(c.domain) + } + if err := c.Close(); err != nil { rd.log.Error("client connection close failed", err) } @@ -270,6 +275,7 @@ func (rd *remoteDelivery) connectionForDomain(ctx context.Context, domain string return nil, err } region.End() + conn.takeDest = true // Relaxed REQUIRETLS mode is not conforming to the specification strictly // but allows to start deploying client support for REQUIRETLS without the diff --git a/internal/target/remote/remote.go b/internal/target/remote/remote.go index 93b26573..daf5a874 100644 --- a/internal/target/remote/remote.go +++ b/internal/target/remote/remote.go @@ -130,7 +130,7 @@ func (rt *Target) Configure(inlineArgs []string, cfg *config.Map) error { return p.L, nil }, &rt.policies) cfg.Custom("limits", false, false, func() (interface{}, error) { - return &limits.Group{}, nil + return limits.Empty(rt.log.Sublogger("limits")), nil }, func(cfg *config.Map, n config.Node) (interface{}, error) { var g *limits.Group if err := modconfig.GroupFromNode("limits", n.Args, n, cfg.Globals, &g); err != nil { @@ -455,6 +455,7 @@ func (rd *remoteDelivery) Commit(ctx context.Context) error { func (rd *remoteDelivery) Close() error { for _, conn := range rd.connections { rd.rt.limits.ReleaseDest(conn.domain) + conn.takeDest = false conn.transactions++ if !conn.Usable() { diff --git a/internal/target/remote/remote_test.go b/internal/target/remote/remote_test.go index ca4fd656..06f0cb80 100644 --- a/internal/target/remote/remote_test.go +++ b/internal/target/remote/remote_test.go @@ -62,7 +62,7 @@ func testTarget(t *testing.T, zones map[string]mockdns.Zone, extResolver *dns.Ex tlsConfig: &tls.Config{}, log: testutils.Logger(t, "remote"), policies: extraPolicies, - limits: &limits.Group{}, + limits: limits.Empty(testutils.Logger(t, "limits")), pool: pool.New(pool.Config{ MaxKeys: 5000, MaxConnsPerKey: 5, // basically, max. amount of idle connections in cache From e42741c7577746a474a45a818d554d578f4f853d Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年5月16日 15:59:40 +0300 Subject: [PATCH 107/111] ci: Re-enable arm64 docker builds --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e7155b68..6ec83b75 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -144,7 +144,7 @@ jobs: id: docker with: context: . - platforms: linux/amd64 #,linux/arm64 Temporary disabled due to SIGSEGV in gcc. + platforms: linux/amd64,linux/arm64 file: Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} From 9c78f96902f931383dda12e6f8fdeade2262a010 Mon Sep 17 00:00:00 2001 From: oidq Date: 2026年5月20日 18:42:10 +0200 Subject: [PATCH 108/111] fix: check for nil Out in Logger on Close() * add Logger.Close(), which safely closes underlying Out * should fix #846 --- framework/log/log.go | 9 +++++++++ maddy.go | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/framework/log/log.go b/framework/log/log.go index 3ad291a6..263732a0 100644 --- a/framework/log/log.go +++ b/framework/log/log.go @@ -205,6 +205,15 @@ func (l *Logger) Write(s []byte) (int, error) { return len(s), nil } +// Close closes underlying output in Out. +func (l *Logger) Close() error { + if l.Out == nil { + return nil + } + + return l.Out.Close() +} + // DebugWriter returns a writer that will act like Logger.Write // but will use debug flag on messages. If Logger.Debug is false, // Write method of returned object will be no-op. diff --git a/maddy.go b/maddy.go index c3a560bc..4d7a8179 100644 --- a/maddy.go +++ b/maddy.go @@ -450,7 +450,7 @@ func moduleMain(configPath string) error { c.DefaultLogger.Msg("server stopped") if c.DefaultLogger.Out != nil { - if err := c.DefaultLogger.Out.Close(); err != nil { + if err := c.DefaultLogger.Close(); err != nil { log.DefaultLogger.Error("failed to close output logger", err) } } @@ -517,7 +517,7 @@ func moduleReload(oldContainer *container.C, configPath string, asyncStopWg *syn oldContainer.DefaultLogger.Error("moduleStop failed", err) } oldContainer.DefaultLogger.Msg("old server stopped") - if err := oldContainer.DefaultLogger.Out.Close(); err != nil { + if err := oldContainer.DefaultLogger.Close(); err != nil { newContainer.DefaultLogger.Error("failed to close old server log", err) } From 6200c517c3b3ce95c5f2ba523fc9b911dbc2af51 Mon Sep 17 00:00:00 2001 From: oidq Date: 2026年5月20日 18:48:47 +0200 Subject: [PATCH 109/111] fix(systemd): report READY=1 after reload SystemD would report maddy in "reloading (reload-notify)" state even after successful reload. It should send "READY=1" after finishing the reload. --- maddy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maddy.go b/maddy.go index c3a560bc..952e8354 100644 --- a/maddy.go +++ b/maddy.go @@ -521,7 +521,7 @@ func moduleReload(oldContainer *container.C, configPath string, asyncStopWg *syn newContainer.DefaultLogger.Error("failed to close old server log", err) } - systemdStatus(SDReloading, "Configuration running.") + systemdStatus(SDReady, "Configuration running.") }() return newContainer From 58e8a11423e140ad37063ce8dfc446de4c1591ed Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年5月24日 01:17:29 +0300 Subject: [PATCH 110/111] maddy 0.9.5 --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index a602fc9e..b0bb8785 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -0.9.4 +0.9.5 From 6bfec6cc7240fec7807829ab34e72f40d8411f19 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: 2026年7月13日 22:55:48 +0300 Subject: [PATCH 111/111] log: Fix logger name duplication in zap adapter --- framework/log/log.go | 15 +++++++++++++++ framework/log/zap.go | 9 +++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/framework/log/log.go b/framework/log/log.go index 263732a0..f17a6a0e 100644 --- a/framework/log/log.go +++ b/framework/log/log.go @@ -251,6 +251,21 @@ func (l *Logger) log(debug bool, s string) { // Logging is disabled - do nothing. } +func (l *Logger) logNameOverwrite(loggerName string, debug bool, s string) { + if loggerName == "" { + loggerName = l.Name + } + if loggerName != "" { + s = loggerName + ": " + s + } + + out := l.output() + out.Write(time.Now(), debug, s) + + // Logging is disabled - do nothing. +} + + func (l *Logger) Sublogger(name string) *Logger { if l.Name != "" && name != "" { name = l.Name + "/" + name diff --git a/framework/log/zap.go b/framework/log/zap.go index 893bf484..4dff1510 100644 --- a/framework/log/zap.go +++ b/framework/log/zap.go @@ -46,9 +46,14 @@ func (l zapLogger) Write(entry zapcore.Entry, fields []zapcore.Field) error { f.AddTo(enc) } if entry.LoggerName != "" { - l.L.Name += "/" + entry.LoggerName + l.L.logNameOverwrite( + l.L.Name+"/"+entry.LoggerName, + entry.Level == zapcore.DebugLevel, + l.L.formatMsg(entry.Message, enc.Fields), + ) + } else { + l.L.log(entry.Level == zapcore.DebugLevel, l.L.formatMsg(entry.Message, enc.Fields)) } - l.L.log(entry.Level == zapcore.DebugLevel, l.L.formatMsg(entry.Message, enc.Fields)) return nil }

AltStyle によって変換されたページ (->オリジナル) /