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

DNSIMPLE: do not support unpaired double quotes in TXT (#1610)

* DNSIMPLE: do not support unpaired double quotes in TXT

DNSimple supports multiple double-quotes strings in a TXT record, but does not correctly support unpaired or escaped double-quotes currently.

IE the following are valid
```
asdf
"asdf"
"asdf" "asdf"
!@#$ %^&*()([][{}{<></'`:;-_=+\
```

however `as\"df` and `as"df` are not

This removes the extra string processing in getTargetRecordPriority as all tests pass without it and currently only double-quotes cause problems in our TXT validations.

I added another test to prove additional quoting is not needed. We can remove it if undesired.

Also applied small lint changes to make my editor happy.

* Use backticks to prevent escaping

* Set TXT target record content to GetTargetRFC1035Quoted()

Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
Amelia Aronsohn
2022-07-11 11:52:18 -07:00
committed by GitHub
parent 14ae3732b6
commit b0d80ae498
3 changed files with 15 additions and 33 deletions

View File

@ -905,6 +905,8 @@ func makeTests(t *testing.T) []*TestGroup {
tc("Create TXT with double-quote", txt("foodq", `quo"te`)), tc("Create TXT with double-quote", txt("foodq", `quo"te`)),
clear(), clear(),
tc("Create TXT with ws at end", txt("foows1", "with space at end ")), tc("Create TXT with ws at end", txt("foows1", "with space at end ")),
clear(),
tc("Create TXT with frequently escaped characters", txt("fooex", `!^.*$@#%^&()([][{}{<></:;-_=+\`)),
), ),
// //

View File

@ -2,10 +2,14 @@ package dnsimple
import ( import (
"github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
) )
// AuditRecords returns an error if any records are not // AuditRecords returns an error if any records are not
// supportable by this provider. // supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error { func AuditRecords(records []*models.RecordConfig) error {
if err := recordaudit.TxtNoDoubleQuotes(records); err != nil {
return err
}
return nil return nil
} }

View File

@ -61,7 +61,7 @@ type dnsimpleProvider struct {
} }
// GetNameservers returns the name servers for a domain. // GetNameservers returns the name servers for a domain.
func (c *dnsimpleProvider) GetNameservers(domainName string) ([]*models.Nameserver, error) { func (c *dnsimpleProvider) GetNameservers(_ string) ([]*models.Nameserver, error) {
return models.ToNameservers(defaultNameServerNames) return models.ToNameservers(defaultNameServerNames)
} }
@ -130,7 +130,7 @@ func (c *dnsimpleProvider) GetZoneRecords(domain string) (models.Records, error)
// GetDomainCorrections returns corrections that update a domain. // GetDomainCorrections returns corrections that update a domain.
func (c *dnsimpleProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (c *dnsimpleProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
corrections := []*models.Correction{} var corrections []*models.Correction
err := dc.Punycode() err := dc.Punycode()
if err != nil { if err != nil {
return nil, err return nil, err
@ -201,7 +201,7 @@ func removeApexNS(records models.Records) models.Records {
// GetRegistrarCorrections returns corrections that update a domain's registrar. // GetRegistrarCorrections returns corrections that update a domain's registrar.
func (c *dnsimpleProvider) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (c *dnsimpleProvider) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
corrections := []*models.Correction{} var corrections []*models.Correction
nameServers, err := c.getNameservers(dc.Name) nameServers, err := c.getNameservers(dc.Name)
if err != nil { if err != nil {
@ -211,7 +211,7 @@ func (c *dnsimpleProvider) GetRegistrarCorrections(dc *models.DomainConfig) ([]*
actual := strings.Join(nameServers, ",") actual := strings.Join(nameServers, ",")
expectedSet := []string{} var expectedSet []string
for _, ns := range dc.Nameservers { for _, ns := range dc.Nameservers {
expectedSet = append(expectedSet, ns.Name) expectedSet = append(expectedSet, ns.Name)
} }
@ -298,7 +298,7 @@ func (c *dnsimpleProvider) getRecords(domainName string) ([]dnsimpleapi.ZoneReco
} }
opts := &dnsimpleapi.ZoneRecordListOptions{} opts := &dnsimpleapi.ZoneRecordListOptions{}
recs := []dnsimpleapi.ZoneRecord{} var recs []dnsimpleapi.ZoneRecord
page := 1 page := 1
for { for {
opts.Page = &page opts.Page = &page
@ -540,7 +540,7 @@ func newDsp(conf map[string]string, metadata json.RawMessage) (providers.DNSServ
return newProvider(conf, metadata) return newProvider(conf, metadata)
} }
func newProvider(m map[string]string, metadata json.RawMessage) (*dnsimpleProvider, error) { func newProvider(m map[string]string, _ json.RawMessage) (*dnsimpleProvider, error) {
api := &dnsimpleProvider{} api := &dnsimpleProvider{}
api.AccountToken = m["token"] api.AccountToken = m["token"]
if api.AccountToken == "" { if api.AccountToken == "" {
@ -584,21 +584,15 @@ func getTargetRecordContent(rc *models.RecordConfig) string {
case "DS": case "DS":
return fmt.Sprintf("%d %d %d %s", rc.DsKeyTag, rc.DsAlgorithm, rc.DsDigestType, rc.DsDigest) return fmt.Sprintf("%d %d %d %s", rc.DsKeyTag, rc.DsAlgorithm, rc.DsDigestType, rc.DsDigest)
case "NAPTR": case "NAPTR":
return fmt.Sprintf("%d %d %s %s %s %s", return fmt.Sprintf(`%d %d "%s" "%s" "%s" %s`,
rc.NaptrOrder, rc.NaptrPreference, rc.NaptrOrder, rc.NaptrPreference, rc.NaptrFlags, rc.NaptrService, rc.NaptrRegexp,
quoteDNSString(rc.NaptrFlags), quoteDNSString(rc.NaptrService),
quoteDNSString(rc.NaptrRegexp),
rc.GetTargetField()) rc.GetTargetField())
case "SSHFP": case "SSHFP":
return fmt.Sprintf("%d %d %s", rc.SshfpAlgorithm, rc.SshfpFingerprint, rc.GetTargetField()) return fmt.Sprintf("%d %d %s", rc.SshfpAlgorithm, rc.SshfpFingerprint, rc.GetTargetField())
case "SRV": case "SRV":
return fmt.Sprintf("%d %d %s", rc.SrvWeight, rc.SrvPort, rc.GetTargetField()) return fmt.Sprintf("%d %d %s", rc.SrvWeight, rc.SrvPort, rc.GetTargetField())
case "TXT": case "TXT":
quoted := make([]string, len(rc.TxtStrings)) return rc.GetTargetRFC1035Quoted()
for i := range rc.TxtStrings {
quoted[i] = quoteDNSString(rc.TxtStrings[i])
}
return strings.Join(quoted, " ")
default: default:
return rc.GetTargetField() return rc.GetTargetField()
} }
@ -618,21 +612,3 @@ func getTargetRecordPriority(rc *models.RecordConfig) int {
return 0 return 0
} }
} }
// Return a DNS string appropriately escaped for DNSimple.
// Should include the surrounding quotes.
//
// Warning: the DNSimple API is severely underdocumented in this area.
// I know that it takes multiple quoted strings just fine, and constructs the
// DNS multiple quoted items.
// I'm not 100% on the escaping, but since it's a JSON API, JSON escaping seems
// reasonable.
// I do know that DNSimple have their own checks, so anything too crazy will
// get a "400 Validation failed" HTTP response.
func quoteDNSString(unquoted string) string {
b, err := json.Marshal(unquoted)
if err != nil {
panic(fmt.Errorf("unable to marshal to JSON: %q", unquoted))
}
return string(b)
}