From cbccbbeb8d981bcd688de1ee6ef8efe8df8a56d9 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Mon, 4 Dec 2023 17:45:25 -0500 Subject: [PATCH] REFACTOR: Opinion: TXT records are one long string (#2631) Co-authored-by: Costas Drogos Co-authored-by: imlonghao Co-authored-by: Jeffrey Cafferata Co-authored-by: Vincent Hagen --- commands/getZones.go | 8 +- commands/previewPush.go | 1 + commands/test_data/example.org.zone.djs | 14 +- commands/test_data/example.org.zone.js | 14 +- commands/test_data/example.org.zone.tsv | 90 +++++----- commands/test_data/example.org.zone.zone | 14 +- commands/test_data/simple.com.zone.tsv | 8 +- documentation/opinions.md | 32 +++- integrationTest/integration_test.go | 97 ++++++---- models/dnsrr.go | 20 ++- models/quotes.go | 12 +- models/quotes_test.go | 29 ++- models/record.go | 54 ++---- models/record_test.go | 4 - models/t_parse.go | 148 ++++++++++++--- models/t_txt.go | 169 +++--------------- models/target.go | 49 ++--- pkg/diff/diff.go | 9 +- pkg/diff/diff2compat.go | 2 + pkg/diff2/handsoff_test.go | 2 +- pkg/js/helpers.js | 6 +- pkg/js/parse_tests/016-backslash.js | 13 -- pkg/js/parse_tests/016-backslash.json | 22 --- pkg/js/parse_tests/016-backslash/foo.com.zone | 2 - pkg/js/parse_tests/017-txt.json | 28 +-- pkg/js/parse_tests/017-txt/foo.com.zone | 4 +- pkg/js/parse_tests/018-dkim.json | 5 +- pkg/js/parse_tests/018-dkim/foo.com.zone | 2 +- pkg/normalize/flatten.go | 2 +- pkg/normalize/validate.go | 2 +- pkg/normalize/validate_test.go | 4 +- pkg/prettyzone/prettyzone.go | 3 +- pkg/prettyzone/prettyzone_test.go | 41 +++++ pkg/rejectif/txt.go | 89 +++------ pkg/txtutil/txtcode.go | 155 ++++++++++++++++ pkg/txtutil/txtcode_test.go | 97 ++++++++++ pkg/txtutil/txtcombined.go | 53 ++++++ pkg/txtutil/txtutil.go | 21 +-- providers/azuredns/auditrecords.go | 2 + providers/bind/bindProvider.go | 2 +- providers/cloudflare/auditrecords.go | 2 - providers/cloudns/auditrecords.go | 2 - providers/cscglobal/api.go | 12 +- providers/cscglobal/auditrecords.go | 6 +- providers/cscglobal/convert.go | 2 + providers/cscglobal/dns.go | 1 - providers/digitalocean/auditrecords.go | 4 + providers/dnsimple/auditrecords.go | 2 +- providers/gandiv5/convert.go | 9 +- providers/gandiv5/gandi_v5Provider.go | 5 +- providers/gcloud/gcloudProvider.go | 10 +- providers/hedns/hednsProvider.go | 6 +- providers/hetzner/types.go | 13 +- providers/hexonet/auditrecords.go | 2 + providers/hexonet/records.go | 37 +--- providers/hexonet/records_test.go | 51 ------ providers/inwx/inwxProvider.go | 6 +- providers/msdns/auditrecords.go | 8 +- providers/namedotcom/auditrecords.go | 6 +- providers/namedotcom/records.go | 4 +- providers/namedotcom/records_test.go | 14 +- providers/ns1/auditrecords.go | 2 +- providers/ns1/ns1Provider.go | 2 +- providers/powerdns/convert_test.go | 3 +- providers/route53/route53Provider.go | 11 +- providers/rwth/auditrecords.go | 2 - providers/transip/auditrecords.go | 8 +- providers/transip/slashes.go | 11 -- providers/transip/slashes_test.go | 25 --- providers/transip/transipProvider.go | 22 +-- providers/vultr/auditrecords.go | 2 - 71 files changed, 877 insertions(+), 742 deletions(-) delete mode 100644 pkg/js/parse_tests/016-backslash.js delete mode 100644 pkg/js/parse_tests/016-backslash.json delete mode 100644 pkg/js/parse_tests/016-backslash/foo.com.zone create mode 100644 pkg/txtutil/txtcode.go create mode 100644 pkg/txtutil/txtcode_test.go create mode 100644 pkg/txtutil/txtcombined.go delete mode 100644 providers/hexonet/records_test.go delete mode 100644 providers/transip/slashes.go delete mode 100644 providers/transip/slashes_test.go diff --git a/commands/getZones.go b/commands/getZones.go index 97bba18168..1d6a0e8ec2 100644 --- a/commands/getZones.go +++ b/commands/getZones.go @@ -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. diff --git a/commands/previewPush.go b/commands/previewPush.go index dfb1592ab9..7ac4cce809 100644 --- a/commands/previewPush.go +++ b/commands/previewPush.go @@ -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"` diff --git a/commands/test_data/example.org.zone.djs b/commands/test_data/example.org.zone.djs index e4b695a3ff..1464580fa7 100644 --- a/commands/test_data/example.org.zone.djs +++ b/commands/test_data/example.org.zone.djs @@ -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") diff --git a/commands/test_data/example.org.zone.js b/commands/test_data/example.org.zone.js index 70f253630d..95f7d4a46a 100644 --- a/commands/test_data/example.org.zone.js +++ b/commands/test_data/example.org.zone.js @@ -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"), diff --git a/commands/test_data/example.org.zone.tsv b/commands/test_data/example.org.zone.tsv index ca15d2fc8a..abae4c9693 100644 --- a/commands/test_data/example.org.zone.tsv +++ b/commands/test_data/example.org.zone.tsv @@ -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. diff --git a/commands/test_data/example.org.zone.zone b/commands/test_data/example.org.zone.zone index 0c48481199..384b8979d6 100644 --- a/commands/test_data/example.org.zone.zone +++ b/commands/test_data/example.org.zone.zone @@ -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" diff --git a/commands/test_data/simple.com.zone.tsv b/commands/test_data/simple.com.zone.tsv index 7e478f4342..b9e95d03d8 100644 --- a/commands/test_data/simple.com.zone.tsv +++ b/commands/test_data/simple.com.zone.tsv @@ -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. diff --git a/documentation/opinions.md b/documentation/opinions.md index 71256a2ef8..1d7e6f47d8 100644 --- a/documentation/opinions.md +++ b/documentation/opinions.md @@ -127,7 +127,7 @@ else is ambiguous and therefore an error. # Opinion #7 Hostnames don't have underscores DNSControl prints warnings if a hostname includes an underscore -(`_`) because underscores are not permitted in hostnames. +(`_`) because underscores are not permitted in hostnames. We want to prevent a naive user from including an underscore when they meant to use a hyphen (`-`). @@ -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. diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go index 52398cb184..2c1725c270 100644 --- a/integrationTest/integration_test.go +++ b/integrationTest/integration_test.go @@ -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(). - - //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))), + // of record. + + // 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 ")), + + // Vultr syntax-checks TXT records with SPF contents. + tc("Create a TXT/SPF", txt("foo", "v=spf1 ip4:99.99.99.99 -all")), - // TODO(tlim): Re-add this when we fix the RFC1035 escaped-quotes issue. - //tc("Create TXT with frequently escaped characters", txt("fooex", `!^.*$@#%^&()([][{}{<> 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() diff --git a/models/target.go b/models/target.go index 55a843c937..d59c598ac2 100644 --- a/models/target.go +++ b/models/target.go @@ -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. diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index 031b9a720f..f2b18b8b1e 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -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 { diff --git a/pkg/diff/diff2compat.go b/pkg/diff/diff2compat.go index 78cb75a684..02ccae5dfb 100644 --- a/pkg/diff/diff2compat.go +++ b/pkg/diff/diff2compat.go @@ -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}) diff --git a/pkg/diff2/handsoff_test.go b/pkg/diff2/handsoff_test.go index 7f89fb82a3..4b28637397 100644 --- a/pkg/diff2/handsoff_test.go +++ b/pkg/diff2/handsoff_test.go @@ -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 } diff --git a/pkg/js/helpers.js b/pkg/js/helpers.js index ac817adc53..f52cea0a1d 100644 --- a/pkg/js/helpers.js +++ b/pkg/js/helpers.js @@ -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(''); } }, }); diff --git a/pkg/js/parse_tests/016-backslash.js b/pkg/js/parse_tests/016-backslash.js deleted file mode 100644 index 4e88f7167c..0000000000 --- a/pkg/js/parse_tests/016-backslash.js +++ /dev/null @@ -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)) -); diff --git a/pkg/js/parse_tests/016-backslash.json b/pkg/js/parse_tests/016-backslash.json deleted file mode 100644 index 2b90a7ae30..0000000000 --- a/pkg/js/parse_tests/016-backslash.json +++ /dev/null @@ -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" - ] - } - ] - } - ] -} diff --git a/pkg/js/parse_tests/016-backslash/foo.com.zone b/pkg/js/parse_tests/016-backslash/foo.com.zone deleted file mode 100644 index 3271869c3e..0000000000 --- a/pkg/js/parse_tests/016-backslash/foo.com.zone +++ /dev/null @@ -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" diff --git a/pkg/js/parse_tests/017-txt.json b/pkg/js/parse_tests/017-txt.json index fd509360cb..1a14e45786 100644 --- a/pkg/js/parse_tests/017-txt.json +++ b/pkg/js/parse_tests/017-txt.json @@ -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" } ] } diff --git a/pkg/js/parse_tests/017-txt/foo.com.zone b/pkg/js/parse_tests/017-txt/foo.com.zone index 9fe37d9887..a31a9e3ce4 100644 --- a/pkg/js/parse_tests/017-txt/foo.com.zone +++ b/pkg/js/parse_tests/017-txt/foo.com.zone @@ -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" diff --git a/pkg/js/parse_tests/018-dkim.json b/pkg/js/parse_tests/018-dkim.json index c7bd2aff82..3f7dd6a126 100644 --- a/pkg/js/parse_tests/018-dkim.json +++ b/pkg/js/parse_tests/018-dkim.json @@ -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;" } ] } diff --git a/pkg/js/parse_tests/018-dkim/foo.com.zone b/pkg/js/parse_tests/018-dkim/foo.com.zone index fb1f901012..f8df5c84f5 100644 --- a/pkg/js/parse_tests/018-dkim/foo.com.zone +++ b/pkg/js/parse_tests/018-dkim/foo.com.zone @@ -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;" diff --git a/pkg/normalize/flatten.go b/pkg/normalize/flatten.go index eef90aacb5..48d848e215 100644 --- a/pkg/normalize/flatten.go +++ b/pkg/normalize/flatten.go @@ -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") diff --git a/pkg/normalize/validate.go b/pkg/normalize/validate.go index 243b8efc3a..057fe6f659 100644 --- a/pkg/normalize/validate.go +++ b/pkg/normalize/validate.go @@ -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)) } diff --git a/pkg/normalize/validate_test.go b/pkg/normalize/validate_test.go index 5a6d3dc7f3..dd6a51a0e8 100644 --- a/pkg/normalize/validate_test.go +++ b/pkg/normalize/validate_test.go @@ -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 { diff --git a/pkg/prettyzone/prettyzone.go b/pkg/prettyzone/prettyzone.go index ae8a485b4c..481fb8ae79 100644 --- a/pkg/prettyzone/prettyzone.go +++ b/pkg/prettyzone/prettyzone.go @@ -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 := "" diff --git a/pkg/prettyzone/prettyzone_test.go b/pkg/prettyzone/prettyzone_test.go index 38070d9e34..ee24e551d0 100644 --- a/pkg/prettyzone/prettyzone_test.go +++ b/pkg/prettyzone/prettyzone_test.go @@ -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 { diff --git a/pkg/rejectif/txt.go b/pkg/rejectif/txt.go index b1d63c0ba2..f9b8427e94 100644 --- a/pkg/rejectif/txt.go +++ b/pkg/rejectif/txt.go @@ -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, "`") { - 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") - } + if strings.Contains(rc.GetTargetTXTJoined(), "`") { + return fmt.Errorf("txtstring contains backtick") } 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, `"`) { - return fmt.Errorf("txtstring contains doublequotes") - } + 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()) - } +// 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 } -// 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") +// TxtHasTrailingSpace audits TXT records for strings that end with space. +func TxtHasTrailingSpace(rc *models.RecordConfig) error { + txt := rc.GetTargetTXTJoined() + if txt != "" && txt[ultimate(txt)] == ' ' { + return fmt.Errorf("txtstring ends with space") } return nil } -// TxtHasTrailingSpace audits TXT records for strings that end with space. -func TxtHasTrailingSpace(rc *models.RecordConfig) error { - for _, txt := range rc.TxtStrings { - if txt != "" && txt[ultimate(txt)] == ' ' { - return fmt.Errorf("txtstring ends with space") - } +// TxtHasUnpairedDoubleQuotes audits TXT records for strings that contain unpaired doublequotes. +func TxtHasUnpairedDoubleQuotes(rc *models.RecordConfig) error { + 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 { - // 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") - } + if len(rc.GetTargetTXTJoined()) == 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 { - return fmt.Errorf("txtstring contains unpaired doublequotes") - } +// 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 } diff --git a/pkg/txtutil/txtcode.go b/pkg/txtutil/txtcode.go new file mode 100644 index 0000000000..6a50bdb605 --- /dev/null +++ b/pkg/txtutil/txtcode.go @@ -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 +} diff --git a/pkg/txtutil/txtcode_test.go b/pkg/txtutil/txtcode_test.go new file mode 100644 index 0000000000..d8375ba40d --- /dev/null +++ b/pkg/txtutil/txtcode_test.go @@ -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) + } + } +} diff --git a/pkg/txtutil/txtcombined.go b/pkg/txtutil/txtcombined.go new file mode 100644 index 0000000000..8fa415a169 --- /dev/null +++ b/pkg/txtutil/txtcombined.go @@ -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):] +// } diff --git a/pkg/txtutil/txtutil.go b/pkg/txtutil/txtutil.go index a37948c741..55045b8c29 100644 --- a/pkg/txtutil/txtutil.go +++ b/pkg/txtutil/txtutil.go @@ -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. diff --git a/providers/azuredns/auditrecords.go b/providers/azuredns/auditrecords.go index 13d73026d4..6c46e4357b 100644 --- a/providers/azuredns/auditrecords.go +++ b/providers/azuredns/auditrecords.go @@ -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) } diff --git a/providers/bind/bindProvider.go b/providers/bind/bindProvider.go index 91fbff0629..16a502553a 100644 --- a/providers/bind/bindProvider.go +++ b/providers/bind/bindProvider.go @@ -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 } diff --git a/providers/cloudflare/auditrecords.go b/providers/cloudflare/auditrecords.go index 71a66a06ed..66cf53d9d0 100644 --- a/providers/cloudflare/auditrecords.go +++ b/providers/cloudflare/auditrecords.go @@ -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 diff --git a/providers/cloudns/auditrecords.go b/providers/cloudns/auditrecords.go index 20d7b1d1d9..3c285db8e1 100644 --- a/providers/cloudns/auditrecords.go +++ b/providers/cloudns/auditrecords.go @@ -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) diff --git a/providers/cscglobal/api.go b/providers/cscglobal/api.go index dd8224013b..6ae57f5c61 100644 --- a/providers/cscglobal/api.go +++ b/providers/cscglobal/api.go @@ -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 { diff --git a/providers/cscglobal/auditrecords.go b/providers/cscglobal/auditrecords.go index 1ee9faf8c6..87ad6f3380 100644 --- a/providers/cscglobal/auditrecords.go +++ b/providers/cscglobal/auditrecords.go @@ -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) } diff --git a/providers/cscglobal/convert.go b/providers/cscglobal/convert.go index 4ffab7c17e..6050cd8ec2 100644 --- a/providers/cscglobal/convert.go +++ b/providers/cscglobal/convert.go @@ -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 } diff --git a/providers/cscglobal/dns.go b/providers/cscglobal/dns.go index e78432a3bb..a9724f66d6 100644 --- a/providers/cscglobal/dns.go +++ b/providers/cscglobal/dns.go @@ -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 { diff --git a/providers/digitalocean/auditrecords.go b/providers/digitalocean/auditrecords.go index 95382cc924..a6e9eff829 100644 --- a/providers/digitalocean/auditrecords.go +++ b/providers/digitalocean/auditrecords.go @@ -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) } diff --git a/providers/dnsimple/auditrecords.go b/providers/dnsimple/auditrecords.go index 0354a63eb1..3e8df5b634 100644 --- a/providers/dnsimple/auditrecords.go +++ b/providers/dnsimple/auditrecords.go @@ -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 diff --git a/providers/gandiv5/convert.go b/providers/gandiv5/convert.go index 229ac0c3f1..46e3259325 100644 --- a/providers/gandiv5/convert.go +++ b/providers/gandiv5/convert.go @@ -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) diff --git a/providers/gandiv5/gandi_v5Provider.go b/providers/gandiv5/gandi_v5Provider.go index d6fb5409fa..0aedb7aed3 100644 --- a/providers/gandiv5/gandi_v5Provider.go +++ b/providers/gandiv5/gandi_v5Provider.go @@ -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()) } } diff --git a/providers/gcloud/gcloudProvider.go b/providers/gcloud/gcloudProvider.go index d0c9e47dfa..6652c2691c 100644 --- a/providers/gcloud/gcloudProvider.go +++ b/providers/gcloud/gcloudProvider.go @@ -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) } diff --git a/providers/hedns/hednsProvider.go b/providers/hedns/hednsProvider.go index 0ee6d14855..d7d70fd97c 100644 --- a/providers/hedns/hednsProvider.go +++ b/providers/hedns/hednsProvider.go @@ -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) diff --git a/providers/hetzner/types.go b/providers/hetzner/types.go index 20dacdfb1e..61125a5d99 100644 --- a/providers/hetzner/types.go +++ b/providers/hetzner/types.go @@ -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) } diff --git a/providers/hexonet/auditrecords.go b/providers/hexonet/auditrecords.go index 371bd49373..8a7f7021b8 100644 --- a/providers/hexonet/auditrecords.go +++ b/providers/hexonet/auditrecords.go @@ -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) diff --git a/providers/hexonet/records.go b/providers/hexonet/records.go index ac313f714f..2ef35aaf70 100644 --- a/providers/hexonet/records.go +++ b/providers/hexonet/records.go @@ -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} -} diff --git a/providers/hexonet/records_test.go b/providers/hexonet/records_test.go deleted file mode 100644 index 8b242931a3..0000000000 --- a/providers/hexonet/records_test.go +++ /dev/null @@ -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, "`, `")) - } - } - } - } -} diff --git a/providers/inwx/inwxProvider.go b/providers/inwx/inwxProvider.go index a2de4cb037..3ac148aedb 100644 --- a/providers/inwx/inwxProvider.go +++ b/providers/inwx/inwxProvider.go @@ -216,10 +216,8 @@ 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, "`") { - return fmt.Errorf("INWX TXT records do not support single-quotes in their target") - } + if strings.ContainsAny(r.GetTargetTXTJoined(), "`") { + return fmt.Errorf("INWX TXT records do not support single-quotes in their target") } } } diff --git a/providers/msdns/auditrecords.go b/providers/msdns/auditrecords.go index f031369f66..8694ffcae9 100644 --- a/providers/msdns/auditrecords.go +++ b/providers/msdns/auditrecords.go @@ -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) } diff --git a/providers/namedotcom/auditrecords.go b/providers/namedotcom/auditrecords.go index 2d9a64c847..8e7d0f329d 100644 --- a/providers/namedotcom/auditrecords.go +++ b/providers/namedotcom/auditrecords.go @@ -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) } diff --git a/providers/namedotcom/records.go b/providers/namedotcom/records.go index af58fde7b1..241d023b6f 100644 --- a/providers/namedotcom/records.go +++ b/providers/namedotcom/records.go @@ -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] == '"' { diff --git a/providers/namedotcom/records_test.go b/providers/namedotcom/records_test.go index 0424178fd8..033d1f5435 100644 --- a/providers/namedotcom/records_test.go +++ b/providers/namedotcom/records_test.go @@ -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 { diff --git a/providers/ns1/auditrecords.go b/providers/ns1/auditrecords.go index d30fc86109..60a0827930 100644 --- a/providers/ns1/auditrecords.go +++ b/providers/ns1/auditrecords.go @@ -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) } diff --git a/providers/ns1/ns1Provider.go b/providers/ns1/ns1Provider.go index a92db2860d..78c466252e 100644 --- a/providers/ns1/ns1Provider.go +++ b/providers/ns1/ns1Provider.go @@ -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{ diff --git a/providers/powerdns/convert_test.go b/providers/powerdns/convert_test.go index a88fd29109..49779a933c 100644 --- a/providers/powerdns/convert_test.go +++ b/providers/powerdns/convert_test.go @@ -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) } diff --git a/providers/route53/route53Provider.go b/providers/route53/route53Provider.go index b1458d370c..b9af6c1289 100644 --- a/providers/route53/route53Provider.go +++ b/providers/route53/route53Provider.go @@ -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) } diff --git a/providers/rwth/auditrecords.go b/providers/rwth/auditrecords.go index fee9c390cc..15d5116775 100644 --- a/providers/rwth/auditrecords.go +++ b/providers/rwth/auditrecords.go @@ -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) diff --git a/providers/transip/auditrecords.go b/providers/transip/auditrecords.go index fd6729c449..8004671b14 100644 --- a/providers/transip/auditrecords.go +++ b/providers/transip/auditrecords.go @@ -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) } diff --git a/providers/transip/slashes.go b/providers/transip/slashes.go deleted file mode 100644 index 1f322da32c..0000000000 --- a/providers/transip/slashes.go +++ /dev/null @@ -1,11 +0,0 @@ -package transip - -import ( - "regexp" -) - -var removeSlashesRegexp = regexp.MustCompile(`(?:\\(\\)+)|(?:\\)`) - -func removeSlashes(s string) string { - return removeSlashesRegexp.ReplaceAllString(s, "$1") -} diff --git a/providers/transip/slashes_test.go b/providers/transip/slashes_test.go deleted file mode 100644 index e17778032b..0000000000 --- a/providers/transip/slashes_test.go +++ /dev/null @@ -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) - } - } - -} diff --git a/providers/transip/transipProvider.go b/providers/transip/transipProvider.go index f273c9241b..021d0eb8f1 100644 --- a/providers/transip/transipProvider.go +++ b/providers/transip/transipProvider.go @@ -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()) - } -} diff --git a/providers/vultr/auditrecords.go b/providers/vultr/auditrecords.go index e6aed47590..72653d0e59 100644 --- a/providers/vultr/auditrecords.go +++ b/providers/vultr/auditrecords.go @@ -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)