diff --git a/pkg/js/js_test.go b/pkg/js/js_test.go index 00b913922..b3ab7ded6 100644 --- a/pkg/js/js_test.go +++ b/pkg/js/js_test.go @@ -118,7 +118,7 @@ func TestParsedFiles(t *testing.T) { } else { zoneFile = filepath.Join(testDir, testName, dc.Name+".zone") } - fmt.Printf("DEBUG: zonefile = %q\n", zoneFile) + //fmt.Printf("DEBUG: zonefile = %q\n", zoneFile) expectedZone, err := os.ReadFile(zoneFile) if err != nil { continue diff --git a/pkg/rtypecontrol/import.go b/pkg/rtypecontrol/import.go index cceb02663..218a04a2e 100644 --- a/pkg/rtypecontrol/import.go +++ b/pkg/rtypecontrol/import.go @@ -18,16 +18,20 @@ func ImportRawRecords(domains []*models.DomainConfig) error { if err != nil { return err } - // TODO(tlim): Check if rec.Name might be a typo of dc.Name. But not if meta["skip_fqdn_check"]=="true" - // See "validate.go" - /* - } - if label == domain || strings.HasSuffix(label, "."+domain) { - if m := meta["skip_fqdn_check"]; m != "true" { - return errors.New(errorRepeat(label, domain)) - } - - */ + if rec.Metadata["skip_fqdn_check"] != "true" && stutters(rec.Name, dc.Name) { + var shortname string + if rec.Name == dc.Name { + shortname = "@" + } else { + shortname = strings.TrimSuffix(rec.Name, "."+dc.Name) + } + return fmt.Errorf( + "The name %q is an error (repeats the domain). Maybe instead of %q you intended %q? If not add DISABLE_REPEATED_DOMAIN_CHECK to this record to disable this check", + rec.NameFQDNRaw, + rec.NameRaw, + shortname, + ) + } // Free memeory: clear(rawRec.Args) @@ -41,6 +45,16 @@ func ImportRawRecords(domains []*models.DomainConfig) error { return nil } +func stutters(name, domain string) bool { + if name == "@" { + return false + } + if name == domain || strings.HasSuffix(name, "."+domain) { + return true + } + return false +} + // NewRecordConfigFromRaw creates a new RecordConfig from the raw ([]any) args, // usually from the parsed dnsconfig.js file, but also useful when a provider // returns the fields of a record as individual values. diff --git a/pkg/rtypecontrol/import_test.go b/pkg/rtypecontrol/import_test.go new file mode 100644 index 000000000..50256ce1a --- /dev/null +++ b/pkg/rtypecontrol/import_test.go @@ -0,0 +1,59 @@ +package rtypecontrol + +import "testing" + +func Test_stutters(t *testing.T) { + tests := []struct { + name string + rName string + want bool + }{ + { + name: "@ symbol should not stutter", + rName: "@", + want: false, + }, + { + name: "exact domain match should stutter", + rName: "example.com", + want: true, + }, + { + name: "subdomain with dot prefix should stutter", + rName: "www.example.com", + want: true, + }, + { + name: "simple subdomain should not stutter", + rName: "www", + want: false, + }, + { + name: "partial match without dot should not stutter", + rName: "testexample.com", + want: false, + }, + { + name: "empty name should not stutter", + rName: "", + want: false, + }, + { + name: "nested subdomain should stutter", + rName: "api.staging.example.com", + want: true, + }, + { + name: "different domain should not stutter", + rName: "example.org", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := stutters(tt.rName, "example.com"); got != tt.want { + t.Errorf("stutters(%q, %q) = %v, want %v", tt.rName, "example.com", got, tt.want) + } + }) + } +} diff --git a/pkg/rtypecontrol/setrecordnames.go b/pkg/rtypecontrol/setrecordnames.go index 796e4128b..3593984b5 100644 --- a/pkg/rtypecontrol/setrecordnames.go +++ b/pkg/rtypecontrol/setrecordnames.go @@ -8,6 +8,11 @@ import ( "github.com/StackExchange/dnscontrol/v4/pkg/domaintags" ) +// This code defines many variables to make the logic easier to read. The Go optimizer +// should eliminate any performance impact. +// We could probably fold some of the logic together, but it would be harder to read. +// It's difficult enough to understand it as-is, so clarity is preferred. + // setRecordNames uses n to update the .Name* fields. If the name is a FQDN // (ends with a "."), it will be handled accordingly. However if it does not // match the domain name, no error is returned but rec.Name* fields will end @@ -100,8 +105,12 @@ func setRecordNamesNonExtend(rec *models.RecordConfig, dcn *domaintags.DomainNam } func setRecordNamesExtend(rec *models.RecordConfig, dcn *domaintags.DomainNameVarieties, n string) error { - // NB(tlim): What's important to remember is that the domain is the parent D(), not D_EXTEND(). - // That is... dcn.NameASCII, not rec.SubDomain+dcn.NameASCII. + // NB(tlim): When a record has a subdomain "foo" and domain "example.com", a + // record such as "www" is added as "www.foo" (short name) or + // "www.foo.example.com" (FQDN name). + // When generating the shortname, we are truncating the "D()" name, not the + // D_EXTEND() name. That is... dcn.NameASCII, not + // rec.SubDomain+dcn.NameASCII. nRaw := n nASCII := domaintags.EfficientToASCII(n)