git » chasquid » commit 8d763f4

Apply more mechanical simplifications using recent Go features

author Alberto Bertogli
2026-10-03 08:08:22 UTC
committer Alberto Bertogli
2026-10-03 08:31:15 UTC
parent 0210c5fd9248ce1ef292ab81daab7b39b18cddd0

Apply more mechanical simplifications using recent Go features

This is a follow-up on the `go fix` run in commit
0210c5fd9248ce1ef292ab81daab7b39b18cddd0.

That commit recorded the automated updates.

This one applies further simplifications that are mechanical and do not
involve logic changes, but are outside of what `go fix` did.

It also removes some now-obsolete comments and statements.

cmd/chasquid-util/chasquid-util.go +6 -15
cmd/chasquid-util/dkim.go +3 -1
cmd/mda-lmtp/mda-lmtp.go +0 -3
cmd/smtp-check/smtp-check.go +0 -3
dnsoverride.go +0 -2
internal/aliases/aliases.go +10 -13
internal/auth/auth.go +2 -4
internal/courier/mda_test.go +3 -1
internal/envelope/envelope.go +3 -7
internal/expvarom/expvarom.go +3 -3
internal/localrpc/localrpc.go +2 -5
internal/nettrace/http.go +3 -3
internal/nettrace/trace.go +7 -14
internal/protoio/protoio.go +3 -1
internal/queue/dsn.go +1 -1
internal/queue/dsn_test.go +2 -2
internal/queue/queue.go +12 -14
internal/smtpsrv/conn.go +15 -22
internal/smtpsrv/dotreader.go +1 -1
internal/smtpsrv/server.go +1 -3
internal/smtpsrv/server_test.go +2 -1
internal/sts/sts.go +7 -7
internal/testlib/testlib_test.go +4 -2
internal/userdb/userdb.go +2 -2
monitoring.go +2 -1
test/cover.sh +0 -3
test/t-03-queue_persistency/addtoqueue.go +0 -1
test/util/conngen/conngen.go +1 -6
test/util/coverhtml/coverhtml.go +0 -2
test/util/fexp/fexp.go +3 -9
test/util/generate_cert/generate_cert.go +0 -2
test/util/loadgen/loadgen.go +1 -5
test/util/minidns/minidns.go +0 -2

diff --git a/cmd/chasquid-util/chasquid-util.go b/cmd/chasquid-util/chasquid-util.go
index 5a33823..5cba912 100644
--- a/cmd/chasquid-util/chasquid-util.go
+++ b/cmd/chasquid-util/chasquid-util.go
@@ -7,9 +7,10 @@ import (
 	"errors"
 	"fmt"
 	"io/fs"
+	"maps"
 	"os"
 	"path/filepath"
-	"sort"
+	"slices"
 	"strconv"
 	"strings"
 	"syscall"
@@ -166,7 +167,7 @@ func checkUserDB() {
 	path := userDBForDomain("")
 	// Check if the file exists. This is because userdb.Load does not consider
 	// it an error.
-	if _, err := os.Stat(path); os.IsNotExist(err) {
+	if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
 		Fatalf("Error: file %q does not exist", path)
 	}
 
@@ -281,13 +282,7 @@ func aliasesResolve() {
 
 	// Result is a map of type -> []addresses.
 	// Sort the types for deterministic output.
-	ts := []string{}
-	for t := range vs {
-		ts = append(ts, t)
-	}
-	sort.Strings(ts)
-
-	for _, t := range ts {
+	for _, t := range slices.Sorted(maps.Keys(vs)) {
 		for _, a := range vs[t] {
 			fmt.Printf("%v  %s\n", t, a)
 		}
@@ -333,12 +328,8 @@ func parseArgs(usage string) map[string]string {
 		// Note: Consider handling end of args marker "--" explicitly in
 		// the future if needed.
 		if strings.HasPrefix(a, "-") {
-			sp := strings.SplitN(a, "=", 2)
-			if len(sp) < 2 {
-				args[a] = ""
-			} else {
-				args[sp[0]] = sp[1]
-			}
+			k, v, _ := strings.Cut(a, "=")
+			args[k] = v
 		} else {
 			args["$"+strconv.Itoa(pos)] = a
 			pos++
diff --git a/cmd/chasquid-util/dkim.go b/cmd/chasquid-util/dkim.go
index e76f5b9..5fe99d5 100644
--- a/cmd/chasquid-util/dkim.go
+++ b/cmd/chasquid-util/dkim.go
@@ -10,8 +10,10 @@ import (
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/pem"
+	"errors"
 	"fmt"
 	"io"
+	"io/fs"
 	"net/mail"
 	"os"
 	"path"
@@ -161,7 +163,7 @@ func dkimKeygen() {
 		keyPath = keyPathFor(domain, selector)
 	}
 
-	if _, err := os.Stat(keyPath); !os.IsNotExist(err) {
+	if _, err := os.Stat(keyPath); !errors.Is(err, fs.ErrNotExist) {
 		Fatalf("Error: key already exists at %q", keyPath)
 	}
 
diff --git a/cmd/mda-lmtp/mda-lmtp.go b/cmd/mda-lmtp/mda-lmtp.go
index cf10bcf..1581e0e 100644
--- a/cmd/mda-lmtp/mda-lmtp.go
+++ b/cmd/mda-lmtp/mda-lmtp.go
@@ -1,9 +1,6 @@
 // mda-lmtp is a very basic MDA that uses LMTP to do the delivery.
 //
 // See the usage below for details.
-//
-//go:build !coverage
-
 package main
 
 import (
diff --git a/cmd/smtp-check/smtp-check.go b/cmd/smtp-check/smtp-check.go
index c6df6f1..6694635 100644
--- a/cmd/smtp-check/smtp-check.go
+++ b/cmd/smtp-check/smtp-check.go
@@ -1,7 +1,4 @@
 // smtp-check is a command-line too for checking SMTP setups.
-//
-//go:build !coverage
-
 package main
 
 import (
diff --git a/dnsoverride.go b/dnsoverride.go
index a391fca..6e51617 100644
--- a/dnsoverride.go
+++ b/dnsoverride.go
@@ -1,9 +1,7 @@
 // Support for overriding DNS lookups, for testing purposes.
 // This is only used in tests, when the "dnsoverride" tag is active.
-// It requires Go >= 1.8.
 //
 //go:build dnsoverride
-// +build dnsoverride
 
 package main
 
diff --git a/internal/aliases/aliases.go b/internal/aliases/aliases.go
index a433db2..bfc8e63 100644
--- a/internal/aliases/aliases.go
+++ b/internal/aliases/aliases.go
@@ -58,8 +58,10 @@ package aliases
 import (
 	"bufio"
 	"context"
+	"errors"
 	"fmt"
 	"io"
+	"io/fs"
 	"maps"
 	"os"
 	"os/exec"
@@ -376,7 +378,7 @@ func (v *Resolver) AddAliasesFile(domain, path string) (int, error) {
 	v.mu.Unlock()
 
 	aliases, err := v.parseFile(domain, path)
-	if os.IsNotExist(err) {
+	if errors.Is(err, fs.ErrNotExist) {
 		return 0, nil
 	}
 	if err != nil {
@@ -404,7 +406,7 @@ func (v *Resolver) Reload() error {
 	for domain, paths := range v.files {
 		for _, path := range paths {
 			aliases, err := v.parseFile(domain, path)
-			if os.IsNotExist(err) {
+			if errors.Is(err, fs.ErrNotExist) {
 				continue
 			}
 			if err != nil {
@@ -447,12 +449,12 @@ func (v *Resolver) parseReader(domain string, r io.Reader) (map[string][]Recipie
 			continue
 		}
 
-		sp := strings.SplitN(line, ":", 2)
-		if len(sp) != 2 {
+		addr, rawalias, ok := strings.Cut(line, ":")
+		if !ok {
 			return nil, newParseError(i, "missing ':' in line")
 		}
 
-		addr, rawalias := strings.TrimSpace(sp[0]), strings.TrimSpace(sp[1])
+		addr, rawalias = strings.TrimSpace(addr), strings.TrimSpace(rawalias)
 		if len(addr) == 0 || len(rawalias) == 0 {
 			return nil, newParseError(i, "missing address or alias")
 		}
@@ -574,12 +576,7 @@ func removeAllAfter(s, seps string) string {
 			continue
 		}
 
-		i := strings.Index(s, c)
-		if i == -1 {
-			continue
-		}
-
-		s = s[:i]
+		s, _, _ = strings.Cut(s, c)
 	}
 
 	return s
@@ -588,7 +585,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.SplitSeq(chars, "") {
-		s = strings.Replace(s, c, "", -1)
+		s = strings.ReplaceAll(s, c, "")
 	}
 
 	return s
@@ -600,7 +597,7 @@ func (v *Resolver) runResolveHook(tr *trace.Trace, addr string) ([]Recipient, er
 		return nil, nil
 	}
 	// TODO: check if the file is executable.
-	if _, err := os.Stat(v.ResolveHook); os.IsNotExist(err) {
+	if _, err := os.Stat(v.ResolveHook); errors.Is(err, fs.ErrNotExist) {
 		hookResults.Add("resolve:skip", 1)
 		return nil, nil
 	}
diff --git a/internal/auth/auth.go b/internal/auth/auth.go
index 8ab86e9..9642eb3 100644
--- a/internal/auth/auth.go
+++ b/internal/auth/auth.go
@@ -214,10 +214,8 @@ func DecodeResponse(response string) (user, domain, passwd string, err error) {
 
 	// Split identity into "user@domain", if possible.
 	user = identity
-	idsp := strings.SplitN(identity, "@", 2)
-	if len(idsp) >= 2 {
-		user = idsp[0]
-		domain = idsp[1]
+	if u, d, ok := strings.Cut(identity, "@"); ok {
+		user, domain = u, d
 	}
 
 	// Normalize the user and domain. This is so users can write the username
diff --git a/internal/courier/mda_test.go b/internal/courier/mda_test.go
index 9ecc46f..ef602bf 100644
--- a/internal/courier/mda_test.go
+++ b/internal/courier/mda_test.go
@@ -2,6 +2,8 @@ package courier
 
 import (
 	"bytes"
+	"errors"
+	"io/fs"
 	"os"
 	"testing"
 	"time"
@@ -67,7 +69,7 @@ func TestExitCode(t *testing.T) {
 	// TODO: This can happen when building under unusual circumstances, such
 	// as Debian package building. Are they reasonable enough for us to keep
 	// this?
-	if _, err := os.Stat("../../test/util/exitcode"); os.IsNotExist(err) {
+	if _, err := os.Stat("../../test/util/exitcode"); errors.Is(err, fs.ErrNotExist) {
 		t.Skipf("util/exitcode not found, running from outside repo?")
 	}
 
diff --git a/internal/envelope/envelope.go b/internal/envelope/envelope.go
index ec04bf9..d61f701 100644
--- a/internal/envelope/envelope.go
+++ b/internal/envelope/envelope.go
@@ -11,12 +11,8 @@ import (
 
 // Split an user@domain address into user and domain.
 func Split(addr string) (string, string) {
-	ps := strings.SplitN(addr, "@", 2)
-	if len(ps) != 2 {
-		return addr, ""
-	}
-
-	return ps[0], ps[1]
+	user, domain, _ := strings.Cut(addr, "@")
+	return user, domain
 }
 
 // UserOf user@domain returns user.
@@ -48,7 +44,7 @@ func AddHeader(data []byte, k, v string) []byte {
 		if v[len(v)-1] == '\n' {
 			v = v[:len(v)-1]
 		}
-		v = strings.Replace(v, "\n", "\n\t", -1)
+		v = strings.ReplaceAll(v, "\n", "\n\t")
 	}
 
 	header := []byte(fmt.Sprintf("%s: %s\n", k, v))
diff --git a/internal/expvarom/expvarom.go b/internal/expvarom/expvarom.go
index 6197cc7..6e961c3 100644
--- a/internal/expvarom/expvarom.go
+++ b/internal/expvarom/expvarom.go
@@ -27,7 +27,7 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"sort"
+	"slices"
 	"strconv"
 	"strings"
 	"sync"
@@ -90,8 +90,8 @@ func MetricsHandler(w http.ResponseWriter, r *http.Request) {
 	})
 
 	// Sort the variables for reproducibility and readability.
-	sort.Slice(vars, func(i, j int) bool {
-		return vars[i].Name < vars[j].Name
+	slices.SortFunc(vars, func(a, b exportedVar) int {
+		return strings.Compare(a.Name, b.Name)
 	})
 
 	for _, v := range vars {
diff --git a/internal/localrpc/localrpc.go b/internal/localrpc/localrpc.go
index f5b99c7..2675408 100644
--- a/internal/localrpc/localrpc.go
+++ b/internal/localrpc/localrpc.go
@@ -128,11 +128,8 @@ func readRequest(r *textproto.Reader) (string, string, error) {
 		return "", "", err
 	}
 
-	sp := strings.SplitN(line, " ", 2)
-	if len(sp) == 1 {
-		return sp[0], "", nil
-	}
-	return sp[0], sp[1], nil
+	name, params, _ := strings.Cut(line, " ")
+	return name, params, nil
 }
 
 func writeError(tr *trace.Trace, tconn *textproto.Conn, err error) {
diff --git a/internal/nettrace/http.go b/internal/nettrace/http.go
index 2f51acc..4e0b51e 100644
--- a/internal/nettrace/http.go
+++ b/internal/nettrace/http.go
@@ -8,7 +8,7 @@ import (
 	"html/template"
 	"math"
 	"net/http"
-	"sort"
+	"slices"
 	"strconv"
 	"time"
 )
@@ -156,8 +156,8 @@ func allEvents(tr *trace) []traceAndEvent {
 	evts := appendAllEvents(tr, []traceAndEvent{}, seen, 0)
 
 	// Sort them by time.
-	sort.Slice(evts, func(i, j int) bool {
-		return evts[i].Event.When.Before(evts[j].Event.When)
+	slices.SortFunc(evts, func(a, b traceAndEvent) int {
+		return a.Event.When.Compare(b.Event.When)
 	})
 
 	return evts
diff --git a/internal/nettrace/trace.go b/internal/nettrace/trace.go
index 542c73b..d9b5950 100644
--- a/internal/nettrace/trace.go
+++ b/internal/nettrace/trace.go
@@ -7,7 +7,7 @@ import (
 	"fmt"
 	"maps"
 	"math/rand/v2"
-	"sort"
+	"slices"
 	"strconv"
 	"strings"
 	"sync"
@@ -29,11 +29,8 @@ func newID(family string, ts int64) id {
 }
 
 func (id id) Family() string {
-	sp := strings.SplitN(string(id), "!", 2)
-	if len(sp) != 2 {
-		return string(id)
-	}
-	return sp[0]
+	family, _, _ := strings.Cut(string(id), "!")
+	return family
 }
 
 // Trace represents a single request trace.
@@ -433,8 +430,8 @@ func (ft *familyTraces) TracesFor(b int, allgt bool) []*trace {
 
 	// Sort them by start, newer first. This is the order that will be used
 	// when displaying them.
-	sort.Slice(trs, func(i, j int) bool {
-		return trs[i].Start.After(trs[j].Start)
+	slices.SortFunc(trs, func(a, b *trace) int {
+		return b.Start.Compare(a.Start)
 	})
 	return trs
 }
@@ -510,13 +507,9 @@ var (
 )
 
 func copyFamilies() map[string]*familyTraces {
-	n := map[string]*familyTraces{}
-
 	familiesMu.Lock()
-	maps.Copy(n, families)
-	familiesMu.Unlock()
-
-	return n
+	defer familiesMu.Unlock()
+	return maps.Clone(families)
 }
 
 func findInFamilies(traceID id, refID id) *trace {
diff --git a/internal/protoio/protoio.go b/internal/protoio/protoio.go
index 77df2f3..029fb56 100644
--- a/internal/protoio/protoio.go
+++ b/internal/protoio/protoio.go
@@ -2,6 +2,8 @@
 package protoio
 
 import (
+	"errors"
+	"io/fs"
 	"net/url"
 	"os"
 	"strings"
@@ -87,7 +89,7 @@ func (s *Store) Put(id string, m proto.Message) error {
 // Get a message from the store.
 func (s *Store) Get(id string, m proto.Message) (bool, error) {
 	err := ReadTextMessage(s.idToFname(id), m)
-	if os.IsNotExist(err) {
+	if errors.Is(err, fs.ErrNotExist) {
 		return false, nil
 	}
 	return err == nil, err
diff --git a/internal/queue/dsn.go b/internal/queue/dsn.go
index c859f83..07cd458 100644
--- a/internal/queue/dsn.go
+++ b/internal/queue/dsn.go
@@ -88,7 +88,7 @@ type dsnInfo struct {
 // indent s with the given number of spaces.
 func indent(sp int, s string) string {
 	pad := strings.Repeat(" ", sp)
-	return strings.Replace(s, "\n", "\n"+pad, -1)
+	return strings.ReplaceAll(s, "\n", "\n"+pad)
 }
 
 var dsnTemplate = template.Must(
diff --git a/internal/queue/dsn_test.go b/internal/queue/dsn_test.go
index 49b8725..e5c4ce8 100644
--- a/internal/queue/dsn_test.go
+++ b/internal/queue/dsn_test.go
@@ -2,7 +2,7 @@ package queue
 
 import (
 	"fmt"
-	"sort"
+	"slices"
 	"strings"
 	"testing"
 )
@@ -182,7 +182,7 @@ func printDiff(print func(s string), expected, got string) {
 
 	// sort the lines, to make it easier to see the differences (this works
 	// ok when there's few, horrible when there's lots).
-	sort.Strings(lines)
+	slices.Sort(lines)
 
 	// print diff of expected vs. got
 	seen := map[string]bool{}
diff --git a/internal/queue/queue.go b/internal/queue/queue.go
index c528957..cf168da 100644
--- a/internal/queue/queue.go
+++ b/internal/queue/queue.go
@@ -252,19 +252,19 @@ func (q *Queue) DumpString() string {
 	defer q.mu.RUnlock()
 	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)))
+	fmt.Fprintf(&s, "date: %v\n", time.Now())
+	fmt.Fprintf(&s, "length: %d\n\n", len(q.q))
 
 	for id, item := range q.q {
-		s.WriteString(fmt.Sprintf("## Item %s\n", id))
+		fmt.Fprintf(&s, "## Item %s\n", id)
 		item.Lock()
-		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))
+		fmt.Fprintf(&s, "created at: %s\n", item.CreatedAt)
+		fmt.Fprintf(&s, "from: %s\n", item.From)
+		fmt.Fprintf(&s, "to: %s\n", item.To)
 		for _, rcpt := range item.Rcpt {
-			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))
+			fmt.Fprintf(&s, "%s %s (%s)\n", rcpt.Status, rcpt.Address, rcpt.Type)
+			fmt.Fprintf(&s, "  original address: %s\n", rcpt.OriginalAddress)
+			fmt.Fprintf(&s, "  last failure: %q\n", rcpt.LastFailureMessage)
 		}
 		item.Unlock()
 		s.WriteString("\n")
@@ -329,8 +329,7 @@ func (item *Item) SendLoop(q *Queue) {
 				continue
 			}
 
-			wg.Add(1)
-			go item.sendOneRcpt(&wg, tr, q, rcpt)
+			wg.Go(func() { item.sendOneRcpt(tr, q, rcpt) })
 		}
 		wg.Wait()
 
@@ -359,8 +358,7 @@ func (item *Item) SendLoop(q *Queue) {
 }
 
 // sendOneRcpt, and update it with the results.
-func (item *Item) sendOneRcpt(wg *sync.WaitGroup, tr *trace.Trace, q *Queue, rcpt *Recipient) {
-	defer wg.Done()
+func (item *Item) sendOneRcpt(tr *trace.Trace, q *Queue, rcpt *Recipient) {
 	to := rcpt.Address
 	tr.Debugf("%s sending", to)
 
@@ -452,7 +450,7 @@ func rewriteSender(from, originalAddr string) string {
 	// we can't know if the other side will support SMTPUTF8.
 	return fmt.Sprintf("%s+fwd_from=%s@%s",
 		envelope.UserOf(originalAddr),
-		strings.Replace(from, "@", "=", -1),
+		strings.ReplaceAll(from, "@", "="),
 		mustIDNAToASCII(envelope.DomainOf(originalAddr)))
 }
 
diff --git a/internal/smtpsrv/conn.go b/internal/smtpsrv/conn.go
index c0494ef..9a35f33 100644
--- a/internal/smtpsrv/conn.go
+++ b/internal/smtpsrv/conn.go
@@ -9,6 +9,7 @@ import (
 	"flag"
 	"fmt"
 	"io"
+	"io/fs"
 	"math/rand/v2"
 	"net"
 	"net/mail"
@@ -343,7 +344,7 @@ loop:
 	}
 
 	if err != nil {
-		if err == io.EOF {
+		if errors.Is(err, io.EOF) {
 			c.tr.Debugf("client closed the connection")
 		} else {
 			c.tr.Errorf("exiting with error: %v", err)
@@ -363,7 +364,7 @@ func (c *Conn) HELO(params string) (code int, msg string) {
 		"liquor emporium", "antique weapons outlet", "delicatessen",
 		"jewelers", "quality apparel and accessories", "hardware",
 		"rare books", "lighting store"}
-	t := types[rand.Int()%len(types)]
+	t := types[rand.IntN(len(types))]
 	msg = fmt.Sprintf("Hello my friend, welcome to chasqui's %s!", t)
 
 	return 250, msg
@@ -408,7 +409,7 @@ func (c *Conn) RSET(params string) (code int, msg string) {
 		"Your mind releases itself from mundane concerns.",
 		"As your mind turns inward on itself, you forget everything else.",
 	}
-	return 250, "2.0.0 " + msgs[rand.Int()%len(msgs)]
+	return 250, "2.0.0 " + msgs[rand.IntN(len(msgs))]
 }
 
 // VRFY SMTP command handler.
@@ -455,7 +456,7 @@ func (c *Conn) MAIL(params string) (code int, msg string) {
 	// It should be written "<>", we check for that and remove spaces just to
 	// be more flexible.
 	addr := ""
-	if strings.Replace(rawAddr, " ", "", -1) == "<>" {
+	if strings.ReplaceAll(rawAddr, " ", "") == "<>" {
 		addr = "<>"
 	} else {
 		e, err := mail.ParseAddress(rawAddr)
@@ -668,11 +669,11 @@ func (c *Conn) DATA(params string) (code int, msg string) {
 	// Read the data. Enforce CRLF correctness, and maximum size.
 	c.data, err = readUntilDot(c.reader, c.maxDataSize)
 	if err != nil {
-		if err == errMessageTooLarge {
+		if errors.Is(err, errMessageTooLarge) {
 			// Message is too big; excess data has already been discarded.
 			return 552, "5.3.4 Message too big"
 		}
-		if err == errInvalidLineEnding {
+		if errors.Is(err, errInvalidLineEnding) {
 			// We can't properly recover from this, so we have to drop the
 			// connection.
 			c.writeResponse(521, "5.5.2 Error reading DATA: invalid line ending")
@@ -734,7 +735,7 @@ func (c *Conn) DATA(params string) (code int, msg string) {
 		"In return to thy service, I grant thee the gift of Immortality!",
 		"You ascend to the status of Demigod(dess)...",
 	}
-	return 250, "2.0.0 " + msgs[rand.Int()%len(msgs)]
+	return 250, "2.0.0 " + msgs[rand.IntN(len(msgs))]
 }
 
 func (c *Conn) addReceivedHeader() {
@@ -884,7 +885,7 @@ func sanitizeEHLODomain(s string) string {
 			c >= '0' && c <= '9',
 			c == '-', c == '.',
 			c == '[', c == ']', c == ':':
-			n.WriteString(string(c))
+			n.WriteRune(c)
 		}
 	}
 
@@ -895,7 +896,7 @@ func sanitizeEHLODomain(s string) string {
 // indicating if it's permanent, and the error itself.
 func (c *Conn) runPostDataHook(data []byte) ([]byte, bool, error) {
 	// TODO: check if the file is executable.
-	if _, err := os.Stat(c.postDataHook); os.IsNotExist(err) {
+	if _, err := os.Stat(c.postDataHook); errors.Is(err, fs.ErrNotExist) {
 		hookResults.Add("post-data:skip", 1)
 		return nil, false, nil
 	}
@@ -1152,8 +1153,8 @@ func (c *Conn) AUTH(params string) (code int, msg string) {
 	// If the response is not there, we reply with 334, and expect the
 	// response back from the client in the next message.
 
-	sp := strings.SplitN(params, " ", 2)
-	if len(sp) < 1 || sp[0] != "PLAIN" {
+	mech, response, hasResponse := strings.Cut(params, " ")
+	if mech != "PLAIN" {
 		// As we only offer plain, this should not really happen.
 		return 534, "5.7.9 Asmodeus demands 534 zorkmids for safe passage"
 	}
@@ -1162,10 +1163,7 @@ func (c *Conn) AUTH(params string) (code int, msg string) {
 	// find their way to the users in some circumstances.
 
 	// Get the response, either from the message or interactively.
-	response := ""
-	if len(sp) == 2 {
-		response = sp[1]
-	} else {
+	if !hasResponse {
 		// Reply 334 and expect the user to provide it.
 		// In this case, the text IS relevant, as it is taken as the
 		// server-side SASL challenge (empty for PLAIN).
@@ -1232,13 +1230,8 @@ func (c *Conn) readCommand() (cmd, params string, err error) {
 		return "", "", err
 	}
 
-	sp := strings.SplitN(msg, " ", 2)
-	cmd = strings.ToUpper(sp[0])
-	if len(sp) > 1 {
-		params = sp[1]
-	}
-
-	return cmd, params, err
+	cmd, params, _ = strings.Cut(msg, " ")
+	return strings.ToUpper(cmd), params, err
 }
 
 func (c *Conn) readLine() (line string, err error) {
diff --git a/internal/smtpsrv/dotreader.go b/internal/smtpsrv/dotreader.go
index 846d9eb..eec7597 100644
--- a/internal/smtpsrv/dotreader.go
+++ b/internal/smtpsrv/dotreader.go
@@ -38,7 +38,7 @@ func readUntilDot(r *bufio.Reader, max int64) ([]byte, error) {
 loop:
 	for {
 		b, err := r.ReadByte()
-		if err == io.EOF {
+		if errors.Is(err, io.EOF) {
 			return buf, io.ErrUnexpectedEOF
 		} else if err != nil {
 			return buf, err
diff --git a/internal/smtpsrv/server.go b/internal/smtpsrv/server.go
index 9f503af..a190c44 100644
--- a/internal/smtpsrv/server.go
+++ b/internal/smtpsrv/server.go
@@ -340,9 +340,7 @@ func (s *Server) ListenAndServe() {
 
 	// Never return. If the serve goroutines have problems, they will abort
 	// execution.
-	for {
-		time.Sleep(24 * time.Hour)
-	}
+	select {}
 }
 
 func (s *Server) serve(l net.Listener, mode SocketMode) {
diff --git a/internal/smtpsrv/server_test.go b/internal/smtpsrv/server_test.go
index 0883869..298b3d2 100644
--- a/internal/smtpsrv/server_test.go
+++ b/internal/smtpsrv/server_test.go
@@ -5,6 +5,7 @@ import (
 	"errors"
 	"flag"
 	"fmt"
+	"io/fs"
 	"net"
 	"net/smtp"
 	"net/textproto"
@@ -486,7 +487,7 @@ func TestStartTLSOnTLS(t *testing.T) {
 func TestAddDKIMSigner(t *testing.T) {
 	s := NewServer()
 	err := s.AddDKIMSigner("example.com", "selector", "keyfile-does-not-exist")
-	if !os.IsNotExist(err) {
+	if !errors.Is(err, fs.ErrNotExist) {
 		t.Errorf("AddDKIMSigner: expected not exist, got %v", err)
 	}
 
diff --git a/internal/sts/sts.go b/internal/sts/sts.go
index 93bf878..aaa4e0f 100644
--- a/internal/sts/sts.go
+++ b/internal/sts/sts.go
@@ -86,13 +86,13 @@ func parsePolicy(raw []byte) (*Policy, error) {
 
 	scanner := bufio.NewScanner(bytes.NewReader(raw))
 	for scanner.Scan() {
-		sp := strings.SplitN(scanner.Text(), ":", 2)
-		if len(sp) != 2 {
+		key, value, ok := strings.Cut(scanner.Text(), ":")
+		if !ok {
 			continue
 		}
 
-		key := strings.TrimSpace(sp[0])
-		value := strings.TrimSpace(sp[1])
+		key = strings.TrimSpace(key)
+		value = strings.TrimSpace(value)
 
 		// Only care for the keys we recognize.
 		switch key {
@@ -293,9 +293,9 @@ func matchDomain(domain, pattern string) bool {
 	// For wildcards, skip the first part of the domain and match the rest.
 	// Note that if the pattern is malformed this might fail, but we are ok
 	// with that.
-	if strings.HasPrefix(pattern, "*.") {
-		parts := strings.SplitN(domain, ".", 2)
-		if len(parts) > 1 && parts[1] == pattern[2:] {
+	if suffix, ok := strings.CutPrefix(pattern, "*."); ok {
+		_, rest, found := strings.Cut(domain, ".")
+		if found && rest == suffix {
 			return true
 		}
 	}
diff --git a/internal/testlib/testlib_test.go b/internal/testlib/testlib_test.go
index 9059c62..7cbc984 100644
--- a/internal/testlib/testlib_test.go
+++ b/internal/testlib/testlib_test.go
@@ -1,6 +1,8 @@
 package testlib
 
 import (
+	"errors"
+	"io/fs"
 	"os"
 	"testing"
 	"time"
@@ -22,7 +24,7 @@ func TestBasic(t *testing.T) {
 	}
 
 	RemoveIfOk(t, dir)
-	if _, err := os.Stat(dir); !os.IsNotExist(err) {
+	if _, err := os.Stat(dir); !errors.Is(err, fs.ErrNotExist) {
 		t.Fatalf("%s existed, should have been deleted: %v", dir, err)
 	}
 }
@@ -45,7 +47,7 @@ func TestLeaveDirOnError(t *testing.T) {
 	myt.Errorf("something bad happened")
 
 	RemoveIfOk(myt, dir)
-	if _, err := os.Stat(dir); os.IsNotExist(err) {
+	if _, err := os.Stat(dir); errors.Is(err, fs.ErrNotExist) {
 		t.Fatalf("%s was removed, should have been kept", dir)
 	}
 
diff --git a/internal/userdb/userdb.go b/internal/userdb/userdb.go
index 3df95e1..9dc4724 100644
--- a/internal/userdb/userdb.go
+++ b/internal/userdb/userdb.go
@@ -33,7 +33,7 @@ import (
 	"crypto/subtle"
 	"errors"
 	"fmt"
-	"os"
+	"io/fs"
 	"sync"
 
 	"golang.org/x/crypto/scrypt"
@@ -73,7 +73,7 @@ func Load(fname string) (*DB, error) {
 		db.db = &ProtoDB{Users: map[string]*Password{}}
 	}
 
-	if os.IsNotExist(err) {
+	if errors.Is(err, fs.ErrNotExist) {
 		// If the file does not exist now, it is not an error, as it might
 		// exist later and we want to be able to read it.
 		err = nil
diff --git a/monitoring.go b/monitoring.go
index dd5408d..d30295a 100644
--- a/monitoring.go
+++ b/monitoring.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"context"
+	"errors"
 	"expvar"
 	"flag"
 	"fmt"
@@ -126,7 +127,7 @@ func launchMonitoringServer(conf *config.Config) {
 	http.HandleFunc("/debug/config", debugConfigHandler(conf))
 	http.HandleFunc("/debug/traces", nettrace.RenderTraces)
 
-	if err := srv.ListenAndServe(); err != http.ErrServerClosed {
+	if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
 		log.Fatalf("Monitoring server failed: %v", err)
 	}
 }
diff --git a/test/cover.sh b/test/cover.sh
index f552b85..726df68 100755
--- a/test/cover.sh
+++ b/test/cover.sh
@@ -5,9 +5,6 @@
 #
 # The .coverage directory is used to store the data, it will be erased and
 # recreated on each run.
-#
-# This is not very tidy, and relies on some hacky tricks (see
-# coverage_test.go), but works for now.
 
 set -e
 . "$(dirname "$0")/util/lib.sh"
diff --git a/test/t-03-queue_persistency/addtoqueue.go b/test/t-03-queue_persistency/addtoqueue.go
index 39bdbf6..b5228be 100644
--- a/test/t-03-queue_persistency/addtoqueue.go
+++ b/test/t-03-queue_persistency/addtoqueue.go
@@ -5,7 +5,6 @@
 // daemon for testing purposes only.
 //
 //go:build ignore
-// +build ignore
 
 package main
 
diff --git a/test/util/conngen/conngen.go b/test/util/conngen/conngen.go
index 309d050..c29d718 100644
--- a/test/util/conngen/conngen.go
+++ b/test/util/conngen/conngen.go
@@ -1,5 +1,3 @@
-//go:build !coverage
-
 // SMTP connection generator, for testing purposes.
 package main
 
@@ -8,7 +6,6 @@ import (
 	"net"
 	"net/http"
 	"net/smtp"
-	"time"
 
 	_ "net/http/pprof"
 
@@ -66,9 +63,7 @@ func main() {
 	log.Infof("done, created %d simultaneous connections", *count)
 
 	if *wait {
-		for {
-			time.Sleep(24 * time.Hour)
-		}
+		select {}
 	}
 
 	for _, c := range conns {
diff --git a/test/util/coverhtml/coverhtml.go b/test/util/coverhtml/coverhtml.go
index ec919fc..eb84df1 100644
--- a/test/util/coverhtml/coverhtml.go
+++ b/test/util/coverhtml/coverhtml.go
@@ -1,5 +1,3 @@
-//go:build !coverage
-
 // Generate an HTML visualization of a Go coverage profile.
 // Serves a similar purpose to "go tool cover -html", but has a different
 // visual style.
diff --git a/test/util/fexp/fexp.go b/test/util/fexp/fexp.go
index 38a95fe..d89b7d8 100644
--- a/test/util/fexp/fexp.go
+++ b/test/util/fexp/fexp.go
@@ -1,5 +1,3 @@
-//go:build !coverage
-
 // Fetch an URL, and check if the response matches what we expect.
 //
 // Useful for testing HTTP(s) servers.
@@ -11,10 +9,11 @@ import (
 	"flag"
 	"fmt"
 	"io"
+	"maps"
 	"net/http"
 	"os"
 	"regexp"
-	"sort"
+	"slices"
 	"strconv"
 	"strings"
 )
@@ -85,12 +84,7 @@ func main() {
 		fmt.Printf("Request: %s\n", url)
 		fmt.Printf("Response:\n")
 		fmt.Printf("  %v  %v\n", resp.Proto, resp.Status)
-		ks := []string{}
-		for k := range resp.Header {
-			ks = append(ks, k)
-		}
-		sort.Strings(ks)
-		for _, k := range ks {
+		for _, k := range slices.Sorted(maps.Keys(resp.Header)) {
 			fmt.Printf("  %v: %s\n", k,
 				strings.Join(resp.Header[k], ", "))
 		}
diff --git a/test/util/generate_cert/generate_cert.go b/test/util/generate_cert/generate_cert.go
index 7a2468f..8ea71b8 100644
--- a/test/util/generate_cert/generate_cert.go
+++ b/test/util/generate_cert/generate_cert.go
@@ -1,5 +1,3 @@
-//go:build !coverage
-
 // Utility to generate self-signed certificates.
 // It generates a self-signed x509 certificate and key pair, and writes them
 // to "fullchain.pem" and "privkey.pem".
diff --git a/test/util/loadgen/loadgen.go b/test/util/loadgen/loadgen.go
index 238fd0a..880792e 100644
--- a/test/util/loadgen/loadgen.go
+++ b/test/util/loadgen/loadgen.go
@@ -1,5 +1,3 @@
-//go:build !coverage
-
 // SMTP load generator, for testing purposes.
 package main
 
@@ -114,9 +112,7 @@ func main() {
 	)
 
 	if *wait {
-		for {
-			time.Sleep(24 * time.Hour)
-		}
+		select {}
 	}
 }
 
diff --git a/test/util/minidns/minidns.go b/test/util/minidns/minidns.go
index a006f7f..f445d4d 100644
--- a/test/util/minidns/minidns.go
+++ b/test/util/minidns/minidns.go
@@ -1,5 +1,3 @@
-//go:build !coverage
-
 // minidns is a trivial DNS server used for testing.
 //
 // It takes an "answers" file which contains lines with the following format: