dnscontrol/providers/msdns/convert.go
2023-05-20 13:21:45 -04:00

127 lines
4 KiB
Go

package msdns
// Convert the provider's native record description to models.RecordConfig.
import (
"encoding/json"
"fmt"
"net"
"github.com/StackExchange/dnscontrol/v4/models"
)
// extractProps and collects Name/Value pairs into maps for easier access.
func extractProps(cip []ciProperty) (map[string]string, map[string]uint32, error) {
// Sadly this structure is dynamic JSON i.e. .Value could be an int, string,
// or a map. We peek at the first byte to guess at the contents.
// We store strings in sprops, numbers in uprops. Maps are special: Currently
// the only map we decode is a map with the same duration in many units. We
// simply pick the Seconds unit and store it as a number.
sprops := map[string]string{}
uprops := map[string]uint32{}
for _, p := range cip {
name := p.Name
if len(p.Value) == 0 {
// Empty string? Skip it.
} else if p.Value[0] == '"' {
// First byte is a quote. Must be a string.
var svalue string
err := json.Unmarshal(p.Value, &svalue)
if err != nil {
return nil, nil, fmt.Errorf("could not unmarshal string value=%q: %w", p.Value, err)
}
sprops[name] = svalue
} else if p.Value[0] == '{' {
// First byte is {. Must be a map.
var dvalue ciValueDuration
err := json.Unmarshal(p.Value, &dvalue)
if err != nil {
return nil, nil, fmt.Errorf("could not unmarshal duration value=%q: %w", p.Value, err)
}
uprops[name] = uint32(dvalue.TotalSeconds)
} else {
// Assume it is a number.
var uvalue uint32
err := json.Unmarshal(p.Value, &uvalue)
if err != nil {
return nil, nil, fmt.Errorf("could not unmarshal uint value=%q: %w", p.Value, err)
}
uprops[name] = uvalue
}
}
return sprops, uprops, nil
}
// nativeToRecord takes a DNS record from DNS and returns a native RecordConfig struct.
func nativeToRecords(nr nativeRecord, origin string) (*models.RecordConfig, error) {
rc := &models.RecordConfig{
Type: nr.RecordType,
Original: nr,
}
rc.SetLabel(nr.HostName, origin)
rc.TTL = uint32(nr.TimeToLive.TotalSeconds)
sprops, uprops, err := extractProps(nr.RecordData.CimInstanceProperties)
if err != nil {
return nil, err
}
switch rtype := nr.RecordType; rtype {
case "A":
contents := sprops["IPv4Address"]
ip := net.ParseIP(contents)
if ip == nil || ip.To4() == nil {
return nil, fmt.Errorf("invalid IP in A record: %q", contents)
}
rc.SetTargetIP(ip)
case "AAAA":
contents := sprops["IPv6Address"]
ip := net.ParseIP(contents)
if ip == nil || ip.To16() == nil {
return nil, fmt.Errorf("invalid IPv6 in AAAA record: %q", contents)
}
rc.SetTargetIP(ip)
case "CNAME":
rc.SetTarget(sprops["HostNameAlias"])
case "MX":
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":
rc.SetTargetSRV(
uint16(uprops["Priority"]),
uint16(uprops["Weight"]),
uint16(uprops["Port"]),
sprops["DomainName"],
)
case "SOA":
// We discard SOA records for now. Windows DNS doesn't let us delete
// them and they get in the way of integration tests. In the future,
// we should support SOA records by (1) ignoring them in the
// integration tests. (2) generatePSModify will have to special-case
// updates.
return nil, nil
// If we weren't ignoring them, the code would look like this:
//rc.SetTargetSOA(sprops["PrimaryServer"], sprops["ResponsiblePerson"],
// uprops["SerialNumber"], uprops["RefreshInterval"], uprops["RetryDelay"],
// uprops["ExpireLimit"], uprops["MinimumTimeToLive"])
case "TXT":
//rc.SetTargetTXTString(sprops["DescriptiveText"])
rc.SetTargetTXT(sprops["DescriptiveText"])
default:
return nil, fmt.Errorf(
"msdns/convert.go:nativeToRecord rtype=%q unknown: props=%+v and %+v",
rtype, sprops, uprops)
}
return rc, nil
}