From 654736be292c62ee25f5f95c6c1a60e420523cb0 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Thu, 24 Jun 2021 18:26:21 -0400 Subject: [PATCH] Improve MSDNS naptr support (#1165) * MSDNS: Improve reliability of zone dump * Update tests * MSDNS: Add initial NAPTR support * Update * fix tests * fix tests * Fixing integration tests for NAPTR * Handle bad JSON. Handle NAPTR TTLs --- commands/getZones.go | 20 ++++ integrationTest/integration_test.go | 2 +- models/t_naptr.go | 3 + models/target.go | 9 +- pkg/js/parse_tests/035-naptr.js | 4 +- pkg/js/parse_tests/035-naptr.json | 46 ++++++--- pkg/js/parse_tests/035-naptr/foo.com.zone | 6 +- pkg/normalize/validate.go | 6 +- providers/msdns/convert.go | 3 + providers/msdns/escape.go | 7 ++ providers/msdns/msdnsProvider.go | 1 + providers/msdns/naptr.go | 110 ++++++++++++++++++++++ providers/msdns/naptr_test.go | 30 ++++++ providers/msdns/powershell.go | 86 ++++++++++++++--- providers/msdns/powershell_test.go | 8 +- 15 files changed, 305 insertions(+), 36 deletions(-) create mode 100644 providers/msdns/escape.go create mode 100644 providers/msdns/naptr.go create mode 100644 providers/msdns/naptr_test.go diff --git a/commands/getZones.go b/commands/getZones.go index b37dfff80..c95a8222d 100644 --- a/commands/getZones.go +++ b/commands/getZones.go @@ -1,6 +1,7 @@ package commands import ( + "encoding/json" "fmt" "os" "strings" @@ -249,6 +250,16 @@ func GetZone(args GetZoneArgs) error { return nil } +// jsonQuoted returns a properly escaped JSON string (without quotes). +func jsonQuoted(i string) string { + // https://stackoverflow.com/questions/51691901 + b, err := json.Marshal(i) + if err != nil { + panic(err) + } + return string(b) +} + func formatDsl(zonename string, rec *models.RecordConfig, defaultTTL uint32) string { target := rec.GetTargetCombined() @@ -272,6 +283,15 @@ func formatDsl(zonename string, rec *models.RecordConfig, defaultTTL uint32) str return makeCaa(rec, ttlop) case "MX": target = fmt.Sprintf("%d, '%s'", rec.MxPreference, rec.GetTargetField()) + case "NAPTR": + target = fmt.Sprintf(`%d, %d, %s, %s, %s, %s`, + rec.NaptrOrder, // 1 + rec.NaptrPreference, // 10 + jsonQuoted(rec.NaptrFlags), // U + jsonQuoted(rec.NaptrService), // E2U+sip + jsonQuoted(rec.NaptrRegexp), // regex + jsonQuoted(rec.GetTargetField()), // . + ) case "SSHFP": target = fmt.Sprintf("%d, %d, '%s'", rec.SshfpAlgorithm, rec.SshfpFingerprint, rec.GetTargetField()) case "SOA": diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go index 5c4ccfb64..d7a45e0f1 100644 --- a/integrationTest/integration_test.go +++ b/integrationTest/integration_test.go @@ -218,7 +218,7 @@ func makeChanges(t *testing.T, prv providers.DNSServiceProvider, dc *models.Doma if len(corrections) != 0 { t.Logf("Expected 0 corrections on second run, but found %d.", len(corrections)) for i, c := range corrections { - t.Logf("#%d: %s", i, c.Msg) + t.Logf("UNEXPECTED #%d: %s", i, c.Msg) } t.FailNow() } diff --git a/models/t_naptr.go b/models/t_naptr.go index 9aae5d361..24517dc62 100644 --- a/models/t_naptr.go +++ b/models/t_naptr.go @@ -8,6 +8,9 @@ import ( // SetTargetNAPTR sets the NAPTR fields. func (rc *RecordConfig) SetTargetNAPTR(order uint16, preference uint16, flags string, service string, regexp string, target string) error { + if target == "" { + target = "." + } rc.NaptrOrder = order rc.NaptrPreference = preference rc.NaptrFlags = flags diff --git a/models/target.go b/models/target.go index ffe8401bb..faa32b294 100644 --- a/models/target.go +++ b/models/target.go @@ -55,14 +55,16 @@ func (rc *RecordConfig) GetTargetCombined() string { case "AZURE_ALIAS": // Differentiate between multiple AZURE_ALIASs on the same label. return fmt.Sprintf("%s atype=%s", rc.target, rc.AzureAlias["type"]) - case "SOA": - return fmt.Sprintf("%s %v %d %d %d %d %d", rc.target, rc.SoaMbox, rc.SoaSerial, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl) default: // Just return the target. return rc.target } } + if rc.Type == "SOA" { + return fmt.Sprintf("%s %v %d %d %d %d %d", rc.target, rc.SoaMbox, rc.SoaSerial, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl) + } + return rc.zoneFileQuoted() } @@ -73,6 +75,9 @@ func (rc *RecordConfig) zoneFileQuoted() string { // Sadly String() always includes a header, which we must strip out. // TODO(tlim): Request the dns project add a function that returns // the string without the header. + if rc.Type == "NAPTR" && rc.GetTargetField() == "" { + rc.SetTarget(".") + } rr := rc.ToRR() header := rr.Header().String() full := rr.String() diff --git a/pkg/js/parse_tests/035-naptr.js b/pkg/js/parse_tests/035-naptr.js index b8cdc9529..a44181f55 100644 --- a/pkg/js/parse_tests/035-naptr.js +++ b/pkg/js/parse_tests/035-naptr.js @@ -1,4 +1,6 @@ D("foo.com","none", NAPTR("@",100,10,"U","E2U+sip","!^.*$!sip:customer-service@example.com!","example"), - NAPTR("@",102,10,"U","E2U+email","!^.*$!mailto:information@example.com!","example") + NAPTR("@",102,10,"U","E2U+email","!^.*$!mailto:information@example.com!","example"), + NAPTR("@",103,10,"U","E2U+email","!^.*$!mailto:information@example.com!",""), + NAPTR("@",104,10,"U","E2U+email","!^.*$!mailto:information@example.com!",".") ); diff --git a/pkg/js/parse_tests/035-naptr.json b/pkg/js/parse_tests/035-naptr.json index 87736f352..dc84f0d58 100644 --- a/pkg/js/parse_tests/035-naptr.json +++ b/pkg/js/parse_tests/035-naptr.json @@ -1,33 +1,53 @@ { - "registrars": [], "dns_providers": [], "domains": [ { - "name": "foo.com", - "registrar": "none", "dnsProviders": {}, + "name": "foo.com", "records": [ { - "type": "NAPTR", "name": "@", - "target": "example", + "naptrflags": "U", "naptrorder": 100, "naptrpreference": 10, - "naptrflags": "U", + "naptrregexp": "!^.*$!sip:customer-service@example.com!", "naptrservice": "E2U+sip", - "naptrregexp": "!^.*$!sip:customer-service@example.com!" + "target": "example", + "type": "NAPTR" }, { - "type": "NAPTR", "name": "@", - "target": "example", + "naptrflags": "U", "naptrorder": 102, "naptrpreference": 10, - "naptrflags": "U", + "naptrregexp": "!^.*$!mailto:information@example.com!", "naptrservice": "E2U+email", - "naptrregexp": "!^.*$!mailto:information@example.com!" + "target": "example", + "type": "NAPTR" + }, + { + "name": "@", + "naptrflags": "U", + "naptrorder": 103, + "naptrpreference": 10, + "naptrregexp": "!^.*$!mailto:information@example.com!", + "naptrservice": "E2U+email", + "target": "", + "type": "NAPTR" + }, + { + "name": "@", + "naptrflags": "U", + "naptrorder": 104, + "naptrpreference": 10, + "naptrregexp": "!^.*$!mailto:information@example.com!", + "naptrservice": "E2U+email", + "target": ".", + "type": "NAPTR" } - ] + ], + "registrar": "none" } - ] + ], + "registrars": [] } diff --git a/pkg/js/parse_tests/035-naptr/foo.com.zone b/pkg/js/parse_tests/035-naptr/foo.com.zone index 4b3db8ef1..ab6ac114f 100644 --- a/pkg/js/parse_tests/035-naptr/foo.com.zone +++ b/pkg/js/parse_tests/035-naptr/foo.com.zone @@ -1,3 +1,5 @@ $TTL 300 -@ IN NAPTR 100 10 "U" "E2U+sip" "!^.*$!sip:customer-service@example.com!" example.foo.com. - IN NAPTR 102 10 "U" "E2U+email" "!^.*$!mailto:information@example.com!" example.foo.com. +@ IN NAPTR 100 10 "U" "E2U+sip" "!^.*$!sip:customer-service@example.com!" example + IN NAPTR 102 10 "U" "E2U+email" "!^.*$!mailto:information@example.com!" example + IN NAPTR 103 10 "U" "E2U+email" "!^.*$!mailto:information@example.com!" . + IN NAPTR 104 10 "U" "E2U+email" "!^.*$!mailto:information@example.com!" . diff --git a/pkg/normalize/validate.go b/pkg/normalize/validate.go index 3852188b9..be7822913 100644 --- a/pkg/normalize/validate.go +++ b/pkg/normalize/validate.go @@ -180,7 +180,9 @@ func checkTargets(rec *models.RecordConfig, domain string) (errs []error) { case "PTR": check(checkTarget(target)) case "NAPTR": - check(checkTarget(target)) + if target != "" { + check(checkTarget(target)) + } case "ALIAS": check(checkTarget(target)) case "SOA": @@ -345,7 +347,7 @@ func ValidateAndNormalizeConfig(config *models.DNSConfig) (errs []error) { } // Canonicalize Targets. - if rec.Type == "CNAME" || rec.Type == "MX" || rec.Type == "NAPTR" || rec.Type == "NS" || rec.Type == "SRV" { + if rec.Type == "CNAME" || rec.Type == "MX" || rec.Type == "NS" || rec.Type == "SRV" { // #rtype_variations // These record types have a target that is a hostname. // We normalize them to a FQDN so there is less variation to handle. If a diff --git a/providers/msdns/convert.go b/providers/msdns/convert.go index 0cd3ab6ea..1f89eb35e 100644 --- a/providers/msdns/convert.go +++ b/providers/msdns/convert.go @@ -90,6 +90,9 @@ func nativeToRecords(nr nativeRecord, origin string) (*models.RecordConfig, erro rc.SetTargetMX(uint16(uprops["Preference"]), sprops["MailExchange"]) case "NS": rc.SetTarget(sprops["NameServer"]) + case "NAPTR": + n := decodeRecordDataNaptr(sprops["Data"]) + rc.SetTargetNAPTR(n.NaptrOrder, n.NaptrPreference, n.NaptrFlags, n.NaptrService, n.NaptrRegexp, n.GetTargetField()) case "PTR": rc.SetTarget(sprops["PtrDomainName"]) case "SRV": diff --git a/providers/msdns/escape.go b/providers/msdns/escape.go new file mode 100644 index 000000000..b3bdcbe4c --- /dev/null +++ b/providers/msdns/escape.go @@ -0,0 +1,7 @@ +package msdns + +import "strings" + +func escapePS(s string) string { + return `'` + strings.Replace(s, `'`, `"`, -1) + `'` +} diff --git a/providers/msdns/msdnsProvider.go b/providers/msdns/msdnsProvider.go index 99d6096fb..2a0808d37 100644 --- a/providers/msdns/msdnsProvider.go +++ b/providers/msdns/msdnsProvider.go @@ -24,6 +24,7 @@ var features = providers.DocumentationNotes{ providers.CanUseAlias: providers.Cannot(), providers.CanUseCAA: providers.Cannot(), providers.CanUseDS: providers.Unimplemented(), + providers.CanUseNAPTR: providers.Can(), providers.CanUsePTR: providers.Can(), providers.CanUseSRV: providers.Can(), providers.CanUseTLSA: providers.Unimplemented(), diff --git a/providers/msdns/naptr.go b/providers/msdns/naptr.go new file mode 100644 index 000000000..169df7568 --- /dev/null +++ b/providers/msdns/naptr.go @@ -0,0 +1,110 @@ +package msdns + +// NAPTR records are not supported by the PowerShell module. +// Until this bug is fixed we use old-school commands instead. + +import ( + "bytes" + "encoding/hex" + "fmt" + "log" + "strconv" + + "github.com/StackExchange/dnscontrol/v3/models" +) + +func generatePSCreateNaptr(dnsServerName, domain string, rec *models.RecordConfig) string { + + var computername string + if dnsServerName != "" { + computername = escapePS(dnsServerName) + " " + } + + var b bytes.Buffer + fmt.Fprintf(&b, `$zoneName = %s ; `, escapePS(domain)) + fmt.Fprintf(&b, `$rrName = %s ; `, escapePS(rec.Name)) + fmt.Fprintf(&b, `$Order = %d ; `, rec.NaptrOrder) + fmt.Fprintf(&b, `$Preference = %d ; `, rec.NaptrPreference) + fmt.Fprintf(&b, `$Flags = %s ; `, escapePS(rec.NaptrFlags)) + fmt.Fprintf(&b, `$Service = %s ; `, escapePS(rec.NaptrService)) + fmt.Fprintf(&b, `$Regex = %s ; `, escapePS(rec.NaptrRegexp)) + fmt.Fprintf(&b, `$Replacement = %s ; `, escapePS(rec.GetTargetField())) + fmt.Fprintf(&b, `dnscmd %s/recordadd $zoneName $rrName %d naptr $Order $Preference $Flags $Service $Regex $Replacement ; `, computername, rec.TTL) + return b.String() +} + +func generatePSDeleteNaptr(dnsServerName, domain string, rec *models.RecordConfig) string { + target := rec.GetTargetField() + if target == "" { + target = "." + } + + var computername string + if dnsServerName != "" { + computername = escapePS(dnsServerName) + " " + } + + var b bytes.Buffer + fmt.Fprintf(&b, `$zoneName = %s ; `, escapePS(domain)) + fmt.Fprintf(&b, `$rrName = %s ; `, escapePS(rec.Name)) + fmt.Fprintf(&b, `$Order = %d ; `, rec.NaptrOrder) + fmt.Fprintf(&b, `$Preference = %d ; `, rec.NaptrPreference) + fmt.Fprintf(&b, `$Flags = %s ; `, escapePS(rec.NaptrFlags)) + fmt.Fprintf(&b, `$Service = %s ; `, escapePS(rec.NaptrService)) + fmt.Fprintf(&b, `$Regex = %s ; `, escapePS(rec.NaptrRegexp)) + fmt.Fprintf(&b, `$Replacement = %s ; `, escapePS(target)) + fmt.Fprintf(&b, `dnscmd %s/recorddelete $zoneName $rrName naptr $Order $Preference $Flags $Service $Regex $Replacement /f ; `, computername) + return b.String() +} + +// decoding + +func decodeRecordDataNaptr(s string) models.RecordConfig { + // These strings look like this: + // C8AFB0B30153075349502B4432540474657374165F7369702E5F7463702E6578616D706C652E6F72672E + // The first 2 groups of 16 bits (4 hex digits) are uinet16. + // The rest are 4 length-prefixed strings. + // The string should be entirely consumed. + rc := models.RecordConfig{} + + s, rc.NaptrOrder = eatUint16(s) + s, rc.NaptrPreference = eatUint16(s) + s, rc.NaptrFlags = eatString(s) + s, rc.NaptrService = eatString(s) + s, rc.NaptrRegexp = eatString(s) + s, targ := eatString(s) + rc.SetTarget(targ) + + // At this point we should have consumed the entire string. + if s != "" { + fmt.Printf("WARNING: REMAINDER:=%q\n", s) + } + + return rc +} + +// eatUint16 consumes the first 16 bits of the string, returns it as a +// uint16, and returns the remaining bytes of the string. +func eatUint16(s string) (string, uint16) { + value, err := strconv.ParseUint(s[2:4]+s[0:2], 16, 64) + if err != nil { + log.Fatal(err) + } + return s[4:], uint16(value) +} + +// eatString consumes an encoded string (8-bit length byte, then the string). +func eatString(s string) (string, string) { + sl, err := strconv.ParseUint(s[:2], 16, 64) + if err != nil { + log.Fatal(err) + } + last := 2 + sl*2 + hexcoded := s[2:last] + ret, err := hex.DecodeString(hexcoded) + if err != nil { + log.Fatal(err) + } + + return s[last:], string(ret) +} diff --git a/providers/msdns/naptr_test.go b/providers/msdns/naptr_test.go new file mode 100644 index 000000000..66dfea862 --- /dev/null +++ b/providers/msdns/naptr_test.go @@ -0,0 +1,30 @@ +package msdns + +import ( + "reflect" + "testing" + + "github.com/StackExchange/dnscontrol/v3/models" +) + +func Test_decodeRecordDataNaptr(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want models.RecordConfig + }{ + {"01", args{"C8AFB0B30153075349502B4432540474657374165F7369702E5F7463702E6578616D706C652E6F72672E"}, models.RecordConfig{NaptrOrder: 45000, NaptrPreference: 46000, NaptrFlags: "S", NaptrService: "SIP+D2T", NaptrRegexp: "test", Name: "_sip._tcp.example.org."}}, + } + for _, tt := range tests { + tt.want.SetTarget(tt.want.Name) + tt.want.Name = "" + t.Run(tt.name, func(t *testing.T) { + if got := decodeRecordDataNaptr(tt.args.s); !reflect.DeepEqual(got, tt.want) { + t.Errorf("decodeRecordDataNaptr() = %+v, want %+v", got, tt.want) + } + }) + } +} diff --git a/providers/msdns/powershell.go b/providers/msdns/powershell.go index 93add2cf5..582b5a1f0 100644 --- a/providers/msdns/powershell.go +++ b/providers/msdns/powershell.go @@ -73,7 +73,10 @@ func (psh *psHandle) GetDNSServerZoneAll(dnsserver string) ([]string, error) { } var zones []dnsZone - json.Unmarshal([]byte(stdout), &zones) + err = json.Unmarshal([]byte(stdout), &zones) + if err != nil { + return nil, err + } var result []string for _, z := range zones { @@ -110,11 +113,11 @@ func (psh *psHandle) GetDNSZoneRecords(dnsserver, domain string) ([]nativeRecord return nil, err } if stderr != "" { - fmt.Printf("STDERROR = %q\n", stderr) + fmt.Printf("STDERROR GetDNSZR = %q\n", stderr) return nil, fmt.Errorf("unexpected stderr from PSZoneDump: %q", stderr) } if stdout != "" { - fmt.Printf("STDOUT = %q\n", stdout) + fmt.Printf("STDOUT GetDNSZR = %q\n", stdout) } contents, err := utfutil.ReadFile(filename, utfutil.UTF8) @@ -123,10 +126,21 @@ func (psh *psHandle) GetDNSZoneRecords(dnsserver, domain string) ([]nativeRecord } os.Remove(filename) // TODO(tlim): There should be a debug flag that leaves the tmp file around. + //fmt.Printf("CONTENTS = %s\n", contents) + //fmt.Printf("CONTENTS STR = %q\n", contents[:10]) + //fmt.Printf("CONTENTS HEX = %v\n", []byte(contents)[:10]) + //ioutil.WriteFile("/temp/list.json", contents, 0777) var records []nativeRecord err = json.Unmarshal(contents, &records) if err != nil { - return nil, fmt.Errorf("PSZoneDump json error: %w", err) + // PowerShell generates bad JSON if there is only one record. Therefore, if there + // is an error we try decoding the bad format before completing erroring out. + // The "bad JSON" is that they generate a single record instead of a list of length 1. + records = append(records, nativeRecord{}) + err2 := json.Unmarshal(contents, &(records[0])) + if err2 != nil { + return nil, fmt.Errorf("PSZoneDump json error: %w", err) + } } return records, nil @@ -141,17 +155,35 @@ func generatePSZoneDump(dnsserver, domainname, filename string) string { // a BOM. A UTF-8 file shouldn't have a BOM, but Microsoft messed up. // When we switch to PowerShell Core, the BOM will disappear. var b bytes.Buffer + + // Set the output to be UTF8. Previously we didn't do that and the + // output was twice as large, plus it required an extra conversion + // step. Windows PowerShell is native UTF16 but PowerShell Core is + // native UTF8, thus this may not be needed if we move to Core. fmt.Fprintf(&b, `$OutputEncoding = [Text.UTF8Encoding]::UTF8 ; `) + + // Output everything we know about the zone. fmt.Fprintf(&b, `Get-DnsServerResourceRecord`) if dnsserver != "" { fmt.Fprintf(&b, ` -ComputerName "%v"`, dnsserver) } fmt.Fprintf(&b, ` -ZoneName "%v"`, domainname) + + // Strip out the `Cim*` properties at the root. This shrinks one + // zone from 99M to 11M. We don't need the Cim* properties (at + // least the ones at the root) and decocding 99M of JSON was slow + // (30+ minutes). + // NB(tlim): Windows PowerShell requires the `-Property *` but + // Windows PowerShell Core makes that optional. fmt.Fprintf(&b, ` | `) fmt.Fprintf(&b, `Select-Object -Property * -ExcludeProperty Cim*`) fmt.Fprintf(&b, ` | `) fmt.Fprintf(&b, `ConvertTo-Json -depth 4`) // Tested with 3 (causes errors). 4 and larger work. fmt.Fprintf(&b, ` | `) + + // Prevously we captured stdout. Now we write it to a file. This is + // safer since there is no chance of junk accidentally being mixed + // into stdout. fmt.Fprintf(&b, `Out-File "%s" -Encoding utf8`, filename) return b.String() } @@ -159,7 +191,16 @@ func generatePSZoneDump(dnsserver, domainname, filename string) string { // Functions for record manipulation func (psh *psHandle) RecordDelete(dnsserver, domain string, rec *models.RecordConfig) error { - _, stderr, err := psh.shell.Execute(generatePSDelete(dnsserver, domain, rec)) + + var c string + if rec.Type == "NAPTR" { + c = generatePSDeleteNaptr(dnsserver, domain, rec) + //fmt.Printf("DEBUG: deleteNAPTR: %s\n", c) + } else { + c = generatePSDelete(dnsserver, domain, rec) + } + + _, stderr, err := psh.shell.Execute(c) if err != nil { return err } @@ -171,9 +212,17 @@ func (psh *psHandle) RecordDelete(dnsserver, domain string, rec *models.RecordCo } func generatePSDelete(dnsserver, domain string, rec *models.RecordConfig) string { + var b bytes.Buffer - fmt.Fprintf(&b, `echo DELETE "%s" "%s" "%s"`, rec.Type, rec.Name, rec.GetTargetCombined()) + fmt.Fprintf(&b, `echo DELETE "%s" "%s" "..."`, rec.Type, rec.Name) fmt.Fprintf(&b, " ; ") + + if rec.Type == "NAPTR" { + x := b.String() + generatePSDeleteNaptr(dnsserver, domain, rec) + //fmt.Printf("NAPTR DELETE: %s\n", x) + return x + } + fmt.Fprintf(&b, `Remove-DnsServerResourceRecord`) if dnsserver != "" { fmt.Fprintf(&b, ` -ComputerName "%s"`, dnsserver) @@ -197,12 +246,23 @@ func generatePSDelete(dnsserver, domain string, rec *models.RecordConfig) string } func (psh *psHandle) RecordCreate(dnsserver, domain string, rec *models.RecordConfig) error { - _, stderr, err := psh.shell.Execute(generatePSCreate(dnsserver, domain, rec)) + + var c string + if rec.Type == "NAPTR" { + c = generatePSCreateNaptr(dnsserver, domain, rec) + //fmt.Printf("DEBUG: createNAPTR: %s\n", c) + } else { + c = generatePSCreate(dnsserver, domain, rec) + //fmt.Printf("DEBUG: PScreate\n") + } + + stdout, stderr, err := psh.shell.Execute(c) if err != nil { return err } if stderr != "" { - fmt.Printf("STDERROR = %q\n", stderr) + fmt.Printf("STDOUT RecordCreate = %s\n", stdout) + fmt.Printf("STDERROR RecordCreate = %q\n", stderr) return fmt.Errorf("unexpected stderr from PSCreate: %q", stderr) } return nil @@ -210,9 +270,13 @@ func (psh *psHandle) RecordCreate(dnsserver, domain string, rec *models.RecordCo func generatePSCreate(dnsserver, domain string, rec *models.RecordConfig) string { var b bytes.Buffer - fmt.Fprintf(&b, `echo CREATE "%s" "%s" "%s"`, rec.Type, rec.Name, rec.GetTargetCombined()) + fmt.Fprintf(&b, `echo CREATE "%s" "%s" "..."`, rec.Type, rec.Name) fmt.Fprintf(&b, " ; ") + if rec.Type == "NAPTR" { + return b.String() + generatePSCreateNaptr(dnsserver, domain, rec) + } + fmt.Fprint(&b, `Add-DnsServerResourceRecord`) if dnsserver != "" { fmt.Fprintf(&b, ` -ComputerName "%s"`, dnsserver) @@ -238,8 +302,8 @@ func generatePSCreate(dnsserver, domain string, rec *models.RecordConfig) string //case "WKS": // fmt.Fprintf(&b, ` -Wks -InternetAddress -InternetProtocol {UDP | TCP} -Service `, rec.GetTargetField()) case "TXT": - fmt.Printf("DEBUG TXT len = %v\n", rec.TxtStrings) - fmt.Printf("DEBUG TXT target = %q\n", rec.GetTargetField()) + //fmt.Printf("DEBUG TXT len = %v\n", rec.TxtStrings) + //fmt.Printf("DEBUG TXT target = %q\n", rec.GetTargetField()) fmt.Fprintf(&b, ` -Txt -DescriptiveText %s`, rec.GetTargetField()) //case "RT": // fmt.Fprintf(&b, ` -RT -IntermediateHost -Preference `, rec.GetTargetField()) diff --git a/providers/msdns/powershell_test.go b/providers/msdns/powershell_test.go index 9e89aed8e..06680bbc8 100644 --- a/providers/msdns/powershell_test.go +++ b/providers/msdns/powershell_test.go @@ -146,16 +146,16 @@ func Test_generatePSModify(t *testing.T) { want string }{ {name: "A", args: args{domain: "example.com", dnsserver: "", old: recA1, rec: recA2}, - want: `echo DELETE "A" "@" "1.2.3.4" ; Remove-DnsServerResourceRecord -Force -ZoneName "example.com" -Name "@" -RRType "A" -RecordData "1.2.3.4" ; echo CREATE "A" "@" "10.20.30.40" ; Add-DnsServerResourceRecord -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -A -IPv4Address "10.20.30.40"`, + want: `echo DELETE "A" "@" "..." ; Remove-DnsServerResourceRecord -Force -ZoneName "example.com" -Name "@" -RRType "A" -RecordData "1.2.3.4" ; echo CREATE "A" "@" "..." ; Add-DnsServerResourceRecord -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -A -IPv4Address "10.20.30.40"`, }, {name: "MX1", args: args{domain: "example.com", dnsserver: "", old: recMX1, rec: recMX2}, - want: `echo DELETE "MX" "@" "5 foo.com." ; Remove-DnsServerResourceRecord -Force -ZoneName "example.com" -Name "@" -RRType "MX" -RecordData 5,"foo.com." ; echo CREATE "MX" "@" "50 foo2.com." ; Add-DnsServerResourceRecord -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -MX -MailExchange "foo2.com." -Preference 50`, + want: `echo DELETE "MX" "@" "..." ; Remove-DnsServerResourceRecord -Force -ZoneName "example.com" -Name "@" -RRType "MX" -RecordData 5,"foo.com." ; echo CREATE "MX" "@" "..." ; Add-DnsServerResourceRecord -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -MX -MailExchange "foo2.com." -Preference 50`, }, {name: "A-remote", args: args{domain: "example.com", dnsserver: "myremote", old: recA1, rec: recA2}, - want: `echo DELETE "A" "@" "1.2.3.4" ; Remove-DnsServerResourceRecord -ComputerName "myremote" -Force -ZoneName "example.com" -Name "@" -RRType "A" -RecordData "1.2.3.4" ; echo CREATE "A" "@" "10.20.30.40" ; Add-DnsServerResourceRecord -ComputerName "myremote" -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -A -IPv4Address "10.20.30.40"`, + want: `echo DELETE "A" "@" "..." ; Remove-DnsServerResourceRecord -ComputerName "myremote" -Force -ZoneName "example.com" -Name "@" -RRType "A" -RecordData "1.2.3.4" ; echo CREATE "A" "@" "..." ; Add-DnsServerResourceRecord -ComputerName "myremote" -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -A -IPv4Address "10.20.30.40"`, }, {name: "MX1-remote", args: args{domain: "example.com", dnsserver: "yourremote", old: recMX1, rec: recMX2}, - want: `echo DELETE "MX" "@" "5 foo.com." ; Remove-DnsServerResourceRecord -ComputerName "yourremote" -Force -ZoneName "example.com" -Name "@" -RRType "MX" -RecordData 5,"foo.com." ; echo CREATE "MX" "@" "50 foo2.com." ; Add-DnsServerResourceRecord -ComputerName "yourremote" -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -MX -MailExchange "foo2.com." -Preference 50`, + want: `echo DELETE "MX" "@" "..." ; Remove-DnsServerResourceRecord -ComputerName "yourremote" -Force -ZoneName "example.com" -Name "@" -RRType "MX" -RecordData 5,"foo.com." ; echo CREATE "MX" "@" "..." ; Add-DnsServerResourceRecord -ComputerName "yourremote" -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -MX -MailExchange "foo2.com." -Preference 50`, }, } for _, tt := range tests {