From ad80498b4c11531e35c97e99ec923f4d983d1f7b Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 19 Apr 2026 13:30:22 +0700 Subject: [PATCH] Add STARTTLS hosts during discover --- checker/discover.go | 85 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 16 deletions(-) diff --git a/checker/discover.go b/checker/discover.go index ca1cef7..b7b8a5d 100644 --- a/checker/discover.go +++ b/checker/discover.go @@ -12,13 +12,7 @@ import ( // // Matching on the service name is more authoritative than matching on the // port: port 636 could carry anything, but _ldaps._tcp unambiguously -// designates LDAP over TLS — even on a non-standard port. Conversely, a -// site may run HTTPS on a non-443 port and still want it probed. -// -// STARTTLS variants (_xmpp-client, _smtp, _submission, _imap, _pop3…) are -// intentionally excluded here; a dedicated endpoint type (e.g. -// "smtp-starttls") will be introduced when a TLS checker grows the -// capability to upgrade those protocols. +// designates LDAP over TLS — even on a non-standard port. var directTLSServices = map[string]bool{ "https": true, "ftps": true, // FTPS implicit @@ -40,9 +34,51 @@ var directTLSServices = map[string]bool{ "turns": true, } +// starttlsSpec describes how to surface a STARTTLS-capable SRV service as +// a DiscoveredEndpoint: the endpoint type (which carries the protocol +// family in its suffix) and whether the protocol historically treats +// STARTTLS as mandatory or opportunistic. +type starttlsSpec struct { + Type string + Opportunistic bool +} + +// starttlsServices enumerates SRV service names that speak plaintext on +// connect and then negotiate TLS via a protocol-specific STARTTLS handshake. +// +// The Type follows the "starttls-" convention agreed with the +// future TLS checker (the consumer); the SDK itself has no opinion on +// these values. The suffix mirrors the SRV service-name vocabulary so +// producers and consumers stay naturally aligned. +// +// The Opportunistic flag is exposed to consumers as Meta["starttls"] = +// "required" | "opportunistic" so rules can pick an appropriate severity +// when the server does not advertise STARTTLS. +var starttlsServices = map[string]starttlsSpec{ + "submission": {"starttls-smtp", false}, // RFC 8314: STARTTLS required + "smtp": {"starttls-smtp", true}, // port 25: opportunistic + "imap": {"starttls-imap", false}, + "pop3": {"starttls-pop3", false}, + "xmpp-client": {"starttls-xmpp-client", false}, // RFC 7590 + "xmpp-server": {"starttls-xmpp-server", true}, // s2s: opportunistic + "ldap": {"starttls-ldap", true}, + "nntp": {"starttls-nntp", true}, + "ftp": {"starttls-ftp", true}, + "sieve": {"starttls-sieve", false}, + "postgresql": {"starttls-postgres", true}, +} + // DiscoverEndpoints is invoked right after Collect. It declares (host, port) -// pairs worth testing by other checkers — here: TLS endpoints whose SRV -// service name is a known direct-TLS protocol (see directTLSServices). +// pairs worth testing by other checkers: +// +// - direct-TLS endpoints (Type="tls") whose SRV service name is listed in +// directTLSServices (e.g. _ldaps, _sips, _https), +// - STARTTLS-capable endpoints (Type="starttls-") whose SRV service +// name is listed in starttlsServices (e.g. _submission, _imap, _xmpp-client). +// +// Unknown service names produce no endpoints: we lean on the SRV naming +// convention rather than guessing from the port, since a port alone +// conveys no protocol semantics. func (p *srvProvider) DiscoverEndpoints(data any) ([]sdk.DiscoveredEndpoint, error) { d, ok := data.(*SRVData) if !ok { @@ -53,15 +89,32 @@ func (p *srvProvider) DiscoverEndpoints(data any) ([]sdk.DiscoveredEndpoint, err if r.IsNullTarget || r.Target == "" { continue } - if !directTLSServices[r.Service] { + + if directTLSServices[r.Service] { + out = append(out, sdk.DiscoveredEndpoint{ + Type: "tls", + Host: r.Target, + Port: r.Port, + SNI: r.Target, + }) continue } - out = append(out, sdk.DiscoveredEndpoint{ - Type: "tls", - Host: r.Target, - Port: r.Port, - SNI: r.Target, - }) + + if spec, ok := starttlsServices[r.Service]; ok { + policy := "required" + if spec.Opportunistic { + policy = "opportunistic" + } + out = append(out, sdk.DiscoveredEndpoint{ + Type: spec.Type, + Host: r.Target, + Port: r.Port, + SNI: r.Target, + Meta: map[string]any{ + "starttls": policy, + }, + }) + } } return out, nil }