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

REFACTOR: providers should not directly access .TxtStrings

This commit is contained in:
Tom Limoncelli
2023-11-18 14:04:59 -05:00
parent 8040e7bba2
commit c7ed607ef9
18 changed files with 48 additions and 29 deletions

View File

@ -157,6 +157,19 @@ func (rc *RecordConfig) GetTargetTXTSegmented() []string {
return splitChunks(strings.Join(rc.TxtStrings, ""), 255)
}
// GetTargetTXTSegmentCount returns the number of 255-octet segments required to store TXT target.
func (rc *RecordConfig) GetTargetTXTSegmentCount() int {
var total int
for i := range rc.TxtStrings {
total = len(rc.TxtStrings[i])
}
segs := total / 255 // integer division, decimals are truncated
if (total % 255) > 0 {
return segs + 1
}
return segs
}
func splitChunks(buf string, lim int) []string {
var chunk string
chunks := make([]string, 0, len(buf)/lim+1)

View File

@ -327,8 +327,8 @@ 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.GetTargetTXTSegmentCount() == 1 &&
last.GetTargetTXTSegmented()[0] == dnssecDummyTxt {
c.hasDnssecRecords = true
foundRecords = foundRecords[0:(len(foundRecords) - 1)]
}

View File

@ -525,9 +525,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] == "") {
if !(rec.GetTargetTXTSegmentCount() == 1 && rec.GetTargetTXTSegmented()[0] == "") {
var txts []*string
for _, txt := range rec.TxtStrings {
for _, txt := range rec.GetTargetTXTSegmented() {
txts = append(txts, to.StringPtr(txt))
}
recordSet.Properties.TxtRecords = append(recordSet.Properties.TxtRecords, &adns.TxtRecord{Value: txts})

View File

@ -132,7 +132,7 @@ func makePurge(domainname string, cor diff.Correlation) zoneResourceRecordEdit {
switch cor.Existing.Type {
case "TXT":
existingTarget = strings.Join(cor.Existing.TxtStrings, "")
existingTarget = cor.Existing.GetTargetTXTJoined()
default:
existingTarget = cor.Existing.GetTargetField()
}
@ -159,7 +159,7 @@ func makeAdd(domainname string, cre diff.Correlation) zoneResourceRecordEdit {
var recTarget string
switch rec.Type {
case "TXT":
recTarget = strings.Join(rec.TxtStrings, "")
recTarget = rec.GetTargetTXTJoined()
default:
recTarget = rec.GetTargetField()
}
@ -185,7 +185,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.GetTargetTXTJoined()
default: // "A", "CNAME", "NS"
// Nothing to do.
}
@ -201,8 +201,8 @@ func makeEdit(domainname string, m diff.Correlation) zoneResourceRecordEdit {
var oldTarget, recTarget string
switch old.Type {
case "TXT":
oldTarget = strings.Join(old.TxtStrings, "")
recTarget = strings.Join(rec.TxtStrings, "")
oldTarget = old.GetTargetTXTJoined()
recTarget = rec.GetTargetTXTJoined()
default:
oldTarget = old.GetTargetField()
recTarget = rec.GetTargetField()

View File

@ -44,10 +44,9 @@ func MaxLengthDO(rc *models.RecordConfig) error {
// In other words, they're doing the checking on the API protocol
// encoded data instead of on on the resulting TXT record. Sigh.
if len(rc.GetTargetField()) > 509 {
if len(rc.GetTargetRFC1035Quoted()) > 509 {
return fmt.Errorf("encoded txt too long")
}
// FIXME(tlim): Try replacing GetTargetField() with (2 + (3*len(rc.TxtStrings) - 1))
return nil
}

View File

@ -30,7 +30,7 @@ func (api *domainNameShopProvider) GetZoneRecordsCorrections(dc *models.DomainCo
// Merge TXT strings to one string
for _, rc := range dc.Records {
if rc.HasFormatIdenticalToTXT() {
rc.SetTargetTXT(strings.Join(rc.TxtStrings, ""))
rc.SetTargetTXT(rc.GetTargetTXTJoined())
}
}

View File

@ -86,7 +86,7 @@ func recordsToNative(rcs []*models.RecordConfig, expectedKey models.RecordKey) *
}
case "TXT": // Avoid double quoting for TXT records
rr = dnssdk.ResourceRecord{
Content: convertTxtSliceToSdkAnySlice(r.TxtStrings),
Content: convertTxtSliceToSdkAnySlice(r.GetTargetTXTSegmented()),
Meta: nil,
Enabled: true,
}

View File

@ -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" && (in.GetTargetTXTSegmentCount() == 1) {
// 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.GetTargetTXTSegmented()[0]
if len(valueNotQuoted) == 254 || len(valueNotQuoted) == 255 {
r.Value = valueNotQuoted
}

View File

@ -242,7 +242,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(rc.GetTargetTXTSegmented())
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')")
@ -267,7 +267,7 @@ func (n *HXClient) deleteRecordString(record *HXRecord, domain string) string {
return record.Raw
}
// encodeTxt encodes TxtStrings for sending in the CREATE/MODIFY API:
// encodeTxt encodes []string for sending in the CREATE/MODIFY API:
func encodeTxt(txts []string) string {
var r []string
for _, txt := range txts {

View File

@ -193,8 +193,9 @@ func recordToNative(rc *models.RecordConfig) *record {
case "A", "AAAA", "ALIAS", "CAA", "CNAME", "DNSKEY", "DS", "NS", "NSEC", "NSEC3", "NSEC3PARAM", "PTR", "RRSIG", "SSHFP", "TSLA":
// Nothing special.
case "TXT":
txtStrings := make([]string, len(rc.TxtStrings))
copy(txtStrings, rc.TxtStrings)
// TODO(tlim): Move this to a function with unit tests.
txtStrings := make([]string, rc.GetTargetTXTSegmentCount())
copy(txtStrings, rc.GetTargetTXTSegmented())
// Escape quotes
for i := range txtStrings {

View File

@ -213,9 +213,10 @@ func (api *inwxAPI) deleteRecord(RecordID int) error {
// checkRecords ensures that there is no single-quote inside TXT records which would be ignored by INWX.
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.TxtStrings {
for _, target := range r.GetTargetTXTSegmented() {
if strings.ContainsAny(target, "`") {
return fmt.Errorf("INWX TXT records do not support single-quotes in their target")
}

View File

@ -2,6 +2,7 @@ package loopia
import (
"fmt"
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/rejectif"
)
@ -24,7 +25,7 @@ func AuditRecords(records []*models.RecordConfig) []error {
// TxtHasSegmentLen450orLonger audits TXT records for strings that are >450 octets.
func TxtHasSegmentLen450orLonger(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
for _, txt := range rc.GetTargetTXTSegmented() {
if len(txt) > 450 {
return fmt.Errorf("%q txtstring length > 450", rc.GetLabel())
}

View File

@ -316,7 +316,7 @@ func generatePSCreate(dnsserver, domain string, rec *models.RecordConfig) string
//case "WKS":
// fmt.Fprintf(&b, ` -Wks -InternetAddress <IPAddress> -InternetProtocol {UDP | TCP} -Service <String[]>`, rec.GetTargetField())
case "TXT":
//printer.Printf("DEBUG TXT len = %v\n", rec.TxtStrings)
//printer.Printf("DEBUG TXT len = %v\n", rec.GetTargetTXTSegmentCount())
//printer.Printf("DEBUG TXT target = %q\n", rec.GetTargetField())
fmt.Fprintf(&b, ` -Txt -DescriptiveText %q`, rec.GetTargetTXTJoined())
//case "RT":

View File

@ -29,18 +29,19 @@ func AuditRecords(records []*models.RecordConfig) []error {
// are longer than permitted by NDC. Sadly their
// length limit is undocumented. This seems to work.
func MaxLengthNDC(rc *models.RecordConfig) error {
if len(rc.TxtStrings) == 0 {
txtStrings := rc.GetTargetTXTSegmented()
if len(txtStrings) == 0 {
return nil
}
sum := 2 // Count the start and end quote.
// Add the length of each segment.
for _, segment := range rc.TxtStrings {
for _, segment := range txtStrings {
sum += len(segment) // The length of each segment
sum += strings.Count(segment, `"`) // Add 1 for any char to be escaped
}
// Add 3 (quote space quote) for each interior join.
sum += 3 * (len(rc.TxtStrings) - 1)
sum += 3 * (len(txtStrings) - 1)
if sum > 512 {
return fmt.Errorf("encoded txt too long")

View File

@ -154,7 +154,7 @@ func (n *namedotcomProvider) createRecord(rc *models.RecordConfig, domain string
case "A", "AAAA", "ANAME", "CNAME", "MX", "NS":
// nothing
case "TXT":
// record.Answer = encodeTxt(rc.TxtStrings)
// nothing
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')")

View File

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

View File

@ -173,7 +173,10 @@ func (s *softlayerProvider) getExistingRecords(domain *datatypes.Dns_Domain) (mo
}
recConfig.SetLabel(fmt.Sprintf("%s.%s", service, strings.ToLower(protocol)), *domain.Name)
case "TXT":
recConfig.TxtStrings = append(recConfig.TxtStrings, *record.Data)
// OLD: recConfig.TxtStrings = append(recConfig.TxtStrings, *record.Data)
recConfig.SetTargetTXTs(append(recConfig.GetTargetTXTSegmented(), *record.Data))
// NB(tlim) The above code seems too complex. Can it be simplied to this?
// recConfig.SetTargetTXT(*record.Data)
fallthrough
case "MX":
if record.MxPriority != nil {

View File

@ -314,7 +314,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 = `"` + strings.Join(rc.GetTargetTXTSegmented(), "") + `"`
default:
}