diff --git a/models/record.go b/models/record.go index dbd06e781..6dce203ec 100644 --- a/models/record.go +++ b/models/record.go @@ -427,7 +427,7 @@ func (rc *RecordConfig) ToRR() dns.RR { rr.(*dns.SOA).Expire = rc.SoaExpire rr.(*dns.SOA).Minttl = rc.SoaMinttl case dns.TypeSPF: - rr.(*dns.SPF).Txt = rc.TxtStrings + rr.(*dns.SPF).Txt = []string{rc.target} case dns.TypeSRV: rr.(*dns.SRV).Priority = rc.SrvPriority rr.(*dns.SRV).Weight = rc.SrvWeight @@ -443,7 +443,7 @@ func (rc *RecordConfig) ToRR() dns.RR { rr.(*dns.TLSA).Selector = rc.TlsaSelector rr.(*dns.TLSA).Certificate = rc.GetTargetField() case dns.TypeTXT: - rr.(*dns.TXT).Txt = rc.TxtStrings + rr.(*dns.TXT).Txt = []string{rc.target} default: panic(fmt.Sprintf("ToRR: Unimplemented rtype %v", rc.Type)) // We panic so that we quickly find any switch statements diff --git a/models/t_txt.go b/models/t_txt.go index 6e59658a3..e1e1bf828 100644 --- a/models/t_txt.go +++ b/models/t_txt.go @@ -49,13 +49,13 @@ func (rc *RecordConfig) SetTargetTXT(s string) error { // SetTargetTXTs sets the TXT fields when there are many strings. // The individual strings are stored in .TxtStrings, and joined to make .Target. func (rc *RecordConfig) SetTargetTXTs(s []string) error { - if rc.Type == "" { - rc.Type = "TXT" - } else if !rc.HasFormatIdenticalToTXT() { - panic("assertion failed: SetTargetTXTs called when .Type is not TXT or compatible type") - } + // if rc.Type == "" { + // rc.Type = "TXT" + // } else if !rc.HasFormatIdenticalToTXT() { + // panic("assertion failed: SetTargetTXTs called when .Type is not TXT or compatible type") + // } - rc.SetTarget(strings.Join(s)) + rc.SetTargetTXT(strings.Join(s, "")) return nil } @@ -74,6 +74,7 @@ func (rc *RecordConfig) GetTargetTXTJoined() string { // "foo bar" << 1 string // "foo" "bar" << 2 strings // foo << error. No quotes! Did you intend to use SetTargetTXT? +// // Deprecated: GetTargetTXTJoined is deprecated. ...or should be. func (rc *RecordConfig) SetTargetTXTfromRFC1035Quoted(s string) error { if s != "" && s[0] != '"' { diff --git a/models/target.go b/models/target.go index 58e1b34ab..774baff8b 100644 --- a/models/target.go +++ b/models/target.go @@ -21,7 +21,7 @@ Not the best design, but we're stuck with it until we re-do RecordConfig, possib var debugWarnTxtField = false // GetTargetField returns the target. There may be other fields (for example -// an MX record also has a .MxPreference field. +// an MX record also has a .MxPreference field; they are not included. func (rc *RecordConfig) GetTargetField() string { return rc.target } diff --git a/pkg/normalize/flatten.go b/pkg/normalize/flatten.go index eef90aacb..fdb7abec2 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.GetTargetField() if txt.Metadata["flatten"] != "" || txt.Metadata["split"] != "" { if cache == nil { cache, err = spflib.NewCache("spfcache.json") diff --git a/pkg/rejectif/txt.go b/pkg/rejectif/txt.go index 8a0883b8c..184d3e2ce 100644 --- a/pkg/rejectif/txt.go +++ b/pkg/rejectif/txt.go @@ -11,30 +11,24 @@ import ( // 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") - } + if strings.Contains(rc.GetTargetField(), "`") { + 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.GetTargetField(), "'") { + return fmt.Errorf("txtstring contains single-quotes") } return nil } // TxtHasDoubleQuotes audits TXT records for strings that contain doublequotes. func TxtHasDoubleQuotes(rc *models.RecordConfig) error { - for _, txt := range rc.TxtStrings { - if strings.Contains(txt, `"`) { - return fmt.Errorf("txtstring contains doublequotes") - } + if strings.Contains(rc.GetTargetField(), `"`) { + return fmt.Errorf("txtstring contains doublequotes") } return nil } @@ -42,27 +36,23 @@ func TxtHasDoubleQuotes(rc *models.RecordConfig) error { // 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") - } + if len(rc.GetTargetField()) == 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()) - } + if len(rc.GetTargetField()) > 255 { + return fmt.Errorf("%q txtstring length > 255", rc.GetLabel()) } return nil } // TxtHasMultipleSegments audits TXT records for multiple strings func TxtHasMultipleSegments(rc *models.RecordConfig) error { - if len(rc.TxtStrings) > 1 { + if len(rc.GetTargetField()) > 255 { return fmt.Errorf("multiple strings in one txt") } return nil @@ -70,35 +60,25 @@ func TxtHasMultipleSegments(rc *models.RecordConfig) error { // 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") - } + txt := rc.GetTargetField() + if txt != "" && txt[ultimate(txt)] == ' ' { + return fmt.Errorf("txtstring ends with space") } return nil } // TxtIsEmpty audits TXT records for empty strings. func TxtIsEmpty(rc *models.RecordConfig) error { - // There must be strings. - if len(rc.TxtStrings) == 0 { - return fmt.Errorf("txt with no strings") - } - // Each string must be non-empty. - for _, txt := range rc.TxtStrings { - if len(txt) == 0 { - return fmt.Errorf("txtstring is empty") - } + if len(rc.GetTargetField()) == 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") - } + if strings.Count(rc.GetTargetField(), `"`)%2 == 1 { + return fmt.Errorf("txtstring contains unpaired doublequotes") } return nil } diff --git a/pkg/txtutil/txtutil.go b/pkg/txtutil/txtutil.go index a6caf5e05..d958f3335 100644 --- a/pkg/txtutil/txtutil.go +++ b/pkg/txtutil/txtutil.go @@ -1,5 +1,23 @@ package txtutil +import "github.com/StackExchange/dnscontrol/v4/models" + +// 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(). +// Deprecated: SplitSingleLongTxt() is deprecated. No longer needed. +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) diff --git a/providers/axfrddns/axfrddnsProvider.go b/providers/axfrddns/axfrddnsProvider.go index d455fc205..2d91ad649 100644 --- a/providers/axfrddns/axfrddnsProvider.go +++ b/providers/axfrddns/axfrddnsProvider.go @@ -328,8 +328,7 @@ func (c *axfrddnsProvider) GetZoneRecords(domain string, meta map[string]string) last := foundRecords[len(foundRecords)-1] if last.Type == "TXT" && last.Name == dnssecDummyLabel && - len(last.TxtStrings) == 1 && - last.TxtStrings[0] == dnssecDummyTxt { + last.GetTargetField() == dnssecDummyTxt { c.hasDnssecRecords = true foundRecords = foundRecords[0:(len(foundRecords) - 1)] } diff --git a/providers/azuredns/azureDnsProvider.go b/providers/azuredns/azureDnsProvider.go index 484f1c572..46ec77ab4 100644 --- a/providers/azuredns/azureDnsProvider.go +++ b/providers/azuredns/azureDnsProvider.go @@ -653,11 +653,9 @@ func (a *azurednsProvider) recordToNative(recordKey models.RecordKey, recordConf recordSet.Properties.TxtRecords = []*adns.TxtRecord{} } // Empty TXT record needs to have no value set in it's properties - if !(len(rec.TxtStrings) == 1 && rec.TxtStrings[0] == "") { - var txts []*string - for _, txt := range rec.TxtStrings { - txts = append(txts, to.StringPtr(txt)) - } + tt := rec.GetTargetField() + if tt != "" { + txts := []*string{to.StringPtr(tt)} recordSet.Properties.TxtRecords = append(recordSet.Properties.TxtRecords, &adns.TxtRecord{Value: txts}) } case "MX": @@ -729,11 +727,9 @@ func (a *azurednsProvider) recordToNativeDiff2(recordKey models.RecordKey, recor recordSet.Properties.TxtRecords = []*adns.TxtRecord{} } // Empty TXT record needs to have no value set in it's properties - if !(len(rec.TxtStrings) == 1 && rec.TxtStrings[0] == "") { - var txts []*string - for _, txt := range rec.TxtStrings { - txts = append(txts, to.StringPtr(txt)) - } + tt := rec.GetTargetField() + if tt != "" { + txts := []*string{to.StringPtr(tt)} recordSet.Properties.TxtRecords = append(recordSet.Properties.TxtRecords, &adns.TxtRecord{Value: txts}) } case "MX": diff --git a/providers/cscglobal/dns.go b/providers/cscglobal/dns.go index e00f1b3d1..14c4de9b5 100644 --- a/providers/cscglobal/dns.go +++ b/providers/cscglobal/dns.go @@ -135,14 +135,7 @@ func (client *providerClient) GetZoneRecordsCorrections(dc *models.DomainConfig, } func makePurge(domainname string, cor diff.Correlation) zoneResourceRecordEdit { - var existingTarget string - - switch cor.Existing.Type { - case "TXT": - existingTarget = strings.Join(cor.Existing.TxtStrings, "") - default: - existingTarget = cor.Existing.GetTargetField() - } + existingTarget := cor.Existing.GetTargetField() zer := zoneResourceRecordEdit{ Action: "PURGE", @@ -163,19 +156,11 @@ func makePurge(domainname string, cor diff.Correlation) zoneResourceRecordEdit { func makeAdd(domainname string, cre diff.Correlation) zoneResourceRecordEdit { rec := cre.Desired - var recTarget string - switch rec.Type { - case "TXT": - recTarget = strings.Join(rec.TxtStrings, "") - default: - recTarget = rec.GetTargetField() - } - zer := zoneResourceRecordEdit{ Action: "ADD", RecordType: rec.Type, NewKey: rec.Name, - NewValue: recTarget, + NewValue: rec.GetTargetField(), NewTTL: rec.TTL, } @@ -192,7 +177,7 @@ func makeAdd(domainname string, cre diff.Correlation) zoneResourceRecordEdit { zer.NewWeight = rec.SrvWeight zer.NewPort = rec.SrvPort case "TXT": - zer.NewValue = strings.Join(rec.TxtStrings, "") + zer.NewValue = rec.GetTargetField() default: // "A", "CNAME", "NS" // Nothing to do. } @@ -205,15 +190,8 @@ func makeEdit(domainname string, m diff.Correlation) zoneResourceRecordEdit { // TODO: Assert that old.Type == rec.Type // TODO: Assert that old.Name == rec.Name - var oldTarget, recTarget string - switch old.Type { - case "TXT": - oldTarget = strings.Join(old.TxtStrings, "") - recTarget = strings.Join(rec.TxtStrings, "") - default: - oldTarget = old.GetTargetField() - recTarget = rec.GetTargetField() - } + oldTarget := old.GetTargetField() + recTarget := rec.GetTargetField() zer := zoneResourceRecordEdit{ Action: "EDIT", diff --git a/providers/domainnameshop/dns.go b/providers/domainnameshop/dns.go index 27acee61b..773e3409e 100644 --- a/providers/domainnameshop/dns.go +++ b/providers/domainnameshop/dns.go @@ -28,13 +28,6 @@ func (api *domainNameShopProvider) GetZoneRecords(domain string, meta map[string // GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records. func (api *domainNameShopProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, existingRecords models.Records) ([]*models.Correction, error) { - // Merge TXT strings to one string - for _, rc := range dc.Records { - if rc.HasFormatIdenticalToTXT() { - rc.SetTargetTXT(strings.Join(rc.TxtStrings, "")) - } - } - // Domainnameshop doesn't allow arbitrary TTLs they must be a multiple of 60. for _, record := range dc.Records { record.TTL = fixTTL(record.TTL) diff --git a/providers/hetzner/types.go b/providers/hetzner/types.go index 80ad3c8e7..8b749f8d0 100644 --- a/providers/hetzner/types.go +++ b/providers/hetzner/types.go @@ -63,7 +63,7 @@ func fromRecordConfig(in *models.RecordConfig, zone *zone) record { ZoneID: zone.ID, } - if r.Type == "TXT" && len(in.TxtStrings) == 1 { + if r.Type == "TXT" { // HACK: HETZNER rejects values that fit into 255 bytes w/o quotes, // but do not fit w/ added quotes (via GetTargetCombined()). // Sending the raw, non-quoted value works for the comprehensive @@ -71,7 +71,7 @@ func fromRecordConfig(in *models.RecordConfig, zone *zone) record { // The HETZNER validation does not provide helpful error messages. // {"error":{"message":"422 Unprocessable Entity: missing: ; ","code":422}} // Last checked: 2023-04-01 - valueNotQuoted := in.TxtStrings[0] + valueNotQuoted := in.GetTargetField() if len(valueNotQuoted) == 254 || len(valueNotQuoted) == 255 { r.Value = valueNotQuoted } diff --git a/providers/hexonet/records.go b/providers/hexonet/records.go index 9102f3990..302c39c18 100644 --- a/providers/hexonet/records.go +++ b/providers/hexonet/records.go @@ -256,7 +256,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.TxtStrings) + record.Answer = encodeTxt([]string{rc.GetTargetField()}) 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')") diff --git a/providers/vultr/vultrProvider.go b/providers/vultr/vultrProvider.go index c493e41c9..4ea485818 100644 --- a/providers/vultr/vultrProvider.go +++ b/providers/vultr/vultrProvider.go @@ -356,7 +356,7 @@ func toVultrRecord(dc *models.DomainConfig, rc *models.RecordConfig, vultrID str // Vultr doesn't permit TXT strings to include double-quotes // therefore, we don't have to escape interior double-quotes. // Vultr's API requires the string to begin and end with double-quotes. - r.Data = `"` + strings.Join(rc.TxtStrings, "") + `"` + r.Data = `"` + rc.GetTargetField() + `"` default: }