diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go index 6b31a10cc..c61ea6349 100644 --- a/integrationTest/integration_test.go +++ b/integrationTest/integration_test.go @@ -1088,7 +1088,7 @@ func makeTests(t *testing.T) []*TestGroup { tc("TXT with 1 backtick", txt("foobt", "blah`blah")), tc("TXT with 1 double-quotes", txt("foodq", `quo"te`)), tc("TXT with 2 double-quotes", txt("foodqs", `q"uo"te`)), - tc("TXT with 1 backslash", txt("fooosbs", `backs\lash`)), + tc("TXT with 1 backslash", txt("fooosbs", `back\slash`)), clear(), tc("TXT interior ws", txt("foosp", "with spaces")), diff --git a/pkg/rejectif/txt.go b/pkg/rejectif/txt.go index ba0fbec88..0a7c1db30 100644 --- a/pkg/rejectif/txt.go +++ b/pkg/rejectif/txt.go @@ -9,18 +9,18 @@ import ( // Keep these in alphabetical order. -// TxtHasBackticks audits TXT records for strings that contain backticks. -func TxtHasBackticks(rc *models.RecordConfig) error { - if strings.Contains(rc.GetTargetField(), "`") { - return fmt.Errorf("txtstring contains backtick") +// TxtHasBackslash audits TXT records for strings that contains one or more backslashes. +func TxtHasBackslash(rc *models.RecordConfig) error { + if strings.Contains(rc.GetTargetField(), `\`) { + return fmt.Errorf("txtstring contains backslashes") } return nil } -// TxtHasSingleQuotes audits TXT records for strings that contain single-quotes. -func TxtHasSingleQuotes(rc *models.RecordConfig) error { - if strings.Contains(rc.GetTargetField(), "'") { - return fmt.Errorf("txtstring contains single-quotes") +// TxtHasBackticks audits TXT records for strings that contain backticks. +func TxtHasBackticks(rc *models.RecordConfig) error { + if strings.Contains(rc.GetTargetField(), "`") { + return fmt.Errorf("txtstring contains backtick") } return nil } @@ -33,15 +33,6 @@ func TxtHasDoubleQuotes(rc *models.RecordConfig) error { 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 { - 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 { if len(rc.GetTargetField()) > 255 { @@ -50,10 +41,10 @@ func TxtHasSegmentLen256orLonger(rc *models.RecordConfig) error { return nil } -// TxtLongerThan255 audits TXT records for multiple strings -func TxtLongerThan255(rc *models.RecordConfig) error { - if len(rc.GetTargetField()) > 255 { - return fmt.Errorf("multiple strings in one txt") +// TxtHasSingleQuotes audits TXT records for strings that contain single-quotes. +func TxtHasSingleQuotes(rc *models.RecordConfig) error { + if strings.Contains(rc.GetTargetField(), "'") { + return fmt.Errorf("txtstring contains single-quotes") } return nil } @@ -67,6 +58,14 @@ func TxtHasTrailingSpace(rc *models.RecordConfig) error { return nil } +// TxtHasUnpairedDoubleQuotes audits TXT records for strings that contain unpaired doublequotes. +func TxtHasUnpairedDoubleQuotes(rc *models.RecordConfig) error { + if strings.Count(rc.GetTargetField(), `"`)%2 == 1 { + return fmt.Errorf("txtstring contains unpaired doublequotes") + } + return nil +} + // TxtIsEmpty audits TXT records for empty strings. func TxtIsEmpty(rc *models.RecordConfig) error { if len(rc.GetTargetField()) == 0 { @@ -75,10 +74,19 @@ func TxtIsEmpty(rc *models.RecordConfig) error { return nil } -// TxtHasUnpairedDoubleQuotes audits TXT records for strings that contain unpaired doublequotes. -func TxtHasUnpairedDoubleQuotes(rc *models.RecordConfig) error { - if strings.Count(rc.GetTargetField(), `"`)%2 == 1 { - return fmt.Errorf("txtstring contains unpaired doublequotes") +// 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 { + if len(rc.GetTargetField()) == 255 { + return fmt.Errorf("txtstring length is 255") + } + return nil +} + +// TxtLongerThan255 audits TXT records for multiple strings +func TxtLongerThan255(rc *models.RecordConfig) error { + if len(rc.GetTargetField()) > 255 { + return fmt.Errorf("multiple strings in one txt") } return nil } diff --git a/providers/digitalocean/auditrecords.go b/providers/digitalocean/auditrecords.go index 49263f45c..e8c9b0df8 100644 --- a/providers/digitalocean/auditrecords.go +++ b/providers/digitalocean/auditrecords.go @@ -19,9 +19,11 @@ func AuditRecords(records []*models.RecordConfig) []error { a.Add("TXT", MaxLengthDO) // Last verified 2021-03-01 - 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.TxtHasBackslash) // Last verified 2023-11-12 + // The web portal rejects blackslashes too + + a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2023-11-12 + // The web portal rejects double quotes return a.Audit(records) } @@ -47,7 +49,6 @@ func MaxLengthDO(rc *models.RecordConfig) error { if len(rc.GetTargetField()) > 509 { return fmt.Errorf("encoded txt too long") } - // FIXME(tlim): Try replacing GetTargetField() with (2 + (3*len(rc.TxtStrings) - 1)) return nil } diff --git a/providers/digitalocean/digitaloceanProvider.go b/providers/digitalocean/digitaloceanProvider.go index 7ae78e34a..24c6298c1 100644 --- a/providers/digitalocean/digitaloceanProvider.go +++ b/providers/digitalocean/digitaloceanProvider.go @@ -10,6 +10,7 @@ import ( "github.com/StackExchange/dnscontrol/v4/models" "github.com/StackExchange/dnscontrol/v4/pkg/diff" + "github.com/StackExchange/dnscontrol/v4/pkg/printer" "github.com/StackExchange/dnscontrol/v4/providers" "github.com/digitalocean/godo" "github.com/miekg/dns/dnsutil" @@ -297,6 +298,7 @@ func toRc(domain string, r *godo.DomainRecord) *models.RecordConfig { t.SetLabelFromFQDN(name, domain) switch rtype := r.Type; rtype { case "TXT": + printer.Printf("DEBUG: DIGITAL TXT inbounds=%s q=%q\n", target, target) t.SetTargetTXT(target) default: t.SetTarget(target) @@ -322,6 +324,7 @@ func toReq(dc *models.DomainConfig, rc *models.RecordConfig) *godo.DomainRecordE case "TXT": // TXT records are the one place where DO combines many items into one field. target = rc.GetTargetTXTJoined() + printer.Printf("DEBUG: DIGITAL TXT outbounds=%s q=%q\n", target, target) default: // no action required }