package dav import ( "net/url" "strconv" sdk "git.happydns.org/checker-sdk-go/checker" ) // DiscoverEndpoints derives TLS endpoints worth handing off to downstream // checkers (notably the dedicated TLS checker) from a completed Observation. // // A CalDAV/CardDAV context URL always implies a direct-TLS HTTPS endpoint, so // we emit a single `tls` entry for the resolved context URL's host:port. If // the endpoint was reached via SRV, we also surface each SRV target as its // own endpoint โ€” those are the names operators actually need certificates on, // and they may differ from the queried domain. // // SNI is always populated (equal to Host for CalDAV/CardDAV, since โ€” unlike // XMPP (RFC 6120 ยง13.7.2.1) โ€” there is no mandated source-domain-vs-target // split: clients negotiate TLS for the hostname they connect to). We fill // the field unconditionally so consumers can rely on it being set, matching // the convention already used by the XMPP checker. func DiscoverEndpoints(obs *Observation) []sdk.DiscoveredEndpoint { if obs == nil || obs.Discovery.ContextURL == "" { return nil } var out []sdk.DiscoveredEndpoint seen := map[string]struct{}{} add := func(host string, port uint16) { if host == "" || port == 0 { return } key := host + ":" + strconv.Itoa(int(port)) if _, dup := seen[key]; dup { return } seen[key] = struct{}{} out = append(out, sdk.DiscoveredEndpoint{ Type: "tls", Host: host, Port: port, SNI: host, }) } // Primary endpoint: the resolved context URL. if host, port, ok := hostPortFromURL(obs.Discovery.ContextURL); ok { add(host, port) } // Secondary endpoints: every TLS SRV target. Clients may connect to any // of them per weight/priority, and all of them need a valid certificate. for _, r := range obs.Discovery.SecureSRV { port := r.Port if port == 0 { port = 443 } add(r.Target, port) } return out } // hostPortFromURL extracts the (host, port) pair from an absolute URL. The // port defaults to 443 for https and 80 for http. Returns ok=false for // malformed URLs so callers can silently skip them. func hostPortFromURL(raw string) (host string, port uint16, ok bool) { u, err := url.Parse(raw) if err != nil { return "", 0, false } host = u.Hostname() if host == "" { return "", 0, false } if p := u.Port(); p != "" { n, convErr := strconv.ParseUint(p, 10, 16) if convErr != nil { return "", 0, false } return host, uint16(n), true } switch u.Scheme { case "https": return host, 443, true case "http": return host, 80, true } return "", 0, false }