| author | Alberto Bertogli
<albertito@blitiri.com.ar> 2026-10-03 08:08:22 UTC |
| committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2026-10-03 08:31:15 UTC |
| parent | 0210c5fd9248ce1ef292ab81daab7b39b18cddd0 |
| 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: