1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

REFACTOR: Opinion: TXT records are one long string (#2631)

Co-authored-by: Costas Drogos <costas.drogos@gmail.com>
Co-authored-by: imlonghao <git@imlonghao.com>
Co-authored-by: Jeffrey Cafferata <jeffrey@jcid.nl>
Co-authored-by: Vincent Hagen <blackshadev@users.noreply.github.com>
This commit is contained in:
Tom Limoncelli
2023-12-04 17:45:25 -05:00
committed by GitHub
parent 88d26c3ea2
commit cbccbbeb8d
71 changed files with 882 additions and 747 deletions

View File

@ -287,7 +287,7 @@ func GetZone(args GetZoneArgs) error {
}
fmt.Fprintf(w, "%s\t%s\t%d\tIN\t%s\t%s%s\n",
rec.NameFQDN, rec.Name, rec.TTL, rec.Type, rec.GetTargetCombined(), cfproxy)
rec.NameFQDN, rec.Name, rec.TTL, rec.Type, rec.GetTargetCombinedFunc(nil), cfproxy)
}
default:
@ -351,11 +351,7 @@ func formatDsl(zonename string, rec *models.RecordConfig, defaultTTL uint32) str
case "TLSA":
target = fmt.Sprintf(`%d, %d, %d, "%s"`, rec.TlsaUsage, rec.TlsaSelector, rec.TlsaMatchingType, rec.GetTargetField())
case "TXT":
if len(rec.TxtStrings) == 1 {
target = `"` + rec.TxtStrings[0] + `"`
} else {
target = `["` + strings.Join(rec.TxtStrings, `", "`) + `"]`
}
target = jsonQuoted(rec.GetTargetTXTJoined())
// TODO(tlim): If this is an SPF record, generate a SPF_BUILDER().
case "NS":
// NS records at the apex should be NAMESERVER() records.

View File

@ -45,6 +45,7 @@ type PreviewArgs struct {
Full bool
}
// ReportItem is a record of corrections for a particular domain/provider/registrar.
type ReportItem struct {
Domain string `json:"domain"`
Corrections int `json:"corrections"`

View File

@ -37,7 +37,7 @@ D("example.org", REG_CHANGEME
, SRV("_pop3._tcp", 0, 0, 0, ".")
, SRV("_pop3s._tcp", 0, 0, 0, ".")
, SRV("_sieve._tcp", 10, 10, 4190, "imap.example.org.")
, TXT("dns-moreinfo", ["Fred Bloggs, TZ=America/New_York", "Chat-Service-X: @handle1", "Chat-Service-Y: federated-handle@example.org"])
, TXT("dns-moreinfo", "Fred Bloggs, TZ=America/New_YorkChat-Service-X: @handle1Chat-Service-Y: federated-handle@example.org")
, SRV("_pgpkey-http._tcp", 0, 0, 0, ".")
, SRV("_pgpkey-https._tcp", 0, 0, 0, ".")
, SRV("_hkp._tcp", 0, 0, 0, ".")
@ -48,9 +48,9 @@ D("example.org", REG_CHANGEME
, AAAA("@", "2001:db8::1:1")
, TXT("_adsp._domainkey", "dkim=all")
, TXT("_dmarc", "v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s")
, TXT("d201911._domainkey", ["v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4SmyE5Tz5/wPL8cb2AKuHnlFeLMOhAl1UX/NYaeDCKMWoBPTgZRT0jonKLmV2UscHdodXu5ZsLr/NAuLCp7HmPLReLz7kxKncP6ppveKxc1aq5SPTKeWe77p6BptlahHc35eiXsZRpTsEzrbEOainy1IWEd+w9p1gWbrSutwE22z0i4V88nQ9UBa1ks", "6cVGxXBZFovWC+i28aGs6Lc7cSfHG5+Mrg3ud5X4evYXTGFMPpunMcCsXrqmS5a+5gRSEMZhngha/cHjLwaJnWzKaywNWF5XOsCjL94QkS0joB7lnGOHMNSZBCcu542Y3Ht3SgHhlpkF9mIbIRfpzA9IoSQIDAQAB"])
, TXT("d201911._domainkey", "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4SmyE5Tz5/wPL8cb2AKuHnlFeLMOhAl1UX/NYaeDCKMWoBPTgZRT0jonKLmV2UscHdodXu5ZsLr/NAuLCp7HmPLReLz7kxKncP6ppveKxc1aq5SPTKeWe77p6BptlahHc35eiXsZRpTsEzrbEOainy1IWEd+w9p1gWbrSutwE22z0i4V88nQ9UBa1ks6cVGxXBZFovWC+i28aGs6Lc7cSfHG5+Mrg3ud5X4evYXTGFMPpunMcCsXrqmS5a+5gRSEMZhngha/cHjLwaJnWzKaywNWF5XOsCjL94QkS0joB7lnGOHMNSZBCcu542Y3Ht3SgHhlpkF9mIbIRfpzA9IoSQIDAQAB")
, TXT("d201911e2._domainkey", "v=DKIM1; k=ed25519; p=GBt2k2L39KUb39fg5brOppXDHXvISy0+ECGgPld/bIo=")
, TXT("d202003._domainkey", ["v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv/1tQvOEs7xtKNm7PbPgY4hQjwHVvqqkDb0+TeqZHYRSczQ3c0LFJrIDFiPIdwQe/7AuKrxvATSh/uXKZ3EP4ouMgROPZnUxVXENeetJj+pc3nfGwTKUBTTTth+SO74gdIWsntjvAfduzosC4ZkxbDwZ9c253qXARGvGu+LB/iAeq0ngEbm5fU13+Jo", "pv0d4dR6oGe9GvMEnGGLZzNrxWl1BPe2x5JZ5/X/3fW8vJx3OgRB5N6fqbAJ6HZ9kcbikDH4lPPl9RIoprFk7mmwno/nXLQYGhPobmqq8wLkDiXEkWtYa5lzujz3XI3Zkk8ZIOGvdbVVfAttT0IVPnYkOhQIDAQAB"])
, TXT("d202003._domainkey", "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv/1tQvOEs7xtKNm7PbPgY4hQjwHVvqqkDb0+TeqZHYRSczQ3c0LFJrIDFiPIdwQe/7AuKrxvATSh/uXKZ3EP4ouMgROPZnUxVXENeetJj+pc3nfGwTKUBTTTth+SO74gdIWsntjvAfduzosC4ZkxbDwZ9c253qXARGvGu+LB/iAeq0ngEbm5fU13+Jopv0d4dR6oGe9GvMEnGGLZzNrxWl1BPe2x5JZ5/X/3fW8vJx3OgRB5N6fqbAJ6HZ9kcbikDH4lPPl9RIoprFk7mmwno/nXLQYGhPobmqq8wLkDiXEkWtYa5lzujz3XI3Zkk8ZIOGvdbVVfAttT0IVPnYkOhQIDAQAB")
, TXT("d202003e2._domainkey", "v=DKIM1; k=ed25519; p=DQI5d9sNMrr0SLDoAi071IFOyKnlbR29hAQdqVQecQg=")
, TXT("_report", "r=abuse-reports@example.org; rf=ARF; re=postmaster@example.org;")
, TXT("_smtp._tls", "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org")
@ -311,9 +311,9 @@ D("example.org", REG_CHANGEME
, A("fred", "192.0.2.93")
, AAAA("fred", "2001:db8::48:4558:5345:5256")
, TXT("fred", "v=spf1 ip4:192.0.2.25 ip6:2001:db8::1:25 mx include:_spf.example.com ~all")
, TXT("d201911._domainkey.fred", ["v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8/OMUa3PnWh9LqXFVwlAgYDdTtbq3zTtTOSBmJq5yWauzXYcUuSmhW7CsV0QQlacCsQgJlwg9Nl1vO1TosAj5EKUCLTeSqjlWrM7KXKPx8FT71Q9H9wXX4MHUyGrqHFo0OPzcmtHwqcd8AD6MIvJHSRoAfiPPBp8Euc0wGnJZdGS75Hk+wA3MQ2/Tlz", "P2eenyiFyqmUTAGOYsGC/tREsWPiegR/OVxNGlzTY6quHsuVK7UYtIyFnYx9PGWdl3b3p7VjQ5V0Rp+2CLtVrCuS6Zs+/3NhZdM7mdD0a9Jgxakwa1le5YmB5lHTGF7T8quy6TlKe9lMUIRNjqTHfSFz/MwIDAQAB"])
, TXT("d201911._domainkey.fred", "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8/OMUa3PnWh9LqXFVwlAgYDdTtbq3zTtTOSBmJq5yWauzXYcUuSmhW7CsV0QQlacCsQgJlwg9Nl1vO1TosAj5EKUCLTeSqjlWrM7KXKPx8FT71Q9H9wXX4MHUyGrqHFo0OPzcmtHwqcd8AD6MIvJHSRoAfiPPBp8Euc0wGnJZdGS75Hk+wA3MQ2/TlzP2eenyiFyqmUTAGOYsGC/tREsWPiegR/OVxNGlzTY6quHsuVK7UYtIyFnYx9PGWdl3b3p7VjQ5V0Rp+2CLtVrCuS6Zs+/3NhZdM7mdD0a9Jgxakwa1le5YmB5lHTGF7T8quy6TlKe9lMUIRNjqTHfSFz/MwIDAQAB")
, TXT("d201911e2._domainkey.fred", "v=DKIM1; k=ed25519; p=rQNsV9YcPJn/WYI1EDLjNbN/VuX1Hqq/oe4htbnhv+A=")
, TXT("d202003._domainkey.fred", ["v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpnx7tnRxAnE/poIRbVb2i+f1uQCXWnBHzHurgEyZX0CmGaiJuCbr8SWOW2PoXq9YX8gIv2TS3uzwGv/4yA2yX9Z9zar1LeWUfGgMWLdCol9xfmWrI+6MUzxuwhw/mXwzigbI4bHoakh3ez/i3J9KPS85GfrOODqA1emR13f2pG8EzAcje+rwW2PtYj", "c0h+FMDpeLuPYyYszFbNlrkVUneesxnoz+o4x/s6P14ZoRqz5CR7u6G02HwnNaHads5Eto6FYYErUUTtFmgWuYabHxgLVGRdRQs6B5OBYT/3L2q/lAgmEgdy/QL+c0Psfj99/XQmO8fcM0scBzw2ukQzcUwIDAQAB"])
, TXT("d202003._domainkey.fred", "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpnx7tnRxAnE/poIRbVb2i+f1uQCXWnBHzHurgEyZX0CmGaiJuCbr8SWOW2PoXq9YX8gIv2TS3uzwGv/4yA2yX9Z9zar1LeWUfGgMWLdCol9xfmWrI+6MUzxuwhw/mXwzigbI4bHoakh3ez/i3J9KPS85GfrOODqA1emR13f2pG8EzAcje+rwW2PtYjc0h+FMDpeLuPYyYszFbNlrkVUneesxnoz+o4x/s6P14ZoRqz5CR7u6G02HwnNaHads5Eto6FYYErUUTtFmgWuYabHxgLVGRdRQs6B5OBYT/3L2q/lAgmEgdy/QL+c0Psfj99/XQmO8fcM0scBzw2ukQzcUwIDAQAB")
, TXT("d202003e2._domainkey.fred", "v=DKIM1; k=ed25519; p=0DAPp/IRLYFI/Z4YSgJRi4gr7xcu1/EfJ5mjVn10aAw=")
, TXT("_adsp._domainkey.fred", "dkim=all")
, TXT("_dmarc.fred", "v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s")
@ -321,9 +321,9 @@ D("example.org", REG_CHANGEME
, TXT("_smtp._tls.fred", "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org")
, TXT("_smtp-tlsrpt.fred", "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org")
, MX("mailtest", 10, "mx.example.org.")
, TXT("d201911._domainkey.mailtest", ["v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo9xHnjHyhm1weA6FjOqM8LKVsklFt26HXWoe/0XCdmBG4i/UzQ7RiSgWO4kv7anPK6qf6rtL1xYsHufaRXG8yLsZxz+BbUP99eZvxZX78tMg4cGf+yU6uFxulCbOzsMy+8Cc3bbQTtIWYjyWBwnHdRRrCkQxjZ5KAd+x7ZB5qzqg2/eLJ7fCuNsr/xn", "0XTY6XYgug95e3h4CEW3Y+bkG81AMeJmT/hoVTcXvT/Gm6ZOUmx6faQWIHSW7qOR3VS6S75HOuclEUk0gt9r7OQHKl01sXh8g02SHRk8SUMEoNVayqplYZTFFF01Z192m7enmpp+St+HHUIT6jW/CAMCO3wIDAQAB"])
, TXT("d201911._domainkey.mailtest", "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo9xHnjHyhm1weA6FjOqM8LKVsklFt26HXWoe/0XCdmBG4i/UzQ7RiSgWO4kv7anPK6qf6rtL1xYsHufaRXG8yLsZxz+BbUP99eZvxZX78tMg4cGf+yU6uFxulCbOzsMy+8Cc3bbQTtIWYjyWBwnHdRRrCkQxjZ5KAd+x7ZB5qzqg2/eLJ7fCuNsr/xn0XTY6XYgug95e3h4CEW3Y+bkG81AMeJmT/hoVTcXvT/Gm6ZOUmx6faQWIHSW7qOR3VS6S75HOuclEUk0gt9r7OQHKl01sXh8g02SHRk8SUMEoNVayqplYZTFFF01Z192m7enmpp+St+HHUIT6jW/CAMCO3wIDAQAB")
, TXT("d201911e2._domainkey.mailtest", "v=DKIM1; k=ed25519; p=afulDDnhaTzdqKQN0jtWV04eOhAcyBk3NCyVheOf53Y=")
, TXT("d202003._domainkey.mailtest", ["v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs2BTVZaVLvL3qZBPaF7tRR0SdOKe+hjcpQ5fqO48lEuYiyTb6lkn8DPjDK11gTN3au0Bm+y8KC7ITKSJosuJXytxt3wqc61Pwtmb/Cy7GzmOF1AuegydB3/88VbgHT5DZucHrh6+ValZk4Trkx+/1K26Uo+h2KL2n/Ldb1y91ATHujp8DqxAOhiZ7KN", "aS1okNRRB4/14jPufAbeiN8/iBPiY5Hl80KHmpjM+7vvjb5jiecZ1ZrVDj7eTES4pmVh2v1c106mZLieoqDPYaf/HVbCM4E4n1B6kjbboSOpANADIcqXxGJQ7Be7/Sk9f7KwRusrsMHXmBHgm4wPmwGVZ3QIDAQAB"])
, TXT("d202003._domainkey.mailtest", "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs2BTVZaVLvL3qZBPaF7tRR0SdOKe+hjcpQ5fqO48lEuYiyTb6lkn8DPjDK11gTN3au0Bm+y8KC7ITKSJosuJXytxt3wqc61Pwtmb/Cy7GzmOF1AuegydB3/88VbgHT5DZucHrh6+ValZk4Trkx+/1K26Uo+h2KL2n/Ldb1y91ATHujp8DqxAOhiZ7KNaS1okNRRB4/14jPufAbeiN8/iBPiY5Hl80KHmpjM+7vvjb5jiecZ1ZrVDj7eTES4pmVh2v1c106mZLieoqDPYaf/HVbCM4E4n1B6kjbboSOpANADIcqXxGJQ7Be7/Sk9f7KwRusrsMHXmBHgm4wPmwGVZ3QIDAQAB")
, TXT("d202003e2._domainkey.mailtest", "v=DKIM1; k=ed25519; p=iqwH/hhozFdeo1xnuldr8KUi7O7g+DzmC+f0SYMKVDc=")
, TXT("_adsp._domainkey.mailtest", "dkim=all")
, TXT("_dmarc.mailtest", "v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s")

View File

@ -37,7 +37,7 @@ D("example.org", REG_CHANGEME,
SRV("_pop3._tcp", 0, 0, 0, "."),
SRV("_pop3s._tcp", 0, 0, 0, "."),
SRV("_sieve._tcp", 10, 10, 4190, "imap.example.org."),
TXT("dns-moreinfo", ["Fred Bloggs, TZ=America/New_York", "Chat-Service-X: @handle1", "Chat-Service-Y: federated-handle@example.org"]),
TXT("dns-moreinfo", "Fred Bloggs, TZ=America/New_YorkChat-Service-X: @handle1Chat-Service-Y: federated-handle@example.org"),
SRV("_pgpkey-http._tcp", 0, 0, 0, "."),
SRV("_pgpkey-https._tcp", 0, 0, 0, "."),
SRV("_hkp._tcp", 0, 0, 0, "."),
@ -48,9 +48,9 @@ D("example.org", REG_CHANGEME,
AAAA("@", "2001:db8::1:1"),
TXT("_adsp._domainkey", "dkim=all"),
TXT("_dmarc", "v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s"),
TXT("d201911._domainkey", ["v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4SmyE5Tz5/wPL8cb2AKuHnlFeLMOhAl1UX/NYaeDCKMWoBPTgZRT0jonKLmV2UscHdodXu5ZsLr/NAuLCp7HmPLReLz7kxKncP6ppveKxc1aq5SPTKeWe77p6BptlahHc35eiXsZRpTsEzrbEOainy1IWEd+w9p1gWbrSutwE22z0i4V88nQ9UBa1ks", "6cVGxXBZFovWC+i28aGs6Lc7cSfHG5+Mrg3ud5X4evYXTGFMPpunMcCsXrqmS5a+5gRSEMZhngha/cHjLwaJnWzKaywNWF5XOsCjL94QkS0joB7lnGOHMNSZBCcu542Y3Ht3SgHhlpkF9mIbIRfpzA9IoSQIDAQAB"]),
TXT("d201911._domainkey", "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4SmyE5Tz5/wPL8cb2AKuHnlFeLMOhAl1UX/NYaeDCKMWoBPTgZRT0jonKLmV2UscHdodXu5ZsLr/NAuLCp7HmPLReLz7kxKncP6ppveKxc1aq5SPTKeWe77p6BptlahHc35eiXsZRpTsEzrbEOainy1IWEd+w9p1gWbrSutwE22z0i4V88nQ9UBa1ks6cVGxXBZFovWC+i28aGs6Lc7cSfHG5+Mrg3ud5X4evYXTGFMPpunMcCsXrqmS5a+5gRSEMZhngha/cHjLwaJnWzKaywNWF5XOsCjL94QkS0joB7lnGOHMNSZBCcu542Y3Ht3SgHhlpkF9mIbIRfpzA9IoSQIDAQAB"),
TXT("d201911e2._domainkey", "v=DKIM1; k=ed25519; p=GBt2k2L39KUb39fg5brOppXDHXvISy0+ECGgPld/bIo="),
TXT("d202003._domainkey", ["v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv/1tQvOEs7xtKNm7PbPgY4hQjwHVvqqkDb0+TeqZHYRSczQ3c0LFJrIDFiPIdwQe/7AuKrxvATSh/uXKZ3EP4ouMgROPZnUxVXENeetJj+pc3nfGwTKUBTTTth+SO74gdIWsntjvAfduzosC4ZkxbDwZ9c253qXARGvGu+LB/iAeq0ngEbm5fU13+Jo", "pv0d4dR6oGe9GvMEnGGLZzNrxWl1BPe2x5JZ5/X/3fW8vJx3OgRB5N6fqbAJ6HZ9kcbikDH4lPPl9RIoprFk7mmwno/nXLQYGhPobmqq8wLkDiXEkWtYa5lzujz3XI3Zkk8ZIOGvdbVVfAttT0IVPnYkOhQIDAQAB"]),
TXT("d202003._domainkey", "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv/1tQvOEs7xtKNm7PbPgY4hQjwHVvqqkDb0+TeqZHYRSczQ3c0LFJrIDFiPIdwQe/7AuKrxvATSh/uXKZ3EP4ouMgROPZnUxVXENeetJj+pc3nfGwTKUBTTTth+SO74gdIWsntjvAfduzosC4ZkxbDwZ9c253qXARGvGu+LB/iAeq0ngEbm5fU13+Jopv0d4dR6oGe9GvMEnGGLZzNrxWl1BPe2x5JZ5/X/3fW8vJx3OgRB5N6fqbAJ6HZ9kcbikDH4lPPl9RIoprFk7mmwno/nXLQYGhPobmqq8wLkDiXEkWtYa5lzujz3XI3Zkk8ZIOGvdbVVfAttT0IVPnYkOhQIDAQAB"),
TXT("d202003e2._domainkey", "v=DKIM1; k=ed25519; p=DQI5d9sNMrr0SLDoAi071IFOyKnlbR29hAQdqVQecQg="),
TXT("_report", "r=abuse-reports@example.org; rf=ARF; re=postmaster@example.org;"),
TXT("_smtp._tls", "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org"),
@ -311,9 +311,9 @@ D("example.org", REG_CHANGEME,
A("fred", "192.0.2.93"),
AAAA("fred", "2001:db8::48:4558:5345:5256"),
TXT("fred", "v=spf1 ip4:192.0.2.25 ip6:2001:db8::1:25 mx include:_spf.example.com ~all"),
TXT("d201911._domainkey.fred", ["v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8/OMUa3PnWh9LqXFVwlAgYDdTtbq3zTtTOSBmJq5yWauzXYcUuSmhW7CsV0QQlacCsQgJlwg9Nl1vO1TosAj5EKUCLTeSqjlWrM7KXKPx8FT71Q9H9wXX4MHUyGrqHFo0OPzcmtHwqcd8AD6MIvJHSRoAfiPPBp8Euc0wGnJZdGS75Hk+wA3MQ2/Tlz", "P2eenyiFyqmUTAGOYsGC/tREsWPiegR/OVxNGlzTY6quHsuVK7UYtIyFnYx9PGWdl3b3p7VjQ5V0Rp+2CLtVrCuS6Zs+/3NhZdM7mdD0a9Jgxakwa1le5YmB5lHTGF7T8quy6TlKe9lMUIRNjqTHfSFz/MwIDAQAB"]),
TXT("d201911._domainkey.fred", "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8/OMUa3PnWh9LqXFVwlAgYDdTtbq3zTtTOSBmJq5yWauzXYcUuSmhW7CsV0QQlacCsQgJlwg9Nl1vO1TosAj5EKUCLTeSqjlWrM7KXKPx8FT71Q9H9wXX4MHUyGrqHFo0OPzcmtHwqcd8AD6MIvJHSRoAfiPPBp8Euc0wGnJZdGS75Hk+wA3MQ2/TlzP2eenyiFyqmUTAGOYsGC/tREsWPiegR/OVxNGlzTY6quHsuVK7UYtIyFnYx9PGWdl3b3p7VjQ5V0Rp+2CLtVrCuS6Zs+/3NhZdM7mdD0a9Jgxakwa1le5YmB5lHTGF7T8quy6TlKe9lMUIRNjqTHfSFz/MwIDAQAB"),
TXT("d201911e2._domainkey.fred", "v=DKIM1; k=ed25519; p=rQNsV9YcPJn/WYI1EDLjNbN/VuX1Hqq/oe4htbnhv+A="),
TXT("d202003._domainkey.fred", ["v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpnx7tnRxAnE/poIRbVb2i+f1uQCXWnBHzHurgEyZX0CmGaiJuCbr8SWOW2PoXq9YX8gIv2TS3uzwGv/4yA2yX9Z9zar1LeWUfGgMWLdCol9xfmWrI+6MUzxuwhw/mXwzigbI4bHoakh3ez/i3J9KPS85GfrOODqA1emR13f2pG8EzAcje+rwW2PtYj", "c0h+FMDpeLuPYyYszFbNlrkVUneesxnoz+o4x/s6P14ZoRqz5CR7u6G02HwnNaHads5Eto6FYYErUUTtFmgWuYabHxgLVGRdRQs6B5OBYT/3L2q/lAgmEgdy/QL+c0Psfj99/XQmO8fcM0scBzw2ukQzcUwIDAQAB"]),
TXT("d202003._domainkey.fred", "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpnx7tnRxAnE/poIRbVb2i+f1uQCXWnBHzHurgEyZX0CmGaiJuCbr8SWOW2PoXq9YX8gIv2TS3uzwGv/4yA2yX9Z9zar1LeWUfGgMWLdCol9xfmWrI+6MUzxuwhw/mXwzigbI4bHoakh3ez/i3J9KPS85GfrOODqA1emR13f2pG8EzAcje+rwW2PtYjc0h+FMDpeLuPYyYszFbNlrkVUneesxnoz+o4x/s6P14ZoRqz5CR7u6G02HwnNaHads5Eto6FYYErUUTtFmgWuYabHxgLVGRdRQs6B5OBYT/3L2q/lAgmEgdy/QL+c0Psfj99/XQmO8fcM0scBzw2ukQzcUwIDAQAB"),
TXT("d202003e2._domainkey.fred", "v=DKIM1; k=ed25519; p=0DAPp/IRLYFI/Z4YSgJRi4gr7xcu1/EfJ5mjVn10aAw="),
TXT("_adsp._domainkey.fred", "dkim=all"),
TXT("_dmarc.fred", "v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s"),
@ -321,9 +321,9 @@ D("example.org", REG_CHANGEME,
TXT("_smtp._tls.fred", "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org"),
TXT("_smtp-tlsrpt.fred", "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org"),
MX("mailtest", 10, "mx.example.org."),
TXT("d201911._domainkey.mailtest", ["v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo9xHnjHyhm1weA6FjOqM8LKVsklFt26HXWoe/0XCdmBG4i/UzQ7RiSgWO4kv7anPK6qf6rtL1xYsHufaRXG8yLsZxz+BbUP99eZvxZX78tMg4cGf+yU6uFxulCbOzsMy+8Cc3bbQTtIWYjyWBwnHdRRrCkQxjZ5KAd+x7ZB5qzqg2/eLJ7fCuNsr/xn", "0XTY6XYgug95e3h4CEW3Y+bkG81AMeJmT/hoVTcXvT/Gm6ZOUmx6faQWIHSW7qOR3VS6S75HOuclEUk0gt9r7OQHKl01sXh8g02SHRk8SUMEoNVayqplYZTFFF01Z192m7enmpp+St+HHUIT6jW/CAMCO3wIDAQAB"]),
TXT("d201911._domainkey.mailtest", "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo9xHnjHyhm1weA6FjOqM8LKVsklFt26HXWoe/0XCdmBG4i/UzQ7RiSgWO4kv7anPK6qf6rtL1xYsHufaRXG8yLsZxz+BbUP99eZvxZX78tMg4cGf+yU6uFxulCbOzsMy+8Cc3bbQTtIWYjyWBwnHdRRrCkQxjZ5KAd+x7ZB5qzqg2/eLJ7fCuNsr/xn0XTY6XYgug95e3h4CEW3Y+bkG81AMeJmT/hoVTcXvT/Gm6ZOUmx6faQWIHSW7qOR3VS6S75HOuclEUk0gt9r7OQHKl01sXh8g02SHRk8SUMEoNVayqplYZTFFF01Z192m7enmpp+St+HHUIT6jW/CAMCO3wIDAQAB"),
TXT("d201911e2._domainkey.mailtest", "v=DKIM1; k=ed25519; p=afulDDnhaTzdqKQN0jtWV04eOhAcyBk3NCyVheOf53Y="),
TXT("d202003._domainkey.mailtest", ["v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs2BTVZaVLvL3qZBPaF7tRR0SdOKe+hjcpQ5fqO48lEuYiyTb6lkn8DPjDK11gTN3au0Bm+y8KC7ITKSJosuJXytxt3wqc61Pwtmb/Cy7GzmOF1AuegydB3/88VbgHT5DZucHrh6+ValZk4Trkx+/1K26Uo+h2KL2n/Ldb1y91ATHujp8DqxAOhiZ7KN", "aS1okNRRB4/14jPufAbeiN8/iBPiY5Hl80KHmpjM+7vvjb5jiecZ1ZrVDj7eTES4pmVh2v1c106mZLieoqDPYaf/HVbCM4E4n1B6kjbboSOpANADIcqXxGJQ7Be7/Sk9f7KwRusrsMHXmBHgm4wPmwGVZ3QIDAQAB"]),
TXT("d202003._domainkey.mailtest", "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs2BTVZaVLvL3qZBPaF7tRR0SdOKe+hjcpQ5fqO48lEuYiyTb6lkn8DPjDK11gTN3au0Bm+y8KC7ITKSJosuJXytxt3wqc61Pwtmb/Cy7GzmOF1AuegydB3/88VbgHT5DZucHrh6+ValZk4Trkx+/1K26Uo+h2KL2n/Ldb1y91ATHujp8DqxAOhiZ7KNaS1okNRRB4/14jPufAbeiN8/iBPiY5Hl80KHmpjM+7vvjb5jiecZ1ZrVDj7eTES4pmVh2v1c106mZLieoqDPYaf/HVbCM4E4n1B6kjbboSOpANADIcqXxGJQ7Be7/Sk9f7KwRusrsMHXmBHgm4wPmwGVZ3QIDAQAB"),
TXT("d202003e2._domainkey.mailtest", "v=DKIM1; k=ed25519; p=iqwH/hhozFdeo1xnuldr8KUi7O7g+DzmC+f0SYMKVDc="),
TXT("_adsp._domainkey.mailtest", "dkim=all"),
TXT("_dmarc.mailtest", "v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s"),

View File

@ -4,7 +4,7 @@ example.org @ 7200 IN NS ns2.example.org.
example.org @ 7200 IN NS ns-a.example.net.
example.org @ 7200 IN NS friend-dns.example.com.
example.org @ 7200 IN MX 10 mx.example.org.
example.org @ 7200 IN TXT "v=spf1 ip4:192.0.2.25 ip6:2001:db8::1:25 mx include:_spf.example.com ~all"
example.org @ 7200 IN TXT v=spf1 ip4:192.0.2.25 ip6:2001:db8::1:25 mx include:_spf.example.com ~all
_client._smtp.example.org _client._smtp 7200 IN SRV 1 1 1 example.org.
_client._smtp.mx.example.org _client._smtp.mx 7200 IN SRV 1 2 1 mx.example.org.
_client._smtp.foo.example.org _client._smtp.foo 7200 IN SRV 1 2 1 foo.example.org.
@ -12,7 +12,7 @@ _kerberos._tcp.example.org _kerberos._tcp 7200 IN SRV 10 1 88 kerb-service.examp
_kerberos._udp.example.org _kerberos._udp 7200 IN SRV 10 1 88 kerb-service.example.org.
_kpasswd._udp.example.org _kpasswd._udp 7200 IN SRV 10 1 464 kerb-service.example.org.
_kerberos-adm._tcp.example.org _kerberos-adm._tcp 7200 IN SRV 10 1 749 kerb-service.example.org.
_kerberos.example.org _kerberos 7200 IN TXT "EXAMPLE.ORG"
_kerberos.example.org _kerberos 7200 IN TXT EXAMPLE.ORG
_ldap._tcp.example.org _ldap._tcp 7200 IN SRV 0 0 0 .
_ldap._udp.example.org _ldap._udp 7200 IN SRV 0 0 0 .
_jabber._tcp.example.org _jabber._tcp 7200 IN SRV 10 2 5269 xmpp-s2s.example.org.
@ -32,7 +32,7 @@ _imaps._tcp.example.org _imaps._tcp 7200 IN SRV 10 10 993 imap.example.org.
_pop3._tcp.example.org _pop3._tcp 7200 IN SRV 0 0 0 .
_pop3s._tcp.example.org _pop3s._tcp 7200 IN SRV 0 0 0 .
_sieve._tcp.example.org _sieve._tcp 7200 IN SRV 10 10 4190 imap.example.org.
dns-moreinfo.example.org dns-moreinfo 7200 IN TXT "Fred Bloggs, TZ=America/New_York" "Chat-Service-X: @handle1" "Chat-Service-Y: federated-handle@example.org"
dns-moreinfo.example.org dns-moreinfo 7200 IN TXT Fred Bloggs, TZ=America/New_YorkChat-Service-X: @handle1Chat-Service-Y: federated-handle@example.org
_pgpkey-http._tcp.example.org _pgpkey-http._tcp 7200 IN SRV 0 0 0 .
_pgpkey-https._tcp.example.org _pgpkey-https._tcp 7200 IN SRV 0 0 0 .
_hkp._tcp.example.org _hkp._tcp 7200 IN SRV 0 0 0 .
@ -41,20 +41,20 @@ _finger._tcp.example.org _finger._tcp 7200 IN SRV 10 10 79 barbican.example.org.
_avatars-sec._tcp.example.org _avatars-sec._tcp 7200 IN SRV 10 10 443 avatars.example.org.
example.org @ 7200 IN A 192.0.2.1
example.org @ 7200 IN AAAA 2001:db8::1:1
_adsp._domainkey.example.org _adsp._domainkey 7200 IN TXT "dkim=all"
_dmarc.example.org _dmarc 7200 IN TXT "v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s"
d201911._domainkey.example.org d201911._domainkey 7200 IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4SmyE5Tz5/wPL8cb2AKuHnlFeLMOhAl1UX/NYaeDCKMWoBPTgZRT0jonKLmV2UscHdodXu5ZsLr/NAuLCp7HmPLReLz7kxKncP6ppveKxc1aq5SPTKeWe77p6BptlahHc35eiXsZRpTsEzrbEOainy1IWEd+w9p1gWbrSutwE22z0i4V88nQ9UBa1ks" "6cVGxXBZFovWC+i28aGs6Lc7cSfHG5+Mrg3ud5X4evYXTGFMPpunMcCsXrqmS5a+5gRSEMZhngha/cHjLwaJnWzKaywNWF5XOsCjL94QkS0joB7lnGOHMNSZBCcu542Y3Ht3SgHhlpkF9mIbIRfpzA9IoSQIDAQAB"
d201911e2._domainkey.example.org d201911e2._domainkey 7200 IN TXT "v=DKIM1; k=ed25519; p=GBt2k2L39KUb39fg5brOppXDHXvISy0+ECGgPld/bIo="
d202003._domainkey.example.org d202003._domainkey 7200 IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv/1tQvOEs7xtKNm7PbPgY4hQjwHVvqqkDb0+TeqZHYRSczQ3c0LFJrIDFiPIdwQe/7AuKrxvATSh/uXKZ3EP4ouMgROPZnUxVXENeetJj+pc3nfGwTKUBTTTth+SO74gdIWsntjvAfduzosC4ZkxbDwZ9c253qXARGvGu+LB/iAeq0ngEbm5fU13+Jo" "pv0d4dR6oGe9GvMEnGGLZzNrxWl1BPe2x5JZ5/X/3fW8vJx3OgRB5N6fqbAJ6HZ9kcbikDH4lPPl9RIoprFk7mmwno/nXLQYGhPobmqq8wLkDiXEkWtYa5lzujz3XI3Zkk8ZIOGvdbVVfAttT0IVPnYkOhQIDAQAB"
d202003e2._domainkey.example.org d202003e2._domainkey 7200 IN TXT "v=DKIM1; k=ed25519; p=DQI5d9sNMrr0SLDoAi071IFOyKnlbR29hAQdqVQecQg="
_report.example.org _report 7200 IN TXT "r=abuse-reports@example.org; rf=ARF; re=postmaster@example.org;"
_smtp._tls.example.org _smtp._tls 7200 IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org"
_smtp-tlsrpt.example.org _smtp-tlsrpt 7200 IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org"
example.net._report._dmarc.example.org example.net._report._dmarc 7200 IN TXT "v=DMARC1"
example.com._report._dmarc.example.org example.com._report._dmarc 7200 IN TXT "v=DMARC1"
xn--2j5b.xn--9t4b11yi5a._report._dmarc.example.org xn--2j5b.xn--9t4b11yi5a._report._dmarc 7200 IN TXT "v=DMARC1"
special.test._report._dmarc.example.org special.test._report._dmarc 7200 IN TXT "v=DMARC1"
xn--qck5b9a5eml3bze.xn--zckzah._report._dmarc.example.org xn--qck5b9a5eml3bze.xn--zckzah._report._dmarc 7200 IN TXT "v=DMARC1"
_adsp._domainkey.example.org _adsp._domainkey 7200 IN TXT dkim=all
_dmarc.example.org _dmarc 7200 IN TXT v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s
d201911._domainkey.example.org d201911._domainkey 7200 IN TXT v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4SmyE5Tz5/wPL8cb2AKuHnlFeLMOhAl1UX/NYaeDCKMWoBPTgZRT0jonKLmV2UscHdodXu5ZsLr/NAuLCp7HmPLReLz7kxKncP6ppveKxc1aq5SPTKeWe77p6BptlahHc35eiXsZRpTsEzrbEOainy1IWEd+w9p1gWbrSutwE22z0i4V88nQ9UBa1ks6cVGxXBZFovWC+i28aGs6Lc7cSfHG5+Mrg3ud5X4evYXTGFMPpunMcCsXrqmS5a+5gRSEMZhngha/cHjLwaJnWzKaywNWF5XOsCjL94QkS0joB7lnGOHMNSZBCcu542Y3Ht3SgHhlpkF9mIbIRfpzA9IoSQIDAQAB
d201911e2._domainkey.example.org d201911e2._domainkey 7200 IN TXT v=DKIM1; k=ed25519; p=GBt2k2L39KUb39fg5brOppXDHXvISy0+ECGgPld/bIo=
d202003._domainkey.example.org d202003._domainkey 7200 IN TXT v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv/1tQvOEs7xtKNm7PbPgY4hQjwHVvqqkDb0+TeqZHYRSczQ3c0LFJrIDFiPIdwQe/7AuKrxvATSh/uXKZ3EP4ouMgROPZnUxVXENeetJj+pc3nfGwTKUBTTTth+SO74gdIWsntjvAfduzosC4ZkxbDwZ9c253qXARGvGu+LB/iAeq0ngEbm5fU13+Jopv0d4dR6oGe9GvMEnGGLZzNrxWl1BPe2x5JZ5/X/3fW8vJx3OgRB5N6fqbAJ6HZ9kcbikDH4lPPl9RIoprFk7mmwno/nXLQYGhPobmqq8wLkDiXEkWtYa5lzujz3XI3Zkk8ZIOGvdbVVfAttT0IVPnYkOhQIDAQAB
d202003e2._domainkey.example.org d202003e2._domainkey 7200 IN TXT v=DKIM1; k=ed25519; p=DQI5d9sNMrr0SLDoAi071IFOyKnlbR29hAQdqVQecQg=
_report.example.org _report 7200 IN TXT r=abuse-reports@example.org; rf=ARF; re=postmaster@example.org;
_smtp._tls.example.org _smtp._tls 7200 IN TXT v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org
_smtp-tlsrpt.example.org _smtp-tlsrpt 7200 IN TXT v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org
example.net._report._dmarc.example.org example.net._report._dmarc 7200 IN TXT v=DMARC1
example.com._report._dmarc.example.org example.com._report._dmarc 7200 IN TXT v=DMARC1
xn--2j5b.xn--9t4b11yi5a._report._dmarc.example.org xn--2j5b.xn--9t4b11yi5a._report._dmarc 7200 IN TXT v=DMARC1
special.test._report._dmarc.example.org special.test._report._dmarc 7200 IN TXT v=DMARC1
xn--qck5b9a5eml3bze.xn--zckzah._report._dmarc.example.org xn--qck5b9a5eml3bze.xn--zckzah._report._dmarc 7200 IN TXT v=DMARC1
*._smimecert.example.org *._smimecert 7200 IN CNAME _ourca-smimea.example.org.
b._dns-sd._udp.example.org b._dns-sd._udp 7200 IN PTR field.example.org.
lb._dns-sd._udp.example.org lb._dns-sd._udp 7200 IN PTR field.example.org.
@ -265,9 +265,9 @@ mx.example.org mx 7200 IN A 192.0.2.25
mx.example.org mx 7200 IN AAAA 2001:db8::48:4558:736d:7470
mx.ipv4.example.org mx.ipv4 7200 IN A 192.0.2.25
mx.ipv6.example.org mx.ipv6 7200 IN AAAA 2001:db8::48:4558:736d:7470
mx.example.org mx 7200 IN TXT "v=spf1 a include:_spflarge.example.net -all"
_mta-sts.example.org _mta-sts 7200 IN TXT "v=STSv1; id=20191231r1;"
mta-sts.example.org mta-sts 7200 IN TXT "v=STSv1; id=20191231r1;"
mx.example.org mx 7200 IN TXT v=spf1 a include:_spflarge.example.net -all
_mta-sts.example.org _mta-sts 7200 IN TXT v=STSv1; id=20191231r1;
mta-sts.example.org mta-sts 7200 IN TXT v=STSv1; id=20191231r1;
mta-sts.example.org mta-sts 7200 IN A 192.0.2.93
mta-sts.example.org mta-sts 7200 IN AAAA 2001:db8::48:4558:5345:5256
xmpp.ipv6.example.org xmpp.ipv6 7200 IN AAAA 2001:db8::f0ab:cdef:1234:f00f
@ -297,34 +297,34 @@ news-feed.example.org news-feed 7200 IN AAAA 2001:db8::48:4558:6e6e:7470
go.example.org go 7200 IN CNAME abcdefghijklmn.cloudfront.net.
foo.example.org foo 7200 IN A 192.0.2.200
gladys.example.org gladys 7200 IN MX 10 mx.example.org.
_adsp._domainkey.gladys.example.org _adsp._domainkey.gladys 7200 IN TXT "dkim=all"
_dmarc.gladys.example.org _dmarc.gladys 7200 IN TXT "v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s"
_report.gladys.example.org _report.gladys 7200 IN TXT "r=abuse-reports@example.org; rf=ARF; re=postmaster@example.org;"
_smtp._tls.gladys.example.org _smtp._tls.gladys 7200 IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org"
_smtp-tlsrpt.gladys.example.org _smtp-tlsrpt.gladys 7200 IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org"
_adsp._domainkey.gladys.example.org _adsp._domainkey.gladys 7200 IN TXT dkim=all
_dmarc.gladys.example.org _dmarc.gladys 7200 IN TXT v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s
_report.gladys.example.org _report.gladys 7200 IN TXT r=abuse-reports@example.org; rf=ARF; re=postmaster@example.org;
_smtp._tls.gladys.example.org _smtp._tls.gladys 7200 IN TXT v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org
_smtp-tlsrpt.gladys.example.org _smtp-tlsrpt.gladys 7200 IN TXT v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org
fred.example.org fred 7200 IN MX 10 mx.example.org.
fred.example.org fred 7200 IN A 192.0.2.93
fred.example.org fred 7200 IN AAAA 2001:db8::48:4558:5345:5256
fred.example.org fred 7200 IN TXT "v=spf1 ip4:192.0.2.25 ip6:2001:db8::1:25 mx include:_spf.example.com ~all"
d201911._domainkey.fred.example.org d201911._domainkey.fred 7200 IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8/OMUa3PnWh9LqXFVwlAgYDdTtbq3zTtTOSBmJq5yWauzXYcUuSmhW7CsV0QQlacCsQgJlwg9Nl1vO1TosAj5EKUCLTeSqjlWrM7KXKPx8FT71Q9H9wXX4MHUyGrqHFo0OPzcmtHwqcd8AD6MIvJHSRoAfiPPBp8Euc0wGnJZdGS75Hk+wA3MQ2/Tlz" "P2eenyiFyqmUTAGOYsGC/tREsWPiegR/OVxNGlzTY6quHsuVK7UYtIyFnYx9PGWdl3b3p7VjQ5V0Rp+2CLtVrCuS6Zs+/3NhZdM7mdD0a9Jgxakwa1le5YmB5lHTGF7T8quy6TlKe9lMUIRNjqTHfSFz/MwIDAQAB"
d201911e2._domainkey.fred.example.org d201911e2._domainkey.fred 7200 IN TXT "v=DKIM1; k=ed25519; p=rQNsV9YcPJn/WYI1EDLjNbN/VuX1Hqq/oe4htbnhv+A="
d202003._domainkey.fred.example.org d202003._domainkey.fred 7200 IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpnx7tnRxAnE/poIRbVb2i+f1uQCXWnBHzHurgEyZX0CmGaiJuCbr8SWOW2PoXq9YX8gIv2TS3uzwGv/4yA2yX9Z9zar1LeWUfGgMWLdCol9xfmWrI+6MUzxuwhw/mXwzigbI4bHoakh3ez/i3J9KPS85GfrOODqA1emR13f2pG8EzAcje+rwW2PtYj" "c0h+FMDpeLuPYyYszFbNlrkVUneesxnoz+o4x/s6P14ZoRqz5CR7u6G02HwnNaHads5Eto6FYYErUUTtFmgWuYabHxgLVGRdRQs6B5OBYT/3L2q/lAgmEgdy/QL+c0Psfj99/XQmO8fcM0scBzw2ukQzcUwIDAQAB"
d202003e2._domainkey.fred.example.org d202003e2._domainkey.fred 7200 IN TXT "v=DKIM1; k=ed25519; p=0DAPp/IRLYFI/Z4YSgJRi4gr7xcu1/EfJ5mjVn10aAw="
_adsp._domainkey.fred.example.org _adsp._domainkey.fred 7200 IN TXT "dkim=all"
_dmarc.fred.example.org _dmarc.fred 7200 IN TXT "v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s"
_report.fred.example.org _report.fred 7200 IN TXT "r=abuse-reports@example.org; rf=ARF; re=postmaster@example.org;"
_smtp._tls.fred.example.org _smtp._tls.fred 7200 IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org"
_smtp-tlsrpt.fred.example.org _smtp-tlsrpt.fred 7200 IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org"
fred.example.org fred 7200 IN TXT v=spf1 ip4:192.0.2.25 ip6:2001:db8::1:25 mx include:_spf.example.com ~all
d201911._domainkey.fred.example.org d201911._domainkey.fred 7200 IN TXT v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8/OMUa3PnWh9LqXFVwlAgYDdTtbq3zTtTOSBmJq5yWauzXYcUuSmhW7CsV0QQlacCsQgJlwg9Nl1vO1TosAj5EKUCLTeSqjlWrM7KXKPx8FT71Q9H9wXX4MHUyGrqHFo0OPzcmtHwqcd8AD6MIvJHSRoAfiPPBp8Euc0wGnJZdGS75Hk+wA3MQ2/TlzP2eenyiFyqmUTAGOYsGC/tREsWPiegR/OVxNGlzTY6quHsuVK7UYtIyFnYx9PGWdl3b3p7VjQ5V0Rp+2CLtVrCuS6Zs+/3NhZdM7mdD0a9Jgxakwa1le5YmB5lHTGF7T8quy6TlKe9lMUIRNjqTHfSFz/MwIDAQAB
d201911e2._domainkey.fred.example.org d201911e2._domainkey.fred 7200 IN TXT v=DKIM1; k=ed25519; p=rQNsV9YcPJn/WYI1EDLjNbN/VuX1Hqq/oe4htbnhv+A=
d202003._domainkey.fred.example.org d202003._domainkey.fred 7200 IN TXT v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpnx7tnRxAnE/poIRbVb2i+f1uQCXWnBHzHurgEyZX0CmGaiJuCbr8SWOW2PoXq9YX8gIv2TS3uzwGv/4yA2yX9Z9zar1LeWUfGgMWLdCol9xfmWrI+6MUzxuwhw/mXwzigbI4bHoakh3ez/i3J9KPS85GfrOODqA1emR13f2pG8EzAcje+rwW2PtYjc0h+FMDpeLuPYyYszFbNlrkVUneesxnoz+o4x/s6P14ZoRqz5CR7u6G02HwnNaHads5Eto6FYYErUUTtFmgWuYabHxgLVGRdRQs6B5OBYT/3L2q/lAgmEgdy/QL+c0Psfj99/XQmO8fcM0scBzw2ukQzcUwIDAQAB
d202003e2._domainkey.fred.example.org d202003e2._domainkey.fred 7200 IN TXT v=DKIM1; k=ed25519; p=0DAPp/IRLYFI/Z4YSgJRi4gr7xcu1/EfJ5mjVn10aAw=
_adsp._domainkey.fred.example.org _adsp._domainkey.fred 7200 IN TXT dkim=all
_dmarc.fred.example.org _dmarc.fred 7200 IN TXT v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s
_report.fred.example.org _report.fred 7200 IN TXT r=abuse-reports@example.org; rf=ARF; re=postmaster@example.org;
_smtp._tls.fred.example.org _smtp._tls.fred 7200 IN TXT v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org
_smtp-tlsrpt.fred.example.org _smtp-tlsrpt.fred 7200 IN TXT v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org
mailtest.example.org mailtest 7200 IN MX 10 mx.example.org.
d201911._domainkey.mailtest.example.org d201911._domainkey.mailtest 7200 IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo9xHnjHyhm1weA6FjOqM8LKVsklFt26HXWoe/0XCdmBG4i/UzQ7RiSgWO4kv7anPK6qf6rtL1xYsHufaRXG8yLsZxz+BbUP99eZvxZX78tMg4cGf+yU6uFxulCbOzsMy+8Cc3bbQTtIWYjyWBwnHdRRrCkQxjZ5KAd+x7ZB5qzqg2/eLJ7fCuNsr/xn" "0XTY6XYgug95e3h4CEW3Y+bkG81AMeJmT/hoVTcXvT/Gm6ZOUmx6faQWIHSW7qOR3VS6S75HOuclEUk0gt9r7OQHKl01sXh8g02SHRk8SUMEoNVayqplYZTFFF01Z192m7enmpp+St+HHUIT6jW/CAMCO3wIDAQAB"
d201911e2._domainkey.mailtest.example.org d201911e2._domainkey.mailtest 7200 IN TXT "v=DKIM1; k=ed25519; p=afulDDnhaTzdqKQN0jtWV04eOhAcyBk3NCyVheOf53Y="
d202003._domainkey.mailtest.example.org d202003._domainkey.mailtest 7200 IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs2BTVZaVLvL3qZBPaF7tRR0SdOKe+hjcpQ5fqO48lEuYiyTb6lkn8DPjDK11gTN3au0Bm+y8KC7ITKSJosuJXytxt3wqc61Pwtmb/Cy7GzmOF1AuegydB3/88VbgHT5DZucHrh6+ValZk4Trkx+/1K26Uo+h2KL2n/Ldb1y91ATHujp8DqxAOhiZ7KN" "aS1okNRRB4/14jPufAbeiN8/iBPiY5Hl80KHmpjM+7vvjb5jiecZ1ZrVDj7eTES4pmVh2v1c106mZLieoqDPYaf/HVbCM4E4n1B6kjbboSOpANADIcqXxGJQ7Be7/Sk9f7KwRusrsMHXmBHgm4wPmwGVZ3QIDAQAB"
d202003e2._domainkey.mailtest.example.org d202003e2._domainkey.mailtest 7200 IN TXT "v=DKIM1; k=ed25519; p=iqwH/hhozFdeo1xnuldr8KUi7O7g+DzmC+f0SYMKVDc="
_adsp._domainkey.mailtest.example.org _adsp._domainkey.mailtest 7200 IN TXT "dkim=all"
_dmarc.mailtest.example.org _dmarc.mailtest 7200 IN TXT "v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s"
_report.mailtest.example.org _report.mailtest 7200 IN TXT "r=abuse-reports@example.org; rf=ARF; re=postmaster@example.org;"
_smtp._tls.mailtest.example.org _smtp._tls.mailtest 7200 IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org"
_smtp-tlsrpt.mailtest.example.org _smtp-tlsrpt.mailtest 7200 IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org"
d201911._domainkey.mailtest.example.org d201911._domainkey.mailtest 7200 IN TXT v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo9xHnjHyhm1weA6FjOqM8LKVsklFt26HXWoe/0XCdmBG4i/UzQ7RiSgWO4kv7anPK6qf6rtL1xYsHufaRXG8yLsZxz+BbUP99eZvxZX78tMg4cGf+yU6uFxulCbOzsMy+8Cc3bbQTtIWYjyWBwnHdRRrCkQxjZ5KAd+x7ZB5qzqg2/eLJ7fCuNsr/xn0XTY6XYgug95e3h4CEW3Y+bkG81AMeJmT/hoVTcXvT/Gm6ZOUmx6faQWIHSW7qOR3VS6S75HOuclEUk0gt9r7OQHKl01sXh8g02SHRk8SUMEoNVayqplYZTFFF01Z192m7enmpp+St+HHUIT6jW/CAMCO3wIDAQAB
d201911e2._domainkey.mailtest.example.org d201911e2._domainkey.mailtest 7200 IN TXT v=DKIM1; k=ed25519; p=afulDDnhaTzdqKQN0jtWV04eOhAcyBk3NCyVheOf53Y=
d202003._domainkey.mailtest.example.org d202003._domainkey.mailtest 7200 IN TXT v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs2BTVZaVLvL3qZBPaF7tRR0SdOKe+hjcpQ5fqO48lEuYiyTb6lkn8DPjDK11gTN3au0Bm+y8KC7ITKSJosuJXytxt3wqc61Pwtmb/Cy7GzmOF1AuegydB3/88VbgHT5DZucHrh6+ValZk4Trkx+/1K26Uo+h2KL2n/Ldb1y91ATHujp8DqxAOhiZ7KNaS1okNRRB4/14jPufAbeiN8/iBPiY5Hl80KHmpjM+7vvjb5jiecZ1ZrVDj7eTES4pmVh2v1c106mZLieoqDPYaf/HVbCM4E4n1B6kjbboSOpANADIcqXxGJQ7Be7/Sk9f7KwRusrsMHXmBHgm4wPmwGVZ3QIDAQAB
d202003e2._domainkey.mailtest.example.org d202003e2._domainkey.mailtest 7200 IN TXT v=DKIM1; k=ed25519; p=iqwH/hhozFdeo1xnuldr8KUi7O7g+DzmC+f0SYMKVDc=
_adsp._domainkey.mailtest.example.org _adsp._domainkey.mailtest 7200 IN TXT dkim=all
_dmarc.mailtest.example.org _dmarc.mailtest 7200 IN TXT v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s
_report.mailtest.example.org _report.mailtest 7200 IN TXT r=abuse-reports@example.org; rf=ARF; re=postmaster@example.org;
_smtp._tls.mailtest.example.org _smtp._tls.mailtest 7200 IN TXT v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org
_smtp-tlsrpt.mailtest.example.org _smtp-tlsrpt.mailtest 7200 IN TXT v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org
_pgpkey-http._tcp.sks.example.org _pgpkey-http._tcp.sks 7200 IN SRV 0 0 0 .
_pgpkey-https._tcp.sks.example.org _pgpkey-https._tcp.sks 7200 IN SRV 0 0 0 .
_hkp._tcp.sks.example.org _hkp._tcp.sks 7200 IN SRV 0 0 0 .
@ -341,7 +341,7 @@ khard.example.org khard 7200 IN NS ns-cloud-d2.googledomains.com.
khard.example.org khard 7200 IN NS ns-cloud-d3.googledomains.com.
khard.example.org khard 7200 IN NS ns-cloud-d4.googledomains.com.
realhost.example.org realhost 7200 IN MX 0 .
realhost.example.org realhost 7200 IN TXT "v=spf1 -all"
realhost.example.org realhost 7200 IN TXT v=spf1 -all
_25._tcp.realhost.example.org _25._tcp.realhost 7200 IN TLSA 3 0 0 0000000000000000000000000000000000000000000000000000000000000000
_fedcba9876543210fedcba9876543210.go.example.org _fedcba9876543210fedcba9876543210.go 7200 IN CNAME _45678901234abcdef45678901234abcd.ggedgsdned.acm-validations.aws.
opqrstuvwxyz.example.org opqrstuvwxyz 7200 IN CNAME gv-abcdefghijklmn.dv.googlehosted.com.

Can't render this file because it contains an unexpected character in line 35 and column 84.

View File

@ -32,9 +32,9 @@ special.test._report._dmarc IN TXT "v=DMARC1"
xn--2j5b.xn--9t4b11yi5a._report._dmarc IN TXT "v=DMARC1"
xn--qck5b9a5eml3bze.xn--zckzah._report._dmarc IN TXT "v=DMARC1"
_adsp._domainkey IN TXT "dkim=all"
d201911._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4SmyE5Tz5/wPL8cb2AKuHnlFeLMOhAl1UX/NYaeDCKMWoBPTgZRT0jonKLmV2UscHdodXu5ZsLr/NAuLCp7HmPLReLz7kxKncP6ppveKxc1aq5SPTKeWe77p6BptlahHc35eiXsZRpTsEzrbEOainy1IWEd+w9p1gWbrSutwE22z0i4V88nQ9UBa1ks" "6cVGxXBZFovWC+i28aGs6Lc7cSfHG5+Mrg3ud5X4evYXTGFMPpunMcCsXrqmS5a+5gRSEMZhngha/cHjLwaJnWzKaywNWF5XOsCjL94QkS0joB7lnGOHMNSZBCcu542Y3Ht3SgHhlpkF9mIbIRfpzA9IoSQIDAQAB"
d201911._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4SmyE5Tz5/wPL8cb2AKuHnlFeLMOhAl1UX/NYaeDCKMWoBPTgZRT0jonKLmV2UscHdodXu5ZsLr/NAuLCp7HmPLReLz7kxKncP6ppveKxc1aq5SPTKeWe77p6BptlahHc35eiXsZRpTsEzrbEOainy1IWEd+w9p1gWbrSutwE22z0i4V88nQ9UBa1ks6cVGxX" "BZFovWC+i28aGs6Lc7cSfHG5+Mrg3ud5X4evYXTGFMPpunMcCsXrqmS5a+5gRSEMZhngha/cHjLwaJnWzKaywNWF5XOsCjL94QkS0joB7lnGOHMNSZBCcu542Y3Ht3SgHhlpkF9mIbIRfpzA9IoSQIDAQAB"
d201911e2._domainkey IN TXT "v=DKIM1; k=ed25519; p=GBt2k2L39KUb39fg5brOppXDHXvISy0+ECGgPld/bIo="
d202003._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv/1tQvOEs7xtKNm7PbPgY4hQjwHVvqqkDb0+TeqZHYRSczQ3c0LFJrIDFiPIdwQe/7AuKrxvATSh/uXKZ3EP4ouMgROPZnUxVXENeetJj+pc3nfGwTKUBTTTth+SO74gdIWsntjvAfduzosC4ZkxbDwZ9c253qXARGvGu+LB/iAeq0ngEbm5fU13+Jo" "pv0d4dR6oGe9GvMEnGGLZzNrxWl1BPe2x5JZ5/X/3fW8vJx3OgRB5N6fqbAJ6HZ9kcbikDH4lPPl9RIoprFk7mmwno/nXLQYGhPobmqq8wLkDiXEkWtYa5lzujz3XI3Zkk8ZIOGvdbVVfAttT0IVPnYkOhQIDAQAB"
d202003._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv/1tQvOEs7xtKNm7PbPgY4hQjwHVvqqkDb0+TeqZHYRSczQ3c0LFJrIDFiPIdwQe/7AuKrxvATSh/uXKZ3EP4ouMgROPZnUxVXENeetJj+pc3nfGwTKUBTTTth+SO74gdIWsntjvAfduzosC4ZkxbDwZ9c253qXARGvGu+LB/iAeq0ngEbm5fU13+Jopv0d4d" "R6oGe9GvMEnGGLZzNrxWl1BPe2x5JZ5/X/3fW8vJx3OgRB5N6fqbAJ6HZ9kcbikDH4lPPl9RIoprFk7mmwno/nXLQYGhPobmqq8wLkDiXEkWtYa5lzujz3XI3Zkk8ZIOGvdbVVfAttT0IVPnYkOhQIDAQAB"
d202003e2._domainkey IN TXT "v=DKIM1; k=ed25519; p=DQI5d9sNMrr0SLDoAi071IFOyKnlbR29hAQdqVQecQg="
_kerberos IN TXT "EXAMPLE.ORG"
_le-amazon-tlsa IN TLSA 2 0 1 18ce6cfe7bf14e60b2e347b8dfe868cb31d02ebb3ada271569f50343b46db3a4
@ -124,7 +124,7 @@ _acme-challenge.conference 15 IN CNAME _acme-challenge.conference.chat-acme.d.ex
_xmpp-server._tcp.conference IN SRV 10 2 5269 chat.example.org.
IN SRV 10 2 5269 xmpp-s2s.example.org.
dict IN CNAME services.example.org.
dns-moreinfo IN TXT "Fred Bloggs, TZ=America/New_York" "Chat-Service-X: @handle1" "Chat-Service-Y: federated-handle@example.org"
dns-moreinfo IN TXT "Fred Bloggs, TZ=America/New_YorkChat-Service-X: @handle1Chat-Service-Y: federated-handle@example.org"
field IN NS ns1.example.org.
IN NS ns2.example.org.
finger IN CNAME barbican.example.org.
@ -136,9 +136,9 @@ fred IN A 192.0.2.93
IN TXT "v=spf1 ip4:192.0.2.25 ip6:2001:db8::1:25 mx include:_spf.example.com ~all"
_dmarc.fred IN TXT "v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s"
_adsp._domainkey.fred IN TXT "dkim=all"
d201911._domainkey.fred IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8/OMUa3PnWh9LqXFVwlAgYDdTtbq3zTtTOSBmJq5yWauzXYcUuSmhW7CsV0QQlacCsQgJlwg9Nl1vO1TosAj5EKUCLTeSqjlWrM7KXKPx8FT71Q9H9wXX4MHUyGrqHFo0OPzcmtHwqcd8AD6MIvJHSRoAfiPPBp8Euc0wGnJZdGS75Hk+wA3MQ2/Tlz" "P2eenyiFyqmUTAGOYsGC/tREsWPiegR/OVxNGlzTY6quHsuVK7UYtIyFnYx9PGWdl3b3p7VjQ5V0Rp+2CLtVrCuS6Zs+/3NhZdM7mdD0a9Jgxakwa1le5YmB5lHTGF7T8quy6TlKe9lMUIRNjqTHfSFz/MwIDAQAB"
d201911._domainkey.fred IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8/OMUa3PnWh9LqXFVwlAgYDdTtbq3zTtTOSBmJq5yWauzXYcUuSmhW7CsV0QQlacCsQgJlwg9Nl1vO1TosAj5EKUCLTeSqjlWrM7KXKPx8FT71Q9H9wXX4MHUyGrqHFo0OPzcmtHwqcd8AD6MIvJHSRoAfiPPBp8Euc0wGnJZdGS75Hk+wA3MQ2/TlzP2eeny" "iFyqmUTAGOYsGC/tREsWPiegR/OVxNGlzTY6quHsuVK7UYtIyFnYx9PGWdl3b3p7VjQ5V0Rp+2CLtVrCuS6Zs+/3NhZdM7mdD0a9Jgxakwa1le5YmB5lHTGF7T8quy6TlKe9lMUIRNjqTHfSFz/MwIDAQAB"
d201911e2._domainkey.fred IN TXT "v=DKIM1; k=ed25519; p=rQNsV9YcPJn/WYI1EDLjNbN/VuX1Hqq/oe4htbnhv+A="
d202003._domainkey.fred IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpnx7tnRxAnE/poIRbVb2i+f1uQCXWnBHzHurgEyZX0CmGaiJuCbr8SWOW2PoXq9YX8gIv2TS3uzwGv/4yA2yX9Z9zar1LeWUfGgMWLdCol9xfmWrI+6MUzxuwhw/mXwzigbI4bHoakh3ez/i3J9KPS85GfrOODqA1emR13f2pG8EzAcje+rwW2PtYj" "c0h+FMDpeLuPYyYszFbNlrkVUneesxnoz+o4x/s6P14ZoRqz5CR7u6G02HwnNaHads5Eto6FYYErUUTtFmgWuYabHxgLVGRdRQs6B5OBYT/3L2q/lAgmEgdy/QL+c0Psfj99/XQmO8fcM0scBzw2ukQzcUwIDAQAB"
d202003._domainkey.fred IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpnx7tnRxAnE/poIRbVb2i+f1uQCXWnBHzHurgEyZX0CmGaiJuCbr8SWOW2PoXq9YX8gIv2TS3uzwGv/4yA2yX9Z9zar1LeWUfGgMWLdCol9xfmWrI+6MUzxuwhw/mXwzigbI4bHoakh3ez/i3J9KPS85GfrOODqA1emR13f2pG8EzAcje+rwW2PtYjc0h+FM" "DpeLuPYyYszFbNlrkVUneesxnoz+o4x/s6P14ZoRqz5CR7u6G02HwnNaHads5Eto6FYYErUUTtFmgWuYabHxgLVGRdRQs6B5OBYT/3L2q/lAgmEgdy/QL+c0Psfj99/XQmO8fcM0scBzw2ukQzcUwIDAQAB"
d202003e2._domainkey.fred IN TXT "v=DKIM1; k=ed25519; p=0DAPp/IRLYFI/Z4YSgJRi4gr7xcu1/EfJ5mjVn10aAw="
_report.fred IN TXT "r=abuse-reports@example.org; rf=ARF; re=postmaster@example.org;"
_smtp-tlsrpt.fred IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org"
@ -247,9 +247,9 @@ kpeople IN AAAA 2001:db8::48:4558:6b70:706c
mailtest IN MX 10 mx.example.org.
_dmarc.mailtest IN TXT "v=DMARC1; p=none; sp=none; rua=mailto:dmarc-notify@example.org; ruf=mailto:dmarc-notify@example.org; adkim=s"
_adsp._domainkey.mailtest IN TXT "dkim=all"
d201911._domainkey.mailtest IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo9xHnjHyhm1weA6FjOqM8LKVsklFt26HXWoe/0XCdmBG4i/UzQ7RiSgWO4kv7anPK6qf6rtL1xYsHufaRXG8yLsZxz+BbUP99eZvxZX78tMg4cGf+yU6uFxulCbOzsMy+8Cc3bbQTtIWYjyWBwnHdRRrCkQxjZ5KAd+x7ZB5qzqg2/eLJ7fCuNsr/xn" "0XTY6XYgug95e3h4CEW3Y+bkG81AMeJmT/hoVTcXvT/Gm6ZOUmx6faQWIHSW7qOR3VS6S75HOuclEUk0gt9r7OQHKl01sXh8g02SHRk8SUMEoNVayqplYZTFFF01Z192m7enmpp+St+HHUIT6jW/CAMCO3wIDAQAB"
d201911._domainkey.mailtest IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo9xHnjHyhm1weA6FjOqM8LKVsklFt26HXWoe/0XCdmBG4i/UzQ7RiSgWO4kv7anPK6qf6rtL1xYsHufaRXG8yLsZxz+BbUP99eZvxZX78tMg4cGf+yU6uFxulCbOzsMy+8Cc3bbQTtIWYjyWBwnHdRRrCkQxjZ5KAd+x7ZB5qzqg2/eLJ7fCuNsr/xn0XTY6X" "Ygug95e3h4CEW3Y+bkG81AMeJmT/hoVTcXvT/Gm6ZOUmx6faQWIHSW7qOR3VS6S75HOuclEUk0gt9r7OQHKl01sXh8g02SHRk8SUMEoNVayqplYZTFFF01Z192m7enmpp+St+HHUIT6jW/CAMCO3wIDAQAB"
d201911e2._domainkey.mailtest IN TXT "v=DKIM1; k=ed25519; p=afulDDnhaTzdqKQN0jtWV04eOhAcyBk3NCyVheOf53Y="
d202003._domainkey.mailtest IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs2BTVZaVLvL3qZBPaF7tRR0SdOKe+hjcpQ5fqO48lEuYiyTb6lkn8DPjDK11gTN3au0Bm+y8KC7ITKSJosuJXytxt3wqc61Pwtmb/Cy7GzmOF1AuegydB3/88VbgHT5DZucHrh6+ValZk4Trkx+/1K26Uo+h2KL2n/Ldb1y91ATHujp8DqxAOhiZ7KN" "aS1okNRRB4/14jPufAbeiN8/iBPiY5Hl80KHmpjM+7vvjb5jiecZ1ZrVDj7eTES4pmVh2v1c106mZLieoqDPYaf/HVbCM4E4n1B6kjbboSOpANADIcqXxGJQ7Be7/Sk9f7KwRusrsMHXmBHgm4wPmwGVZ3QIDAQAB"
d202003._domainkey.mailtest IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs2BTVZaVLvL3qZBPaF7tRR0SdOKe+hjcpQ5fqO48lEuYiyTb6lkn8DPjDK11gTN3au0Bm+y8KC7ITKSJosuJXytxt3wqc61Pwtmb/Cy7GzmOF1AuegydB3/88VbgHT5DZucHrh6+ValZk4Trkx+/1K26Uo+h2KL2n/Ldb1y91ATHujp8DqxAOhiZ7KNaS1okN" "RRB4/14jPufAbeiN8/iBPiY5Hl80KHmpjM+7vvjb5jiecZ1ZrVDj7eTES4pmVh2v1c106mZLieoqDPYaf/HVbCM4E4n1B6kjbboSOpANADIcqXxGJQ7Be7/Sk9f7KwRusrsMHXmBHgm4wPmwGVZ3QIDAQAB"
d202003e2._domainkey.mailtest IN TXT "v=DKIM1; k=ed25519; p=iqwH/hhozFdeo1xnuldr8KUi7O7g+DzmC+f0SYMKVDc="
_report.mailtest IN TXT "r=abuse-reports@example.org; rf=ARF; re=postmaster@example.org;"
_smtp-tlsrpt.mailtest IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-reports@example.org"

View File

@ -8,12 +8,12 @@ simple.com @ 300 IN MX 5 alt1.aspmx.l.google.com.
simple.com @ 300 IN MX 5 alt2.aspmx.l.google.com.
simple.com @ 300 IN MX 10 alt3.aspmx.l.google.com.
simple.com @ 300 IN MX 10 alt4.aspmx.l.google.com.
simple.com @ 300 IN TXT "google-site-verification=O54a_pYHGr4EB8iLoGFgX8OTZ1DkP1KWnOLpx0YCazI"
simple.com @ 300 IN TXT "v=spf1 mx include:mktomail.com ~all"
m1._domainkey.simple.com m1._domainkey 300 IN TXT "v=DKIM1;k=rsa;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCZfEV2C82eJ4OA3Mslz4C6msjYYalg1eUcHeJQ//QM1hOZSvn4qz+hSKGi7jwNDqsZNzM8vCt2+XzdDYL3JddwUEhoDsIsZsJW0qzIVVLLWCg6TLNS3FpVyjc171o94dpoHFekfswWDoEwFQ03Woq2jchYWBrbUf7MMcdEj/EQqwIDAQAB"
simple.com @ 300 IN TXT google-site-verification=O54a_pYHGr4EB8iLoGFgX8OTZ1DkP1KWnOLpx0YCazI
simple.com @ 300 IN TXT v=spf1 mx include:mktomail.com ~all
m1._domainkey.simple.com m1._domainkey 300 IN TXT v=DKIM1;k=rsa;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCZfEV2C82eJ4OA3Mslz4C6msjYYalg1eUcHeJQ//QM1hOZSvn4qz+hSKGi7jwNDqsZNzM8vCt2+XzdDYL3JddwUEhoDsIsZsJW0qzIVVLLWCg6TLNS3FpVyjc171o94dpoHFekfswWDoEwFQ03Woq2jchYWBrbUf7MMcdEj/EQqwIDAQAB
dev.simple.com dev 300 IN CNAME stackoverflowsandbox2.mktoweb.com.
dev-email.simple.com dev-email 300 IN CNAME mkto-sj310056.com.
m1._domainkey.dev-email.simple.com m1._domainkey.dev-email 300 IN TXT "v=DKIM1;k=rsa;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCIBezZ2Gc+/3PghWk+YOE6T9HdwgUTMTR0Fne2i51MNN9Qs7AqDitVdG/949iDbI2fPNZSnKtOcnlLYwvve9MhMAMI1nZ26ILhgaBJi2BMZQpGFlO4ucuo/Uj4DPZ5Ge/NZHCX0CRhAhR5sRmL2OffNcFXFrymzUuz4KzI/NyUiwIDAQAB"
m1._domainkey.dev-email.simple.com m1._domainkey.dev-email 300 IN TXT v=DKIM1;k=rsa;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCIBezZ2Gc+/3PghWk+YOE6T9HdwgUTMTR0Fne2i51MNN9Qs7AqDitVdG/949iDbI2fPNZSnKtOcnlLYwvve9MhMAMI1nZ26ILhgaBJi2BMZQpGFlO4ucuo/Uj4DPZ5Ge/NZHCX0CRhAhR5sRmL2OffNcFXFrymzUuz4KzI/NyUiwIDAQAB
email.simple.com email 300 IN CNAME mkto-sj280138.com.
info.simple.com info 300 IN CNAME stackoverflow.mktoweb.com.
_sip._tcp.simple.com _sip._tcp 300 IN SRV 10 60 5060 bigbox.example.com.

1 simple.com @ 300 IN SOA ns3.serverfault.com. sysadmin.stackoverflow.com. 2020022300 3600 600 604800 1440
8 simple.com @ 300 IN MX 5 alt2.aspmx.l.google.com.
9 simple.com @ 300 IN MX 10 alt3.aspmx.l.google.com.
10 simple.com @ 300 IN MX 10 alt4.aspmx.l.google.com.
11 simple.com @ 300 IN TXT google-site-verification=O54a_pYHGr4EB8iLoGFgX8OTZ1DkP1KWnOLpx0YCazI
12 simple.com @ 300 IN TXT v=spf1 mx include:mktomail.com ~all
13 m1._domainkey.simple.com m1._domainkey 300 IN TXT v=DKIM1;k=rsa;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCZfEV2C82eJ4OA3Mslz4C6msjYYalg1eUcHeJQ//QM1hOZSvn4qz+hSKGi7jwNDqsZNzM8vCt2+XzdDYL3JddwUEhoDsIsZsJW0qzIVVLLWCg6TLNS3FpVyjc171o94dpoHFekfswWDoEwFQ03Woq2jchYWBrbUf7MMcdEj/EQqwIDAQAB
14 dev.simple.com dev 300 IN CNAME stackoverflowsandbox2.mktoweb.com.
15 dev-email.simple.com dev-email 300 IN CNAME mkto-sj310056.com.
16 m1._domainkey.dev-email.simple.com m1._domainkey.dev-email 300 IN TXT v=DKIM1;k=rsa;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCIBezZ2Gc+/3PghWk+YOE6T9HdwgUTMTR0Fne2i51MNN9Qs7AqDitVdG/949iDbI2fPNZSnKtOcnlLYwvve9MhMAMI1nZ26ILhgaBJi2BMZQpGFlO4ucuo/Uj4DPZ5Ge/NZHCX0CRhAhR5sRmL2OffNcFXFrymzUuz4KzI/NyUiwIDAQAB
17 email.simple.com email 300 IN CNAME mkto-sj280138.com.
18 info.simple.com info 300 IN CNAME stackoverflow.mktoweb.com.
19 _sip._tcp.simple.com _sip._tcp 300 IN SRV 10 60 5060 bigbox.example.com.

View File

@ -150,3 +150,33 @@ Therefore we print a warning if a label has an underscore in it,
unless the rtype is SRV, TLSA, TXT, or if the name starts with
certain prefixes such as `_dmarc`. We're always willing to
[add more exceptions](https://github.com/StackExchange/dnscontrol/pull/453/files).
# Opinion #8 TXT Records are one long string
* TXT records are a single string with a length of 0 to 65,280 bytes
(the maximum possible TXT record size).
It is the provider's responsibility to split, join, quote, parse,
encode, or decoded the string as needed by the provider's API. This
should be invisible to the user.
The user may represent the string any way that JavaScript permits
strings to be represented (usually double-quotes). For backwards
compatibility they may also provide a list of strings which will be
concatenated.
You may be wondering: Isn't a TXT record really a series of 255-octet
segments? Yes, TXT record's wire-format is a series of strings, each
no longer than 255-octets. However that kind of detail should be
hidden from users. The user should represent the string they want and
DNSControl should magically do the right thing behind the scenes. The
same with quoting and escaping required by APIs.
You may be wondering: Are there any higher-level applications which
ascribe semantic value to the TXT string boundaries? I believe that
the answer is "no". My proof is not based on reading RFCs, but
instead based on (a) observing that I've never seen a DNS provider's
control panel let you specify the boundaries, (b) I've never seen a
FAQ or reddit post asking how to specify those boundaries. Therefore,
there is no need for this. I also assert that there will be no such
need in the future.

View File

@ -640,7 +640,6 @@ func makeOvhNativeRecord(name, target, rType string) *models.RecordConfig {
r := makeRec(name, "", "TXT")
r.Metadata = make(map[string]string)
r.Metadata["create_ovh_native_record"] = rType
r.TxtStrings = []string{target}
r.SetTarget(target)
return r
}
@ -1085,39 +1084,69 @@ func makeTests(t *testing.T) []*TestGroup {
// Do not use only()/not()/requires() in this section.
// If your provider needs to skip one of these tests, update
// "provider/*/recordaudit.AuditRecords()" to reject that kind
// of record. When the provider fixes the bug or changes behavior,
// update the AuditRecords().
// of record.
//clear(),
//tc("a 255-byte TXT", txt("foo255", strings.Repeat("C", 255))),
//clear(),
//tc("a 256-byte TXT", txt("foo256", strings.Repeat("D", 256))),
//clear(),
//tc("a 512-byte TXT", txt("foo512", strings.Repeat("C", 512))),
//clear(),
//tc("a 513-byte TXT", txt("foo513", strings.Repeat("D", 513))),
// Some of these test cases are commented out because they test
// something that isn't widely used or supported. For example
// many APIs don't support a backslack (`\`) in a TXT record;
// luckily we've never seen a need for that "in the wild". If
// you want to future-proof your provider, temporarily remove
// the comments and get those tests working, or reject it using
// auditrecords.go.
// ProTip: Unsure how a provider's API escapes something? Try
// adding the TXT record via the Web UI and watch how the string
// is escaped when you download the records.
// Nobody needs this and many APIs don't allow it.
tc("a 0-byte TXT", txt("foo0", "")),
// Test edge cases around 255, 255*2, 255*3:
tc("a 254-byte TXT", txt("foo254", strings.Repeat("A", 254))), // 255-1
tc("a 255-byte TXT", txt("foo255", strings.Repeat("B", 255))), // 255
tc("a 256-byte TXT", txt("foo256", strings.Repeat("C", 256))), // 255+1
tc("a 509-byte TXT", txt("foo509", strings.Repeat("D", 509))), // 255*2-1
tc("a 510-byte TXT", txt("foo510", strings.Repeat("E", 510))), // 255*2
tc("a 511-byte TXT", txt("foo511", strings.Repeat("F", 511))), // 255*2+1
tc("a 764-byte TXT", txt("foo764", strings.Repeat("G", 764))), // 255*3-1
tc("a 765-byte TXT", txt("foo765", strings.Repeat("H", 765))), // 255*3
tc("a 766-byte TXT", txt("foo766", strings.Repeat("J", 766))), // 255*3+1
//clear(),
tc("TXT with 1 single-quote", txt("foosq", "quo'te")),
//clear(),
tc("TXT with 1 backtick", txt("foobt", "blah`blah")),
//clear(),
tc("TXT with 1 double-quotes", txt("foodq", `quo"te`)),
//clear(),
tc("TXT with 2 double-quotes", txt("foodqs", `q"uo"te`)),
//clear(),
tc("TXT with 1 dq-1interior", txt("foodq", `in"side`)),
tc("TXT with 2 dq-2interior", txt("foodqs", `in"ter"ior`)),
tc("TXT with 1 dq-left", txt("foodqs", `"left`)),
tc("TXT with 1 dq-right", txt("foodqs", `right"`)),
tc("a TXT with interior ws", txt("foosp", "with spaces")),
//clear(),
tc("TXT with ws at end", txt("foows1", "with space at end ")),
//clear(),
// Semicolons don't need special treatment.
// https://serverfault.com/questions/743789
tc("TXT with semicolon", txt("foosc1", `semi;colon`)),
tc("TXT with semicolon ws", txt("foosc2", `wssemi ; colon`)),
//tc("Create a TXT/SPF", txt("foo", "v=spf1 ip4:99.99.99.99 -all")),
// This was added because Vultr syntax-checks TXT records with SPF contents.
//clear(),
tc("TXT interior ws", txt("foosp", "with spaces")),
tc("TXT trailing ws", txt("foows1", "with space at end ")),
// TODO(tlim): Re-add this when we fix the RFC1035 escaped-quotes issue.
//tc("Create TXT with frequently escaped characters", txt("fooex", `!^.*$@#%^&()([][{}{<></:;-_=+\`)),
// Vultr syntax-checks TXT records with SPF contents.
tc("Create a TXT/SPF", txt("foo", "v=spf1 ip4:99.99.99.99 -all")),
// Nobody needs this and many APIs don't allow it.
tc("TXT with 1 backslash", txt("fooosbs1", `1back\slash`)),
tc("TXT with 2 backslash", txt("fooosbs2", `2back\\slash`)),
tc("TXT with 3 backslash", txt("fooosbs3", `3back\\\slash`)),
tc("TXT with 4 backslash", txt("fooosbs4", `4back\\\\slash`)),
// Nobody needs this and many APIs don't allow it.
//tc("Create TXT with frequently difficult characters", txt("fooex", `!^.*$@#%^&()([][{}{<></:;-_=+\`)),
),
testgroup("TXT backslashes",
tc("TXT with backslashs",
txt("fooosbs1", `1back\slash`),
txt("fooosbs2", `2back\\slash`),
txt("fooosbs3", `3back\\\slash`),
txt("fooosbs4", `4back\\\\slash`)),
),
//
@ -1244,7 +1273,7 @@ func makeTests(t *testing.T) []*TestGroup {
"AZURE_DNS", // Removed because it is too slow
"CLOUDFLAREAPI", // Infinite pagesize but due to slow speed, skipping.
"DIGITALOCEAN", // No paging. Why bother?
"CSCGLOBAL", // Doesn't page. Works fine. Due to the slow API we skip.
//"CSCGLOBAL", // Doesn't page. Works fine. Due to the slow API we skip.
"GANDI_V5", // Their API is so damn slow. We'll add it back as needed.
"HEDNS", // Doesn't page. Works fine. Due to the slow API we skip.
"LOOPIA", // Their API is so damn slow. Plus, no paging.

View File

@ -32,6 +32,16 @@ func RRstoRCs(rrs []dns.RR, origin string) (Records, error) {
// RRtoRC converts dns.RR to RecordConfig
func RRtoRC(rr dns.RR, origin string) (RecordConfig, error) {
return helperRRtoRC(rr, origin, false)
}
// RRtoRCTxtBug converts dns.RR to RecordConfig. Compensates for the backslash bug in github.com/miekg/dns/issues/1384.
func RRtoRCTxtBug(rr dns.RR, origin string) (RecordConfig, error) {
return helperRRtoRC(rr, origin, true)
}
// helperRRtoRC converts dns.RR to RecordConfig. If fixBug is true, replaces `\\` to `\` in TXT records to compensate for github.com/miekg/dns/issues/1384.
func helperRRtoRC(rr dns.RR, origin string, fixBug bool) (RecordConfig, error) {
// Convert's dns.RR into our native data type (RecordConfig).
// Records are translated directly with no changes.
header := rr.Header()
@ -73,7 +83,15 @@ func RRtoRC(rr dns.RR, origin string) (RecordConfig, error) {
case *dns.TLSA:
err = rc.SetTargetTLSA(v.Usage, v.Selector, v.MatchingType, v.Certificate)
case *dns.TXT:
if fixBug {
t := strings.Join(v.Txt, "")
te := t
te = strings.ReplaceAll(te, `\\`, `\`)
te = strings.ReplaceAll(te, `\"`, `"`)
err = rc.SetTargetTXT(te)
} else {
err = rc.SetTargetTXTs(v.Txt)
}
default:
return *rc, fmt.Errorf("rrToRecord: Unimplemented zone record type=%s (%v)", rc.Type, rr)
}

View File

@ -7,8 +7,12 @@ import (
"github.com/miekg/dns"
)
// IsQuoted returns true if the string starts and ends with a double quote.
func IsQuoted(s string) bool {
/*
TODO(tlim): Move this file to pkgs/txtutil. It doesn't need to be part
*/
// isQuoted returns true if the string starts and ends with a double quote.
func isQuoted(s string) bool {
if s == "" {
return false
}
@ -24,7 +28,7 @@ func IsQuoted(s string) bool {
// StripQuotes returns the string with the starting and ending quotes removed.
// If it is not quoted, the original string is returned.
func StripQuotes(s string) string {
if IsQuoted(s) {
if isQuoted(s) {
return s[1 : len(s)-1]
}
return s
@ -41,7 +45,7 @@ func StripQuotes(s string) string {
// NOTE: This doesn't handle escaped quotes.
// NOTE: You probably want to use ParseQuotedFields() for RFC 1035-compliant quoting.
func ParseQuotedTxt(s string) []string {
if !IsQuoted(s) {
if !isQuoted(s) {
return []string{s}
}
return strings.Split(StripQuotes(s), `" "`)

View File

@ -1,6 +1,9 @@
package models
import "testing"
import (
"strings"
"testing"
)
func TestIsQuoted(t *testing.T) {
tests := []struct {
@ -16,7 +19,7 @@ func TestIsQuoted(t *testing.T) {
{`"aaa" "bbb"`, true},
}
for i, test := range tests {
r := IsQuoted(test.d1)
r := isQuoted(test.d1)
if r != test.e1 {
t.Errorf("%v: expected (%v) got (%v)", i, test.e1, r)
}
@ -48,6 +51,8 @@ func TestStripQuotes(t *testing.T) {
}
}
func r(s string, c int) string { return strings.Repeat(s, c) }
func TestParseQuotedTxt(t *testing.T) {
tests := []struct {
d1 string
@ -59,6 +64,26 @@ func TestParseQuotedTxt(t *testing.T) {
{`foo bar`, []string{`foo bar`}},
{`"aaa" "bbb"`, []string{`aaa`, `bbb`}},
{`"a"a" "bbb"`, []string{`a"a`, `bbb`}},
// Seen in live traffic:
{"\"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\"",
[]string{r("B", 254)}},
{"\"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\"",
[]string{r("C", 255)}},
{"\"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD\" \"D\"",
[]string{r("D", 255), "D"}},
{"\"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\" \"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\"",
[]string{r("E", 255), r("E", 255)}},
{"\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\" \"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\" \"F\"",
[]string{r("F", 255), r("F", 255), "F"}},
{"\"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\" \"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\" \"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\"",
[]string{r("G", 255), r("G", 255), r("G", 255)}},
{"\"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\" \"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\" \"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\" \"H\"",
[]string{r("H", 255), r("H", 255), r("H", 255), "H"}},
{"\"quo'te\"", []string{`quo'te`}},
{"\"blah`blah\"", []string{"blah`blah"}},
//{"\"quo\\\"te\"", []string{`quo"te`}},
//{"\"q\\\"uo\\\"te\"", []string{`q"uo"te`}},
//{"\"backs\\\\lash\"", []string{`back\slash`}},
}
for i, test := range tests {
ls := ParseQuotedTxt(test.d1)

View File

@ -4,9 +4,9 @@ import (
"encoding/json"
"fmt"
"log"
"sort"
"strings"
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
"github.com/jinzhu/copier"
"github.com/miekg/dns"
"github.com/miekg/dns/dnsutil"
@ -127,7 +127,6 @@ type RecordConfig struct {
TlsaUsage uint8 `json:"tlsausage,omitempty"`
TlsaSelector uint8 `json:"tlsaselector,omitempty"`
TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"`
TxtStrings []string `json:"txtstrings,omitempty"` // TxtStrings stores all strings (including the first). Target stores all the strings joined.
R53Alias map[string]string `json:"r53_alias,omitempty"`
AzureAlias map[string]string `json:"azure_alias,omitempty"`
}
@ -195,7 +194,6 @@ func (rc *RecordConfig) UnmarshalJSON(b []byte) error {
TlsaUsage uint8 `json:"tlsausage,omitempty"`
TlsaSelector uint8 `json:"tlsaselector,omitempty"`
TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"`
TxtStrings []string `json:"txtstrings,omitempty"` // TxtStrings stores all strings (including the first). Target stores only the first one.
R53Alias map[string]string `json:"r53_alias,omitempty"`
AzureAlias map[string]string `json:"azure_alias,omitempty"`
@ -305,47 +303,22 @@ func (rc *RecordConfig) GetLabelFQDN() string {
return rc.NameFQDN
}
// ToDiffable returns a string that is comparable by a differ.
// extraMaps: a list of maps that should be included in the comparison.
// NB(tlim): This will be deprecated when pkg/diff is replaced by pkg/diff2.
// Use // ToComparableNoTTL() instead.
func (rc *RecordConfig) ToDiffable(extraMaps ...map[string]string) string {
var content string
switch rc.Type {
case "SOA":
content = fmt.Sprintf("%s %v %d %d %d %d ttl=%d", rc.target, rc.SoaMbox, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl, rc.TTL)
// SoaSerial is not used in comparison
default:
content = fmt.Sprintf("%v ttl=%d", rc.GetTargetCombined(), rc.TTL)
}
for _, valueMap := range extraMaps {
// sort the extra values map keys to perform a deterministic
// comparison since Golang maps iteration order is not guaranteed
// FIXME(tlim) The keys of each map is sorted per-map, not across
// all maps. This may be intentional since we'd have no way to
// deal with duplicates.
keys := make([]string, 0)
for k := range valueMap {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := valueMap[k]
content += fmt.Sprintf(" %s=%s", k, v)
}
}
return content
}
// ToComparableNoTTL returns a comparison string. If you need to compare two
// RecordConfigs, you can simply compare the string returned by this function.
// The comparison includes all fields except TTL and any provider-specific
// metafields. Provider-specific metafields like CF_PROXY are not the same as
// pseudo-records like ANAME or R53_ALIAS
// This replaces ToDiff()
func (rc *RecordConfig) ToComparableNoTTL() string {
switch rc.Type {
case "SOA":
return fmt.Sprintf("%s %v %d %d %d %d", rc.target, rc.SoaMbox, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl)
// SoaSerial is not included because it isn't used in comparisons.
case "TXT":
//fmt.Fprintf(os.Stdout, "DEBUG: ToComNoTTL raw txts=%s q=%q\n", rc.target, rc.target)
r := txtutil.EncodeQuoted(rc.target)
//fmt.Fprintf(os.Stdout, "DEBUG: ToComNoTTL cmp txts=%s q=%q\n", r, r)
return r
}
return rc.GetTargetCombined()
}
@ -390,7 +363,6 @@ func (rc *RecordConfig) ToRR() dns.RR {
rr.(*dns.DS).Digest = rc.DsDigest
rr.(*dns.DS).KeyTag = rc.DsKeyTag
case dns.TypeLOC:
//this is for records from .js files and read from API
// fmt.Printf("ToRR long: %d, lat:%d, sz: %d, hz:%d, vt:%d\n", rc.LocLongitude, rc.LocLatitude, rc.LocSize, rc.LocHorizPre, rc.LocVertPre)
// fmt.Printf("ToRR rc: %+v\n", *rc)
rr.(*dns.LOC).Version = rc.LocVersion
@ -423,7 +395,7 @@ func (rc *RecordConfig) ToRR() dns.RR {
rr.(*dns.SOA).Expire = rc.SoaExpire
rr.(*dns.SOA).Minttl = rc.SoaMinttl
case dns.TypeSPF:
rr.(*dns.SPF).Txt = rc.TxtStrings
rr.(*dns.SPF).Txt = rc.GetTargetTXTSegmented()
case dns.TypeSRV:
rr.(*dns.SRV).Priority = rc.SrvPriority
rr.(*dns.SRV).Weight = rc.SrvWeight
@ -439,7 +411,7 @@ func (rc *RecordConfig) ToRR() dns.RR {
rr.(*dns.TLSA).Selector = rc.TlsaSelector
rr.(*dns.TLSA).Certificate = rc.GetTargetField()
case dns.TypeTXT:
rr.(*dns.TXT).Txt = rc.TxtStrings
rr.(*dns.TXT).Txt = rc.GetTargetTXTSegmented()
default:
panic(fmt.Sprintf("ToRR: Unimplemented rtype %v", rc.Type))
// We panic so that we quickly find any switch statements

View File

@ -88,7 +88,6 @@ func TestRecordConfig_Copy(t *testing.T) {
TlsaUsage uint8
TlsaSelector uint8
TlsaMatchingType uint8
TxtStrings []string
R53Alias map[string]string
AzureAlias map[string]string
Original interface{}
@ -135,7 +134,6 @@ func TestRecordConfig_Copy(t *testing.T) {
TlsaUsage: 1,
TlsaSelector: 2,
TlsaMatchingType: 3,
TxtStrings: []string{"one", "two", "three"},
R53Alias: map[string]string{"a": "eh", "b": "bee"},
AzureAlias: map[string]string{"az": "az", "ure": "your"},
//Original interface{},
@ -174,7 +172,6 @@ func TestRecordConfig_Copy(t *testing.T) {
TlsaUsage: 1,
TlsaSelector: 2,
TlsaMatchingType: 3,
TxtStrings: []string{"one", "two", "three"},
R53Alias: map[string]string{"a": "eh", "b": "bee"},
AzureAlias: map[string]string{"az": "az", "ure": "your"},
//Original interface{},
@ -217,7 +214,6 @@ func TestRecordConfig_Copy(t *testing.T) {
TlsaUsage: tt.fields.TlsaUsage,
TlsaSelector: tt.fields.TlsaSelector,
TlsaMatchingType: tt.fields.TlsaMatchingType,
TxtStrings: tt.fields.TxtStrings,
R53Alias: tt.fields.R53Alias,
AzureAlias: tt.fields.AzureAlias,
Original: tt.fields.Original,

View File

@ -5,6 +5,110 @@ import (
"net"
)
// PopulateFromStringFunc populates a RecordConfig by parsing a common RFC1035-like format.
//
// rtype: the resource record type (rtype)
// contents: a string that contains all parameters of the record's rdata (see below)
// txtFn: If rtype == "TXT", this function is used to parse contents, or nil if no parsing is needed.
//
// The "contents" field is the format used in RFC1035 zonefiles. It is the text
// after the rtype. For example, in the line: foo IN MX 10 mx.example.com.
// contents stores everything after the "MX" (not including the space).
//
// Typical values for txtFn include:
//
// nil: no parsing required.
// txtutil.ParseQuoted: Parse via Tom's interpretation of RFC1035.
// txtutil.ParseCombined: Backwards compatible with Parse via miekg's interpretation of RFC1035.
//
// Many providers deliver record data in this format or something close to it.
// This function is provided to reduce the amount of duplicate code across
// providers. If a particular rtype is not handled as a particular provider
// expects, simply handle it beforehand as a special case.
//
// Example 1: Normal use.
//
// rtype := FILL_IN_RTYPE
// rc := &models.RecordConfig{Type: rtype, TTL: FILL_IN_TTL}
// rc.SetLabelFromFQDN(FILL_IN_NAME, origin)
// rc.Original = FILL_IN_ORIGINAL // The raw data received from provider (if needed later)
// if err = rc.PopulateFromStringFunc(rtype, target, origin, nil); err != nil {
// return nil, fmt.Errorf("unparsable record type=%q received from PROVDER_NAME: %w", rtype, err)
// }
// return rc, nil
//
// Example 2: Use your own MX parser.
//
// rtype := FILL_IN_RTYPE
// rc := &models.RecordConfig{Type: rtype, TTL: FILL_IN_TTL}
// rc.SetLabelFromFQDN(FILL_IN_NAME, origin)
// rc.Original = FILL_IN_ORIGINAL // The raw data received from provider (if needed later)
// switch rtype {
// case "MX":
// // MX priority in a separate field.
// err = rc.SetTargetMX(cr.Priority, target)
// default:
// err = rc.PopulateFromString(rtype, target, origin)
// }
// if err != nil {
// return nil, fmt.Errorf("unparsable record type=%q received from PROVDER_NAME: %w", rtype, err)
// }
// return rc, nil
func (rc *RecordConfig) PopulateFromStringFunc(rtype, contents, origin string, txtFn func(s string) (string, error)) error {
if rc.Type != "" && rc.Type != rtype {
return fmt.Errorf("assertion failed: rtype already set (%s) (%s)", rtype, rc.Type)
}
switch rc.Type = rtype; rtype { // #rtype_variations
case "A":
ip := net.ParseIP(contents)
if ip == nil || ip.To4() == nil {
return fmt.Errorf("invalid IP in A record: %s", contents)
}
return rc.SetTargetIP(ip) // Reformat to canonical form.
case "AAAA":
ip := net.ParseIP(contents)
if ip == nil || ip.To16() == nil {
return fmt.Errorf("invalid IP in AAAA record: %s", contents)
}
return rc.SetTargetIP(ip) // Reformat to canonical form.
case "AKAMAICDN", "ALIAS", "ANAME", "CNAME", "NS", "PTR":
return rc.SetTarget(contents)
case "CAA":
return rc.SetTargetCAAString(contents)
case "DS":
return rc.SetTargetDSString(contents)
case "DHCID":
return rc.SetTarget(contents)
case "LOC":
return rc.SetTargetLOCString(origin, contents)
case "MX":
return rc.SetTargetMXString(contents)
case "NAPTR":
return rc.SetTargetNAPTRString(contents)
case "SOA":
return rc.SetTargetSOAString(contents)
case "SPF", "TXT":
if txtFn == nil {
return rc.SetTargetTXT(contents)
}
t, err := txtFn(contents)
if err != nil {
return fmt.Errorf("invalid TXT record: %s", contents)
}
return rc.SetTargetTXT(t)
case "SRV":
return rc.SetTargetSRVString(contents)
case "SSHFP":
return rc.SetTargetSSHFPString(contents)
case "TLSA":
return rc.SetTargetTLSAString(contents)
default:
return fmt.Errorf("unknown rtype (%s) when parsing (%s) domain=(%s)",
rtype, contents, origin)
}
}
// PopulateFromString populates a RecordConfig given a type and string. Many
// providers give all the parameters of a resource record in one big string.
// This helper function lets you not re-invent the wheel.
@ -35,7 +139,6 @@ import (
// return nil, fmt.Errorf("unparsable record type=%q received from PROVDER_NAME: %w", rtype, err)
// }
// return rc, nil
func (rc *RecordConfig) PopulateFromString(rtype, contents, origin string) error {
if rc.Type != "" && rc.Type != rtype {
panic(fmt.Errorf("assertion failed: rtype already set (%s) (%s)", rtype, rc.Type))
@ -70,9 +173,6 @@ func (rc *RecordConfig) PopulateFromString(rtype, contents, origin string) error
case "SOA":
return rc.SetTargetSOAString(contents)
case "SPF", "TXT":
// Parsing the contents may be unexpected. If your provider gives you a
// string that needs no further parsing, special case TXT and use
// rc.SetTargetTXT(target) like in the example above.
return rc.SetTargetTXTs(ParseQuotedTxt(contents))
case "SRV":
return rc.SetTargetSRVString(contents)

View File

@ -1,113 +1,34 @@
package models
import (
"fmt"
"strings"
)
/*
Sadly many providers handle TXT records in strange and non-compliant
Sadly many providers handle TXT records in strange and unexpeected
ways. DNSControl has to handle all of them. Over the years we've
tried many things. This explain the current state of the code.
What are some of these variations?
DNSControl stores the TXT record target as a single string of any length.
Providers take care of any splitting, excaping, or quoting.
* The RFCs say that a TXT record is a series of strings, each 255-octets
or fewer. Yet, most provider APIs only support a single string which
is split into 255-octetl chunks behind the scenes. Some only support
a single string that is 255-octets or less.
NOTE: Older versions of DNSControl stored the TXT record as
represented by the provider, which could be a single string, a series
of smaller strings, or a single string that is quoted/escaped. This
created tons of edge-cases and other distractions.
* The RFCs don't say much about the content of the strings. Some
providers accept any octet, some only accept ASCII-printable chars,
some get confused by TXT records that include backticks, quotes, or
whitespace at the end of the string.
If a provider doesn't support certain charactors in a TXT record, use
the providers/$PROVIDER/auditrecords.go file to indicate this.
DNSControl uses this information to warn users of unsupporrted input,
and to skip related integration tests.
DNSControl has tried many different ways to handle all these
variations over the years. This is what we found works best:
There are 2 ways to create a TXT record:
SetTargetTXT(): Create from a string.
SetTargetTXTs(): Create from an array of strings that need to be joined.
Principle 1. Store the string as the user input it.
DNSControl stores the string as the user specified in dnsconfig.js.
The user can specify a string of any length, or many individual
strings of any length.
No matter how the user presented the data in dnsconfig.js, the data is
stored as a list of strings (RecordConfig.TxtStrings []string). If
they input 1 string, the list has one element. If the user input many
individual strings, the list is copied into .TxtStrings.
When we store the data in .TxtStrings there is no length checking. The data is not manipulated.
Principle 2. When downloading zone records, receive the data as appropriate.
When the API returns a TXT record, the provider's code must properly
store it in the .TxtStrings field of RecordConfig.
We've found most APIs return TXT strings in one of three ways:
* The API returns a single string: use RecordConfig.SetTargetTXT().
* The API returns multiple strings: use RecordConfig.SetTargetTXTs().
* (THIS IS RARE) The API returns a single string that must be parsed
into multiple strings: The provider is responsible for the
parsing. However, usually the format is "quoted like in RFC 1035"
which is vague, but we've implemented it as
RecordConfig.SetTargetTXTfromRFC1035Quoted().
If the format is something else, please write the parser as a separate
function and write unit tests based on actual data received from the
API.
Principle 3. When sending TXT records to the API, send what the API expects.
The provider's code must decide how to take the list of strings in
.TxtStrings and present them to the API.
Most providers fall into one of these categories:
* If the API expects one long string, the provider code joins all
the smaller strings and sends one big string. Use the helper
function RecordConfig.GetTargetTXTJoined()
* If the API expects many strings of any size, the provider code
sends the individual strings. Those strings are accessed as
the array RecordConfig.TxtStrings
* (THIS IS RARE) If the API expects multiple strings to be sent as
one long string, quoted RFC 1025-style, call
RecordConfig.GetTargetRFC1035Quoted() and send that string.
Note: If the API expects many strings, each 255-octets or smaller, the
provider code must split the longer strings into smaller strings. The
helper function txtutil.SplitSingleLongTxt(dc.Records) will iterate
over all TXT records and split out any strings longer than 255 octets.
Call this once in GetDomainCorrections(). (Yes, this violates
Principle 1, but we decided it is best to do it once, than provide a
getter that would re-split the strings on every call.)
Principle 4. Providers can communicate back to DNSControl strings they can't handle.
As mentioned before, some APIs reject TXT records for various reasons:
Illegal chars, whitespace at the end, etc. We can't make a flag for
every variation. Instead we call the provider's AuditRecords()
function and it reports if there are any records that it can't
process.
We've provided many helper functions to make this easier. Look at any
of the providers/.../auditrecord.go` files for examples.
The integration tests call AuditRecords() to skip any tests that we
know will fail. If one of the integration tests is failing, it is
often better to update AuditRecords() than to try to figure out why,
for example, the provider doesn't support backticks in strings. Don't
spend a lot of effort trying to fix situations that are rare or will
not appear in real-world situations.
Companies do update their APIs occasionally. You might want to try
eliminating the checks one at a time to see if the API has improved.
Don't feel obligated to do this more than once a year.
Conclusion:
When we follow these 4 principles, and stick with the helper functions
provided, we're able to handle all the variations.
There are 2 ways to get the value (target) of a TXT record:
GetTargetTXTJoined(): Returns one big string
GetTargetTXTSegmented(): Returns an array 255-octet segments.
*/
@ -119,8 +40,6 @@ func (rc *RecordConfig) HasFormatIdenticalToTXT() bool {
}
// SetTargetTXT sets the TXT fields when there is 1 string.
// The string is stored in .Target, and split into 255-octet chunks
// for .TxtStrings.
func (rc *RecordConfig) SetTargetTXT(s string) error {
if rc.Type == "" {
rc.Type = "TXT"
@ -128,41 +47,27 @@ func (rc *RecordConfig) SetTargetTXT(s string) error {
panic("assertion failed: SetTargetTXT called when .Type is not TXT or compatible type")
}
rc.TxtStrings = []string{s}
rc.SetTarget(rc.zoneFileQuoted())
return nil
return rc.SetTarget(s)
}
// SetTargetTXTs sets the TXT fields when there are many strings.
// The individual strings are stored in .TxtStrings, and joined to make .Target.
// SetTargetTXTs sets the TXT fields when there are many strings. They are stored concatenated.
func (rc *RecordConfig) SetTargetTXTs(s []string) error {
if rc.Type == "" {
rc.Type = "TXT"
} else if !rc.HasFormatIdenticalToTXT() {
panic("assertion failed: SetTargetTXTs called when .Type is not TXT or compatible type")
}
rc.TxtStrings = s
rc.SetTarget(rc.zoneFileQuoted())
return nil
return rc.SetTargetTXT(strings.Join(s, ""))
}
// GetTargetTXTJoined returns the TXT target as one string.
func (rc *RecordConfig) GetTargetTXTJoined() string {
return strings.Join(rc.TxtStrings, "")
return rc.target
}
// GetTargetTXTSegmented returns the TXT target as 255-octet segments, with the remainder in the last segment.
func (rc *RecordConfig) GetTargetTXTSegmented() []string {
return splitChunks(strings.Join(rc.TxtStrings, ""), 255)
return splitChunks(rc.target, 255)
}
// GetTargetTXTSegmentCount returns the number of 255-octet segments required to store TXT target.
func (rc *RecordConfig) GetTargetTXTSegmentCount() int {
var total int
for i := range rc.TxtStrings {
total = len(rc.TxtStrings[i])
}
total := len(rc.target)
segs := total / 255 // integer division, decimals are truncated
if (total % 255) > 0 {
return segs + 1
@ -171,6 +76,10 @@ func (rc *RecordConfig) GetTargetTXTSegmentCount() int {
}
func splitChunks(buf string, lim int) []string {
if len(buf) == 0 {
return nil
}
var chunk string
chunks := make([]string, 0, len(buf)/lim+1)
for len(buf) >= lim {
@ -182,27 +91,3 @@ func splitChunks(buf string, lim int) []string {
}
return chunks
}
// SetTargetTXTfromRFC1035Quoted parses a series of quoted strings
// and sets .TxtStrings based on the result.
// Note: Most APIs do notThis is rarely used. Try using SetTargetTXT() first.
// Ex:
//
// "foo" << 1 string
// "foo bar" << 1 string
// "foo" "bar" << 2 strings
// foo << error. No quotes! Did you intend to use SetTargetTXT?
func (rc *RecordConfig) SetTargetTXTfromRFC1035Quoted(s string) error {
if s != "" && s[0] != '"' {
// If you get this error, it is likely that you should use
// SetTargetTXT() instead of SetTargetTXTfromRFC1035Quoted().
return fmt.Errorf("non-quoted string used with SetTargetTXTfromRFC1035Quoted: (%s)", s)
}
many, err := ParseQuotedFields(s)
if err != nil {
return err
}
return rc.SetTargetTXTs(many)
}
// There is no GetTargetTXTfromRFC1025Quoted(). Use GetTargetRFC1035Quoted()

View File

@ -3,7 +3,6 @@ package models
import (
"fmt"
"net"
"runtime/debug"
"strings"
"github.com/miekg/dns"
@ -14,22 +13,9 @@ If an rType has more than one field, one field goes in .target and the remaining
Not the best design, but we're stuck with it until we re-do RecordConfig, possibly using generics.
*/
// Set debugWarnTxtField to true if you want a warning when
// GetTargetField is called on a TXT record.
// GetTargetField works fine on TXT records for casual output but it
// is often better to access .TxtStrings directly or call
// GetTargetRFC1035Quoted() for nicely quoted text.
var debugWarnTxtField = false
// GetTargetField returns the target. There may be other fields (for example
// an MX record also has a .MxPreference field.
// GetTargetField returns the target. There may be other fields, but they are
// not included. For example, the .MxPreference field of an MX record isn't included.
func (rc *RecordConfig) GetTargetField() string {
if debugWarnTxtField {
if rc.Type == "TXT" {
fmt.Printf("DEBUG: WARNING: GetTargetField called on TXT record is frequently wrong: %q\n", rc.target)
debug.PrintStack()
}
}
return rc.target
}
@ -41,8 +27,23 @@ func (rc *RecordConfig) GetTargetIP() net.IP {
return net.ParseIP(rc.target)
}
// GetTargetCombinedFunc returns all the rdata fields of a RecordConfig as one
// string. How TXT records are encoded is defined by encodeFn. If encodeFn is
// nil the TXT data is returned unaltered.
func (rc *RecordConfig) GetTargetCombinedFunc(encodeFn func(s string) string) string {
if rc.Type == "TXT" {
if encodeFn == nil {
return rc.target
}
return encodeFn(rc.target)
}
return rc.GetTargetCombined()
}
// GetTargetCombined returns a string with the various fields combined.
// For example, an MX record might output `10 mx10.example.tld`.
// WARNING: How TXT records are handled is buggy but we can't change it because
// code depends on the bugs. Use Get GetTargetCombinedFunc() instead.
func (rc *RecordConfig) GetTargetCombined() string {
// Pseudo records:
if _, ok := dns.StringToType[rc.Type]; !ok {
@ -59,7 +60,10 @@ func (rc *RecordConfig) GetTargetCombined() string {
}
}
if rc.Type == "SOA" {
switch rc.Type {
case "TXT":
return rc.zoneFileQuoted()
case "SOA":
return fmt.Sprintf("%s %v %d %d %d %d %d", rc.target, rc.SoaMbox, rc.SoaSerial, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl)
}
@ -93,14 +97,13 @@ func (rc *RecordConfig) GetTargetRFC1035Quoted() string {
return rc.zoneFileQuoted()
}
// GetTargetSortable returns a string that is sortable.
func (rc *RecordConfig) GetTargetSortable() string {
return rc.GetTargetDebug()
}
// GetTargetDebug returns a string with the various fields spelled out.
func (rc *RecordConfig) GetTargetDebug() string {
content := fmt.Sprintf("%s %s %s %d", rc.Type, rc.NameFQDN, rc.target, rc.TTL)
target := rc.target
if rc.Type == "TXT" {
target = fmt.Sprintf("%q", target)
}
content := fmt.Sprintf("%s %s %s %d", rc.Type, rc.NameFQDN, target, rc.TTL)
switch rc.Type { // #rtype_variations
case "A", "AAAA", "AKAMAICDN", "CNAME", "DHCID", "NS", "PTR", "TXT":
// Nothing special.

View File

@ -1,6 +1,8 @@
package diff
import (
"fmt"
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/fatih/color"
)
@ -29,12 +31,7 @@ type differ struct {
// get normalized content for record. target, ttl, mxprio, and specified metadata
func (d *differ) content(r *models.RecordConfig) string {
return r.ToDiffable()
}
// CorrectionLess returns true when comparing corrections.
func CorrectionLess(c []*models.Correction, i, j int) bool {
return c[i].Msg < c[j].Msg
return fmt.Sprintf("%s ttl=%d", r.ToComparableNoTTL(), r.TTL)
}
func (c Correlation) String() string {

View File

@ -61,6 +61,8 @@ func (d *differCompat) IncrementalDiff(existing []*models.RecordConfig) (reportM
return
}
// GenerateMessageCorrections turns a list of strings into a list of corrections
// that output those messages (and are otherwise a no-op).
func GenerateMessageCorrections(msgs []string) (corrections []*models.Correction) {
for _, msg := range msgs {
corrections = append(corrections, &models.Correction{Msg: msg})

View File

@ -18,7 +18,7 @@ func parseZoneContents(content string, zoneName string, zonefileName string) (mo
foundRecords := models.Records{}
for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
rec, err := models.RRtoRC(rr, zoneName)
rec, err := models.RRtoRCTxtBug(rr, zoneName)
if err != nil {
return nil, err
}

View File

@ -543,11 +543,9 @@ var TXT = recordBuilder('TXT', {
record.name = args.name;
// Store the strings from the user verbatim.
if (_.isString(args.target)) {
record.txtstrings = [args.target];
record.target = args.target; // Overwritten by the Go code
record.target = args.target;
} else {
record.txtstrings = args.target;
record.target = args.target.join(''); // Overwritten by the Go code
record.target = args.target.join('');
}
},
});

View File

@ -1,13 +0,0 @@
dmarc = [
"v=DMARC1\\;",
'p=reject\\;',
'sp=reject\\;',
'pct=100\\;',
'rua=mailto:xx...@yyyy.com\\;',
'ruf=mailto:xx...@yyyy.com\\;',
'fo=1'
].join(' ');
D("foo.com","none",
TXT('_dmarc', dmarc, TTL(300))
);

View File

@ -1,22 +0,0 @@
{
"registrars": [],
"dns_providers": [],
"domains": [
{
"name": "foo.com",
"registrar": "none",
"dnsProviders": {},
"records": [
{
"type": "TXT",
"name": "_dmarc",
"target": "v=DMARC1\\; p=reject\\; sp=reject\\; pct=100\\; rua=mailto:xx...@yyyy.com\\; ruf=mailto:xx...@yyyy.com\\; fo=1",
"ttl": 300,
"txtstrings": [
"v=DMARC1\\; p=reject\\; sp=reject\\; pct=100\\; rua=mailto:xx...@yyyy.com\\; ruf=mailto:xx...@yyyy.com\\; fo=1"
]
}
]
}
]
}

View File

@ -1,2 +0,0 @@
$TTL 300
_dmarc IN TXT "v=DMARC1; p=reject; sp=reject; pct=100; rua=mailto:xx...@yyyy.com; ruf=mailto:xx...@yyyy.com; fo=1"

View File

@ -10,45 +10,27 @@
{
"type": "TXT",
"name": "a",
"target": "simple",
"txtstrings": [
"simple"
]
"target": "simple"
},
{
"type": "TXT",
"name": "b",
"target": "ws at end ",
"txtstrings": [
"ws at end "
]
"target": "ws at end "
},
{
"type": "TXT",
"name": "c",
"target": "one",
"txtstrings": [
"one"
]
"target": "one"
},
{
"type": "TXT",
"name": "d",
"target": "bonieclyde",
"txtstrings": [
"bonie",
"clyde"
]
"target": "bonieclyde"
},
{
"type": "TXT",
"name": "e",
"target": "strawwoodbrick",
"txtstrings": [
"straw",
"wood",
"brick"
]
"target": "strawwoodbrick"
}
]
}

View File

@ -2,5 +2,5 @@ $TTL 300
a IN TXT "simple"
b IN TXT "ws at end "
c IN TXT "one"
d IN TXT "bonie" "clyde"
e IN TXT "straw" "wood" "brick"
d IN TXT "bonieclyde"
e IN TXT "strawwoodbrick"

View File

@ -10,10 +10,7 @@
{
"type": "TXT",
"name": "dkimtest2",
"target": "this string is 255 bytes long.hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnKZogtjOlHoeY8iZ5o5brlPOsj/a2Q9Bopu1kHxlxrdw7tZVL9FzUMngiIYGrl8dbP7Rvk7TLMoxHxVkRZPBtIpsKIab/gOUoPLQVYbrAmzyguHYBwAApi3H/pvjUsK8+XF0dKY17AR96lokAPqvfBaUb+DSx8zNw2hrYWYVqvCtnxHUGEUhT1bTlEZBptH3jthis is the remainder. it is 156 bytes long.mOhl2JmbsFKy+RoMTwbkk0/meRvcEFWLHkr4MSgbnie6OpQvM4Y51+kO6DUVr3rwjrdVO9wpFt+n/hdQ92TNif17RMJtE5AGaQ6BN3yJQIDAQAB;",
"txtstrings": [
"this string is 255 bytes long.hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnKZogtjOlHoeY8iZ5o5brlPOsj/a2Q9Bopu1kHxlxrdw7tZVL9FzUMngiIYGrl8dbP7Rvk7TLMoxHxVkRZPBtIpsKIab/gOUoPLQVYbrAmzyguHYBwAApi3H/pvjUsK8+XF0dKY17AR96lokAPqvfBaUb+DSx8zNw2hrYWYVqvCtnxHUGEUhT1bTlEZBptH3jthis is the remainder. it is 156 bytes long.mOhl2JmbsFKy+RoMTwbkk0/meRvcEFWLHkr4MSgbnie6OpQvM4Y51+kO6DUVr3rwjrdVO9wpFt+n/hdQ92TNif17RMJtE5AGaQ6BN3yJQIDAQAB;"
]
"target": "this string is 255 bytes long.hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnKZogtjOlHoeY8iZ5o5brlPOsj/a2Q9Bopu1kHxlxrdw7tZVL9FzUMngiIYGrl8dbP7Rvk7TLMoxHxVkRZPBtIpsKIab/gOUoPLQVYbrAmzyguHYBwAApi3H/pvjUsK8+XF0dKY17AR96lokAPqvfBaUb+DSx8zNw2hrYWYVqvCtnxHUGEUhT1bTlEZBptH3jthis is the remainder. it is 156 bytes long.mOhl2JmbsFKy+RoMTwbkk0/meRvcEFWLHkr4MSgbnie6OpQvM4Y51+kO6DUVr3rwjrdVO9wpFt+n/hdQ92TNif17RMJtE5AGaQ6BN3yJQIDAQAB;"
}
]
}

View File

@ -1,2 +1,2 @@
$TTL 300
dkimtest2 IN TXT "this string is 255 bytes long.hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnKZogtjOlHoeY8iZ5o5brlPOsj/a2Q9Bopu1kHxlxrdw7tZVL9FzUMngiIYGrl8dbP7Rvk7TLMoxHxVkRZPBtIpsKIab/gOUoPLQVYbrAmzyguHYBwAApi3H/pvjUsK8+XF0dKY17AR96lokAPqvfBaUb+DSx8zNw2hrYWYVqvCtnxHUGEUhT1bTlEZBptH3jthis is the remainder. it is 156 bytes long.mOhl2JmbsFKy+RoMTwbkk0/meRvcEFWLHkr4MSgbnie6OpQvM4Y51+kO6DUVr3rwjrdVO9wpFt+n/hdQ92TNif17RMJtE5AGaQ6BN3yJQIDAQAB;"
dkimtest2 IN TXT "this string is 255 bytes long.hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnKZogtjOlHoeY8iZ5o5brlPOsj/a2Q9Bopu1kHxlxrdw7tZVL9FzUMngiIYGrl8dbP7Rvk7TLMoxHxVkRZPBtIpsKIab/gOUoPLQVYbrAmzyguHYBwAApi3H/pvjUsK8+XF0dKY17AR96lokAPqvfBaUb+DSx8zNw2hrYWYVqvCtnxHUGEUhT1bTlEZBptH3j" "this is the remainder. it is 156 bytes long.mOhl2JmbsFKy+RoMTwbkk0/meRvcEFWLHkr4MSgbnie6OpQvM4Y51+kO6DUVr3rwjrdVO9wpFt+n/hdQ92TNif17RMJtE5AGaQ6BN3yJQIDAQAB;"

View File

@ -32,7 +32,7 @@ func flattenSPFs(cfg *models.DNSConfig) []error {
// flatten all spf records that have the "flatten" metadata
for _, txt := range txtRecords {
var rec *spflib.SPFRecord
txtTarget := strings.Join(txt.TxtStrings, "")
txtTarget := txt.GetTargetTXTJoined()
if txt.Metadata["flatten"] != "" || txt.Metadata["split"] != "" {
if cache == nil {
cache, err = spflib.NewCache("spfcache.json")

View File

@ -569,7 +569,7 @@ func checkCNAMEs(dc *models.DomainConfig) (errs []error) {
func checkDuplicates(records []*models.RecordConfig) (errs []error) {
seen := map[string]*models.RecordConfig{}
for _, r := range records {
diffable := fmt.Sprintf("%s %s %s", r.GetLabelFQDN(), r.Type, r.ToDiffable())
diffable := fmt.Sprintf("%s %s %s", r.GetLabelFQDN(), r.Type, r.ToComparableNoTTL())
if seen[diffable] != nil {
errs = append(errs, fmt.Errorf("exact duplicate record found: %s", diffable))
}

View File

@ -314,13 +314,11 @@ func TestCheckDuplicates(t *testing.T) {
// The only difference is the rType:
makeRC("aaa", "example.com", "uniquestring.com.", models.RecordConfig{Type: "NS"}),
makeRC("aaa", "example.com", "uniquestring.com.", models.RecordConfig{Type: "PTR"}),
// The only difference is the TTL.
makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: 111}),
makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: 222}),
// Three records each with a different target.
makeRC("@", "example.com", "ns1.foo.com.", models.RecordConfig{Type: "NS"}),
makeRC("@", "example.com", "ns2.foo.com.", models.RecordConfig{Type: "NS"}),
makeRC("@", "example.com", "ns3.foo.com.", models.RecordConfig{Type: "NS"}),
// NOTE: The comparison ignores ttl. Therefore we don't test that.
}
errs := checkDuplicates(records)
if len(errs) != 0 {

View File

@ -10,6 +10,7 @@ import (
"strings"
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
"github.com/miekg/dns"
)
@ -138,7 +139,7 @@ func (z *ZoneGenData) generateZoneFileHelper(w io.Writer) error {
typeStr := rr.Type
// the remaining line
target := rr.GetTargetCombined()
target := rr.GetTargetCombinedFunc(txtutil.EncodeQuoted)
// comment
comment := ""

View File

@ -5,6 +5,7 @@ import (
"fmt"
"log"
"math/rand"
"strings"
"testing"
"github.com/StackExchange/dnscontrol/v4/models"
@ -247,6 +248,46 @@ var testdataZFCAA = `$TTL 300
IN CAA 0 issuewild ";"
`
// r is shorthand for strings.Repeat()
func r(s string, c int) string { return strings.Repeat(s, c) }
func TestWriteZoneFileTxt(t *testing.T) {
// Do round-trip tests on various length TXT records.
t10 := `t10 IN TXT "ten4567890"`
t254 := `t254 IN TXT "` + r("a", 254) + `"`
t255 := `t255 IN TXT "` + r("b", 255) + `"`
t256 := `t256 IN TXT "` + r("c", 255) + `" "` + r("D", 1) + `"`
t509 := `t509 IN TXT "` + r("e", 255) + `" "` + r("F", 254) + `"`
t510 := `t510 IN TXT "` + r("g", 255) + `" "` + r("H", 255) + `"`
t511 := `t511 IN TXT "` + r("i", 255) + `" "` + r("J", 255) + `" "` + r("k", 1) + `"`
t512 := `t511 IN TXT "` + r("L", 255) + `" "` + r("M", 255) + `" "` + r("n", 2) + `"`
t513 := `t511 IN TXT "` + r("o", 255) + `" "` + r("P", 255) + `" "` + r("q", 3) + `"`
for i, d := range []string{t10, t254, t255, t256, t509, t510, t511, t512, t513} {
// Make the rr:
rr, err := dns.NewRR(d)
if err != nil {
t.Fatal(err)
}
// Make the expected zonefile:
ez := "$TTL 3600\n" + d + "\n"
// Generate the zonefile:
buf := &bytes.Buffer{}
WriteZoneFileRR(buf, []dns.RR{rr}, "bosun.org")
gz := buf.String()
if gz != ez {
t.Log("got: " + gz)
t.Log("wnt: " + ez)
t.Fatalf("Zone file %d does not match.", i)
}
// Reverse the process. Turn the zonefile into a list of records
parseAndRegen(t, buf, ez)
}
}
// Test 1 of each record type
func mustNewRR(s string) dns.RR {

View File

@ -11,104 +11,65 @@ import (
// TxtHasBackslash audits TXT records for strings that contains one or more backslashes.
func TxtHasBackslash(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if strings.Contains(txt, `\`) {
return fmt.Errorf("txtstring contains backslash")
}
if strings.Contains(rc.GetTargetTXTJoined(), `\`) {
return fmt.Errorf("txtstring contains backslashes")
}
return nil
}
// TxtHasBackticks audits TXT records for strings that contain backticks.
func TxtHasBackticks(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if strings.Contains(txt, "`") {
if strings.Contains(rc.GetTargetTXTJoined(), "`") {
return fmt.Errorf("txtstring contains backtick")
}
}
return nil
}
// TxtHasSingleQuotes audits TXT records for strings that contain single-quotes.
func TxtHasSingleQuotes(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if strings.Contains(txt, "'") {
return fmt.Errorf("txtstring contains single-quotes")
}
}
return nil
}
// TxtHasDoubleQuotes audits TXT records for strings that contain doublequotes.
func TxtHasDoubleQuotes(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if strings.Contains(txt, `"`) {
if strings.Contains(rc.GetTargetTXTJoined(), `"`) {
return fmt.Errorf("txtstring contains doublequotes")
}
}
return nil
}
// TxtIsExactlyLen255 audits TXT records for strings exactly 255 octets long.
// This is rare; you probably want to use TxtNoStringsLen256orLonger() instead.
func TxtIsExactlyLen255(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if len(txt) == 255 {
return fmt.Errorf("txtstring length is 255")
}
}
return nil
}
// TxtHasSegmentLen256orLonger audits TXT records for strings that are >255 octets.
func TxtHasSegmentLen256orLonger(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if len(txt) > 255 {
return fmt.Errorf("%q txtstring length > 255", rc.GetLabel())
}
}
return nil
}
// TxtHasMultipleSegments audits TXT records for multiple strings
func TxtHasMultipleSegments(rc *models.RecordConfig) error {
if len(rc.TxtStrings) > 1 {
return fmt.Errorf("multiple strings in one txt")
// TxtHasSingleQuotes audits TXT records for strings that contain single-quotes.
func TxtHasSingleQuotes(rc *models.RecordConfig) error {
if strings.Contains(rc.GetTargetTXTJoined(), "'") {
return fmt.Errorf("txtstring contains single-quotes")
}
return nil
}
// TxtHasTrailingSpace audits TXT records for strings that end with space.
func TxtHasTrailingSpace(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
txt := rc.GetTargetTXTJoined()
if txt != "" && txt[ultimate(txt)] == ' ' {
return fmt.Errorf("txtstring ends with space")
}
}
return nil
}
// TxtIsEmpty audits TXT records for empty strings.
func TxtIsEmpty(rc *models.RecordConfig) error {
// There must be strings.
if len(rc.TxtStrings) == 0 {
return fmt.Errorf("txt with no strings")
}
// Each string must be non-empty.
for _, txt := range rc.TxtStrings {
if len(txt) == 0 {
return fmt.Errorf("txtstring is empty")
}
}
return nil
}
// TxtHasUnpairedDoubleQuotes audits TXT records for strings that contain unpaired doublequotes.
func TxtHasUnpairedDoubleQuotes(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if strings.Count(txt, `"`)%2 == 1 {
if strings.Count(rc.GetTargetTXTJoined(), `"`)%2 == 1 {
return fmt.Errorf("txtstring contains unpaired doublequotes")
}
}
return nil
}
// TxtIsEmpty audits TXT records for empty strings.
func TxtIsEmpty(rc *models.RecordConfig) error {
if len(rc.GetTargetTXTJoined()) == 0 {
return fmt.Errorf("txtstring is empty")
}
return nil
}
// TxtLongerThan255 audits TXT records for multiple strings
func TxtLongerThan255(rc *models.RecordConfig) error {
if len(rc.GetTargetTXTJoined()) > 255 {
return fmt.Errorf("TXT records longer than 255 octets (chars)")
}
return nil
}

155
pkg/txtutil/txtcode.go Normal file
View File

@ -0,0 +1,155 @@
//go:generate stringer -type=State
package txtutil
import (
"bytes"
"fmt"
"strings"
)
// ParseQuoted parses a string of RFC1035-style quoted items. The resulting
// items are then joined into one string. This is useful for parsing TXT
// records.
// Examples:
// `foo` => foo
// `"foo"` => foo
// `"f\"oo"` => f"oo
// `"f\\oo"` => f\oo
// `"foo" "bar"` => foobar
// `"foo" bar` => foobar
func ParseQuoted(s string) (string, error) {
return txtDecode(s)
}
// EncodeQuoted encodes a string into a series of quoted 255-octet chunks. That
// is, when decoded each chunk would be 255-octets with the remainder in the
// last chunk.
//
// The output looks like:
//
// `""` empty
// `"255\"octets"` quotes are escaped
// `"255\\octets"` backslashes are escaped
// `"255octets" "255octets" "remainder"` long strings are chunked
func EncodeQuoted(t string) string {
return txtEncode(ToChunks(t))
}
type State int
const (
StateStart State = iota // Looking for a non-space
StateUnquoted // A run of unquoted text
StateQuoted // Quoted text
StateBackslash // last char was backlash in a quoted string
StateWantSpace // expect space after closing quote
)
func isRemaining(s string, i, r int) bool {
return (len(s) - 1 - i) > r
}
// txtDecode decodes TXT strings quoted/escaped as Tom interprets RFC10225.
func txtDecode(s string) (string, error) {
// Parse according to RFC1035 zonefile specifications.
// "foo" -> one string: `foo``
// "foo" "bar" -> two strings: `foo` and `bar`
// quotes and backslashes are escaped using \
/*
BNF:
txttarget := `""`` | item | item ` ` item*
item := quoteditem | unquoteditem
quoteditem := quote innertxt quote
quote := `"`
innertxt := (escaped | printable )*
escaped := `\\` | `\"`
printable := (printable ASCII chars)
unquoteditem := (printable ASCII chars but not `"` nor ' ')
*/
//printer.Printf("DEBUG: txtDecode txt inboundv=%v\n", s)
b := &bytes.Buffer{}
state := StateStart
for i, c := range s {
//printer.Printf("DEBUG: state=%v rune=%v\n", state, string(c))
switch state {
case StateStart:
if c == ' ' {
// skip whitespace
} else if c == '"' {
state = StateQuoted
} else {
state = StateUnquoted
b.WriteRune(c)
}
case StateUnquoted:
if c == ' ' {
state = StateStart
} else {
b.WriteRune(c)
}
case StateQuoted:
if c == '\\' {
if isRemaining(s, i, 1) {
state = StateBackslash
} else {
return "", fmt.Errorf("txtDecode quoted string ends with backslash q(%q)", s)
}
} else if c == '"' {
state = StateWantSpace
} else {
b.WriteRune(c)
}
case StateBackslash:
b.WriteRune(c)
state = StateQuoted
case StateWantSpace:
if c == ' ' {
state = StateStart
} else {
return "", fmt.Errorf("txtDecode expected whitespace after close quote q(%q)", s)
}
}
}
r := b.String()
//printer.Printf("DEBUG: txtDecode txt decodedv=%v\n", r)
return r, nil
}
// txtEncode encodes TXT strings in RFC1035 format as interpreted by Tom.
func txtEncode(ts []string) string {
//printer.Printf("DEBUG: txtEncode txt outboundv=%v\n", ts)
if (len(ts) == 0) || (strings.Join(ts, "") == "") {
return `""`
}
var r []string
for i := range ts {
tx := ts[i]
tx = strings.ReplaceAll(tx, `\`, `\\`)
tx = strings.ReplaceAll(tx, `"`, `\"`)
tx = `"` + tx + `"`
r = append(r, tx)
}
t := strings.Join(r, ` `)
//printer.Printf("DEBUG: txtEncode txt encodedv=%v\n", t)
return t
}

View File

@ -0,0 +1,97 @@
package txtutil
import (
"strings"
"testing"
)
func r(s string, c int) string { return strings.Repeat(s, c) }
func TestTxtDecode(t *testing.T) {
tests := []struct {
data string
expected []string
}{
{``, []string{``}},
{`""`, []string{``}},
{`foo`, []string{`foo`}},
{`"foo"`, []string{`foo`}},
{`"foo bar"`, []string{`foo bar`}},
{`foo bar`, []string{`foo`, `bar`}},
{`"aaa" "bbb"`, []string{`aaa`, `bbb`}},
{`"a\"a" "bbb"`, []string{`a"a`, `bbb`}},
// Seen in live traffic:
{"\"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\"",
[]string{r("B", 254)}},
{"\"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\"",
[]string{r("C", 255)}},
{"\"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD\" \"D\"",
[]string{r("D", 255), "D"}},
{"\"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\" \"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\"",
[]string{r("E", 255), r("E", 255)}},
{"\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\" \"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\" \"F\"",
[]string{r("F", 255), r("F", 255), "F"}},
{"\"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\" \"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\" \"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\"",
[]string{r("G", 255), r("G", 255), r("G", 255)}},
{"\"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\" \"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\" \"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\" \"H\"",
[]string{r("H", 255), r("H", 255), r("H", 255), "H"}},
{"\"quo'te\"", []string{`quo'te`}},
{"\"blah`blah\"", []string{"blah`blah"}},
{"\"quo\\\"te\"", []string{`quo"te`}},
{"\"q\\\"uo\\\"te\"", []string{`q"uo"te`}},
/// Backslashes are meaningless in unquoted strings. Unquoted strings run until they hit a space.
{`1backs\lash`, []string{`1backs\lash`}},
{`2backs\\lash`, []string{`2backs\\lash`}},
{`3backs\\\lash`, []string{`3backs\\\lash`}},
{`4backs\\\\lash`, []string{`4backs\\\\lash`}},
/// Inside quotes, a backlash means take the next byte literally.
{`"q1backs\lash"`, []string{`q1backslash`}},
{`"q2backs\\lash"`, []string{`q2backs\lash`}},
{`"q3backs\\\lash"`, []string{`q3backs\lash`}},
{`"q4backs\\\\lash"`, []string{`q4backs\\lash`}},
// HETZNER includes a space after the last quote. Make sure we handle that.
{`"one" "more" `, []string{`one`, `more`}},
}
for i, test := range tests {
got, err := txtDecode(test.data)
if err != nil {
t.Error(err)
}
want := strings.Join(test.expected, "")
if got != want {
t.Errorf("%v: expected TxtStrings=(%q) got (%q)", i, want, got)
}
}
}
func TestTxtEncode(t *testing.T) {
tests := []struct {
data []string
expected string
}{
{[]string{}, `""`},
{[]string{``}, `""`},
{[]string{`foo`}, `"foo"`},
{[]string{`aaa`, `bbb`}, `"aaa" "bbb"`},
{[]string{`ccc`, `ddd`, `eee`}, `"ccc" "ddd" "eee"`},
{[]string{`a"a`, `bbb`}, `"a\"a" "bbb"`},
{[]string{`quo'te`}, "\"quo'te\""},
{[]string{"blah`blah"}, "\"blah`blah\""},
{[]string{`quo"te`}, "\"quo\\\"te\""},
{[]string{`quo"te`}, `"quo\"te"`},
{[]string{`q"uo"te`}, "\"q\\\"uo\\\"te\""},
{[]string{`1backs\lash`}, `"1backs\\lash"`},
{[]string{`2backs\\lash`}, `"2backs\\\\lash"`},
{[]string{`3backs\\\lash`}, `"3backs\\\\\\lash"`},
{[]string{strings.Repeat("M", 26), `quo"te`}, `"MMMMMMMMMMMMMMMMMMMMMMMMMM" "quo\"te"`},
}
for i, test := range tests {
got := txtEncode(test.data)
want := test.expected
if got != want {
t.Errorf("%v: expected TxtStrings=v(%v) got (%v)", i, want, got)
}
}
}

View File

@ -0,0 +1,53 @@
//go:generate stringer -type=State
package txtutil
// func ParseCombined(s string) (string, error) {
// return txtDecodeCombined(s)
// }
// // // txtDecode decodes TXT strings received from ROUTE53 and GCLOUD.
// func txtDecodeCombined(s string) (string, error) {
// // The dns package doesn't expose the quote parser. Therefore we create a TXT record and extract the strings.
// rr, err := dns.NewRR("example.com. IN TXT " + s)
// if err != nil {
// return "", fmt.Errorf("could not parse %q TXT: %w", s, err)
// }
// return strings.Join(rr.(*dns.TXT).Txt, ""), nil
// }
// func EncodeCombined(t string) string {
// return txtEncodeCombined(ToChunks(t))
// }
// // txtEncode encodes TXT strings as the old GetTargetCombined() function did.
// func txtEncodeCombined(ts []string) string {
// //printer.Printf("DEBUG: route53 txt outboundv=%v\n", ts)
// // Don't call this on fake types.
// rdtype := dns.StringToType["TXT"]
// // Magically create an RR of the correct type.
// rr := dns.TypeToRR[rdtype]()
// // Fill in the header.
// rr.Header().Name = "example.com."
// rr.Header().Rrtype = rdtype
// rr.Header().Class = dns.ClassINET
// rr.Header().Ttl = 300
// // Fill in the TXT data.
// rr.(*dns.TXT).Txt = ts
// // Generate the quoted string:
// header := rr.Header().String()
// full := rr.String()
// if !strings.HasPrefix(full, header) {
// panic("assertion failed. dns.Hdr.String() behavior has changed in an incompatible way")
// }
// //printer.Printf("DEBUG: route53 txt encodedv=%v\n", t)
// return full[len(header):]
// }

View File

@ -1,20 +1,13 @@
package txtutil
import "github.com/StackExchange/dnscontrol/v4/models"
// SplitSingleLongTxt does nothing.
// Deprecated: This is a no-op for backwards compatibility.
func SplitSingleLongTxt(records any) {
}
// SplitSingleLongTxt finds TXT records with a single long string and splits it
// into 255-octet chunks. This is used by providers that, when a user specifies
// one long TXT string, split it into smaller strings behind the scenes.
// This should be called from GetZoneRecordsCorrections().
func SplitSingleLongTxt(records []*models.RecordConfig) {
for _, rc := range records {
if rc.HasFormatIdenticalToTXT() {
s := rc.TxtStrings[0]
if len(rc.TxtStrings) == 1 && len(s) > 255 {
rc.SetTargetTXTs(splitChunks(s, 255))
}
}
}
// ToChunks returns the string as chunks of 255-octet strings (the last string being the remainder).
func ToChunks(s string) []string {
return splitChunks(s, 255)
}
// Segment returns the string as 255-octet segments, the last being the remainder.

View File

@ -13,5 +13,7 @@ func AuditRecords(records []*models.RecordConfig) []error {
a.Add("MX", rejectif.MxNull) // Last verified 2020-12-28
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2023-11-11
return a.Audit(records)
}

View File

@ -195,7 +195,7 @@ func ParseZoneContents(content string, zoneName string, zonefileName string) (mo
foundRecords := models.Records{}
for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
rec, err := models.RRtoRC(rr, zoneName)
rec, err := models.RRtoRCTxtBug(rr, zoneName)
if err != nil {
return nil, err
}

View File

@ -11,8 +11,6 @@ import (
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
a.Add("TXT", rejectif.TxtHasMultipleSegments) // Last verified 2022-06-18
a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2022-06-18
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2022-06-18

View File

@ -19,8 +19,6 @@ func AuditRecords(records []*models.RecordConfig) []error {
a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2021-03-01
a.Add("TXT", rejectif.TxtHasMultipleSegments) // Last verified 2021-03-01
a.Add("SRV", rejectif.SrvHasNullTarget) // Last verified 2023-03-30
return a.Audit(records)

View File

@ -478,7 +478,7 @@ func (client *providerClient) clearRequests(domain string) error {
if cscDebug {
printer.Printf("DEBUG: Clearing requests for %q\n", domain)
}
var bodyString, err = client.get("/zones/edits?filter=zoneName==" + domain)
var bodyString, err = client.get(`/zones/edits?size=99999&filter=zoneName==` + domain)
if err != nil {
return err
}
@ -486,10 +486,12 @@ func (client *providerClient) clearRequests(domain string) error {
var dr pagedZoneEditResponsePagedZoneEditResponse
json.Unmarshal(bodyString, &dr)
// TODO(tlim): Properly handle paganation.
if dr.Meta.Pages > 1 {
return fmt.Errorf("cancelPendingEdits failed: Pages=%d", dr.Meta.Pages)
}
// TODO(tlim): Ignore what's beyond the first page.
// It is unlikely that there are active jobs beyond the first page.
// If there are, the next edit will just wait.
//if dr.Meta.Pages > 1 {
// return fmt.Errorf("cancelPendingEdits failed: Pages=%d", dr.Meta.Pages)
//}
for i, ze := range dr.ZoneEdits {
if cscDebug {

View File

@ -17,11 +17,11 @@ func AuditRecords(records []*models.RecordConfig) []error {
a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2022-08-08
a.Add("TXT", rejectif.TxtHasMultipleSegments) // Last verified 2022-06-10
a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2022-06-10
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2022-06-10
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2023-12-03
//a.Add("TXT", rejectif.TxtLongerThan255) // Last verified 2022-06-10
return a.Audit(records)
}

View File

@ -6,6 +6,7 @@ import (
"net"
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
)
// nativeToRecordA takes an A record from DNS and returns a native RecordConfig struct.
@ -64,6 +65,7 @@ func nativeToRecordTXT(nr nativeRecordTXT, origin string, defaultTTL uint32) *mo
TTL: ttl,
}
rc.SetLabel(nr.Key, origin)
printer.Printf("DEBUG: inbound raw s=%s\n", nr.Value)
rc.SetTargetTXT(nr.Value)
return rc
}

View File

@ -76,7 +76,6 @@ func (client *providerClient) GetNameservers(domain string) ([]*models.Nameserve
// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
func (client *providerClient) GetZoneRecordsCorrections(dc *models.DomainConfig, foundRecords models.Records) ([]*models.Correction, error) {
//txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
toReport, creates, dels, modifications, err := diff.NewCompat(dc).IncrementalDiff(foundRecords)
if err != nil {

View File

@ -19,10 +19,14 @@ func AuditRecords(records []*models.RecordConfig) []error {
a.Add("TXT", MaxLengthDO) // Last verified 2021-03-01
a.Add("TXT", rejectif.TxtHasBackslash) // Last verified 2023-11-11
a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2021-03-01
// Double-quotes not permitted in TXT strings. I have a hunch that
// this is due to a broken parser on the DO side.
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2023-11-11
return a.Audit(records)
}

View File

@ -13,7 +13,7 @@ func AuditRecords(records []*models.RecordConfig) []error {
a.Add("MX", rejectif.MxNull) // Last verified 2023-03
a.Add("TXT", rejectif.TxtHasMultipleSegments) // Last verified 2023-03
a.Add("TXT", rejectif.TxtLongerThan255) // Last verified 2023-03
a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2023-03

View File

@ -7,6 +7,7 @@ import (
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
"github.com/go-gandi/go-gandi/livedns"
)
@ -29,10 +30,8 @@ func nativeToRecords(n livedns.DomainRecord, origin string) (rcs []*models.Recor
case "ALIAS":
rc.Type = "ALIAS"
err = rc.SetTarget(value)
case "TXT":
err = rc.SetTargetTXTfromRFC1035Quoted(value)
default:
err = rc.PopulateFromString(rtype, value, origin)
err = rc.PopulateFromStringFunc(rtype, value, origin, txtutil.ParseQuoted)
}
if err != nil {
return nil, fmt.Errorf("unparsable record received from gandi: %w", err)
@ -65,11 +64,11 @@ func recordsToNative(rcs []*models.RecordConfig, origin string) []livedns.Domain
RrsetType: r.Type,
RrsetTTL: int(r.TTL),
RrsetName: label,
RrsetValues: []string{r.GetTargetCombined()},
RrsetValues: []string{r.GetTargetCombinedFunc(txtutil.EncodeQuoted)},
}
keys[key] = &zr
} else {
zr.RrsetValues = append(zr.RrsetValues, r.GetTargetCombined())
zr.RrsetValues = append(zr.RrsetValues, r.GetTargetCombinedFunc(txtutil.EncodeQuoted))
if r.TTL != uint32(zr.RrsetTTL) {
printer.Warnf("All TTLs for a rrset (%v) must be the same. Using smaller of %v and %v.\n", key, r.TTL, zr.RrsetTTL)

View File

@ -176,9 +176,6 @@ func PrepDesiredRecords(dc *models.DomainConfig) {
printer.Warnf("Gandi does not support ttls > 30 days. Setting %s from %d to 2592000\n", rec.GetLabelFQDN(), rec.TTL)
rec.TTL = 2592000
}
if rec.Type == "TXT" {
rec.SetTarget("\"" + rec.GetTargetField() + "\"") // FIXME(tlim): Should do proper quoting.
}
if rec.Type == "NS" && rec.GetLabel() == "@" {
if !strings.HasSuffix(rec.GetTargetField(), ".gandi.net.") {
printer.Warnf("Gandi does not support changing apex NS records. Ignoring %s\n", rec.GetTargetField())
@ -293,7 +290,7 @@ func (client *gandiv5Provider) GetZoneRecordsCorrections(dc *models.DomainConfig
func debugRecords(note string, recs []*models.RecordConfig) {
printer.Debugf(note)
for k, v := range recs {
printer.Printf(" %v: %v %v %v %v\n", k, v.GetLabel(), v.Type, v.TTL, v.GetTargetCombined())
printer.Printf(" %v: %v %v %v %v\n", k, v.GetLabel(), v.Type, v.TTL, v.GetTargetDebug())
}
}

View File

@ -306,7 +306,7 @@ func (g *gcloudProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exis
}
for _, r := range dc.Records {
if keyForRec(r) == ck {
newRRs.Rrdatas = append(newRRs.Rrdatas, r.GetTargetCombined())
newRRs.Rrdatas = append(newRRs.Rrdatas, r.GetTargetCombinedFunc(txtutil.EncodeQuoted))
newRRs.Ttl = int64(r.TTL)
}
}
@ -403,13 +403,7 @@ func nativeToRecord(set *gdns.ResourceRecordSet, rec, origin string) (*models.Re
r.SetLabelFromFQDN(set.Name, origin)
r.TTL = uint32(set.Ttl)
rtype := set.Type
var err error
switch rtype {
case "TXT":
err = r.SetTargetTXTs(models.ParseQuotedTxt(rec))
default:
err = r.PopulateFromString(rtype, rec, origin)
}
err := r.PopulateFromStringFunc(rtype, rec, origin, txtutil.ParseQuoted)
if err != nil {
return nil, fmt.Errorf("unparsable record %q received from GCLOUD: %w", rtype, err)
}

View File

@ -325,10 +325,8 @@ func (c *hednsProvider) GetZoneRecords(domain string, meta map[string]string) (m
// Convert to TXT record as SPF is deprecated
rc.Type = "TXT"
fallthrough
case "TXT":
err = rc.SetTargetTXTs(models.ParseQuotedTxt(data))
default:
err = rc.PopulateFromString(rc.Type, data, domain)
err = rc.PopulateFromStringFunc(rc.Type, data, domain, txtutil.ParseQuoted)
}
if err != nil {
@ -563,7 +561,7 @@ func (c *hednsProvider) editZoneRecord(zoneID uint64, recordID uint64, rc *model
values.Set("Weight", strconv.FormatUint(uint64(rc.SrvWeight), 10))
values.Set("Port", strconv.FormatUint(uint64(rc.SrvPort), 10))
default:
values.Set("Content", rc.GetTargetCombined())
values.Set("Content", rc.GetTargetCombinedFunc(txtutil.EncodeQuoted))
}
response, err := c.httpClient.PostForm(apiEndpoint, values)

View File

@ -1,9 +1,8 @@
package hetzner
import (
"strings"
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
)
type bulkCreateRecordsRequest struct {
@ -58,7 +57,7 @@ func fromRecordConfig(in *models.RecordConfig, zone *zone) record {
r := record{
Name: in.GetLabel(),
Type: in.Type,
Value: in.GetTargetCombined(),
Value: in.GetTargetCombinedFunc(txtutil.EncodeQuoted),
TTL: &in.TTL,
ZoneID: zone.ID,
}
@ -88,13 +87,9 @@ func toRecordConfig(domain string, r *record) (*models.RecordConfig, error) {
}
rc.SetLabel(r.Name, domain)
value := r.Value
// HACK: Hetzner is inserting a trailing space after multiple, quoted values.
// NOTE: The actual DNS answer does not contain the space.
// NOTE: The txtutil.ParseQuoted parser handles this just fine.
// Last checked: 2023-04-01
if r.Type == "TXT" && len(value) > 0 && value[len(value)-1] == ' ' {
// Per RFC 1035 spaces outside quoted values are irrelevant.
value = strings.TrimRight(value, " ")
}
return &rc, rc.PopulateFromString(r.Type, value, domain)
return &rc, rc.PopulateFromStringFunc(r.Type, r.Value, domain, txtutil.ParseQuoted)
}

View File

@ -13,6 +13,8 @@ func AuditRecords(records []*models.RecordConfig) []error {
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-10-01
a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2023-11-30
a.Add("SRV", rejectif.SrvHasNullTarget) // Last verified 2020-12-28
return a.Audit(records)

View File

@ -3,7 +3,6 @@ package hexonet
import (
"bytes"
"fmt"
"regexp"
"strconv"
"strings"
@ -129,10 +128,6 @@ func toRecord(r *HXRecord, origin string) *models.RecordConfig {
rc.SetLabelFromFQDN(fqdn, origin)
switch rtype := r.Type; rtype {
case "TXT":
if err := rc.SetTargetTXTs(decodeTxt(r.Answer)); err != nil {
panic(fmt.Errorf("unparsable TXT record received from hexonet api: %w", err))
}
case "MX":
if err := rc.SetTargetMX(uint16(r.Priority), r.Answer); err != nil {
panic(fmt.Errorf("unparsable MX record received from hexonet api: %w", err))
@ -142,7 +137,7 @@ func toRecord(r *HXRecord, origin string) *models.RecordConfig {
panic(fmt.Errorf("unparsable SRV record received from hexonet api: %w", err))
}
default: // "A", "AAAA", "ANAME", "CNAME", "NS"
if err := rc.PopulateFromString(rtype, r.Answer, r.Fqdn); err != nil {
if err := rc.PopulateFromStringFunc(rtype, r.Answer, r.Fqdn, txtutil.ParseQuoted); err != nil {
panic(fmt.Errorf("unparsable record received from hexonet api: %w", err))
}
}
@ -242,7 +237,7 @@ func (n *HXClient) createRecordString(rc *models.RecordConfig, domain string) (s
case "CAA":
record.Answer = fmt.Sprintf(`%v %s "%s"`, rc.CaaFlag, rc.CaaTag, record.Answer)
case "TXT":
record.Answer = encodeTxt(rc.GetTargetTXTSegmented())
record.Answer = txtutil.EncodeQuoted(rc.GetTargetTXTJoined())
case "SRV":
if rc.GetTargetField() == "." {
return "", fmt.Errorf("SRV records with empty targets are not supported (as of 2020-02-27, the API returns 'Invalid attribute value syntax')")
@ -266,31 +261,3 @@ func (n *HXClient) createRecordString(rc *models.RecordConfig, domain string) (s
func (n *HXClient) deleteRecordString(record *HXRecord, domain string) string {
return record.Raw
}
// encodeTxt encodes []string for sending in the CREATE/MODIFY API:
func encodeTxt(txts []string) string {
var r []string
for _, txt := range txts {
n := `"` + strings.Replace(txt, `"`, `\"`, -1) + `"`
r = append(r, n)
}
return strings.Join(r, " ")
}
// finds a string surrounded by quotes that might contain an escaped quote character.
var quotedStringRegexp = regexp.MustCompile(`"((?:[^"\\]|\\.)*)"`)
// decodeTxt decodes the TXT record as received from hexonet api and
// returns the list of strings.
func decodeTxt(s string) []string {
if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
txtStrings := []string{}
for _, t := range quotedStringRegexp.FindAllStringSubmatch(s, -1) {
txtString := strings.Replace(t[1], `\"`, `"`, -1)
txtStrings = append(txtStrings, txtString)
}
return txtStrings
}
return []string{s}
}

View File

@ -1,51 +0,0 @@
package hexonet
import (
"strings"
"testing"
)
var txtData = []struct {
decoded []string
encoded string
}{
{[]string{`simple`}, `"simple"`},
{[]string{`changed`}, `"changed"`},
{[]string{`with spaces`}, `"with spaces"`},
{[]string{`with whitespace`}, `"with whitespace"`},
{[]string{"one", "two"}, `"one" "two"`},
{[]string{"eh", "bee", "cee"}, `"eh" "bee" "cee"`},
{[]string{"o\"ne", "tw\"o"}, `"o\"ne" "tw\"o"`},
{[]string{"dimple"}, `"dimple"`},
{[]string{"fun", "two"}, `"fun" "two"`},
{[]string{"eh", "bzz", "cee"}, `"eh" "bzz" "cee"`},
}
func TestEncodeTxt(t *testing.T) {
// Test encoded the lists of strings into a string:
for i, test := range txtData {
enc := encodeTxt(test.decoded)
if enc != test.encoded {
t.Errorf("%v: txt\n data: []string{%v}\nexpected: %q\n got: %q",
i, "`"+strings.Join(test.decoded, "`, `")+"`", test.encoded, enc)
}
}
}
func TestDecodeTxt(t *testing.T) {
// Test decoded a string into the list of strings:
for i, test := range txtData {
data := test.encoded
got := decodeTxt(data)
wanted := test.decoded
if len(got) != len(wanted) {
t.Errorf("%v: txt\n decode: %v\nexpected: %q\n got: %q\n", i, data, strings.Join(wanted, "`, `"), strings.Join(got, "`, `"))
} else {
for j := range got {
if got[j] != wanted[j] {
t.Errorf("%v: txt\n decode: %v\nexpected: %q\n got: %q\n", i, data, strings.Join(wanted, "`, `"), strings.Join(got, "`, `"))
}
}
}
}
}

View File

@ -216,13 +216,11 @@ func checkRecords(records models.Records) error {
// TODO(tlim) Remove this function. auditrecords.go takes care of this now.
for _, r := range records {
if r.Type == "TXT" {
for _, target := range r.GetTargetTXTSegmented() {
if strings.ContainsAny(target, "`") {
if strings.ContainsAny(r.GetTargetTXTJoined(), "`") {
return fmt.Errorf("INWX TXT records do not support single-quotes in their target")
}
}
}
}
return nil
}

View File

@ -19,17 +19,11 @@ func AuditRecords(records []*models.RecordConfig) []error {
a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2023-02-02
a.Add("TXT", rejectif.TxtHasMultipleSegments) // Last verified 2023-02-02
a.Add("TXT", rejectif.TxtHasSegmentLen256orLonger) // Last verified 2023-02-02
a.Add("TXT", rejectif.TxtLongerThan255) // Last verified 2023-02-02
a.Add("TXT", rejectif.TxtHasSingleQuotes) // Last verified 2023-02-02
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2023-02-02
a.Add("TXT", rejectif.TxtIsExactlyLen255) // Last verified 2023-02-02
a.Add("TXT", rejectif.TxtIsExactlyLen255) // Last verified 2023-02-02
return a.Audit(records)
}

View File

@ -20,7 +20,11 @@ func AuditRecords(records []*models.RecordConfig) []error {
a.Add("TXT", MaxLengthNDC) // Last verified 2021-03-01
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-03-01
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2023-11-18
a.Add("TXT", rejectif.TxtHasBackslash) // Last verified 2023-11-18
// Backslashes may be allowed in the API but the current
// encodeTxt/decodeTxt functions don't support backslashes.
return a.Audit(records)
}

View File

@ -8,6 +8,7 @@ import (
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/diff"
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
"github.com/namedotcom/go/namecom"
)
@ -154,7 +155,7 @@ func (n *namedotcomProvider) createRecord(rc *models.RecordConfig, domain string
case "A", "AAAA", "ANAME", "CNAME", "MX", "NS":
// nothing
case "TXT":
// nothing
record.Answer = txtutil.EncodeQuoted(rc.GetTargetTXTJoined())
case "SRV":
if rc.GetTargetField() == "." {
return errors.New("SRV records with empty targets are not supported (as of 2019-11-05, the API returns 'Parameter Value Error - Invalid Srv Format')")
@ -175,6 +176,7 @@ var quotedStringRegexp = regexp.MustCompile(`"((?:[^"\\]|\\.)*)"`)
// decodeTxt decodes the TXT record as received from name.com and
// returns the list of strings.
// NB(tlim): This is very similar to txtutil.ParseQuoted. Maybe replace it some day?
func decodeTxt(s string) []string {
if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {

View File

@ -17,21 +17,13 @@ var txtData = []struct {
{[]string{"eh", "bee", "cee"}, `"eh""bee""cee"`},
{[]string{"o\"ne", "tw\"o"}, `"o\"ne""tw\"o"`},
{[]string{"dimple"}, `dimple`},
//{[]string{`back\slash`}, `"back\\slash"`}, // Not yet supported
//{[]string{`back\\slash`}, `"back\\\\slash"`}, // Not yet supported
//{[]string{`back\\\slash`}, `"back\\\\\\slash"`}, // Not yet supported
{[]string{"fun", "two"}, `"fun""two"`},
{[]string{"eh", "bzz", "cee"}, `"eh""bzz""cee"`},
}
// func TestEncodeTxt(t *testing.T) {
// // Test encoded the lists of strings into a string:
// for i, test := range txtData {
// enc := encodeTxt(test.decoded)
// if enc != test.encoded {
// t.Errorf("%v: txt\n data: []string{%v}\nexpected: %s\n got: %s",
// i, "`"+strings.Join(test.decoded, "`, `")+"`", test.encoded, enc)
// }
// }
//}
func TestDecodeTxt(t *testing.T) {
// Test decoded a string into the list of strings:
for i, test := range txtData {

View File

@ -11,7 +11,7 @@ import (
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
a.Add("TXT", rejectif.TxtHasMultipleSegments)
a.Add("TXT", rejectif.TxtIsEmpty)
return a.Audit(records)
}

View File

@ -302,7 +302,7 @@ func buildRecord(recs models.Records, domain string, id string) *dns.Record {
if r.Type == "MX" {
rec.AddAnswer(&dns.Answer{Rdata: strings.Fields(fmt.Sprintf("%d %v", r.MxPreference, r.GetTargetField()))})
} else if r.Type == "TXT" {
rec.AddAnswer(&dns.Answer{Rdata: r.GetTargetTXTSegmented()})
rec.AddAnswer(&dns.Answer{Rdata: []string{r.GetTargetTXTJoined()}})
} else if r.Type == "CAA" {
rec.AddAnswer(&dns.Answer{
Rdata: []string{

View File

@ -29,7 +29,8 @@ func TestToRecordConfig(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "large.example.com", recordConfig.NameFQDN)
assert.Equal(t, largeContent, recordConfig.String())
assert.Equal(t, `"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"`,
recordConfig.String())
assert.Equal(t, uint32(5), recordConfig.TTL)
assert.Equal(t, "TXT", recordConfig.Type)
}

View File

@ -345,7 +345,7 @@ func (r *route53Provider) GetZoneRecordsCorrections(dc *models.DomainConfig, exi
for _, r := range inst.New {
rr := r53Types.ResourceRecord{
Value: aws.String(r.GetTargetCombined()),
Value: aws.String(r.GetTargetCombinedFunc(txtutil.EncodeQuoted)),
}
rrset.ResourceRecords = append(rrset.ResourceRecords, rr)
i := int64(r.TTL)
@ -488,17 +488,10 @@ func nativeToRecords(set r53Types.ResourceRecordSet, origin string) ([]*models.R
}
}
var err error
rc := &models.RecordConfig{TTL: uint32(aws.ToInt64(set.TTL))}
rc.SetLabelFromFQDN(unescape(set.Name), origin)
rc.Original = set
switch rtypeString {
case "TXT":
err = rc.SetTargetTXTs(models.ParseQuotedTxt(val))
default:
err = rc.PopulateFromString(rtypeString, val, origin)
}
if err != nil {
if err := rc.PopulateFromStringFunc(rtypeString, val, origin, txtutil.ParseQuoted); err != nil {
return nil, fmt.Errorf("unparsable record type=%q received from ROUTE53: %w", rtypeString, err)
}

View File

@ -11,8 +11,6 @@ import (
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
a.Add("TXT", rejectif.TxtHasMultipleSegments)
a.Add("TXT", rejectif.TxtHasTrailingSpace)
a.Add("TXT", rejectif.TxtIsEmpty)

View File

@ -11,13 +11,13 @@ import (
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
a.Add("MX", rejectif.MxNull) // Last verified 2023-01-28
a.Add("MX", rejectif.MxNull) // Last verified 2023-12-04
a.Add("TXT", rejectif.TxtHasBackticks) // Last verified 2023-01-28
a.Add("TXT", rejectif.TxtHasBackticks) // Last verified 2023-12-04
a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2023-01-28
a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2023-12-04
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2023-01-28
a.Add("TXT", rejectif.TxtHasBackslash) // Last verified 2023-12-04
return a.Audit(records)
}

View File

@ -1,11 +0,0 @@
package transip
import (
"regexp"
)
var removeSlashesRegexp = regexp.MustCompile(`(?:\\(\\)+)|(?:\\)`)
func removeSlashes(s string) string {
return removeSlashesRegexp.ReplaceAllString(s, "$1")
}

View File

@ -1,25 +0,0 @@
package transip
import (
"testing"
)
func TestRemoveSlashes(t *testing.T) {
data := [][]string{
{
`quote"d`, `quote"d`,
`quote\"d`, `quote"d`,
`quote\\"d`, `quote\"d`,
`m\o\\r\\\\e`, `mo\r\\e`,
},
}
for _, testCase := range data {
result := removeSlashes(testCase[0])
if result != testCase[1] {
t.Fatalf(`Failed on "%s". Expected "%s"; got "%s".`, testCase[0], testCase[1], result)
}
}
}

View File

@ -8,6 +8,7 @@ import (
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
"github.com/StackExchange/dnscontrol/v4/providers"
"github.com/transip/gotransip/v6"
"github.com/transip/gotransip/v6/domain"
@ -299,6 +300,7 @@ func (n *transipProvider) GetNameservers(domainName string) ([]*models.Nameserve
return models.ToNameservers(nss)
}
// recordToNative convrts RecordConfig TO Native.
func recordToNative(config *models.RecordConfig, useOriginal bool) (domain.DNSEntry, error) {
if useOriginal && config.Original != nil {
return config.Original.(domain.DNSEntry), nil
@ -308,10 +310,11 @@ func recordToNative(config *models.RecordConfig, useOriginal bool) (domain.DNSEn
Name: config.Name,
Expire: int(config.TTL),
Type: config.Type,
Content: getTargetRecordContent(config),
Content: config.GetTargetCombinedFunc(txtutil.EncodeQuoted),
}, nil
}
// nativeToRecord converts native to RecordConfig.
func nativeToRecord(entry domain.DNSEntry, origin string) (*models.RecordConfig, error) {
rc := &models.RecordConfig{
TTL: uint32(entry.Expire),
@ -319,7 +322,7 @@ func nativeToRecord(entry domain.DNSEntry, origin string) (*models.RecordConfig,
Original: entry,
}
rc.SetLabel(entry.Name, origin)
if err := rc.PopulateFromString(entry.Type, entry.Content, origin); err != nil {
if err := rc.PopulateFromStringFunc(entry.Type, entry.Content, origin, txtutil.ParseQuoted); err != nil {
return nil, fmt.Errorf("unparsable record received from TransIP: %w", err)
}
@ -338,18 +341,3 @@ func removeOtherNS(dc *models.DomainConfig) {
}
dc.Records = newList
}
func getTargetRecordContent(rc *models.RecordConfig) string {
switch rtype := rc.Type; rtype {
case "SSHFP":
return fmt.Sprintf("%d %d %s", rc.SshfpAlgorithm, rc.SshfpFingerprint, rc.GetTargetField())
case "DS":
return fmt.Sprintf("%d %d %d %s", rc.DsKeyTag, rc.DsAlgorithm, rc.DsDigestType, rc.DsDigest)
case "SRV":
return fmt.Sprintf("%d %d %d %s", rc.SrvPriority, rc.SrvWeight, rc.SrvPort, rc.GetTargetField())
case "TXT":
return removeSlashes(models.StripQuotes(rc.GetTargetCombined()))
default:
return models.StripQuotes(rc.GetTargetCombined())
}
}

View File

@ -17,8 +17,6 @@ func AuditRecords(records []*models.RecordConfig) []error {
// Needs investigation. Could be a dnscontrol issue or
// the provider doesn't support double quotes.
a.Add("TXT", rejectif.TxtHasMultipleSegments)
a.Add("CAA", rejectif.CaaTargetContainsWhitespace) // Last verified 2023-01-19
return a.Audit(records)