CalDAV and CardDAV checkers sharing a single Go module. Discovery follows RFC 6764 (/.well-known + SRV/TXT), authenticated probes cover principal, home-set, collections and a minimal REPORT query on top of go-webdav. Common shape in internal/dav/; CalDAV adds a scheduling rule. Surfaces its context URL (and each secure-SRV target) as TLS endpoints via the EndpointDiscoverer interface, so the dedicated TLS checker can pick them up without re-parsing observations. HTML report foregrounds common misconfigs (well-known returning 200, missing SRV, plaintext-only SRV, missing DAV capability, skipped auth phase) as action-item callouts before the full phase breakdown.
89 lines
2.3 KiB
Go
89 lines
2.3 KiB
Go
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.
|
|
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, sni string) {
|
|
if host == "" || port == 0 {
|
|
return
|
|
}
|
|
key := host + ":" + strconv.Itoa(int(port))
|
|
if _, dup := seen[key]; dup {
|
|
return
|
|
}
|
|
seen[key] = struct{}{}
|
|
ep := sdk.DiscoveredEndpoint{
|
|
Type: "tls",
|
|
Host: host,
|
|
Port: port,
|
|
}
|
|
if sni != "" && sni != host {
|
|
ep.SNI = sni
|
|
}
|
|
out = append(out, ep)
|
|
}
|
|
|
|
// 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
|
|
}
|