git » chasquid » commit 0210c5f

Apply `go fix` to simplify using modern features

author Alberto Bertogli
2026-10-02 17:34:25 UTC
committer Alberto Bertogli
2026-10-02 17:34:25 UTC
parent 9785738a5862311f1f6891352a003df6efce8e1a

Apply `go fix` to simplify using modern features

This patch contains the result of running `go fix ./...`, which applies
simplifications that make use of modern language features.

There are no logic or behaviour changes.

cmd/chasquid-util/chasquid-util.go +2 -2
cmd/chasquid-util/dkim.go +2 -2
cmd/mda-lmtp/mda-lmtp.go +3 -4
cmd/smtp-check/smtp-check.go +0 -1
internal/aliases/aliases.go +8 -11
internal/config/config.go +2 -2
internal/config/config_test.go +2 -2
internal/dkim/context.go +2 -2
internal/dkim/context_test.go +1 -1
internal/dkim/dns.go +2 -2
internal/dkim/file_test.go +1 -1
internal/dkim/header.go +1 -1
internal/localrpc/client_test.go +1 -2
internal/localrpc/e2e_test.go +1 -1
internal/maillog/maillog.go +1 -1
internal/nettrace/bench_test.go +2 -2
internal/nettrace/histogram.go +1 -1
internal/nettrace/http_test.go +1 -1
internal/nettrace/trace.go +8 -9
internal/nettrace/trace_test.go +3 -3
internal/normalize/normalize_test.go +2 -2
internal/queue/dsn_test.go +2 -2
internal/queue/queue.go +16 -17
internal/queue/queue_test.go +1 -1
internal/smtp/smtp.go +1 -1
internal/smtp/smtp_test.go +1 -1
internal/smtpsrv/conn.go +6 -6
internal/smtpsrv/server_test.go +3 -3
internal/sts/sts_test.go +1 -1
internal/trace/trace.go +3 -3
test/util/conngen/conngen.go +0 -1
test/util/coverhtml/coverhtml.go +1 -2
test/util/fexp/fexp.go +2 -3
test/util/generate_cert/generate_cert.go +3 -4
test/util/loadgen/loadgen.go +0 -1
test/util/minidns/minidns.go +2 -5
test/util/smtpc/smtpc.go +1 -1

diff --git a/cmd/chasquid-util/chasquid-util.go b/cmd/chasquid-util/chasquid-util.go
index e2d16b0..5a33823 100644
--- a/cmd/chasquid-util/chasquid-util.go
+++ b/cmd/chasquid-util/chasquid-util.go
@@ -113,13 +113,13 @@ func main() {
 
 // Fatalf prints the given message to stderr, then exits the program with an
 // error code.
-func Fatalf(s string, arg ...interface{}) {
+func Fatalf(s string, arg ...any) {
 	fmt.Fprintf(os.Stderr, s+"\n", arg...)
 	os.Exit(1)
 }
 
 // Warnf prints the given message to stderr, but does not exit the program.
-func Warnf(s string, arg ...interface{}) {
+func Warnf(s string, arg ...any) {
 	fmt.Fprintf(os.Stderr, s+"\n", arg...)
 }
 
diff --git a/cmd/chasquid-util/dkim.go b/cmd/chasquid-util/dkim.go
index 49ad311..e76f5b9 100644
--- a/cmd/chasquid-util/dkim.go
+++ b/cmd/chasquid-util/dkim.go
@@ -54,7 +54,7 @@ func dkimSign() {
 	ctx := context.Background()
 	if _, verbose := args["-v"]; verbose {
 		ctx = dkim.WithTraceFunc(ctx,
-			func(format string, args ...interface{}) {
+			func(format string, args ...any) {
 				fmt.Fprintf(os.Stderr, format+"\n", args...)
 			})
 	}
@@ -77,7 +77,7 @@ func dkimVerify() {
 	ctx := context.Background()
 	if _, verbose := args["-v"]; verbose {
 		ctx = dkim.WithTraceFunc(ctx,
-			func(format string, args ...interface{}) {
+			func(format string, args ...any) {
 				fmt.Fprintf(os.Stderr, format+"\n", args...)
 			})
 	}
diff --git a/cmd/mda-lmtp/mda-lmtp.go b/cmd/mda-lmtp/mda-lmtp.go
index dc01125..cf10bcf 100644
--- a/cmd/mda-lmtp/mda-lmtp.go
+++ b/cmd/mda-lmtp/mda-lmtp.go
@@ -3,7 +3,6 @@
 // See the usage below for details.
 //
 //go:build !coverage
-// +build !coverage
 
 package main
 
@@ -51,13 +50,13 @@ Flags:
 }
 
 // Exit with EX_TEMPFAIL.
-func tempExit(format string, args ...interface{}) {
+func tempExit(format string, args ...any) {
 	fmt.Printf(format+"\n", args...)
 	// 75 = EX_TEMPFAIL "temporary failure" exit code (sysexits.h).
 	os.Exit(75)
 }
 
-func permExit(format string, args ...interface{}) {
+func permExit(format string, args ...any) {
 	fmt.Printf(format+"\n", args...)
 	os.Exit(2)
 }
@@ -142,7 +141,7 @@ func main() {
 }
 
 // cmd sends a command and checks it matched the expected code.
-func cmd(conn *textproto.Conn, expectCode int, format string, args ...interface{}) {
+func cmd(conn *textproto.Conn, expectCode int, format string, args ...any) {
 	id, err := conn.Cmd(format, args...)
 	if err != nil {
 		tempExit("Sent %q, got %v", fmt.Sprintf(format, args...), err)
diff --git a/cmd/smtp-check/smtp-check.go b/cmd/smtp-check/smtp-check.go
index 7bff34d..c6df6f1 100644
--- a/cmd/smtp-check/smtp-check.go
+++ b/cmd/smtp-check/smtp-check.go
@@ -1,7 +1,6 @@
 // smtp-check is a command-line too for checking SMTP setups.
 //
 //go:build !coverage
-// +build !coverage
 
 package main
 
diff --git a/internal/aliases/aliases.go b/internal/aliases/aliases.go
index 1d5213d..a433db2 100644
--- a/internal/aliases/aliases.go
+++ b/internal/aliases/aliases.go
@@ -60,6 +60,7 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"maps"
 	"os"
 	"os/exec"
 	"strings"
@@ -384,9 +385,7 @@ func (v *Resolver) AddAliasesFile(domain, path string) (int, error) {
 
 	// Add the aliases to the resolver, overriding any previous values.
 	v.mu.Lock()
-	for addr, rs := range aliases {
-		v.aliases[addr] = rs
-	}
+	maps.Copy(v.aliases, aliases)
 	v.mu.Unlock()
 
 	return len(aliases), nil
@@ -413,9 +412,7 @@ func (v *Resolver) Reload() error {
 			}
 
 			// Add the aliases to the resolver, overriding any previous values.
-			for addr, rs := range aliases {
-				newAliases[addr] = rs
-			}
+			maps.Copy(newAliases, aliases)
 		}
 	}
 
@@ -500,7 +497,7 @@ func parseRHS(rawalias, domain string) ([]Recipient, error) {
 	}
 
 	rs := []Recipient{}
-	for _, a := range strings.Split(rawalias, ",") {
+	for a := range strings.SplitSeq(rawalias, ",") {
 		a = strings.TrimSpace(a)
 		if a == "" {
 			// Ignore empty aliases. This is out of convenience, so we allow
@@ -558,7 +555,7 @@ func parseForward(rawalias string) (string, []string, error) {
 	// The via part is a list of servers, separated by "/". Split it up.
 	// For now we don't validate the servers, but we may in the future.
 	via := []string{}
-	for _, v := range strings.Split(viaS, "/") {
+	for v := range strings.SplitSeq(viaS, "/") {
 		server := strings.TrimSpace(v)
 		if server == "" {
 			return "", nil, fmt.Errorf("empty server in via list")
@@ -572,7 +569,7 @@ func parseForward(rawalias string) (string, []string, error) {
 // removeAllAfter removes everything from s that comes after the separators,
 // including them.
 func removeAllAfter(s, seps string) string {
-	for _, c := range strings.Split(seps, "") {
+	for c := range strings.SplitSeq(seps, "") {
 		if c == "" {
 			continue
 		}
@@ -590,7 +587,7 @@ func removeAllAfter(s, seps string) string {
 
 // removeChars removes the runes in "chars" from s.
 func removeChars(s, chars string) string {
-	for _, c := range strings.Split(chars, "") {
+	for c := range strings.SplitSeq(chars, "") {
 		s = strings.Replace(s, c, "", -1)
 	}
 
@@ -650,7 +647,7 @@ func (e *parseError) Error() string {
 	return fmt.Sprintf("line %d: %v", e.line, e.err)
 }
 
-func newParseError(line int, f string, args ...interface{}) error {
+func newParseError(line int, f string, args ...any) error {
 	return &parseError{
 		line: line,
 		err:  fmt.Errorf(f, args...),
diff --git a/internal/config/config.go b/internal/config/config.go
index ca2a850..2f3c067 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -27,8 +27,8 @@ var defaultConfig = &Config{
 
 	DataDir: "/var/lib/chasquid",
 
-	SuffixSeparators: proto.String("+"),
-	DropCharacters:   proto.String("."),
+	SuffixSeparators: new("+"),
+	DropCharacters:   new("."),
 
 	MailLogPath: "<syslog>",
 
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 8d8c0ec..d46f6de 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -86,8 +86,8 @@ func TestFullConfig(t *testing.T) {
 
 		DataDir: "/var/lib/chasquid",
 
-		SuffixSeparators: proto.String(""),
-		DropCharacters:   proto.String(""),
+		SuffixSeparators: new(""),
+		DropCharacters:   new(""),
 
 		MailLogPath: "<syslog>",
 
diff --git a/internal/dkim/context.go b/internal/dkim/context.go
index 406807e..50b9ea5 100644
--- a/internal/dkim/context.go
+++ b/internal/dkim/context.go
@@ -9,7 +9,7 @@ type contextKey string
 
 const traceKey contextKey = "trace"
 
-func trace(ctx context.Context, f string, args ...interface{}) {
+func trace(ctx context.Context, f string, args ...any) {
 	traceFunc, ok := ctx.Value(traceKey).(TraceFunc)
 	if !ok {
 		return
@@ -17,7 +17,7 @@ func trace(ctx context.Context, f string, args ...interface{}) {
 	traceFunc(f, args...)
 }
 
-type TraceFunc func(f string, a ...interface{})
+type TraceFunc func(f string, a ...any)
 
 func WithTraceFunc(ctx context.Context, trace TraceFunc) context.Context {
 	return context.WithValue(ctx, traceKey, trace)
diff --git a/internal/dkim/context_test.go b/internal/dkim/context_test.go
index 5f6d6ba..cfaba7d 100644
--- a/internal/dkim/context_test.go
+++ b/internal/dkim/context_test.go
@@ -16,7 +16,7 @@ func TestTraceNoCtx(t *testing.T) {
 
 func TestTrace(t *testing.T) {
 	s := ""
-	traceF := func(f string, a ...interface{}) {
+	traceF := func(f string, a ...any) {
 		s = fmt.Sprintf(f, a...)
 	}
 	ctx := WithTraceFunc(context.Background(), traceF)
diff --git a/internal/dkim/dns.go b/internal/dkim/dns.go
index 2b5e044..930f14b 100644
--- a/internal/dkim/dns.go
+++ b/internal/dkim/dns.go
@@ -104,8 +104,8 @@ func parsePublicKey(v string) (*publicKey, error) {
 
 	// h is a colon-separated list of hashing algorithm names.
 	if tags["h"] != "" {
-		hs := strings.Split(eatWhitespace.Replace(tags["h"]), ":")
-		for _, h := range hs {
+		hs := strings.SplitSeq(eatWhitespace.Replace(tags["h"]), ":")
+		for h := range hs {
 			x, err := hashFromString(h)
 			if err != nil {
 				// Unrecognized algorithms must be ignored.
diff --git a/internal/dkim/file_test.go b/internal/dkim/file_test.go
index 268a788..1a248e8 100644
--- a/internal/dkim/file_test.go
+++ b/internal/dkim/file_test.go
@@ -113,7 +113,7 @@ func loadDNS(t *testing.T, ctx context.Context, path string) context.Context {
 	// Unfold \-terminated lines.
 	c = strings.ReplaceAll(c, "\\\n", "")
 
-	for _, line := range strings.Split(c, "\n") {
+	for line := range strings.SplitSeq(c, "\n") {
 		if line == "" || strings.HasPrefix(line, "#") {
 			continue
 		}
diff --git a/internal/dkim/header.go b/internal/dkim/header.go
index da3a6a6..5558178 100644
--- a/internal/dkim/header.go
+++ b/internal/dkim/header.go
@@ -312,7 +312,7 @@ func parseTags(s string) (tags, error) {
 	s = strings.TrimSuffix(s, ";")
 
 	tags := make(tags)
-	for _, tv := range strings.Split(s, ";") {
+	for tv := range strings.SplitSeq(s, ";") {
 		t, v, found := strings.Cut(tv, "=")
 		if !found {
 			return nil, fmt.Errorf("%w: missing '='", errInvalidTag)
diff --git a/internal/localrpc/client_test.go b/internal/localrpc/client_test.go
index e704a52..44d55a3 100644
--- a/internal/localrpc/client_test.go
+++ b/internal/localrpc/client_test.go
@@ -56,8 +56,7 @@ func TestBadServer(t *testing.T) {
 	if err == nil {
 		t.Fatal("expected error")
 	}
-	var protoErr textproto.ProtocolError
-	if !errors.As(err, &protoErr) {
+	if _, ok := errors.AsType[textproto.ProtocolError](err); !ok {
 		t.Errorf("wanted textproto.ProtocolError, got: %v (%T)", err, err)
 	}
 }
diff --git a/internal/localrpc/e2e_test.go b/internal/localrpc/e2e_test.go
index 7f72d90..ad9744f 100644
--- a/internal/localrpc/e2e_test.go
+++ b/internal/localrpc/e2e_test.go
@@ -114,7 +114,7 @@ func TestEndToEnd(t *testing.T) {
 
 func waitForServer(t *testing.T, path string) {
 	t.Helper()
-	for i := 0; i < 100; i++ {
+	for range 100 {
 		time.Sleep(10 * time.Millisecond)
 		conn, err := net.Dial("unix", path)
 		if conn != nil {
diff --git a/internal/maillog/maillog.go b/internal/maillog/maillog.go
index f010bc0..7da6808 100644
--- a/internal/maillog/maillog.go
+++ b/internal/maillog/maillog.go
@@ -70,7 +70,7 @@ func NewSyslog() (*Logger, error) {
 	return &Logger{inner: inner}, nil
 }
 
-func (l *Logger) printf(format string, args ...interface{}) {
+func (l *Logger) printf(format string, args ...any) {
 	err := l.inner.Log(log.Info, 2, format, args...)
 	if err != nil {
 		l.once.Do(func() {
diff --git a/internal/nettrace/bench_test.go b/internal/nettrace/bench_test.go
index a58fdbb..9fd1f81 100644
--- a/internal/nettrace/bench_test.go
+++ b/internal/nettrace/bench_test.go
@@ -9,9 +9,9 @@ import (
 func runBench(b *testing.B, events int) {
 	nTraces := (b.N + events + 1) / events
 
-	for i := 0; i < nTraces; i++ {
+	for range nTraces {
 		tr := New("bench", "test")
-		for j := 0; j < events; j++ {
+		for j := range events {
 			tr.Printf("%d", j)
 		}
 		tr.Finish()
diff --git a/internal/nettrace/histogram.go b/internal/nettrace/histogram.go
index 662a887..8d78958 100644
--- a/internal/nettrace/histogram.go
+++ b/internal/nettrace/histogram.go
@@ -53,7 +53,7 @@ func (h *histogram) Snapshot() *histSnapshot {
 	}
 
 	var cumCount uint64
-	for i := 0; i < nBuckets; i++ {
+	for i := range nBuckets {
 		cumCount += h.count[i]
 		l := line{
 			Start:     buckets[i],
diff --git a/internal/nettrace/http_test.go b/internal/nettrace/http_test.go
index 24c82f6..4664179 100644
--- a/internal/nettrace/http_test.go
+++ b/internal/nettrace/http_test.go
@@ -141,7 +141,7 @@ func TestHTTP(t *testing.T) {
 func TestHTTPLong(t *testing.T) {
 	// Test a long trace.
 	tr := New("TestHTTPLong", "verbose")
-	for i := 0; i < 1000; i++ {
+	for i := range 1000 {
 		tr.Printf("entry #%d", i)
 	}
 	tr.Finish()
diff --git a/internal/nettrace/trace.go b/internal/nettrace/trace.go
index 914cc05..542c73b 100644
--- a/internal/nettrace/trace.go
+++ b/internal/nettrace/trace.go
@@ -5,6 +5,7 @@ package nettrace
 import (
 	"container/ring"
 	"fmt"
+	"maps"
 	"math/rand/v2"
 	"sort"
 	"strconv"
@@ -51,11 +52,11 @@ type Trace interface {
 	SetError()
 
 	// Printf adds a message to the trace.
-	Printf(format string, a ...interface{})
+	Printf(format string, a ...any)
 
 	// Errorf adds a message to the trace, marks it as an error, and returns
 	// an error for it.
-	Errorf(format string, a ...interface{}) error
+	Errorf(format string, a ...any) error
 
 	// Finish marks the trace as complete.
 	// The trace should not be used after calling this method.
@@ -220,7 +221,7 @@ func (tr *trace) SetError() {
 	tr.mu.Unlock()
 }
 
-func (tr *trace) Printf(format string, a ...interface{}) {
+func (tr *trace) Printf(format string, a ...any) {
 	evt := &event{
 		When: time.Now(),
 		Type: evtLOG,
@@ -230,7 +231,7 @@ func (tr *trace) Printf(format string, a ...interface{}) {
 	tr.append(evt)
 }
 
-func (tr *trace) Errorf(format string, a ...interface{}) error {
+func (tr *trace) Errorf(format string, a ...any) error {
 	tr.SetError()
 	err := fmt.Errorf(format, a...)
 	tr.Printf("%s", err.Error())
@@ -354,7 +355,7 @@ func (r *traceRing) Len() int {
 }
 
 func (r *traceRing) Do(f func(tr *trace)) {
-	r.ring.Do(func(x interface{}) {
+	r.ring.Do(func(x any) {
 		if x == nil {
 			return
 		}
@@ -382,7 +383,7 @@ type familyTraces struct {
 func newFamilyTraces() *familyTraces {
 	ft := &familyTraces{}
 	ft.active = map[id]*trace{}
-	for i := 0; i < nBuckets; i++ {
+	for i := range nBuckets {
 		ft.finished[i] = newTraceRing(tracesInBucket)
 	}
 	ft.errors = newTraceRing(tracesInBucket)
@@ -512,9 +513,7 @@ func copyFamilies() map[string]*familyTraces {
 	n := map[string]*familyTraces{}
 
 	familiesMu.Lock()
-	for f, trs := range families {
-		n[f] = trs
-	}
+	maps.Copy(n, families)
 	familiesMu.Unlock()
 
 	return n
diff --git a/internal/nettrace/trace_test.go b/internal/nettrace/trace_test.go
index ebdb5c5..34d0d85 100644
--- a/internal/nettrace/trace_test.go
+++ b/internal/nettrace/trace_test.go
@@ -37,13 +37,13 @@ func TestLong(t *testing.T) {
 	tr.SetMaxEvents(100)
 
 	// First 90 events, no drop.
-	for i := 0; i < 90; i++ {
+	for i := range 90 {
 		tr.Printf("evt %d", i)
 	}
 	expectEvents(t, tr, 90)
 
 	// Up to 99, still no drop.
-	for i := 0; i < 9; i++ {
+	for i := range 9 {
 		tr.Printf("evt %d", i)
 	}
 	expectEvents(t, tr, 99)
@@ -57,7 +57,7 @@ func TestLong(t *testing.T) {
 	expectEvents(t, tr, 101)
 
 	// Add more events, expect none of them to exceed 101.
-	for i := 0; i < 9; i++ {
+	for i := range 9 {
 		tr.Printf("evt %d", i)
 		expectEvents(t, tr, 101)
 	}
diff --git a/internal/normalize/normalize_test.go b/internal/normalize/normalize_test.go
index 66bd324..31fa0f2 100644
--- a/internal/normalize/normalize_test.go
+++ b/internal/normalize/normalize_test.go
@@ -192,7 +192,7 @@ func FuzzDomainToUnicode(f *testing.F) {
 func BenchmarkToCRLF(b *testing.B) {
 	// Generate a 1000-line message.
 	bb := bytes.Buffer{}
-	for i := 0; i < 1000; i++ {
+	for range 1000 {
 		bb.WriteString("this is a very pretty line 🐅\n")
 	}
 	buf := bb.Bytes()
@@ -206,7 +206,7 @@ func BenchmarkToCRLF(b *testing.B) {
 func BenchmarkStringToCRLF(b *testing.B) {
 	// Generate a 1000-line message.
 	sb := strings.Builder{}
-	for i := 0; i < 1000; i++ {
+	for range 1000 {
 		sb.WriteString("this is a very pretty line 🐅\n")
 	}
 	s := sb.String()
diff --git a/internal/queue/dsn_test.go b/internal/queue/dsn_test.go
index 9b2988e..49b8725 100644
--- a/internal/queue/dsn_test.go
+++ b/internal/queue/dsn_test.go
@@ -168,14 +168,14 @@ func printDiff(print func(s string), expected, got string) {
 
 	// expected lines and map.
 	eM := map[string]int{}
-	for _, l := range strings.Split(expected, "\n") {
+	for l := range strings.SplitSeq(expected, "\n") {
 		eM[l]++
 		lines = append(lines, l)
 	}
 
 	// got lines and map.
 	gM := map[string]int{}
-	for _, l := range strings.Split(got, "\n") {
+	for l := range strings.SplitSeq(got, "\n") {
 		gM[l]++
 		lines = append(lines, l)
 	}
diff --git a/internal/queue/queue.go b/internal/queue/queue.go
index 202f6f4..c528957 100644
--- a/internal/queue/queue.go
+++ b/internal/queue/queue.go
@@ -15,6 +15,7 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
+	"slices"
 	"strings"
 	"sync"
 	"time"
@@ -249,26 +250,27 @@ func (q *Queue) Remove(id string) {
 func (q *Queue) DumpString() string {
 	q.mu.RLock()
 	defer q.mu.RUnlock()
-	s := "# Queue status\n\n"
-	s += fmt.Sprintf("date: %v\n", time.Now())
-	s += fmt.Sprintf("length: %d\n\n", len(q.q))
+	var s strings.Builder
+	s.WriteString("# Queue status\n\n")
+	s.WriteString(fmt.Sprintf("date: %v\n", time.Now()))
+	s.WriteString(fmt.Sprintf("length: %d\n\n", len(q.q)))
 
 	for id, item := range q.q {
-		s += fmt.Sprintf("## Item %s\n", id)
+		s.WriteString(fmt.Sprintf("## Item %s\n", id))
 		item.Lock()
-		s += fmt.Sprintf("created at: %s\n", item.CreatedAt)
-		s += fmt.Sprintf("from: %s\n", item.From)
-		s += fmt.Sprintf("to: %s\n", item.To)
+		s.WriteString(fmt.Sprintf("created at: %s\n", item.CreatedAt))
+		s.WriteString(fmt.Sprintf("from: %s\n", item.From))
+		s.WriteString(fmt.Sprintf("to: %s\n", item.To))
 		for _, rcpt := range item.Rcpt {
-			s += fmt.Sprintf("%s %s (%s)\n", rcpt.Status, rcpt.Address, rcpt.Type)
-			s += fmt.Sprintf("  original address: %s\n", rcpt.OriginalAddress)
-			s += fmt.Sprintf("  last failure: %q\n", rcpt.LastFailureMessage)
+			s.WriteString(fmt.Sprintf("%s %s (%s)\n", rcpt.Status, rcpt.Address, rcpt.Type))
+			s.WriteString(fmt.Sprintf("  original address: %s\n", rcpt.OriginalAddress))
+			s.WriteString(fmt.Sprintf("  last failure: %q\n", rcpt.LastFailureMessage))
 		}
 		item.Unlock()
-		s += "\n"
+		s.WriteString("\n")
 	}
 
-	return s
+	return s.String()
 }
 
 // An Item in the queue.
@@ -458,11 +460,8 @@ func rewriteSender(from, originalAddr string) string {
 func (item *Item) countRcpt(statuses ...Recipient_Status) int {
 	c := 0
 	for _, rcpt := range item.Rcpt {
-		for _, status := range statuses {
-			if rcpt.Status == status {
-				c++
-				break
-			}
+		if slices.Contains(statuses, rcpt.Status) {
+			c++
 		}
 	}
 	return c
diff --git a/internal/queue/queue_test.go b/internal/queue/queue_test.go
index 62892fc..ee587c7 100644
--- a/internal/queue/queue_test.go
+++ b/internal/queue/queue_test.go
@@ -277,7 +277,7 @@ func TestNextDelay(t *testing.T) {
 	}
 	for _, c := range cases {
 		// Repeat each case a few times to exercise the perturbation a bit.
-		for i := 0; i < 10; i++ {
+		for range 10 {
 			delay := nextDelay(time.Now().Add(-c.since))
 
 			max := c.min + 1*time.Minute
diff --git a/internal/smtp/smtp.go b/internal/smtp/smtp.go
index 4f77470..0b30189 100644
--- a/internal/smtp/smtp.go
+++ b/internal/smtp/smtp.go
@@ -42,7 +42,7 @@ func NewClient(conn net.Conn, host string) (*Client, error) {
 
 // cmd sends a command and returns the response over the text connection.
 // Based on Go's method of the same name.
-func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
+func (c *Client) cmd(expectCode int, format string, args ...any) (int, string, error) {
 	id, err := c.Text.Cmd(format, args...)
 	if err != nil {
 		return 0, "", err
diff --git a/internal/smtp/smtp_test.go b/internal/smtp/smtp_test.go
index 75e5cfb..c1fcaed 100644
--- a/internal/smtp/smtp_test.go
+++ b/internal/smtp/smtp_test.go
@@ -232,7 +232,7 @@ var _ net.Conn = faker{}
 func fakeDialog(dialog string) (faker, string) {
 	var client, server string
 
-	for _, l := range strings.Split(dialog, "\n") {
+	for l := range strings.SplitSeq(dialog, "\n") {
 		if strings.HasPrefix(l, "< ") {
 			server += l[2:] + "\r\n"
 		} else if strings.HasPrefix(l, "> ") {
diff --git a/internal/smtpsrv/conn.go b/internal/smtpsrv/conn.go
index faba2a3..c0494ef 100644
--- a/internal/smtpsrv/conn.go
+++ b/internal/smtpsrv/conn.go
@@ -519,7 +519,7 @@ func (c *Conn) checkSPF(addr string) (spf.Result, error) {
 		defer spfTr.Finish()
 		res, err := spf.CheckHostWithSender(
 			tcp.IP, envelope.DomainOf(addr), addr,
-			spf.WithTraceFunc(func(f string, a ...interface{}) {
+			spf.WithTraceFunc(func(f string, a ...any) {
 				spfTr.Debugf(f, a...)
 			}))
 
@@ -872,7 +872,7 @@ func checkData(data []byte) error {
 // as post-data hooks), so this function sanitizes the value to make it
 // shell-safe.
 func sanitizeEHLODomain(s string) string {
-	n := ""
+	var n strings.Builder
 	for _, c := range s {
 		// Allow a-zA-Z0-9 and []-.:
 		// That's enough for all domains, IPv4 and IPv6 literals, and also
@@ -884,11 +884,11 @@ func sanitizeEHLODomain(s string) string {
 			c >= '0' && c <= '9',
 			c == '-', c == '.',
 			c == '[', c == ']', c == ':':
-			n += string(c)
+			n.WriteString(string(c))
 		}
 	}
 
-	return n
+	return n.String()
 }
 
 // runPostDataHook and return the new headers to add, and on error a boolean
@@ -909,7 +909,7 @@ func (c *Conn) runPostDataHook(data []byte) ([]byte, bool, error) {
 
 	// Prepare the environment, copying some common variables so the hook has
 	// something reasonable, and then setting the specific ones for this case.
-	for _, v := range strings.Fields("USER PWD SHELL PATH") {
+	for v := range strings.FieldsSeq("USER PWD SHELL PATH") {
 		cmd.Env = append(cmd.Env, v+"="+os.Getenv(v))
 	}
 	cmd.Env = append(cmd.Env, "REMOTE_ADDR="+c.remoteAddr.String())
@@ -1269,7 +1269,7 @@ func (c *Conn) writeResponse(code int, msg string) error {
 	return writeResponse(c.writer, code, msg)
 }
 
-func (c *Conn) printfLine(format string, args ...interface{}) {
+func (c *Conn) printfLine(format string, args ...any) {
 	fmt.Fprintf(c.writer, format+"\r\n", args...)
 	c.writer.Flush()
 }
diff --git a/internal/smtpsrv/server_test.go b/internal/smtpsrv/server_test.go
index bcea656..0883869 100644
--- a/internal/smtpsrv/server_test.go
+++ b/internal/smtpsrv/server_test.go
@@ -307,7 +307,7 @@ func TestTooManyRecipients(t *testing.T) {
 		t.Fatalf("Mail: %v", err)
 	}
 
-	for i := 0; i < 101; i++ {
+	for i := range 101 {
 		if err := c.Rcpt(fmt.Sprintf("to%d@somewhere", i)); err != nil {
 			t.Fatalf("Rcpt: %v", err)
 		}
@@ -363,7 +363,7 @@ func sendLargeEmail(tb testing.TB, c *smtp.Client, sizeMiB int) error {
 	}
 
 	// Write the 1 MiB string sizeMiB times.
-	for i := 0; i < sizeMiB; i++ {
+	for range sizeMiB {
 		if _, err := w.Write([]byte(str1MiB)); err != nil {
 			tb.Fatalf("Data write: %v", err)
 		}
@@ -615,7 +615,7 @@ func realMain(m *testing.M) int {
 
 	// Create a 1MiB string, which the large message tests use.
 	buf := make([]byte, 1024*1024)
-	for i := 0; i < len(buf); i++ {
+	for i := range buf {
 		buf[i] = 'a'
 	}
 	str1MiB = string(buf)
diff --git a/internal/sts/sts_test.go b/internal/sts/sts_test.go
index 6f8dc34..d581da9 100644
--- a/internal/sts/sts_test.go
+++ b/internal/sts/sts_test.go
@@ -229,7 +229,7 @@ func TestFetch(t *testing.T) {
 func TestPolicyTooBig(t *testing.T) {
 	// Construct a valid but very large JSON as a policy.
 	raw := `{"version": "STSv1", "mode": "enforce", "mx": [`
-	for i := 0; i < 2000; i++ {
+	for i := range 2000 {
 		raw += fmt.Sprintf("\"mx%d\", ", i)
 	}
 	raw += `"mxlast"], "max_age": 100}`
diff --git a/internal/trace/trace.go b/internal/trace/trace.go
index 67d8987..4690819 100644
--- a/internal/trace/trace.go
+++ b/internal/trace/trace.go
@@ -38,7 +38,7 @@ func (t *Trace) NewChild(family, title string) *Trace {
 }
 
 // Printf adds this message to the trace's log.
-func (t *Trace) Printf(format string, a ...interface{}) {
+func (t *Trace) Printf(format string, a ...any) {
 	t.t.Printf(format, a...)
 
 	log.Log(log.Info, 1, "%s %s: %s", t.family, t.title,
@@ -46,7 +46,7 @@ func (t *Trace) Printf(format string, a ...interface{}) {
 }
 
 // Debugf adds this message to the trace's log, with a debugging level.
-func (t *Trace) Debugf(format string, a ...interface{}) {
+func (t *Trace) Debugf(format string, a ...any) {
 	t.t.Printf(format, a...)
 
 	log.Log(log.Debug, 1, "%s %s: %s",
@@ -54,7 +54,7 @@ func (t *Trace) Debugf(format string, a ...interface{}) {
 }
 
 // Errorf adds this message to the trace's log, with an error level.
-func (t *Trace) Errorf(format string, a ...interface{}) error {
+func (t *Trace) Errorf(format string, a ...any) error {
 	// Note we can't just call t.Error here, as it breaks caller logging.
 	err := fmt.Errorf(format, a...)
 	t.t.SetError()
diff --git a/test/util/conngen/conngen.go b/test/util/conngen/conngen.go
index 35e51a1..309d050 100644
--- a/test/util/conngen/conngen.go
+++ b/test/util/conngen/conngen.go
@@ -1,5 +1,4 @@
 //go:build !coverage
-// +build !coverage
 
 // SMTP connection generator, for testing purposes.
 package main
diff --git a/test/util/coverhtml/coverhtml.go b/test/util/coverhtml/coverhtml.go
index 229c913..ec919fc 100644
--- a/test/util/coverhtml/coverhtml.go
+++ b/test/util/coverhtml/coverhtml.go
@@ -1,5 +1,4 @@
 //go:build !coverage
-// +build !coverage
 
 // Generate an HTML visualization of a Go coverage profile.
 // Serves a similar purpose to "go tool cover -html", but has a different
@@ -25,7 +24,7 @@ var (
 	notes  = flag.String("notes", "", "notes to add at the beginning (HTML)")
 )
 
-func errorf(f string, a ...interface{}) {
+func errorf(f string, a ...any) {
 	fmt.Printf(f, a...)
 	os.Exit(1)
 }
diff --git a/test/util/fexp/fexp.go b/test/util/fexp/fexp.go
index 02ddbb0..38a95fe 100644
--- a/test/util/fexp/fexp.go
+++ b/test/util/fexp/fexp.go
@@ -1,5 +1,4 @@
 //go:build !coverage
-// +build !coverage
 
 // Fetch an URL, and check if the response matches what we expect.
 //
@@ -187,12 +186,12 @@ func mkTransport(caCert string) http.RoundTripper {
 	}
 }
 
-func fatalf(s string, a ...interface{}) {
+func fatalf(s string, a ...any) {
 	fmt.Fprintf(os.Stderr, s, a...)
 	os.Exit(1)
 }
 
-func errorf(s string, a ...interface{}) {
+func errorf(s string, a ...any) {
 	fmt.Fprintf(os.Stderr, s, a...)
 	exitCode = 1
 }
diff --git a/test/util/generate_cert/generate_cert.go b/test/util/generate_cert/generate_cert.go
index e3e0b6f..7a2468f 100644
--- a/test/util/generate_cert/generate_cert.go
+++ b/test/util/generate_cert/generate_cert.go
@@ -1,5 +1,4 @@
 //go:build !coverage
-// +build !coverage
 
 // Utility to generate self-signed certificates.
 // It generates a self-signed x509 certificate and key pair, and writes them
@@ -34,7 +33,7 @@ var (
 		"Should this cert be its own CA?")
 )
 
-func fatalf(f string, a ...interface{}) {
+func fatalf(f string, a ...any) {
 	fmt.Printf(f, a...)
 	os.Exit(1)
 }
@@ -71,8 +70,8 @@ func main() {
 		tmpl.IsCA = true
 	}
 
-	hosts := strings.Split(*host, ",")
-	for _, h := range hosts {
+	hosts := strings.SplitSeq(*host, ",")
+	for h := range hosts {
 		if ip := net.ParseIP(h); ip != nil {
 			tmpl.IPAddresses = append(tmpl.IPAddresses, ip)
 		} else {
diff --git a/test/util/loadgen/loadgen.go b/test/util/loadgen/loadgen.go
index 212a075..238fd0a 100644
--- a/test/util/loadgen/loadgen.go
+++ b/test/util/loadgen/loadgen.go
@@ -1,5 +1,4 @@
 //go:build !coverage
-// +build !coverage
 
 // SMTP load generator, for testing purposes.
 package main
diff --git a/test/util/minidns/minidns.go b/test/util/minidns/minidns.go
index 4c625ba..a006f7f 100644
--- a/test/util/minidns/minidns.go
+++ b/test/util/minidns/minidns.go
@@ -1,5 +1,4 @@
 //go:build !coverage
-// +build !coverage
 
 // minidns is a trivial DNS server used for testing.
 //
@@ -63,11 +62,9 @@ func main() {
 	srv.loadZones(zonesFile)
 
 	var wg sync.WaitGroup
-	wg.Add(1)
-	go func() {
-		defer wg.Done()
+	wg.Go(func() {
 		srv.listenAndServeUDP(*addr)
-	}()
+	})
 	go func() {
 		defer wg.Done()
 		srv.listenAndServeTCP(*addr)
diff --git a/test/util/smtpc/smtpc.go b/test/util/smtpc/smtpc.go
index 0d95972..4ddf017 100644
--- a/test/util/smtpc/smtpc.go
+++ b/test/util/smtpc/smtpc.go
@@ -101,7 +101,7 @@ func loadConfig() {
 	}
 	notnil(err)
 
-	for _, line := range strings.Split(string(data), "\n") {
+	for line := range strings.SplitSeq(string(data), "\n") {
 		k, v, ok := strings.Cut(line, " ")
 		if !ok {
 			continue