expose fewer internals in packages, for easier software reuse

- prometheus is now behind an interface, they aren't dependencies for the
  reusable components anymore.
- some dependencies have been inverted: instead of packages importing a main
  package to get configuration, the main package now sets configuration in
  these packages. that means fewer internals are pulled in.
- some functions now have new parameters for values that were retrieved from
  package "mox-".
This commit is contained in:
Mechiel Lukkien
2023-12-05 21:13:57 +01:00
parent fcaa504878
commit 72ac1fde29
51 changed files with 696 additions and 568 deletions

View File

@ -18,46 +18,24 @@ import (
"golang.org/x/exp/slog"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/mjl-/adns"
"github.com/mjl-/mox/dane"
"github.com/mjl-/mox/dns"
"github.com/mjl-/mox/metrics"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/mox-"
"github.com/mjl-/mox/moxio"
"github.com/mjl-/mox/sasl"
"github.com/mjl-/mox/smtp"
"github.com/mjl-/mox/stub"
"github.com/mjl-/mox/tlsrpt"
)
// todo future: add function to deliver message to multiple recipients. requires more elaborate return value, indicating success per message: some recipients may succeed, others may fail, and we should still deliver. to prevent backscatter, we also sometimes don't allow multiple recipients. ../rfc/5321:1144
var (
metricCommands = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "mox_smtpclient_command_duration_seconds",
Help: "SMTP client command duration and result codes in seconds.",
Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.100, 0.5, 1, 5, 10, 20, 30, 60, 120},
},
[]string{
"cmd",
"code",
"secode",
},
)
metricTLSRequiredNoIgnored = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "mox_smtpclient_tlsrequiredno_ignored_total",
Help: "Connection attempts with TLS policy findings ignored due to message with TLS-Required: No header. Does not cover case where TLS certificate cannot be PKIX-verified.",
},
[]string{
"ignored", // daneverification (no matching tlsa record)
},
)
MetricCommands stub.HistogramVec = stub.HistogramVecIgnore{}
MetricTLSRequiredNoIgnored stub.CounterVec = stub.CounterVecIgnore{}
MetricPanicInc = func() {}
)
var (
@ -281,7 +259,7 @@ func New(ctx context.Context, elog *slog.Logger, conn net.Conn, tlsMode TLSMode,
c.firstReadAfterHandshake = true
c.tlsResultAdd(1, 0, nil)
c.conn = tlsconn
tlsversion, ciphersuite := mox.TLSInfo(tlsconn)
tlsversion, ciphersuite := moxio.TLSInfo(tlsconn)
c.log.Debug("tls client handshake done",
slog.String("tls", tlsversion),
slog.String("ciphersuite", ciphersuite),
@ -334,7 +312,7 @@ func (c *Client) tlsConfig() *tls.Config {
// DANE verification.
// daneRecords can be non-nil and empty, that's intended.
if c.daneRecords != nil {
verified, record, err := dane.Verify(c.log.Logger, c.daneRecords, cs, c.remoteHostname, c.daneMoreHostnames)
verified, record, err := dane.Verify(c.log.Logger, c.daneRecords, cs, c.remoteHostname, c.daneMoreHostnames, c.rootCAs)
c.log.Debugx("dane verification", err, slog.Bool("verified", verified), slog.Any("record", record))
if verified {
if c.daneVerifiedRecord != nil {
@ -355,7 +333,7 @@ func (c *Client) tlsConfig() *tls.Config {
if c.ignoreTLSVerifyErrors {
// We ignore the failure and continue the connection.
c.log.Infox("verifying dane failed, continuing with connection", err)
metricTLSRequiredNoIgnored.WithLabelValues("daneverification").Inc()
MetricTLSRequiredNoIgnored.IncLabels("daneverification")
} else {
// This connection will fail.
daneErr = dane.ErrNoMatch
@ -547,7 +525,7 @@ func (c *Client) readecode(ecodes bool) (code int, secode, lastLine string, text
c.cmds = c.cmds[1:]
}
}
metricCommands.WithLabelValues(cmd, fmt.Sprintf("%d", co), sec).Observe(float64(time.Since(c.cmdStart)) / float64(time.Second))
MetricCommands.ObserveLabels(float64(time.Since(c.cmdStart))/float64(time.Second), cmd, fmt.Sprintf("%d", co), sec)
c.log.Debug("smtpclient command result",
slog.String("cmd", cmd),
slog.Int("code", co),
@ -651,7 +629,7 @@ func (c *Client) recover(rerr *error) {
}
cerr, ok := x.(Error)
if !ok {
metrics.PanicInc(metrics.Smtpclient)
MetricPanicInc()
panic(x)
}
*rerr = cerr
@ -779,7 +757,7 @@ func (c *Client) hello(ctx context.Context, tlsMode TLSMode, ehloHostname dns.Do
c.r = bufio.NewReader(c.tr)
c.w = bufio.NewWriter(c.tw)
tlsversion, ciphersuite := mox.TLSInfo(nconn)
tlsversion, ciphersuite := moxio.TLSInfo(nconn)
c.log.Debug("starttls client handshake done",
slog.Any("tlsmode", tlsMode),
slog.Bool("verifypkix", c.tlsVerifyPKIX),

View File

@ -10,7 +10,6 @@ import (
"github.com/mjl-/mox/dns"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/mox-"
)
// DialHook can be used during tests to override the regular dialer from being used.
@ -50,10 +49,9 @@ type Dialer interface {
// Dial updates dialedIPs, callers may want to save it so it can be taken into
// account for future delivery attempts.
//
// If we have fully specified local SMTP listener IPs, we set those for the
// outgoing connection. The admin probably configured these same IPs in SPF, but
// others possibly not.
func Dial(ctx context.Context, elog *slog.Logger, dialer Dialer, host dns.IPDomain, ips []net.IP, port int, dialedIPs map[string][]net.IP) (conn net.Conn, ip net.IP, rerr error) {
// The first matching protocol family from localIPs is set for the local side
// of the TCP connection.
func Dial(ctx context.Context, elog *slog.Logger, dialer Dialer, host dns.IPDomain, ips []net.IP, port int, dialedIPs map[string][]net.IP, localIPs []net.IP) (conn net.Conn, ip net.IP, rerr error) {
log := mlog.New("smtpclient", elog)
timeout := 30 * time.Second
if deadline, ok := ctx.Deadline(); ok && len(ips) > 0 {
@ -66,7 +64,7 @@ func Dial(ctx context.Context, elog *slog.Logger, dialer Dialer, host dns.IPDoma
addr := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
log.Debug("dialing host", slog.String("addr", addr))
var laddr net.Addr
for _, lip := range mox.Conf.Static.SpecifiedSMTPListenIPs {
for _, lip := range localIPs {
ipIs4 := ip.To4() != nil
lipIs4 := lip.To4() != nil
if ipIs4 == lipIs4 {

View File

@ -41,7 +41,7 @@ func TestDialHost(t *testing.T) {
if err != nil || !reflect.DeepEqual(ips, []net.IP{net.ParseIP("10.0.0.1"), net.ParseIP("2001:db8::1")}) || !dualstack {
t.Fatalf("expected err nil, address 10.0.0.1,2001:db8::1, dualstack true, got %v %v %v", err, ips, dualstack)
}
_, ip, err := Dial(ctxbg, log.Logger, nil, ipdomain("dualstack.example"), ips, 25, dialedIPs)
_, ip, err := Dial(ctxbg, log.Logger, nil, ipdomain("dualstack.example"), ips, 25, dialedIPs, nil)
if err != nil || ip.String() != "10.0.0.1" {
t.Fatalf("expected err nil, address 10.0.0.1, dualstack true, got %v %v %v", err, ip, dualstack)
}
@ -50,7 +50,7 @@ func TestDialHost(t *testing.T) {
if err != nil || !reflect.DeepEqual(ips, []net.IP{net.ParseIP("2001:db8::1"), net.ParseIP("10.0.0.1")}) || !dualstack {
t.Fatalf("expected err nil, address 2001:db8::1,10.0.0.1, dualstack true, got %v %v %v", err, ips, dualstack)
}
_, ip, err = Dial(ctxbg, log.Logger, nil, ipdomain("dualstack.example"), ips, 25, dialedIPs)
_, ip, err = Dial(ctxbg, log.Logger, nil, ipdomain("dualstack.example"), ips, 25, dialedIPs, nil)
if err != nil || ip.String() != "2001:db8::1" {
t.Fatalf("expected err nil, address 2001:db8::1, dualstack true, got %v %v %v", err, ip, dualstack)
}