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:
@ -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.
|
||||
|
@ -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"`
|
||||
|
@ -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")
|
||||
|
@ -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"),
|
||||
|
@ -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.
|
@ -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"
|
||||
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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), `" "`)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
169
models/t_txt.go
169
models/t_txt.go
@ -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()
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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})
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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('');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -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))
|
||||
);
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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"
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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;"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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;"
|
||||
|
@ -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")
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 := ""
|
||||
|
@ -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 {
|
||||
|
@ -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
155
pkg/txtutil/txtcode.go
Normal 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
|
||||
}
|
97
pkg/txtutil/txtcode_test.go
Normal file
97
pkg/txtutil/txtcode_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
53
pkg/txtutil/txtcombined.go
Normal file
53
pkg/txtutil/txtcombined.go
Normal 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):]
|
||||
// }
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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}
|
||||
}
|
||||
|
@ -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, "`, `"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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] == '"' {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
package transip
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var removeSlashesRegexp = regexp.MustCompile(`(?:\\(\\)+)|(?:\\)`)
|
||||
|
||||
func removeSlashes(s string) string {
|
||||
return removeSlashesRegexp.ReplaceAllString(s, "$1")
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
Reference in New Issue
Block a user