package checker import ( "fmt" sdk "git.happydns.org/checker-sdk-go/checker" ) // directTLSServices enumerates SRV service names (the "service" part of // _service._proto.domain) that by convention mean "direct TLS on connect", // as opposed to STARTTLS or plaintext. // // 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. var directTLSServices = map[string]bool{ "https": true, "ftps": true, // FTPS implicit "smtps": true, // SMTP over TLS (legacy port 465 semantics) "submissions": true, // RFC 8314: SMTP submission over TLS "imaps": true, "pop3s": true, "nntps": true, "ircs": true, "telnets": true, "ldaps": true, "sips": true, "ipps": true, // IPP over TLS (printing) "xmpps-client": true, // XMPP client over direct TLS "xmpps-server": true, // XMPP server-to-server over direct TLS "mqtts": true, "coaps": true, "stuns": true, "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: // // - 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 { return nil, fmt.Errorf("unexpected data type %T", data) } var out []sdk.DiscoveredEndpoint for _, r := range d.Records { if r.IsNullTarget || r.Target == "" { continue } if directTLSServices[r.Service] { out = append(out, sdk.DiscoveredEndpoint{ Type: "tls", Host: r.Target, Port: r.Port, SNI: r.Target, }) continue } 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 }