package checker import ( "net" "strconv" sdk "git.happydns.org/checker-sdk-go/checker" tlsct "git.happydns.org/checker-tls/contract" ) func Provider() sdk.ObservationProvider { return &smtpProvider{} } type smtpProvider struct{} func (p *smtpProvider) Key() sdk.ObservationKey { return ObservationKeySMTP } // DiscoverEntries implements sdk.DiscoveryPublisher. // // We publish one tls.endpoint.v1 entry per MX target so the TLS checker // picks up the connection and runs the cert/chain/SAN/expiry posture // against it. STARTTLS is "smtp" and RequireSTARTTLS stays false because // MX SMTP on port 25 is opportunistic (RFC 7672 / RFC 8461 are what turn // it into a hard requirement, and they live in separate checkers). // // SNI is the MX target hostname: that is the name the receiving MTA // controls and will typically present in its certificate. RFC 7672 // DANE-TLSA binds the TLSA record to <_port._tcp.mx-target>, so the // target's A/AAAA+name are also the right reference for DANE. func (p *smtpProvider) DiscoverEntries(data any) ([]sdk.DiscoveryEntry, error) { d, ok := data.(*SMTPData) if !ok || d == nil { return nil, nil } if d.MX.NullMX { return nil, nil } var out []sdk.DiscoveryEntry seen := map[string]bool{} for _, rec := range d.MX.Records { if rec.Target == "" || rec.IsIPLiteral { continue } key := endpointKey(rec.Target, smtpPort) if seen[key] { continue } seen[key] = true ep := tlsct.TLSEndpoint{ Host: rec.Target, Port: smtpPort, SNI: rec.Target, STARTTLS: "smtp", RequireSTARTTLS: false, } entry, err := tlsct.NewEntry(ep) if err != nil { return nil, err } out = append(out, entry) } return out, nil } func endpointKey(host string, port uint16) string { return net.JoinHostPort(host, strconv.FormatUint(uint64(port), 10)) }