package checker import ( "github.com/miekg/dns" ) // DeprecatedSeverity grades a deprecated record family. // // "Critical" reflects record types whose continued use breaks DNSSEC // validation or modern resolvers (KEY/SIG/NXT, replaced by the DNSSEC-bis // triplet, RFC 3755). "Warning" covers types that still parse but have a // long-standing replacement (SPF→TXT, A6→AAAA, …). "Info" is reserved for // experimental types nobody implements anymore (NULL, NSAP, …): present in // a zone is harmless but pointless. type DeprecatedSeverity int const ( SeverityInfo DeprecatedSeverity = iota SeverityWarn SeverityCrit ) func (s DeprecatedSeverity) String() string { switch s { case SeverityCrit: return "crit" case SeverityWarn: return "warn" default: return "info" } } // DeprecationInfo describes one deprecated RR type. type DeprecationInfo struct { Reason string // Replacement is the modern record type to use instead, or "" when the // type has no replacement (just remove the record). Replacement string // HowToFix is the actionable instruction shown in the HTML report. // Phrased as a direct imperative so the user can act without context // switching to the relevant RFC. HowToFix string Severity DeprecatedSeverity } // deprecatedTypes is the source of truth for what counts as legacy. // // Numeric keys (instead of dns.TypeXxx) are used for types miekg/dns does // not export as named constants; they remain valid wire types and may // well show up in zones imported from BIND or older tooling. var deprecatedTypes = map[uint16]DeprecationInfo{ // --- DNSSEC predecessors (RFC 3755, RFC 4033 family) ----------------- dns.TypeKEY: { Reason: "RFC 3755 obsoleted KEY in favour of DNSKEY", Replacement: "DNSKEY", HowToFix: "Re-sign the zone with a DNSSEC implementation that emits DNSKEY/RRSIG/NSEC records, then remove the KEY entries.", Severity: SeverityCrit, }, dns.TypeSIG: { Reason: "RFC 3755 obsoleted SIG in favour of RRSIG", Replacement: "RRSIG", HowToFix: "SIG records are not validated by modern resolvers. Drop them; RRSIG records are produced automatically when the zone is DNSSEC-signed.", Severity: SeverityCrit, }, dns.TypeNXT: { Reason: "RFC 3755 obsoleted NXT in favour of NSEC", Replacement: "NSEC", HowToFix: "NXT predates DNSSEC-bis and is not understood by current validators. Re-sign the zone to produce NSEC (or NSEC3) records and remove NXT.", Severity: SeverityCrit, }, // --- Replaced by a clear modern equivalent --------------------------- dns.TypeSPF: { Reason: "RFC 7208 §3.1 deprecated the SPF record type; publish SPF policy in TXT only", Replacement: "TXT", HowToFix: "Publish the SPF policy as a TXT record (`v=spf1 …`) at the same owner name, then delete the SPF-typed record. Some receivers ignore SPF-typed records entirely.", Severity: SeverityWarn, }, 38: { // A6 Reason: "RFC 6563 moved A6 to historic status", Replacement: "AAAA", HowToFix: "Replace each A6 record with an equivalent AAAA record carrying the full IPv6 address.", Severity: SeverityWarn, }, dns.TypeMD: { Reason: "RFC 973 obsoleted MD in 1986; use MX", Replacement: "MX", HowToFix: "Translate the mail-destination into an MX record (preference + exchange host) and delete the MD record.", Severity: SeverityWarn, }, dns.TypeMF: { Reason: "RFC 973 obsoleted MF in 1986; use MX", Replacement: "MX", HowToFix: "Translate the mail-forwarder into an MX record (preference + exchange host) and delete the MF record.", Severity: SeverityWarn, }, dns.TypeGPOS: { Reason: "RFC 1712 superseded GPOS with LOC", Replacement: "LOC", HowToFix: "If geolocation is genuinely needed, publish a LOC record instead. Otherwise delete the GPOS record.", Severity: SeverityInfo, }, // --- Privacy/info-leak deprecations ---------------------------------- dns.TypeMB: { Reason: "RFC 2505/RFC 1035 §3.3: experimental, unused; replaced by MX", Replacement: "MX", HowToFix: "Delete the MB record; route mailbox traffic via MX.", Severity: SeverityInfo, }, dns.TypeMG: { Reason: "RFC 1035 §3.3: experimental mail-group record, never widely deployed", Replacement: "", HowToFix: "Delete the MG record; mail-group semantics now belong on the SMTP layer.", Severity: SeverityInfo, }, dns.TypeMR: { Reason: "RFC 1035 §3.3: experimental mail-rename record, never widely deployed", Replacement: "", HowToFix: "Delete the MR record.", Severity: SeverityInfo, }, dns.TypeMINFO: { Reason: "RFC 1035 §3.3: experimental mailbox-info record, never widely deployed", Replacement: "", HowToFix: "Delete the MINFO record.", Severity: SeverityInfo, }, dns.TypeNULL: { Reason: "RFC 1035 §3.3.10: experimental, must not appear in master files", Replacement: "", HowToFix: "Delete the NULL record. If it is used as a private channel, switch to TXT or a dedicated underscore label.", Severity: SeverityInfo, }, 11: { // WKS Reason: "RFC 1123 §6.1.3.6 discouraged WKS; modern stacks ignore it", Replacement: "", HowToFix: "Delete the WKS record. Service availability belongs in SRV, ALPN, or HTTPS/SVCB records, not WKS.", Severity: SeverityInfo, }, // --- Historical address families (no live deployment) ---------------- 22: { // NSAP Reason: "RFC 1706 historical: OSI/CLNP addressing, no current deployment", Replacement: "", HowToFix: "Delete the NSAP record.", Severity: SeverityInfo, }, dns.TypeNSAPPTR: { Reason: "RFC 1706 historical: OSI reverse mapping, no current deployment", Replacement: "", HowToFix: "Delete the NSAP-PTR record.", Severity: SeverityInfo, }, dns.TypeX25: { Reason: "RFC 1183 historical: X.25 addressing, no current deployment", Replacement: "", HowToFix: "Delete the X25 record.", Severity: SeverityInfo, }, dns.TypeISDN: { Reason: "RFC 1183 historical: ISDN addressing, no current deployment", Replacement: "", HowToFix: "Delete the ISDN record.", Severity: SeverityInfo, }, dns.TypeRT: { Reason: "RFC 1183 historical: route-through, superseded by direct routing", Replacement: "", HowToFix: "Delete the RT record.", Severity: SeverityInfo, }, dns.TypeATMA: { Reason: "ATM Forum AF-SAA-0069 historical: ATM addressing, no current deployment", Replacement: "", HowToFix: "Delete the ATMA record.", Severity: SeverityInfo, }, 31: { // EID Reason: "Nimrod EID: never deployed beyond the experiment", Replacement: "", HowToFix: "Delete the EID record.", Severity: SeverityInfo, }, 32: { // NIMLOC Reason: "Nimrod NIMLOC: never deployed beyond the experiment", Replacement: "", HowToFix: "Delete the NIMLOC record.", Severity: SeverityInfo, }, 40: { // SINK Reason: "draft-eastlake-kitchen-sink: never standardised", Replacement: "", HowToFix: "Delete the SINK record.", Severity: SeverityInfo, }, 56: { // NINFO Reason: "draft-reid-dnsext-zs: never standardised", Replacement: "TXT", HowToFix: "If you need free-form zone metadata, use a TXT record at the apex with a clearly scoped prefix.", Severity: SeverityInfo, }, 57: { // RKEY Reason: "draft-reid-dnsext-rkey: never standardised", Replacement: "", HowToFix: "Delete the RKEY record.", Severity: SeverityInfo, }, } // extraTypeNames covers the deprecated record types that miekg/dns does // not list in TypeToString (WKS, NSAP, A6, SINK). Without this fallback, // typeLabel would return "TYPEnnn" for them and the report would lose the // human-friendly name. var extraTypeNames = map[uint16]string{ 11: "WKS", 22: "NSAP", 38: "A6", 40: "SINK", } // typeLabel returns the textual record type name. dns.TypeToString covers // the well-known set; for unknown rrtypes we fall back to RFC 3597 form // ("TYPEnnn") so the report stays readable. func typeLabel(rrtype uint16) string { if name, ok := dns.TypeToString[rrtype]; ok { return name } if name, ok := extraTypeNames[rrtype]; ok { return name } // dns.Type stringer produces "TYPEnnn" for unknown types (RFC 3597). return dns.Type(rrtype).String() }