From de88bfe8b7dab8fc7fd2b37cc7353a9d98bb3c91 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Thu, 4 Jan 2018 19:19:35 -0500 Subject: [PATCH] Add support for TXT records with multiple strings (BIND, ROUTE53) (#293) * BIND: Support TXT records with multiple strings (#289) * ROUTE53: Add support for TXT records with multiple strings (#292) --- docs/_functions/domain/TXT.md | 13 +- integrationTest/integration_test.go | 45 ++++- integrationTest/zones/example.com.zone | 2 +- models/dns.go | 21 ++- models/txt.go | 57 +++++++ models/txt_test.go | 71 ++++++++ pkg/js/helpers.js | 26 ++- pkg/js/parse_tests/016-backslash.json | 5 +- pkg/js/parse_tests/017-txt.js | 6 + pkg/js/parse_tests/017-txt.json | 37 +++++ pkg/js/static.go | 155 +++++++++--------- pkg/normalize/flatten.go | 6 +- pkg/normalize/validate.go | 14 +- providers/activedir/activedirProvider.go | 14 +- providers/activedir/domains.go | 2 +- providers/bind/bindProvider.go | 21 +-- providers/capabilities.go | 10 +- providers/cloudflare/cloudflareProvider.go | 14 +- .../digitalocean/digitaloceanProvider.go | 7 +- providers/dnsimple/dnsimpleProvider.go | 16 +- providers/gandi/gandiProvider.go | 16 +- providers/gandi/protocol.go | 4 +- providers/gcloud/gcloudProvider.go | 11 +- providers/linode/linodeProvider.go | 11 +- providers/namecheap/namecheapProvider.go | 15 +- providers/namedotcom/namedotcomProvider.go | 12 +- providers/namedotcom/records.go | 2 +- providers/ns1/ns1provider.go | 2 +- providers/ovh/ovhProvider.go | 20 ++- providers/route53/route53Provider.go | 16 +- providers/softlayer/softlayerProvider.go | 8 +- providers/vultr/vultrProvider.go | 14 +- 32 files changed, 489 insertions(+), 184 deletions(-) create mode 100644 models/txt.go create mode 100644 models/txt_test.go create mode 100644 pkg/js/parse_tests/017-txt.js create mode 100644 pkg/js/parse_tests/017-txt.json diff --git a/docs/_functions/domain/TXT.md b/docs/_functions/domain/TXT.md index 1fcf8f2db..e4d0314a7 100644 --- a/docs/_functions/domain/TXT.md +++ b/docs/_functions/domain/TXT.md @@ -9,10 +9,14 @@ parameters: TXT adds an TXT record To a domain. The name should be the relative label for the record. Use `@` for the domain apex. -The contents is a single string. While DNS permits multiple -strings in TXT records, that is not supported at this time. +The contents is either a single or multiple strings. To +specify multiple strings, include them in an array. -The string is a JavaScript string (quoted using single or double +TXT records with multiple strings are only supported by some +providers. DNSControl will produce a validation error if the +provider does not support multiple strings. + +Each string is a JavaScript string (quoted using single or double quotes). The (somewhat complex) quoting rules of the DNS protocol will be done for you. @@ -24,6 +28,9 @@ Modifers can be any number of [record modifiers](#record-modifiers) or json obje D("example.com", REGISTRAR, ...., TXT('@', '598611146-3338560'), TXT('listserve', 'google-site-verification=12345'), + TXT('multiple', ['one', 'two', 'three']), // Multiple strings + TXT('quoted', 'any "quotes" and escapes? ugh; no worries!'), + TXT('_domainkey', 't=y; o=-;') // Escapes are done for you automatically. ); {%endhighlight%} diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go index 47ea2e427..7faefcf13 100644 --- a/integrationTest/integration_test.go +++ b/integrationTest/integration_test.go @@ -112,7 +112,7 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string, } dom.Records = append(dom.Records, &rc) } - models.Downcase(dom.Records) + models.PostProcessRecords(dom.Records) dom2, _ := dom.Copy() // get corrections for first time corrections, err := prv.GetDomainCorrections(dom) @@ -237,7 +237,17 @@ func srv(name string, priority, weight, port uint16, target string) *rec { } func txt(name, target string) *rec { - return makeRec(name, target, "TXT") + // FYI: This must match the algorithm in pkg/js/helpers.js TXT. + r := makeRec(name, target, "TXT") + r.TxtStrings = []string{target} + return r +} + +func txtmulti(name string, target []string) *rec { + // FYI: This must match the algorithm in pkg/js/helpers.js TXT. + r := makeRec(name, target[0], "TXT") + r.TxtStrings = target + return r } func caa(name string, tag string, flag uint8, target string) *rec { @@ -427,15 +437,42 @@ func makeTests(t *testing.T) []*TestCase { ) } - // Case + // TXT (single) tests = append(tests, tc("Empty"), - // TXT tc("Empty"), tc("Create a TXT", txt("foo", "simple")), tc("Change a TXT", txt("foo", "changed")), + tc("Empty"), tc("Create a TXT with spaces", txt("foo", "with spaces")), tc("Change a TXT with spaces", txt("foo", "with whitespace")), + tc("Create 1 TXT as array", txtmulti("foo", []string{"simple"})), ) + // TXTMulti + if !providers.ProviderHasCabability(*providerToRun, providers.CanUseTXTMulti) { + t.Log("Skipping TXTMulti Tests because provider does not support them") + } else { + tests = append(tests, + tc("Empty"), + tc("Create TXTMulti 1", + txtmulti("foo1", []string{"simple"}), + ), + tc("Create TXTMulti 2", + txtmulti("foo1", []string{"simple"}), + txtmulti("foo2", []string{"one", "two"}), + ), + tc("Create TXTMulti 3", + txtmulti("foo1", []string{"simple"}), + txtmulti("foo2", []string{"one", "two"}), + txtmulti("foo3", []string{"eh", "bee", "cee"}), + ), + tc("Change TXTMulti", + txtmulti("foo1", []string{"dimple"}), + txtmulti("foo2", []string{"fun", "two"}), + txtmulti("foo3", []string{"eh", "bzz", "cee"}), + ), + ) + } + return tests } diff --git a/integrationTest/zones/example.com.zone b/integrationTest/zones/example.com.zone index bb7ab6ea8..7cf305dbd 100644 --- a/integrationTest/zones/example.com.zone +++ b/integrationTest/zones/example.com.zone @@ -1,4 +1,4 @@ $TTL 300 -@ IN SOA DEFAULT_NOT_SET. DEFAULT_NOT_SET. 2017091830 3600 600 604800 1440 +@ IN SOA DEFAULT_NOT_SET. DEFAULT_NOT_SET. 2018010281 3600 600 604800 1440 IN NS ns1.otherdomain.tld. IN NS ns2.otherdomain.tld. diff --git a/models/dns.go b/models/dns.go index c4f9e6c4d..f224c6706 100644 --- a/models/dns.go +++ b/models/dns.go @@ -101,6 +101,7 @@ type RecordConfig struct { TlsaUsage uint8 `json:"tlsausage,omitempty"` TlsaSelector uint8 `json:"tlsaselector,omitempty"` TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"` + TxtStrings []string `json:"txtstrings,omitempty"` // TxtStrings stores all strings (including the first). Target stores only the first one. CombinedTarget bool `json:"-"` @@ -247,7 +248,7 @@ func (rc *RecordConfig) ToRR() dns.RR { rr.(*dns.TLSA).Selector = rc.TlsaSelector rr.(*dns.TLSA).Certificate = rc.Target case dns.TypeTXT: - rr.(*dns.TXT).Txt = []string{rc.Target} + rr.(*dns.TXT).Txt = rc.TxtStrings default: panic(fmt.Sprintf("ToRR: Unimplemented rtype %v", rc.Type)) // We panic so that we quickly find any switch statements @@ -275,6 +276,12 @@ func (r Records) Grouped() map[RecordKey]Records { return groups } +// PostProcessRecords does any post-processing of the downloaded DNS records. +func PostProcessRecords(recs []*RecordConfig) { + Downcase(recs) + fixTxt(recs) +} + // Downcase converts all labels and targets to lowercase in a list of RecordConfig. func Downcase(recs []*RecordConfig) { for _, r := range recs { @@ -292,6 +299,17 @@ func Downcase(recs []*RecordConfig) { return } +// fixTxt fixes TXT records generated by providers that do not understand CanUseTXTMulti. +func fixTxt(recs []*RecordConfig) { + for _, r := range recs { + if r.Type == "TXT" { + if len(r.TxtStrings) == 0 { + r.TxtStrings = []string{r.Target} + } + } + } +} + type RecordKey struct { Name string Type string @@ -453,7 +471,6 @@ func (dc *DomainConfig) CombineCAAs() { panic(pm) } rec.Target = rec.Content() - fmt.Printf("DEBUG: NEW TARGET: %v\n", rec.Target) rec.CombinedTarget = true } } diff --git a/models/txt.go b/models/txt.go new file mode 100644 index 000000000..db924c07e --- /dev/null +++ b/models/txt.go @@ -0,0 +1,57 @@ +package models + +import "strings" + +// SetTxt sets the value of a TXT record to s. +func (rc *RecordConfig) SetTxt(s string) { + rc.Target = s + rc.TxtStrings = []string{s} +} + +// SetTxts sets the value of a TXT record to the list of strings s. +func (rc *RecordConfig) SetTxts(s []string) { + rc.Target = s[0] + rc.TxtStrings = s +} + +// SetTxtParse sets the value of TXT record if the list of strings is combined into one string. +// `foo` -> []string{"foo"} +// `"foo"` -> []string{"foo"} +// `"foo" "bar"` -> []string{"foo" "bar"} +func (rc *RecordConfig) SetTxtParse(s string) { + rc.SetTxts(ParseQuotedTxt(s)) +} + +// IsQuoted returns true if the string starts and ends with a double quote. +func IsQuoted(s string) bool { + if s == "" { + return false + } + if len(s) < 2 { + return false + } + if s[0] == '"' && s[len(s)-1] == s[0] { + return true + } + return false +} + +// StripQuotes returns the string with the starting and ending quotes removed. +func StripQuotes(s string) string { + if IsQuoted(s) { + return s[1 : len(s)-1] + } + return s +} + +// ParseQuotedTxt returns the individual strings of a combined quoted string. +// `foo` -> []string{"foo"} +// `"foo"` -> []string{"foo"} +// `"foo" "bar"` -> []string{"foo" "bar"} +// NOTE: it is assumed there is exactly one space between the quotes. +func ParseQuotedTxt(s string) []string { + if !IsQuoted(s) { + return []string{s} + } + return strings.Split(StripQuotes(s), `" "`) +} diff --git a/models/txt_test.go b/models/txt_test.go new file mode 100644 index 000000000..680bc1f0e --- /dev/null +++ b/models/txt_test.go @@ -0,0 +1,71 @@ +package models + +import ( + "testing" +) + +func TestIsQuoted(t *testing.T) { + tests := []struct { + d1 string + e1 bool + }{ + {``, false}, + {`foo`, false}, + {`""`, true}, + {`"a"`, true}, + {`"bb"`, true}, + {`"ccc"`, true}, + {`"aaa" "bbb"`, true}, + } + for i, test := range tests { + r := IsQuoted(test.d1) + if r != test.e1 { + t.Errorf("%v: expected (%v) got (%v)", i, test.e1, r) + } + } +} + +func TestStripQuotes(t *testing.T) { + tests := []struct { + d1 string + e1 string + }{ + {``, ``}, + {`a`, `a`}, + {`bb`, `bb`}, + {`ccc`, `ccc`}, + {`dddd`, `dddd`}, + {`"A"`, `A`}, + {`"BB"`, `BB`}, + {`"CCC"`, `CCC`}, + {`"DDDD"`, `DDDD`}, + {`"EEEEE"`, `EEEEE`}, + {`"aaa" "bbb"`, `aaa" "bbb`}, + } + for i, test := range tests { + r := StripQuotes(test.d1) + if r != test.e1 { + t.Errorf("%v: expected (%v) got (%v)", i, test.e1, r) + } + } +} + +func TestSetTxtParse(t *testing.T) { + tests := []struct { + d1 string + e1 string + e2 []string + }{ + {``, ``, []string{``}}, + {`foo`, `foo`, []string{`foo`}}, + {`"foo"`, `foo`, []string{`foo`}}, + {`"aaa" "bbb"`, `aaa`, []string{`aaa`, `bbb`}}, + } + for i, test := range tests { + x := &RecordConfig{Type: "TXT"} + x.SetTxtParse(test.d1) + if x.Target != test.e1 { + t.Errorf("%v: expected Target=(%v) got (%v)", i, test.e1, x.Target) + } + } +} diff --git a/pkg/js/helpers.js b/pkg/js/helpers.js index d9f1b4673..1f041d942 100644 --- a/pkg/js/helpers.js +++ b/pkg/js/helpers.js @@ -220,8 +220,32 @@ var TLSA = recordBuilder('TLSA', { }, }); +function isStringOrArray(x) { + return _.isString(x) || _.isArray(x); +} + // TXT(name,target, recordModifiers...) -var TXT = recordBuilder('TXT'); +var TXT = recordBuilder("TXT", { + args: [["name", _.isString], ["target", isStringOrArray]], + transform: function(record, args, modifiers) { + record.name = args.name; + // Store the strings twice: + // .target is the first string + // .txtstrings is the individual strings. + // NOTE: If there are more than 1 string, providers should only access + // .txtstrings, thus it doesn't matter what we store in .target. + // However, by storing the first string there, it improves backwards + // compatibility when the len(array) == 1 and (intentionally) breaks + // broken providers early in the integration tests. + if (_.isString(args.target)) { + record.target = args.target; + record.txtstrings = [args.target]; + } else { + record.target = args.target[0] + record.txtstrings = args.target; + } + } +}); // MX(name,priority,target, recordModifiers...) var MX = recordBuilder('MX', { diff --git a/pkg/js/parse_tests/016-backslash.json b/pkg/js/parse_tests/016-backslash.json index 712b2613b..e32999721 100644 --- a/pkg/js/parse_tests/016-backslash.json +++ b/pkg/js/parse_tests/016-backslash.json @@ -11,9 +11,10 @@ "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 + "ttl": 300, + "txtstrings":["v=DMARC1\\; p=reject\\; sp=reject\\; pct=100\\; rua=mailto:xx...@yyyy.com\\; ruf=mailto:xx...@yyyy.com\\; fo=1"] } ] } ] -} \ No newline at end of file +} diff --git a/pkg/js/parse_tests/017-txt.js b/pkg/js/parse_tests/017-txt.js new file mode 100644 index 000000000..721705092 --- /dev/null +++ b/pkg/js/parse_tests/017-txt.js @@ -0,0 +1,6 @@ +D("foo.com","none" + , TXT("@","simple") + , TXT("@",["one"]) + , TXT("@",["bonie", "clyde"]) + , TXT("@",["straw", "wood", "brick"]) +); diff --git a/pkg/js/parse_tests/017-txt.json b/pkg/js/parse_tests/017-txt.json new file mode 100644 index 000000000..e6c9e3fbd --- /dev/null +++ b/pkg/js/parse_tests/017-txt.json @@ -0,0 +1,37 @@ +{ + "registrars": [], + "dns_providers": [], + "domains": [ + { + "name": "foo.com", + "registrar": "none", + "dnsProviders": {}, + "records": [ + { + "type": "TXT", + "name": "@", + "target": "simple", + "txtstrings": [ "simple" ] + }, + { + "type": "TXT", + "name": "@", + "target": "one", + "txtstrings": [ "one" ] + }, + { + "type": "TXT", + "name": "@", + "target": "bonie", + "txtstrings": [ "bonie", "clyde" ] + }, + { + "type": "TXT", + "name": "@", + "target": "straw", + "txtstrings": [ "straw", "wood", "brick" ] + } + ] + } + ] +} diff --git a/pkg/js/static.go b/pkg/js/static.go index 3c08100bd..37c46b6b6 100644 --- a/pkg/js/static.go +++ b/pkg/js/static.go @@ -192,83 +192,88 @@ var _escData = map[string]*_escFile{ "/helpers.js": { local: "pkg/js/helpers.js", - size: 16222, + size: 17122, modtime: 0, compressed: ` -H4sIAAAAAAAC/+w7a3PbOJLf9St6UrdDMZYp2Zlkt+RobzR+TLnOr5LlrLd0OhUsQhISiuQBoDS+jPPb -r/AiAT4kz9TtzJfzh0QEuxuN7kZ3o9H0MoaBcUrm3DtptTaIwjyJFzCAry0AAIqXhHGKKOvDZNqRY2HM -ZilNNiTEznCyRiSuDMxitMZ69EVPEeIFyiI+pEsGA5hMT1qtRRbPOUliIDHhBEXkf3Db10w4HDVxtYOz -Wu5eThSTFVZeLGZu8HZk5mqLhXSAP6e4A2vMkWGPLKAtRn2LQ/EMgwF418Obh+GVpyZ7kf8KCVC8FCsC -QbMPBeW+Rb8v/zWMCiEExcKDNGOrNsVL/0Qrimc0lpQqSziL2Z2Wyt5FJAs160Awnzx9xnPuwfffg0fS -2TyJN5gyksTMAxI7+OJPPAcuHAxgkdA14jPO2zXv/bJgQpb+HsE4mleyCVm6TzYx3p5Ju9BiycXr5+Yv -MYslWmxVrbFf/Ow4QunD1xcbfp7QsGq6d4Xl2uDaQsfjqz70Og4nDNONY+kv7vpSmswxY2eILll73dGb -wCyu2xW6AYzmK1gnIVkQTDvCEAgHwgAFQZDDaYp9mKMoEgBbwleangFClKLnvplULDOjjGxw9GwglD0J -9dElltPEPJESChFHuR3OAsIu9Iztte+YWFuvQdsN4IjhHGkoOChhiCW2hWV9liZrvxJ/rogmn6e5lE5y -uJe6uW7lWkqTzQL8C8dxqLkMxNI6sHa5tbzEiiZb8P4xHN1c3vzc1zPnylBeJItZlqYJ5TjsgwcHDvtm -y5aGPVB2XUXQjKm9oBb30mp1u3Cm9kCxBfpwSjHiGBCc3dxrggE8MAx8hSFFFK0xx5QBYsamAcWhYJ8F -hRGeNW0uud3Vigc7tqJiM1cjgQH0ToDAR9t3BxGOl3x1AuTgwFaIo14LfkLKin6pTnOspkF0ma1xzBsn -EfBrGBSAEzI9qWdhXTursCnlxqyQGZA4xL/cLqRAfPhuMIDDI79iPeItHIAntmyI5xGiWKiACi2hGJJ4 -jp3oY81jHKXNUJUNCSN5ODGmcn4xfLga34P2uAwQMMwhWRiVFKIAngBK0+hZ/ogiWGQ8o9jE40DQOxce -SDoWnhTEtySKYB5hRAHFz5BSvCFJxmCDogwzMaFtZBorzxmqcb3Jivaq1zYzKQxbz767i8bjq/bG78M9 -5nKXjMdXclK1h9QusdhW4FYIFp7lnlMSL9sbx7NsYCDztHg5Ts4yiqRv3DhWpIOVId6mNj4NOI9gAJuT -ukBRQ9napGvE5yss5LgJ5O9297/a/xke+O0JW6/Cbfw8/Xf/37qaGbGMHGMAcRZFVavdGJONEw5I6JSE -EOrZNTuO2WYx4TAAj3mVWSbHU3sCDVm8dFIMGAjPxfBlzHP8I6NFsdhMph+sD0cdWPfhQ68Dqz68+9Dr -mYQjm3ihN4UBZMEK3sLxD/nwVg+H8Bb+mo/G1ui7Xj78bA9/eK85gLcDyCZiDVMnednkmy9PBxxDMxvP -GJwcUy7b2iU27r/I6kJn6wRF9tJofGv0BZ8OhxcRWrbl5i5lX4VBy+3jWLXaUHOEFhFawq8D5R3sabpd -OB0OZ6ejy/Hl6fBKRDXCyRxFYhgEmjyS2DDSegqejuDjR+j5J0r8Vi79xmScN2iN33Sg5wuImJ0mWSy9 -YQ/WGMUMwiT2OIijVkJ1ZMPKq1lZXGAji21hqGsiAh1Fka3OSl6v0WuSekNY5vVZHOIFiXHo2cLMQeDw -6Ldo2MpcJ4INYdaaVkkRQ8UmSTtac9c602FBEPhSD0MY6Hc/ZSQSK/OGnpb9cDh8DYXhsI7IcFjQuboc -3itCHNEl5juICdAaamLYkDs1XHG07Ej7a6Z3Wsfb6XDodYqkfHx7dtvmEVn7fbjkwFZJFoXwhAHFgClN -qNCrnMc40J6wq6Pjv6l8XSQafZhMPMGU14Fid087MPE4WlYHJTl3WB8pOEUxE2e4fnkjduRMnTxdZTU7 -U2YnMjNiVs7pbl2OlgaEo2UFQqnIQNj7WzFopr/J1k+Y1nDp+JSq12Blt9FpvRjN3gyvz19nKBK0RrVi -2BjK3Xj0OmJ341GV1N14ZAjdjz4pQiklCSX8ubPFZLniHXFM2Ev9fvSpSv1+9Cm3QW1AubxqLcl6a7jQ -EEoRDoRir/m94Lv5rVpQ3fx/jI0yujFLNHDmuQ5WLdZAqqdamgnNocTvPZavnio2qhx/xtASd4DhCM95 -Qjsq/SHxUlVN5physiBzxLE0gfHVfY0fEqO/2wgkB806NJw1Q9gc/0ZbgG7XWQrEGIvDKLxR4G/yJP8P -tBoeMSSFYqDkQy2YEY6BNM+1wLacDII99vvMaPw4fp1vGj+OayzncWx80/VjyTXtI3j9WKV3/fgvdEZ/ -tjtZ/5JSvMAUx3O815/sV16eDs5XeP5FnFLb8hczzIaYze2MEBX1EviosMxz9aAmkBsLJPoE7ZCoHJ/F -lN8pkAmZytnFublciCumk0fDw3zLggcHQOzz4jyhFM+5LH55lTKdzjVvXpnh3dSkdzd5bifC9/356NO5 -E7l9q7xeAgAN0XCEKeXOdvovSwulwrek1df/w4tfe34qCuy54c44eoqwVegdCy4mkyjZyoPtiixXfTju -QIy3PyGG+/BOpIHy9Q/m9Xv5+vKuDx+mU0NIVmzfHME3OIZv8A6+ncAP8A3ewzeAb/DhTX6OjkiM95Ve -Svzuqq+RFAZleKfMJoAkuzAAkgby54ljhHKobHZu6ViBlGHk4UiTngVrlCq4TqFWUodiXz1k6+Mw4W1i -VZVzs/WDzwmJ217HK72t1IvLzBiyiu0Scqv6S8tIaDyXknioyEkM7pWUBGqQlZ4il5Z4/lPlpRmyJCbZ -f53MhGcawCTnKg2iZOt3wBoQW8bP95PeOZZ5yu2gL+2SrV4BfAPPr6umKGgNdAJeXnq9vL67HY1n49Hw -5v7idnSttnwkCzNqU+QFZundyvBVX1eGKAfeiVeZwpNHRjWN+s155Mbb/8tI6v3o7QmLipVqoMUcafYL -pyGrboXLVGG1vEK/OqGsnipoHlXSp7uH0c/nbSsuqIHc3YfBf2CcPsRf4mQbCwZQxLBR6s3trIKfjzWS -4DTTFN6+bcFb+DHEKcUixQ9b8LZbkFpinoe9tpI644hyp8SbhI3OWgLntfLGOC+vfUx93CmNW4YtgGym -R1K66qLrSZmkXIu8XYKvqvb4ot5bsHUwScpZIKeeTnpTGJr0QViRDW/kMnBRjqZwm4pxFKlyNOIJ3YWX -2xWYu8rirsO5/jBVf3hrRDVGXzA0bAQfELPuJGAYPxebRF2KPGGLlpiQ4BCe8EJd+hCW77XAqh+tM464 -urpbkg2ObbYaRSMWY2ynZpkFXzyRlBVN1/xcf6POo4K6sR3xW4YKXSpm7a8vCqJjWdfeopbM6YXfKRLY -3+d8dKKjIJXAV2iDrcWiiGIUPhvRlzEFbaMoQLG+9ZZ7yro01RXYlhv99pwg7DisPG3bOhfUBuOywzQx -y8Z7ZRjdeySpiaOWPhxrqtFJozbqUsccuMkdOZezSQiDAkXmjRXAaudBEvpNeco6Cc11RE2GUt8psINc -twuqKYYXVis3lXJurBZJXoEloeWIvv8erDYI+1XjzHoxFhGnY8ehcVJL4aV2NO+EsGKxVHGzvOoZ1D0S -56PR7agPJvw5LRJeDclme1Q5pDaA8vmsfOyQd4WhvkX++uIeNwqPoJvYbM2Ur5XhYxFuak7bhmaOdkWY -2GM5TmWJMrUuMmqO13uSagEy6U3rMuoqcZ1iQznHVuqQ8figguUZr0nxf2eEYlZpPzEO3xZDLaEigrbr -aLhiqiHgB3AbR8+wE3kXA1tMMbBMufiShSmB2pWHlrOTo0g4/Hya1i5HVpZGrSPTlnEmYgaRUdWyDOcY -bKDV/VBTT4plpAVNI42/w1GdJYmYmMVFbiQIGPnUOtPvHOqTo6m+3fV37vQG06qYmLcDyJ24N91JL68z -6ZXJkgoiUUXru/yKbPTJfcWkzIA4c1hXTM02k7uUepupMZbXdLDY12TNPSwlrnaWroo+VqmMQY1Kra7N -yrtqU2SOxaO+0zbggryUAnc1Ta1JJ06qKHlQy8EL7bmobvdcoBvdTPttTQag5abeWZJ17sL3HNlQGKrT -Tjs0zbp2RVByyKzyHlmYGiFhIsN7wrQDiLFsjYGkghzFjAV5kkF40KrJJWvSyEre6KSMdkPz3LGCOu3X -Nc+6JU5rvNkOTK3caYd1LUoLu77DNcRzEmJ4QgyHII4zglUDf5gfc0yvK1O9rsXxRhzQxJNzpyRRb2v7 -WwWs0+MqYc119eUFXD8WlJXKpB7NOltWssdqW1vdvHhvJFmrZLg+JOxovi2acCme1x8adnbHFv7utyW7 -cu2Nae4rktx1U3q7M7mtJrZ2Ulvq7f2NYI0p7zyJWRLhIEqW7dq1FN3C141twl6nPsDqZuH6t177/gtJ -UxIvv/O9CsSeSulLq949uh34FM91zYukUHwFkMcYBguarGHFedrvdhlH8y/JBtNFlGyDebLuou7fjnrv -//pDr3t0fPThQ6/V7cKGIIPwGW0Qm1OS8gA9JRmXOBF5oog+d58ikmqzC1Z8Xfjay7t2mDjFMBHPwoQH -LI0Ib3uByYG7XUgp5pxgekiWcUKxvbi2/DsIJ72pD2/h+P0HHw5ADBxN/dLIcWXk3dQvfZtgKtXZ2r68 -i7O17OHKW7jcuqnkxPPKzcVWg5+gV4MTZ+vKpxjK68NfBJ81dcF3wuP8XTqew0OnkUzwCNeIr4JFlCRU -Mt2Vqy2syKEOB+AFHhxAWFMzDPM+vijJwkWEKAYUEcQw66srZ8xlAzIX3kPySOKQbEiYoch0pgeqS+di -dje6ffzn7PbiQvZ5znOSs5Qmvzz3wUsWCw9eToS278QQhIShpwiHZRI3jRRilwCO6/AvHq6umigssihy -aByMEImWWVzQEm8wPTSfDNgi6LcK3nVbaLJYqFAYc5J3X0Pb6hz1+y57uqO6UVIzjVdIrGbWuDpp0zQ3 -e2eRUlWG8HA/vr3uwN3o9tPl2fkI7u/OTy8vLk9hdH56OzqD8T/vzu+tzTTTuT2WJnQh6I9wSKiIUU57 -mDy32O2wlROLSYtVAb9irBIhb933Op4vt+vhkTRivfTR+dnl6Py0ppHCermjA4IlGZ3LKmjzupyWhxAz -TmJ5tnkV1h97faOWI3xAR/gAdaVTcOxetmgRjs+v73bL0YH4f2E2CvNhdFWV38PoSkQ9/f5d76gW5F3v -yEBdjGr7H+Vw3rZ4dzH76eHySuxYjr5gVlTHpctKEeWsD2P1dRFnkCxk8nx/d2ES5DZP4AnD50SEPpWY -e+D50h1G6AlHCv3s5l495r3wKSVrRJ8tWgG0C+fyoyd7tyna9uEfK0wxtLcrMl8pKr7KThMq6/lZjCKO -KQ7B5C8Wn8YHS45kAqE44nidRohj9TVIGBJ91WQ+nFLrmssvrkKbsxlLF38JFXuLCHGO4z4MISJMfXCj -vqPR+BpAxIfC+Vlir3F2ymEpef/6K1iPReHyuNoX5NnKzMt9iEOEEeNwDDjCsr5QyUX0jFqwdrk1H7YN -vYJI0baKRtFWIM0o2rJ0kaMqz6zKs7INZoVzyVmSV75bHYlTVeg10CKwWrc2wg6wDGzyVCeC6PhxXNyl -iekkC6bgo0Wpr/I9PydcWJFrNibTvFwYbZJ4KY6DQsiYcRx2YIljTNWnecXs1jEVbUtEjQgVS5quOEc5 -A0UBsOd8Q5cjDErwNX0YVKX+48dxO9dMR8ukaHWwFmkSfLFEluK58IBhR+c5ageJRZTXYNBcRiV4zqaB -Kc/6827xuSrXSi0vS9qpWVgHUr90o0BFvP/fAAAA//8yjfX5Xj8AAA== +H4sIAAAAAAAC/+w7a3PjNpLf/Ss6U7ehOOZQsieZ3ZKjvSh+5FznV8marLd0OhdMQhLGFMkDQGl8iee3 +X+FFAnzIztRt9sv6w4wINhr9Qnej0fQKhoFxSiLuHe3tbRCFKEsXMIJf9wAAKF4SximibAizeSDH4pTd +5zTbkBg7w9kakbQxcJ+iNdajz3qJGC9QkfAxXTIYwWx+tLe3KNKIkywFkhJOUEL+F/d8TYRDURdVOyhr +pe75SBHZIOXZIuYKbydmrZ5gJAD+lOMA1pgjQx5ZQE+M+haF4hlGI/Aux1cfxxeeWuxZ/iskQPFScAQC +5xAqzEML/1D+awgVQggrxsO8YKsexUv/SCuKFzSVmBosnKTsRkvlRSayhVp1JIjPHj7hiHvw7bfgkfw+ +ytINpoxkKfOApM588SeeQxcORrDI6Brxe857Le/9umBiln+NYBzNK9nELH9JNinenki70GIpxeuX5i9n +VixaZDWtcVj9DByhDOHXZxs+ymjcNN2bynJtcG2h0+nFEAaBQwnDdONY+rPLX06zCDN2guiS9daB3gSG +uX5f6AYwilawzmKyIJgGwhAIB8IAhWFYwmmMQ4hQkgiALeErjc8AIUrR09AsKtgsKCMbnDwZCGVPQn10 +ieUyKc+khGLEUWmH9yFhZ3rF3tp3TKynedB2AzhhuJw0FhTUZggWe8KyPkmTtV+JP1dEs0/zUkpHJdxz +21rXkpfaYvch/sxxGmsqQ8FaAGuXWstLrGi2Be9v48nV+dXPQ71yqQzlRYqUFXmeUY7jIXiw75Bvtmxt +2ANl180JmjC1FxRzz3t7/T6cqD1QbYEhHFOMOAYEJ1e3GmEIHxkGvsKQI4rWmGPKADFj04DSWJDPwsoI +T7o2l9zuiuPRjq2oyCzVSGAEgyMg8IPtu8MEp0u+OgKyv28rxFGvBT8jdUU/N5c5VMsguizWOOWdiwj4 +NYwqwBmZH7WTsG5dVdiUcmNWyAxJGuPP1wspEB++GY3g3YHfsB7xFvbBE1s2xlGCKBYqoEJLKIUsjbAT +fax1jKO0CWqSIWEkDUfGVE7Pxh8vpregPS4DBAxzyBZGJZUogGeA8jx5kj+SBBYFLyg28TgU+E6FB5KO +hWcV8i1JEogSjCig9AlyijckKxhsUFJgJha0jUzPKnOGZlzvsqIX1WubmRSGrWff3UXT6UVv4w/hFnO5 +S6bTC7mo2kNql1hkK3ArBAvPcsspSZe9jeNZNjCSeVq6nGYnBUXSN24cK9LByiDvUXs+DTlPYASbo7ZA +0YLZ2qRrxKMVFnLchPJ3r//fvf+K9/3ejK1X8TZ9mv+7/299TYxgo5wxgrRIkqbVbozJphkHJHRKYoj1 +6pocx2yLlHAYgce8xiqzw7m9gIasXjopBoyE52L4POXl/AOjRcFsIdMPNoSDANZD+DAIYDWE9x8GA5Nw +FDMv9uYwgiJcwVs4/K4c3urhGN7Cn8vR1Bp9PyiHn+zhD99rCuDtCIqZ4GHuJC+bcvOV6YBjaGbjGYOT +Y8plW7vEnvsPsrrY2Tphlb10Gt8aPeLj8fgsQcue3Ny17KsyaLl9HKtWGypCaJGgJfw2Ut7BXqbfh+Px ++P54cj49Px5fiKhGOIlQIoZBTJNHEhtGWk9F0wH88AMM/CMlfiuXfmMyziu0xm8CGPgCImXHWZFKbziA +NUYpgzhLPQ7iqJVRHdmw8mpWFhfak8W2MNg1EjEdJYmtzkZer6e3JPUGsczrizTGC5Li2LOFWYLAu4Pf +o2Erc50JMoRZa1w1RYwVmSQPtOYudabDwjD0pR7GMNLvfipIIjjzxp6W/Xg8fg2G8bgNyXhc4bk4H98q +RBzRJeY7kAnQFmxi2KA7NlRxtAyk/XXjO26j7Xg89oIqKZ9en1z3eELW/hDOObBVViQxPGBAKWBKMyr0 +KtcxDnQg7Org8C8qXxeJxhBmM08Q5QVQ7e55ADOPo2VzUKJzh/WRglOUMnGGG9Y3YiBXCsp0lbXsTJmd +yMyIWTmnu3U5WhoQjpYNCKUiA2Hvb0WgWf6qWD9g2kKl41OaXoPV3Uaw92w0ezW+PH2doUjQFtWKYWMo +N9PJ65DdTCdNVDfTiUF0O/lFIcopySjhT8EWk+WKB+KY8CL228kvTey3k19KG9QGVMqr1ZKst4YKDaEU +4UAo8rrfC7q73yqG2tb/Y2yU0Y1h0cCZ5zZYxayBVE+tODNaQonfL1i+emrYqHL8BUNLHADDCY54RgOV +/pB0qaomEaacLEiEOJYmML24bfFDYvSrjUBS0K1DQ1k3hE3x77QF6PcdViDFWBxG4Y0Cf1Mm+X+g1fCE +ISkUAyUfWsGMcAykeW4FtuVkJthjX2FGVfFVi/SaqlLKZ8W4zgCsNPGzD7/9BlXN5XN5OJzeTV/n5KZ3 +04YJvpneTd8oCzRR7E2qkis3YBnFBnWSVdj6CtXuVKvwuVydqbFOiBnwLYnw0LwHMIImTIItCGVcA9tA +n7lBoAFJGpMNiQuUGNRhBX91PT0dwvlCQFIMiGLrcH+gJwRlrshMupClyROgKMKMtS4eAF8VDAiHOMNM +pKdrxEVWul0hDlvBpViGpIYti6b/yLZ4g2kAD08SjKTLBseK3kAW+daCOszgAUWPW0Rji6IoW+eIkweS +COe6XeFUYkpw2pPlRF8cIQ9kWalHUo5ToUqUJE8+PFCMHi1UDzR7xKklCYxo8iQ4UELmeKmPlxwzrmVc +O/1YO8Q6B724jyqQSrkjmFlw89YK4A7Es8F8B+rmTt6TNQgdEy7vaunBS3vx8q4ZDS7v/oEJwT87pK8/ +5xQvMMVphF+M6b/DgUYrHD2O6ZL15C9miI0xi+xTGapqlvCDmmWem8USMbmzSKmrWA6KRglLLPmNApmR +uVx9RuZ+vRheLSfLM+/KsAke7AOxazZRRimOuCxAe41SuY4FV688ZV21HLGuyvOVSKFvTye/nDrZs29d +cdUAQEN0lBFq51f7CC7Le7XLJ4lrqP+HZ7+1hlFdcpWGe8/RQ4Kty5apoGI2S7KtLC6tyHI1hMMAUrz9 +CTE8hPcissnX35nX38vX5zdD+DCfG0Ty1uTNAXyBQ/gC7+HLEXwHX+B7+ALwBT68KWtZCUnxS+XPGr27 +atwkh1Ed3il1CyBJLoyA5KH8eeQYoRyqm517faNA6jCyQKFR34drlCu4oFIraZtiX/8V68M44z1i3eyU +ZuuHnzKS9rzAq71t3NnUiTFoFdm1yXvNX1pGQuOllMRDQ05i8EVJSaAOWeklSmmJ53+qvDRBlsQk+a+T +mfBMI5iVVOVhkm39AKwBsWX8cj/pnWOZp9wO+uI822oO4At4fltFU0FroCPwygz3/PLmejK9n07GV7dn +15NLteUTmVuoTVFe8kjvVodv+ro6RD3wzrzGEp4s26hl1G/OEzfe/n9GUu9H74WwqEhpBlrMkSa/chqy +8l25TBVW6xz6zQXlDYaC5knjJHzzcfLzac+KC2qgdPdx+J8Y5x/TxzTbpoIAlDBslHp1fd+YX451ouC0 +0Bjevt2Dt/BjjHOKxTE73oO3/QrVEvMy7PWU1BlHlDvXLFnc6awlcHlf1Rnn5dWruaNyrqcswxZANtET +KV112fygTFLyIm944VeVdT6r9xZsG0yWcxbKpeezwRzGJn0QVmTDG7mM3CkHc7jOVYavroQQz+iueaVd +gekXqO4bnStIc/MGb42opugRQ8dG8AEx614QxulTtUnUxeQDtnCJBQmO4QEv1NmMsHKvhVYNd11wxNUh +ckk2OLXJ6hSNYMbYTgubFV08k5gVTtf8XH+jakICu7Ed8VuGCn1dw3q/PiuIwLKuFwvLMqcXfqdKYL/O ++ehER0Eqga/QBlvMooRiFD8Z0ddnCtxGUYBS3Xki95TVuKBvQfbc6PfCCcKOw8rTdhwbGzhLh2lilj3v +lWH0FWfRRhy19OFYU4tOOrXRljqWwF3uyGmQyGIYVVNk3tgAbHb/ZLHflaess9hcCbZkKO3dOjvQ9fug +GtN4ZbVyU+ljd+skeQ2dxZYj+vZbqyzmvOpcWTNjIXG65hwcR60YnltHy24kKxZLFXfLq51A3ad0Oplc +T4Zgwp/TpuS1oOy2R5VDagOon8/qxw55Xx/rTo5fn93jRuURdCOprZl6awf8UIWbltO2wVlOuyBM7LFy +ToNFmVpXGTXH6xeSagEyG8zbMuomcp1iQz3HVuqQ8Xi/McszXpPi/ykIxazRAmYcvi2GVkRVBO214XDF +1ILAD+E6TZ5g5+RdBGwxxcAK5eJrFqYEalce9pydnCTC4ZfL7O1yZHVptDoybRknImYQGVUty3COwQZa +3dF29YVZRlrhNNL4Kxy0WZKIiUVa5UYCgZFPqzP9xsE+O5jrDgt/507vMK2GiXk7gNyFB/Od+Mo6k+ZM +llQQSRpa3+VXZLNd6StmdQLEmcO65u22mdKltNtMi7G8povMvqru7iOrUbWzdFX1kktljFpUanVON941 +G5PLWTwZOq07LshzLXA309SWdOKoOaUMaiV4pT13qtvBGupmU9MC35IBaLmpd5ZknX6UF45sKI7VaacX +m4Z5uyIoKWRWeY8soLoASmViGABirFhjILlARzFjYZlkEO5eQehcsiWNbOSNTspof1QQOVbQpv22Bna3 +xGmNd9uBqZU7LemuRT0f7egyj3FEYgwPiOEYxHFGkGrg35XHHNNvzlS/eXW8EQc08eTc68qp16095gLW +6TOXsKZl5PwMLu8qzEplUo+Gzz0r2WOt7eVuXvxiJFmrZLg9JOxogK8a4SmO2g8NOzvUK3/3+5JdyXtn +mvuKJHfdld7uTG6bia2d1Nb6638nWGfKG2UpyxIcJtmy18pL1bF/2dmq7wXtAVY37Le/9Xq3jyTPSbr8 +xvcaEC9USp/32t2j+xUMxZGueZEcqi9xyhjDYEGzNaw4z4f9PuMoesw2mC6SbBtG2bqP+n85GHz/5+8G +/YPDgw8fBnv9PmwIMhM+oQ1iESU5D9FDVnA5JyEPFNGn/kNCcm124YqvK197ftOLM6cYJuJZnPGQ5Qnh +PS80OXC/DznFnBNM35FlmlFsM9eTf/vxbDD34S0cfv/Bh30QAwdzvzZy2Bh5P/dr3weZSnWxti/v0mIt ++yjLNkq3biop8bx6g791zSzwtcxJi3Xjcyjl9eFPgs6WuuB74XH+Kh3Pu3dOM6egES4RX4WLJMuoJLov +ua2syMEO++CFHuxD3FIzjMte2iQr4kWCKAaUEMQwG6orZ8zlRwBc3lALGq22BmORqlPu7P5mcn339/vr +szPZax2VKO9zmn1+GoKXLRYePB8Jbd+IIYgJQw8JjusorjoxpC4CnLbNP/t4cdGFYVEkiYNjf4JIsizS +Cpd4g+k789mOLYLhXkW7bs3OFgsVClNOyi8goGd1b/tDlzz9VUOnpO71vEpiLaumzUW7lrl6cRUpVWUI +H2+n15cB3Eyufzk/OZ3A7c3p8fnZ+TFMTo+vJycw/fvN6a21me51bo+lCZ0J/BMcEypilNOiKc8tdkt6 +48Ri0mJVwG8Yq5xQfj7jBZ4vt+u7A2nEmvXJ6cn55PR42tKzWb3c0QHBsoJGsgrazZfT8hBjxkkqzzav +mvXHXt8odoQPCIQPUFc6FcXuZYsW4fT08ma3HB2IfwmzU5gfJxdN+X2cXIiop9+/Hxy0grwfHBios0lr +D7IcLluHb87uf/p4fiF2LEePmFXVcemyckQ5G8JUfeHHGWSy9UzMMwlyj2fwgOFTJkKfSsw98HzpDhP0 +gBM1/eTqVj2W36PklKwRfbJwhdCrnMuPnvx+gqLtEP4mu9162xWJVgqLr7LTjMp6fpGihGOKYzD5i0Wn +8cGSIplAKIo4XucJ4lh9kRXHRF81mY8XFV+R/Ooxtim7Z/niT7Eib5EgznE6hDEkhKmP3tS3bHq+BhDx +oXJ+lthbnJ1yWErev/0G1mNVuDxs9gV5tjLLch/ikGDEOBwCTrCsLzRyEb2iFqxdbi2HbUNvTKRo25xG +0VZMuqdoy/JFOVV5ZlWelW0wK1xKzpK88t3qSJyrQq+BFoHVurURdoBlYJOnOhFEp3fT6i5NLCdJMAUf +LUp9le/5JeLKilyzMZnm+cJok6RLcRwUQsaM4ziAJU4xVZ/HVqtbx1S0rSE1IlQkabziHOUMVAXAgfMd +azlhVINv6cOgKvWf3k17pWYCLZOq1cFi0iT4gkWW40h4wDjQeY7aQYKJOg9mmkuoBC/JNDD1VX/eLT5X +5VqpdbaknRrGAsj92o0CFfH+/wIAAP//czzbquJCAAA= `, }, diff --git a/pkg/normalize/flatten.go b/pkg/normalize/flatten.go index 1e0491df6..dd0593710 100644 --- a/pkg/normalize/flatten.go +++ b/pkg/normalize/flatten.go @@ -35,7 +35,7 @@ func flattenSPFs(cfg *models.DNSConfig) []error { } if flatten, ok := txt.Metadata["flatten"]; ok && strings.HasPrefix(txt.Target, "v=spf1") { rec = rec.Flatten(flatten) - txt.Target = rec.TXT() + txt.SetTxt(rec.TXT()) } // now split if needed if split, ok := txt.Metadata["split"]; ok { @@ -46,10 +46,10 @@ func flattenSPFs(cfg *models.DNSConfig) []error { recs := rec.TXTSplit(split + "." + domain.Name) for k, v := range recs { if k == "@" { - txt.Target = v + txt.SetTxt(v) } else { cp, _ := txt.Copy() - cp.Target = v + cp.SetTxt(v) cp.NameFQDN = k cp.Name = dnsutil.TrimDomainName(k, domain.Name) domain.Records = append(domain.Records, cp) diff --git a/pkg/normalize/validate.go b/pkg/normalize/validate.go index 51e6bea24..79fa37650 100644 --- a/pkg/normalize/validate.go +++ b/pkg/normalize/validate.go @@ -252,6 +252,7 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) { for _, domain := range config.Domains { pTypes := []string{} + txtMultiDissenters := []string{} for p := range domain.DNSProviders { pType, ok := ptypeMap[p] if !ok { @@ -264,6 +265,11 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) { if domain.KeepUnknown && providers.ProviderHasCabability(pType, providers.CantUseNOPURGE) { errs = append(errs, fmt.Errorf("%s uses NO_PURGE which is not supported by %s(%s)", domain.Name, p, pType)) } + + // Record if any providers do not support TXTMulti: + if !providers.ProviderHasCabability(pType, providers.CanUseTXTMulti) { + txtMultiDissenters = append(txtMultiDissenters, p) + } } // Normalize Nameservers. @@ -272,7 +278,7 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) { ns.Name = strings.TrimRight(ns.Name, ".") } // Normalize Records. - models.Downcase(domain.Records) + models.PostProcessRecords(domain.Records) for _, rec := range domain.Records { if rec.TTL == 0 { rec.TTL = models.DefaultTTL @@ -315,6 +321,12 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) { errs = append(errs, fmt.Errorf("TLSA MatchingType %d is invalid in record %s (domain %s)", rec.TlsaMatchingType, rec.Name, domain.Name)) } + } else if rec.Type == "TXT" && len(txtMultiDissenters) != 0 && len(rec.TxtStrings) > 1 { + // There are providers that don't support TXTMulti yet there is + // a TXT record with multiple strings: + errs = append(errs, + fmt.Errorf("TXT records with multiple strings (label %v domain: %v) not supported by %s", + rec.Name, domain.Name, strings.Join(txtMultiDissenters, ","))) } // Populate FQDN: diff --git a/providers/activedir/activedirProvider.go b/providers/activedir/activedirProvider.go index 9bd031ecd..6c977a852 100644 --- a/providers/activedir/activedirProvider.go +++ b/providers/activedir/activedirProvider.go @@ -16,20 +16,20 @@ type adProvider struct { psLog string } -var docNotes = providers.DocumentationNotes{ - providers.DocDualHost: providers.Cannot("This driver does not manage NS records, so should not be used for dual-host scenarios"), - providers.DocCreateDomains: providers.Cannot("AD depends on the zone already existing on the dns server"), - providers.DocOfficiallySupported: providers.Can(), +var features = providers.DocumentationNotes{ providers.CanUseAlias: providers.Cannot(), - providers.CanUseSRV: providers.Cannot(), - providers.CanUsePTR: providers.Cannot(), providers.CanUseCAA: providers.Cannot(), + providers.CanUsePTR: providers.Cannot(), + providers.CanUseSRV: providers.Cannot(), + providers.DocCreateDomains: providers.Cannot("AD depends on the zone already existing on the dns server"), + providers.DocDualHost: providers.Cannot("This driver does not manage NS records, so should not be used for dual-host scenarios"), + providers.DocOfficiallySupported: providers.Can(), } // Register with the dnscontrol system. // This establishes the name (all caps), and the function to call to initialize it. func init() { - providers.RegisterDomainServiceProviderType("ACTIVEDIRECTORY_PS", newDNS, docNotes) + providers.RegisterDomainServiceProviderType("ACTIVEDIRECTORY_PS", newDNS, features) } func newDNS(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) { diff --git a/providers/activedir/domains.go b/providers/activedir/domains.go index 9b8d28340..87cd04078 100644 --- a/providers/activedir/domains.go +++ b/providers/activedir/domains.go @@ -47,7 +47,7 @@ func (c *adProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Co } // Normalize - models.Downcase(foundRecords) + models.PostProcessRecords(foundRecords) differ := diff.New(dc) _, creates, dels, modifications := differ.IncrementalDiff(foundRecords) diff --git a/providers/bind/bindProvider.go b/providers/bind/bindProvider.go index d8d6bdc33..bb2307b44 100644 --- a/providers/bind/bindProvider.go +++ b/providers/bind/bindProvider.go @@ -30,9 +30,15 @@ import ( "github.com/StackExchange/dnscontrol/providers/diff" ) -var docNotes = providers.DocumentationNotes{ - providers.DocDualHost: providers.Can(), +var features = providers.DocumentationNotes{ + providers.CanUseCAA: providers.Can(), + providers.CanUsePTR: providers.Can(), + providers.CanUseSRV: providers.Can(), + providers.CanUseTLSA: providers.Can(), + providers.CanUseTXTMulti: providers.Can(), + providers.CantUseNOPURGE: providers.Cannot(), providers.DocCreateDomains: providers.Can("Driver just maintains list of zone files. It should automatically add missing ones."), + providers.DocDualHost: providers.Can(), providers.DocOfficiallySupported: providers.Can(), } @@ -56,13 +62,7 @@ func initBind(config map[string]string, providermeta json.RawMessage) (providers } func init() { - providers.RegisterDomainServiceProviderType("BIND", initBind, - providers.CanUsePTR, - providers.CanUseSRV, - providers.CanUseCAA, - providers.CanUseTLSA, - providers.CantUseNOPURGE, - docNotes) + providers.RegisterDomainServiceProviderType("BIND", initBind, features) } type SoaInfo struct { @@ -144,6 +144,7 @@ func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordCo rc.Target = v.Certificate case *dns.TXT: rc.Target = strings.Join(v.Txt, " ") + rc.TxtStrings = v.Txt default: log.Fatalf("rrToRecord: Unimplemented zone record type=%s (%v)\n", rc.Type, rr) } @@ -243,7 +244,7 @@ func (c *Bind) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correcti } // Normalize - models.Downcase(foundRecords) + models.PostProcessRecords(foundRecords) differ := diff.New(dc) _, create, del, mod := differ.IncrementalDiff(foundRecords) diff --git a/providers/capabilities.go b/providers/capabilities.go index e9261c85a..10818fae6 100644 --- a/providers/capabilities.go +++ b/providers/capabilities.go @@ -19,6 +19,9 @@ const ( // CanUseSRV indicates the provider can handle SRV records CanUseSRV + // CanUseTXTMulti indicates the provider can handle TXT records with multiple strings + CanUseTXTMulti + // CanUseCAA indicates the provider can handle CAA records CanUseCAA @@ -66,12 +69,12 @@ type ProviderMetadata interface{} var Notes = map[string]DocumentationNotes{} func unwrapProviderCapabilities(pName string, meta []ProviderMetadata) { + if providerCapabilities[pName] == nil { + providerCapabilities[pName] = map[Capability]bool{} + } for _, pm := range meta { switch x := pm.(type) { case Capability: - if providerCapabilities[pName] == nil { - providerCapabilities[pName] = map[Capability]bool{} - } providerCapabilities[pName][x] = true case DocumentationNotes: if Notes[pName] == nil { @@ -79,6 +82,7 @@ func unwrapProviderCapabilities(pName string, meta []ProviderMetadata) { } for k, v := range x { Notes[pName][k] = v + providerCapabilities[pName][k] = v.HasFeature } default: log.Fatalf("Unrecognized ProviderMetadata type: %T", pm) diff --git a/providers/cloudflare/cloudflareProvider.go b/providers/cloudflare/cloudflareProvider.go index 032ae95c7..215b74f18 100644 --- a/providers/cloudflare/cloudflareProvider.go +++ b/providers/cloudflare/cloudflareProvider.go @@ -33,15 +33,17 @@ Domain level metadata available: - ip_conversions */ -var docNotes = providers.DocumentationNotes{ - providers.DocDualHost: providers.Cannot("Cloudflare will not work well in situations where it is not the only DNS server"), - providers.DocCreateDomains: providers.Can(), - providers.DocOfficiallySupported: providers.Can(), +var features = providers.DocumentationNotes{ providers.CanUseAlias: providers.Can("CF automatically flattens CNAME records into A records dynamically"), + providers.CanUseCAA: providers.Can(), + providers.CanUseSRV: providers.Can(), + providers.DocCreateDomains: providers.Can(), + providers.DocDualHost: providers.Cannot("Cloudflare will not work well in situations where it is not the only DNS server"), + providers.DocOfficiallySupported: providers.Can(), } func init() { - providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", newCloudflare, providers.CanUseSRV, providers.CanUseAlias, providers.CanUseCAA, docNotes) + providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", newCloudflare, features) providers.RegisterCustomRecordType("CF_REDIRECT", "CLOUDFLAREAPI", "") providers.RegisterCustomRecordType("CF_TEMP_REDIRECT", "CLOUDFLAREAPI", "") } @@ -122,7 +124,7 @@ func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models checkNSModifications(dc) // Normalize - models.Downcase(records) + models.PostProcessRecords(records) differ := diff.New(dc, getProxyMetadata) _, create, del, mod := differ.IncrementalDiff(records) diff --git a/providers/digitalocean/digitaloceanProvider.go b/providers/digitalocean/digitaloceanProvider.go index f52432abf..2c3e4522a 100644 --- a/providers/digitalocean/digitaloceanProvider.go +++ b/providers/digitalocean/digitaloceanProvider.go @@ -60,13 +60,14 @@ func NewDo(m map[string]string, metadata json.RawMessage) (providers.DNSServiceP return api, nil } -var docNotes = providers.DocumentationNotes{ +var features = providers.DocumentationNotes{ providers.DocCreateDomains: providers.Can(), providers.DocOfficiallySupported: providers.Cannot(), + providers.CanUseSRV: providers.Can(), } func init() { - providers.RegisterDomainServiceProviderType("DIGITALOCEAN", NewDo, providers.CanUseSRV, docNotes) + providers.RegisterDomainServiceProviderType("DIGITALOCEAN", NewDo, features) } func (api *DoApi) EnsureDomainExists(domain string) error { @@ -102,7 +103,7 @@ func (api *DoApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Corre } // Normalize - models.Downcase(existingRecords) + models.PostProcessRecords(existingRecords) differ := diff.New(dc) _, create, delete, modify := differ.IncrementalDiff(existingRecords) diff --git a/providers/dnsimple/dnsimpleProvider.go b/providers/dnsimple/dnsimpleProvider.go index 3125e219f..f81f16819 100644 --- a/providers/dnsimple/dnsimpleProvider.go +++ b/providers/dnsimple/dnsimpleProvider.go @@ -15,16 +15,20 @@ import ( dnsimpleapi "github.com/dnsimple/dnsimple-go/dnsimple" ) -var docNotes = providers.DocumentationNotes{ - providers.DocDualHost: providers.Cannot("DNSimple does not allow sufficient control over the apex NS records"), - providers.DocCreateDomains: providers.Cannot(), - providers.DocOfficiallySupported: providers.Cannot(), +var features = providers.DocumentationNotes{ + providers.CanUseAlias: providers.Can(), + providers.CanUseCAA: providers.Can(), + providers.CanUsePTR: providers.Can(), + providers.CanUseSRV: providers.Can(), providers.CanUseTLSA: providers.Cannot(), + providers.DocCreateDomains: providers.Cannot(), + providers.DocDualHost: providers.Cannot("DNSimple does not allow sufficient control over the apex NS records"), + providers.DocOfficiallySupported: providers.Cannot(), } func init() { providers.RegisterRegistrarType("DNSIMPLE", newReg) - providers.RegisterDomainServiceProviderType("DNSIMPLE", newDsp, providers.CanUsePTR, providers.CanUseAlias, providers.CanUseCAA, providers.CanUseSRV, docNotes) + providers.RegisterDomainServiceProviderType("DNSIMPLE", newDsp, features) } const stateRegistered = "registered" @@ -92,7 +96,7 @@ func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C }) // Normalize - models.Downcase(actual) + models.PostProcessRecords(actual) differ := diff.New(dc) _, create, delete, modify := differ.IncrementalDiff(actual) diff --git a/providers/gandi/gandiProvider.go b/providers/gandi/gandiProvider.go index f316dbebe..aba233fc6 100644 --- a/providers/gandi/gandiProvider.go +++ b/providers/gandi/gandiProvider.go @@ -26,19 +26,17 @@ Info required in `creds.json`: */ -var docNotes = providers.DocumentationNotes{ +var features = providers.DocumentationNotes{ + providers.CanUseCAA: providers.Can(), + providers.CanUsePTR: providers.Can(), + providers.CanUseSRV: providers.Can(), + providers.CantUseNOPURGE: providers.Cannot(), providers.DocCreateDomains: providers.Cannot("Can only manage domains registered through their service"), providers.DocOfficiallySupported: providers.Cannot(), } func init() { - providers.RegisterDomainServiceProviderType("GANDI", newDsp, - providers.CanUseCAA, - providers.CanUsePTR, - providers.CanUseSRV, - providers.CantUseNOPURGE, - docNotes, - ) + providers.RegisterDomainServiceProviderType("GANDI", newDsp, features) providers.RegisterRegistrarType("GANDI", newReg) } @@ -121,7 +119,7 @@ func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Corr dc.Records = recordsToKeep // Normalize - models.Downcase(foundRecords) + models.PostProcessRecords(foundRecords) differ := diff.New(dc) _, create, del, mod := differ.IncrementalDiff(foundRecords) diff --git a/providers/gandi/protocol.go b/providers/gandi/protocol.go index de95e618d..1459ef815 100644 --- a/providers/gandi/protocol.go +++ b/providers/gandi/protocol.go @@ -192,8 +192,10 @@ func convert(r *gandirecord.RecordInfo, origin string) *models.RecordConfig { TTL: uint32(r.Ttl), } switch r.Type { - case "A", "AAAA", "NS", "CNAME", "PTR", "TXT": + case "A", "AAAA", "NS", "CNAME", "PTR": // no-op + case "TXT": + rc.SetTxtParse(r.Value) case "CAA": var err error rc.CaaTag, rc.CaaFlag, rc.Target, err = models.SplitCombinedCaaValue(r.Value) diff --git a/providers/gcloud/gcloudProvider.go b/providers/gcloud/gcloudProvider.go index 517299680..ea82e61f4 100644 --- a/providers/gcloud/gcloudProvider.go +++ b/providers/gcloud/gcloudProvider.go @@ -14,14 +14,17 @@ import ( "github.com/StackExchange/dnscontrol/providers/diff" ) -var docNotes = providers.DocumentationNotes{ - providers.DocDualHost: providers.Can(), +var features = providers.DocumentationNotes{ providers.DocCreateDomains: providers.Can(), + providers.DocDualHost: providers.Can(), providers.DocOfficiallySupported: providers.Can(), + providers.CanUsePTR: providers.Can(), + providers.CanUseSRV: providers.Can(), + providers.CanUseCAA: providers.Can(), } func init() { - providers.RegisterDomainServiceProviderType("GCLOUD", New, providers.CanUsePTR, providers.CanUseSRV, providers.CanUseCAA, docNotes) + providers.RegisterDomainServiceProviderType("GCLOUD", New, features) } type gcloud struct { @@ -137,7 +140,7 @@ func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correc } // Normalize - models.Downcase(existingRecords) + models.PostProcessRecords(existingRecords) // first collect keys that have changed differ := diff.New(dc) diff --git a/providers/linode/linodeProvider.go b/providers/linode/linodeProvider.go index 888407df8..8d983dd12 100644 --- a/providers/linode/linodeProvider.go +++ b/providers/linode/linodeProvider.go @@ -13,9 +13,10 @@ import ( "net/url" - "golang.org/x/oauth2" "regexp" "strings" + + "golang.org/x/oauth2" ) /* @@ -84,14 +85,14 @@ func NewLinode(m map[string]string, metadata json.RawMessage) (providers.DNSServ return api, nil } -var docNotes = providers.DocumentationNotes{ - providers.DocOfficiallySupported: providers.Cannot(), +var features = providers.DocumentationNotes{ providers.DocDualHost: providers.Cannot(), + providers.DocOfficiallySupported: providers.Cannot(), } func init() { // SRV support is in this provider, but Linode doesn't seem to support it properly - providers.RegisterDomainServiceProviderType("LINODE", NewLinode, docNotes) + providers.RegisterDomainServiceProviderType("LINODE", NewLinode, features) } func (api *LinodeApi) GetNameservers(domain string) ([]*models.Nameserver, error) { @@ -138,7 +139,7 @@ func (api *LinodeApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C } // Normalize - models.Downcase(existingRecords) + models.PostProcessRecords(existingRecords) // Linode doesn't allow selecting an arbitrary TTL, only a set of predefined values // We need to make sure we don't change it every time if it is as close as it's going to get diff --git a/providers/namecheap/namecheapProvider.go b/providers/namecheap/namecheapProvider.go index 46001bd77..9e93a043f 100644 --- a/providers/namecheap/namecheapProvider.go +++ b/providers/namecheap/namecheapProvider.go @@ -25,20 +25,21 @@ type Namecheap struct { client *nc.Client } -var docNotes = providers.DocumentationNotes{ - providers.DocCreateDomains: providers.Cannot("Requires domain registered through their service"), - providers.DocOfficiallySupported: providers.Cannot(), - providers.DocDualHost: providers.Cannot("Doesn't allow control of apex NS records"), +var features = providers.DocumentationNotes{ providers.CanUseAlias: providers.Cannot(), providers.CanUseCAA: providers.Cannot(), - providers.CanUseSRV: providers.Cannot("The namecheap web console allows you to make SRV records, but their api does not let you read or set them"), providers.CanUsePTR: providers.Cannot(), + providers.CanUseSRV: providers.Cannot("The namecheap web console allows you to make SRV records, but their api does not let you read or set them"), providers.CanUseTLSA: providers.Cannot(), + providers.CantUseNOPURGE: providers.Cannot(), + providers.DocCreateDomains: providers.Cannot("Requires domain registered through their service"), + providers.DocDualHost: providers.Cannot("Doesn't allow control of apex NS records"), + providers.DocOfficiallySupported: providers.Cannot(), } func init() { providers.RegisterRegistrarType("NAMECHEAP", newReg) - providers.RegisterDomainServiceProviderType("NAMECHEAP", newDsp, providers.CantUseNOPURGE, docNotes) + providers.RegisterDomainServiceProviderType("NAMECHEAP", newDsp, features) providers.RegisterCustomRecordType("URL", "NAMECHEAP", "") providers.RegisterCustomRecordType("URL301", "NAMECHEAP", "") providers.RegisterCustomRecordType("FRAME", "NAMECHEAP", "") @@ -156,7 +157,7 @@ func (n *Namecheap) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Cor } // Normalize - models.Downcase(actual) + models.PostProcessRecords(actual) differ := diff.New(dc) _, create, delete, modify := differ.IncrementalDiff(actual) diff --git a/providers/namedotcom/namedotcomProvider.go b/providers/namedotcom/namedotcomProvider.go index 88facc8b7..ada2e0d52 100644 --- a/providers/namedotcom/namedotcomProvider.go +++ b/providers/namedotcom/namedotcomProvider.go @@ -19,11 +19,13 @@ type nameDotCom struct { APIKey string `json:"apikey"` } -var docNotes = providers.DocumentationNotes{ - providers.DocDualHost: providers.Cannot("Apex NS records not editable"), - providers.DocCreateDomains: providers.Cannot("New domains require registration"), - providers.DocOfficiallySupported: providers.Can(), +var features = providers.DocumentationNotes{ + providers.CanUseAlias: providers.Can(), providers.CanUsePTR: providers.Cannot("PTR records are not supported (See Link)", "https://www.name.com/support/articles/205188508-Reverse-DNS-records"), + providers.CanUseSRV: providers.Can(), + providers.DocCreateDomains: providers.Cannot("New domains require registration"), + providers.DocDualHost: providers.Cannot("Apex NS records not editable"), + providers.DocOfficiallySupported: providers.Can(), } func newReg(conf map[string]string) (providers.Registrar, error) { @@ -48,7 +50,7 @@ func newProvider(conf map[string]string) (*nameDotCom, error) { func init() { providers.RegisterRegistrarType("NAMEDOTCOM", newReg) - providers.RegisterDomainServiceProviderType("NAMEDOTCOM", newDsp, providers.CanUseAlias, providers.CanUseSRV, docNotes) + providers.RegisterDomainServiceProviderType("NAMEDOTCOM", newDsp, features) } /// diff --git a/providers/namedotcom/records.go b/providers/namedotcom/records.go index ca0c66fc6..1a6cb03b1 100644 --- a/providers/namedotcom/records.go +++ b/providers/namedotcom/records.go @@ -38,7 +38,7 @@ func (n *nameDotCom) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Co checkNSModifications(dc) // Normalize - models.Downcase(actual) + models.PostProcessRecords(actual) differ := diff.New(dc) _, create, del, mod := differ.IncrementalDiff(actual) diff --git a/providers/ns1/ns1provider.go b/providers/ns1/ns1provider.go index 884cefc1f..4b1982966 100644 --- a/providers/ns1/ns1provider.go +++ b/providers/ns1/ns1provider.go @@ -67,7 +67,7 @@ func (n *nsone) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correct desiredGrouped := dc.Records.Grouped() // Normalize - models.Downcase(found) + models.PostProcessRecords(found) differ := diff.New(dc) changedGroups := differ.ChangedGroups(found) diff --git a/providers/ovh/ovhProvider.go b/providers/ovh/ovhProvider.go index 685ec462b..9742cf39f 100644 --- a/providers/ovh/ovhProvider.go +++ b/providers/ovh/ovhProvider.go @@ -3,13 +3,14 @@ package ovh import ( "encoding/json" "fmt" + "sort" + "strings" + "github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/providers" "github.com/StackExchange/dnscontrol/providers/diff" "github.com/miekg/dns/dnsutil" "github.com/xlucas/go-ovh/ovh" - "sort" - "strings" ) type ovhProvider struct { @@ -17,14 +18,15 @@ type ovhProvider struct { zones map[string]bool } -var docNotes = providers.DocumentationNotes{ - providers.DocDualHost: providers.Can(), - providers.DocCreateDomains: providers.Cannot("New domains require registration"), - providers.DocOfficiallySupported: providers.Cannot(), +var features = providers.DocumentationNotes{ providers.CanUseAlias: providers.Cannot(), - providers.CanUseTLSA: providers.Can(), providers.CanUseCAA: providers.Cannot(), providers.CanUsePTR: providers.Cannot(), + providers.CanUseSRV: providers.Can(), + providers.CanUseTLSA: providers.Can(), + providers.DocCreateDomains: providers.Cannot("New domains require registration"), + providers.DocDualHost: providers.Can(), + providers.DocOfficiallySupported: providers.Cannot(), } func newOVH(m map[string]string, metadata json.RawMessage) (*ovhProvider, error) { @@ -50,7 +52,7 @@ func newReg(conf map[string]string) (providers.Registrar, error) { func init() { providers.RegisterRegistrarType("OVH", newReg) - providers.RegisterDomainServiceProviderType("OVH", newDsp, providers.CanUseSRV, providers.CanUseTLSA, docNotes) + providers.RegisterDomainServiceProviderType("OVH", newDsp, features) } func (c *ovhProvider) GetNameservers(domain string) ([]*models.Nameserver, error) { @@ -124,7 +126,7 @@ func (c *ovhProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C } // Normalize - models.Downcase(actual) + models.PostProcessRecords(actual) differ := diff.New(dc) _, create, delete, modify := differ.IncrementalDiff(actual) diff --git a/providers/route53/route53Provider.go b/providers/route53/route53Provider.go index 470740fd8..636768c64 100644 --- a/providers/route53/route53Provider.go +++ b/providers/route53/route53Provider.go @@ -55,15 +55,19 @@ func newRoute53(m map[string]string, metadata json.RawMessage) (*route53Provider return api, nil } -var docNotes = providers.DocumentationNotes{ - providers.DocDualHost: providers.Can(), - providers.DocCreateDomains: providers.Can(), - providers.DocOfficiallySupported: providers.Can(), +var features = providers.DocumentationNotes{ providers.CanUseAlias: providers.Cannot("R53 does not provide a generic ALIAS functionality. They do have 'ALIAS' CNAME types to point at various AWS infrastructure, but dnscontrol has not implemented those."), + providers.DocCreateDomains: providers.Can(), + providers.DocDualHost: providers.Can(), + providers.DocOfficiallySupported: providers.Can(), + providers.CanUsePTR: providers.Can(), + providers.CanUseSRV: providers.Can(), + providers.CanUseTXTMulti: providers.Can(), + providers.CanUseCAA: providers.Can(), } func init() { - providers.RegisterDomainServiceProviderType("ROUTE53", newRoute53Dsp, providers.CanUsePTR, providers.CanUseSRV, providers.CanUseCAA, docNotes) + providers.RegisterDomainServiceProviderType("ROUTE53", newRoute53Dsp, features) providers.RegisterRegistrarType("ROUTE53", newRoute53Reg) } @@ -170,7 +174,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode } // Normalize - models.Downcase(existingRecords) + models.PostProcessRecords(existingRecords) //diff differ := diff.New(dc) diff --git a/providers/softlayer/softlayerProvider.go b/providers/softlayer/softlayerProvider.go index f93453a3d..77d8cde96 100644 --- a/providers/softlayer/softlayerProvider.go +++ b/providers/softlayer/softlayerProvider.go @@ -21,8 +21,12 @@ type SoftLayer struct { Session *session.Session } +var features = providers.DocumentationNotes{ + providers.CanUseSRV: providers.Can(), +} + func init() { - providers.RegisterDomainServiceProviderType("SOFTLAYER", newReg, providers.CanUseSRV) + providers.RegisterDomainServiceProviderType("SOFTLAYER", newReg, features) } func newReg(conf map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) { @@ -164,7 +168,7 @@ func (s *SoftLayer) getExistingRecords(domain *datatypes.Dns_Domain) ([]*models. } // Normalize - models.Downcase(actual) + models.PostProcessRecords(actual) return actual, nil } diff --git a/providers/vultr/vultrProvider.go b/providers/vultr/vultrProvider.go index afc20c623..ee312962c 100644 --- a/providers/vultr/vultrProvider.go +++ b/providers/vultr/vultrProvider.go @@ -24,16 +24,18 @@ Info required in `creds.json`: */ -var docNotes = providers.DocumentationNotes{ +var features = providers.DocumentationNotes{ + providers.CanUseAlias: providers.Cannot(), + providers.CanUseCAA: providers.Can(), + providers.CanUsePTR: providers.Cannot(), + providers.CanUseSRV: providers.Can(), + providers.CanUseTLSA: providers.Cannot(), providers.DocCreateDomains: providers.Can(), providers.DocOfficiallySupported: providers.Cannot(), - providers.CanUseAlias: providers.Cannot(), - providers.CanUseTLSA: providers.Cannot(), - providers.CanUsePTR: providers.Cannot(), } func init() { - providers.RegisterDomainServiceProviderType("VULTR", NewVultr, providers.CanUseSRV, providers.CanUseCAA, docNotes) + providers.RegisterDomainServiceProviderType("VULTR", NewVultr, features) } // VultrApi represents the Vultr DNSServiceProvider @@ -98,7 +100,7 @@ func (api *VultrApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Co } // Normalize - models.Downcase(curRecords) + models.PostProcessRecords(curRecords) differ := diff.New(dc) _, create, delete, modify := differ.IncrementalDiff(curRecords)