2020-12-29 05:07:33 +08:00
|
|
|
package msdns
|
|
|
|
|
|
|
|
// Convert the provider's native record description to models.RecordConfig.
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
|
2023-05-21 01:21:45 +08:00
|
|
|
"github.com/StackExchange/dnscontrol/v4/models"
|
2020-12-29 05:07:33 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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"])
|
2021-06-25 06:26:21 +08:00
|
|
|
case "NAPTR":
|
|
|
|
n := decodeRecordDataNaptr(sprops["Data"])
|
|
|
|
rc.SetTargetNAPTR(n.NaptrOrder, n.NaptrPreference, n.NaptrFlags, n.NaptrService, n.NaptrRegexp, n.GetTargetField())
|
2020-12-29 05:07:33 +08:00
|
|
|
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":
|
2022-08-15 08:46:56 +08:00
|
|
|
//rc.SetTargetTXTString(sprops["DescriptiveText"])
|
2023-02-03 04:45:16 +08:00
|
|
|
rc.SetTargetTXT(sprops["DescriptiveText"])
|
2020-12-29 05:07:33 +08:00
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf(
|
|
|
|
"msdns/convert.go:nativeToRecord rtype=%q unknown: props=%+v and %+v",
|
|
|
|
rtype, sprops, uprops)
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc, nil
|
|
|
|
}
|