checker-tls/checker/starttls.go
Pierre-Olivier Mercier e32633ca40 Harden STARTTLS handlers and add per-dialect tests
Bound line reads with readLineLimited to prevent a peer from exhausting
memory by withholding line terminators, wrap previously bare error
returns for consistent context, surface XML decoder Skip errors, and
replace the goto in the XMPP feature scan with a labeled break. New
starttls_test.go exercises SMTP/IMAP/POP3/XMPP/LDAP success and
not-advertised paths through net.Pipe-mocked servers.
2026-04-25 23:15:17 +07:00

50 lines
1.6 KiB
Go

package checker
import (
"bufio"
"fmt"
"io"
"net"
)
// maxSTARTTLSLineBytes caps the length of a single line read from a STARTTLS
// peer. Real banners and CAPABILITY responses are well under 1 KiB; this
// bound prevents a malicious or buggy server from exhausting memory by
// withholding the line terminator.
const maxSTARTTLSLineBytes = 8 * 1024
// readLineLimited reads bytes from r up to and including the next '\n', or
// until maxSTARTTLSLineBytes have been read without one (in which case it
// returns an error). The returned string keeps the trailing '\n' so callers
// can use the same parsing logic as bufio.Reader.ReadString('\n').
func readLineLimited(r *bufio.Reader) (string, error) {
out := make([]byte, 0, 128)
for {
b, err := r.ReadByte()
if err != nil {
if err == io.EOF && len(out) > 0 {
return string(out), io.ErrUnexpectedEOF
}
return string(out), err
}
out = append(out, b)
if b == '\n' {
return string(out), nil
}
if len(out) >= maxSTARTTLSLineBytes {
return string(out), fmt.Errorf("line exceeds %d bytes without terminator", maxSTARTTLSLineBytes)
}
}
}
// starttlsUpgrader performs the plaintext portion of a STARTTLS upgrade on
// conn, leaving conn ready for tls.Client(conn, …).Handshake(). On success
// the returned function returns nil; on failure it returns a descriptive
// error (wrap errStartTLSNotOffered when the server advertises no STARTTLS).
type starttlsUpgrader func(conn net.Conn, sni string) error
var starttlsUpgraders = map[string]starttlsUpgrader{}
func registerStartTLS(protocol string, upgrader starttlsUpgrader) {
starttlsUpgraders[protocol] = upgrader
}