You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ncdns/ncdomain/convert.go

1201 lines
28 KiB
Go

package ncdomain
import "encoding/json"
import "net"
import "fmt"
import "github.com/miekg/dns"
import "encoding/base64"
import "encoding/hex"
import "github.com/hlandau/ncdns/util"
import "strings"
const depthLimit = 16
const mergeDepthLimit = 4
const defaultTTL = 600
// Note: Name values in Value (e.g. those in Alias and Target, Services, MXs,
// etc.) are not necessarily fully qualified and must be fully qualified before
// being used. Non-fully-qualified names are relative to the name apex, and
// should be qualified as such based on whatever the corresponding name for the
// Value is and the zone apex you are using for .bit. These assumptions are not
// built-in for you to give flexibility in where the .bit zone is mounted,
// DNS namespace-wise. If you just call RRs() or RRsRecursive() you don't have
// to worry about any of this.
//
// Because empty values are used to indicate the non-presence of an option
// in some cases, namely for Alias and Translate, the empty string is represented as "=".
// Therefore when qualifying names in a Value yourself you must check if the
// input string is "=" and if so, replace it with "" first.
type Value struct {
IP []net.IP
IP6 []net.IP
NS []string
Alias string
HasAlias bool // True if Alias was specified. Necessary as "" is a valid relative alias.
Translate string
HasTranslate bool // True if Translate was specified. Necessary as "" is a valid relative value for Translate.
DS []*dns.DS
TXT [][]string
Service []*dns.SRV // header name contains e.g. "_http._tcp"
Hostmaster string // "hostmaster@example.com"
MX []*dns.MX // header name is left blank
TLSA []*dns.TLSA // header name contains e.g. "_443._tcp"
Map map[string]*Value // may contain and "*", will not contain ""
// set if the value is at the top level (alas necessary for relname interpretation)
IsTopLevel bool
}
func (v *Value) mkString(i string) string {
s := i[1:] + "Value:"
i += " "
if v.HasAlias {
s += i + "CNAME: \"" + v.Alias + "\""
}
if v.HasTranslate {
s += i + "DNAME: \"" + v.Translate + "\""
}
if v.Hostmaster != "" {
s += i + "Hostmaster: " + v.Hostmaster
}
for _, ip := range v.IP {
s += i + "IPv4 Address: " + ip.String()
}
for _, ip := range v.IP6 {
s += i + "IPv6 Address: " + ip.String()
}
for _, ns := range v.NS {
s += i + "Nameserver: " + ns
}
for _, ds := range v.DS {
s += i + "DS Record: " + ds.String()
}
for _, txt := range v.TXT {
s += i + "TXT Record:"
for _, txtc := range txt {
s += i + " " + txtc
}
}
for _, srv := range v.Service {
s += i + "SRV Record: " + srv.String()
}
for _, tlsa := range v.TLSA {
s += i + "TLSA Record: " + tlsa.String()
}
if len(v.Map) > 0 {
s += i + "Subdomains:"
for k, v := range v.Map {
s += i + " " + k + ":"
s += v.mkString(i + " ")
}
}
return s
}
func (v *Value) String() string {
return v.mkString("\n")
}
func (v *Value) RRs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
il := len(out)
suffix = dns.Fqdn(suffix)
apexSuffix = dns.Fqdn(apexSuffix)
out, _ = v.appendNSs(out, suffix, apexSuffix)
if len(v.NS) == 0 {
out, _ = v.appendTranslate(out, suffix, apexSuffix)
if !v.HasTranslate {
out, _ = v.appendAlias(out, suffix, apexSuffix)
if !v.HasAlias {
out, _ = v.appendIPs(out, suffix, apexSuffix)
out, _ = v.appendIP6s(out, suffix, apexSuffix)
out, _ = v.appendTXTs(out, suffix, apexSuffix)
out, _ = v.appendMXs(out, suffix, apexSuffix)
}
// SRV and TLSA records are assigned to a subdomain, but CNAMEs are not recursive so CNAME must not inhibit them
out, _ = v.appendServices(out, suffix, apexSuffix)
out, _ = v.appendTLSA(out, suffix, apexSuffix)
}
}
out, _ = v.appendDSs(out, suffix, apexSuffix)
xout := out[il:]
for i := range xout {
h := xout[i].Header()
if rrtypeHasPrefix(h.Rrtype) {
h.Name += "." + suffix
} else {
h.Name = suffix
}
}
return out, nil
}
func rrtypeHasPrefix(t uint16) bool {
return t == dns.TypeSRV || t == dns.TypeTLSA
}
func (v *Value) appendIPs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
for _, ip := range v.IP {
out = append(out, &dns.A{
Hdr: dns.RR_Header{
Name: suffix,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: defaultTTL,
},
A: ip,
})
}
return out, nil
}
func (v *Value) appendIP6s(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
for _, ip := range v.IP6 {
out = append(out, &dns.AAAA{
Hdr: dns.RR_Header{
Name: suffix,
Rrtype: dns.TypeAAAA,
Class: dns.ClassINET,
Ttl: defaultTTL,
},
AAAA: ip,
})
}
return out, nil
}
func (v *Value) appendNSs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
for _, ns := range v.NS {
qn, ok := v.qualify(ns, suffix, apexSuffix)
if !ok {
continue
}
out = append(out, &dns.NS{
Hdr: dns.RR_Header{
Name: suffix,
Rrtype: dns.TypeNS,
Class: dns.ClassINET,
Ttl: defaultTTL,
},
Ns: qn,
})
}
return out, nil
}
func (v *Value) appendTXTs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
for _, txt := range v.TXT {
out = append(out, &dns.TXT{
Hdr: dns.RR_Header{
Name: suffix,
Rrtype: dns.TypeTXT,
Class: dns.ClassINET,
Ttl: defaultTTL,
},
Txt: txt,
})
}
return out, nil
}
func (v *Value) appendDSs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
for _, ds := range v.DS {
out = append(out, ds)
}
return out, nil
}
func (v *Value) appendMXs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
for _, mx := range v.MX {
out = append(out, mx)
}
return out, nil
}
func (v *Value) appendServices(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
for _, svc := range v.Service {
out = append(out, svc)
}
return out, nil
}
func (v *Value) appendTLSA(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
for _, tlsa := range v.TLSA {
out = append(out, tlsa)
}
return out, nil
}
func (v *Value) appendAlias(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
if v.HasAlias {
qn, ok := v.qualify(v.Alias, suffix, apexSuffix)
if !ok {
return out, fmt.Errorf("bad alias")
}
out = append(out, &dns.CNAME{
Hdr: dns.RR_Header{
Name: suffix,
Rrtype: dns.TypeCNAME,
Class: dns.ClassINET,
Ttl: defaultTTL,
},
Target: qn,
})
}
return out, nil
}
func (v *Value) appendTranslate(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
if v.HasTranslate {
qn, ok := v.qualify(v.Translate, suffix, apexSuffix)
if !ok {
return out, fmt.Errorf("bad translate")
}
out = append(out, &dns.DNAME{
Hdr: dns.RR_Header{
Name: suffix,
Rrtype: dns.TypeDNAME,
Class: dns.ClassINET,
Ttl: defaultTTL,
},
Target: qn,
})
}
return out, nil
}
func (v *Value) RRsRecursive(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
out, err := v.RRs(out, suffix, apexSuffix)
if err != nil {
return nil, err
}
for mk, mv := range v.Map {
if !util.ValidateLabel(mk) && mk != "" && mk != "*" {
continue
}
out, err = mv.RRsRecursive(out, mk+"."+suffix, apexSuffix)
//if err != nil {
// return nil, err
//}
}
return out, nil
}
func (v *Value) findSubdomainByName(subdomain string) (*Value, error) {
if subdomain == "" {
return v, nil
}
if strings.HasSuffix(subdomain, ".") {
return nil, fmt.Errorf("a subdomain name should not be fully qualified")
}
head, rest := util.SplitDomainHead(subdomain)
if sub, ok := v.Map[head]; ok {
return sub.findSubdomainByName(rest)
}
return nil, fmt.Errorf("subdomain part not found: %s", head)
}
type rawValue_old struct {
IP interface{} `json:"ip"`
IP6 interface{} `json:"ip6"`
NS interface{} `json:"ns"`
nsSet map[string]struct{}
DNS interface{} `json:"dns"` // actually an alias for NS
Alias interface{} `json:"alias"`
Translate interface{} `json:"translate"`
DS interface{} `json:"ds"`
TXT interface{} `json:"txt"`
Hostmaster interface{} `json:"email"` // Hostmaster
MX interface{} `json:"mx"`
TLSA interface{} `json:"tls"`
Map json.RawMessage `json:"map"`
Service interface{} `json:"service"`
Import interface{} `json:"import"`
Delegate interface{} `json:"delegate"`
}
type ResolveFunc func(name string) (string, error)
type ErrorFunc func(err error, isWarning bool)
func (ef ErrorFunc) add(err error) {
if ef != nil && err != nil {
ef(err, false)
}
}
func (ef ErrorFunc) addWarning(err error) {
if ef != nil && err != nil {
ef(err, true)
}
}
// Call to convert a given JSON value to a parsed Namecoin domain value.
//
// If ResolveFunc is given, it will be called to obtain the values for domains
// referenced by "import" and "delegate" statements. The name passed is in
// Namecoin form (e.g. "d/example"). The JSON value or an error should be
// returned. If no ResolveFunc is passed, "import" and "delegate" statements
// always fail.
//
// Returns nil if the JSON could not be parsed. For all other errors processing
// continues and recovers as much as possible; errFunc is called for all errors
// and warnings if specified.
func ParseValue(name, jsonValue string, resolve ResolveFunc, errFunc ErrorFunc) (value *Value) {
var rv interface{}
v := &Value{}
err := json.Unmarshal([]byte(jsonValue), &rv)
if err != nil {
errFunc.add(err)
return
}
if resolve == nil {
resolve = func(name string) (string, error) {
return "", fmt.Errorf("not supported")
}
}
mergedNames := map[string]struct{}{}
mergedNames[name] = struct{}{}
parse(rv, v, resolve, errFunc, 0, 0, "", "", mergedNames)
v.IsTopLevel = true
value = v
return
}
func parse(rv interface{}, v *Value, resolve ResolveFunc, errFunc ErrorFunc, depth, mergeDepth int, subdomain, relname string, mergedNames map[string]struct{}) {
rvm, ok := rv.(map[string]interface{})
if !ok {
errFunc.add(fmt.Errorf("value is not an object"))
return
}
if depth > depthLimit {
errFunc.add(fmt.Errorf("depth limit exceeded"))
return
}
realv := v
if subdomain != "" {
// substitute a dummy value. We will then parse everything into this, find the appropriate level and copy
// the value to the argument value.
v = &Value{}
}
ok, _ = parseDelegate(rvm, v, resolve, errFunc, depth, mergeDepth, relname, mergedNames)
if ok {
return
}
parseImport(rvm, v, resolve, errFunc, depth, mergeDepth, relname, mergedNames)
if ip, ok := rvm["ip"]; ok {
parseIP(rvm, v, errFunc, ip, false)
}
if ip6, ok := rvm["ip6"]; ok {
parseIP(rvm, v, errFunc, ip6, true)
}
parseNS(rvm, v, errFunc, relname)
parseAlias(rvm, v, errFunc, relname)
parseTranslate(rvm, v, errFunc, relname)
parseHostmaster(rvm, v, errFunc)
parseDS(rvm, v, errFunc)
parseTXT(rvm, v, errFunc)
parseService(rvm, v, errFunc, relname)
parseMX(rvm, v, errFunc, relname)
parseTLSA(rvm, v, errFunc)
parseMap(rvm, v, resolve, errFunc, depth, mergeDepth, relname)
v.moveEmptyMapItems()
if subdomain != "" {
subv, err := v.findSubdomainByName(subdomain)
if err != nil {
errFunc.add(fmt.Errorf("couldn't find subdomain by name in import or delegate item: %v", err))
return
}
*realv = *subv
}
}
func (v *Value) qualifyIntl(name, suffix, apexSuffix string) string {
if strings.HasSuffix(name, ".") {
return name
}
if !v.IsTopLevel {
_, suffix = util.SplitDomainTail(suffix)
}
if name == "" {
return suffix
}
if name == "@" {
return apexSuffix
}
if strings.HasSuffix(name, ".@") {
return name[0:len(name)-2] + "." + apexSuffix
}
return name + "." + suffix
}
func (v *Value) qualify(name, suffix, apexSuffix string) (string, bool) {
s := v.qualifyIntl(name, suffix, apexSuffix)
if !util.ValidateHostName(s) {
return "", false
}
return s, true
}
func parseMerge(rv map[string]interface{}, mergeValue string, v *Value, resolve ResolveFunc, errFunc ErrorFunc, depth, mergeDepth int, subdomain, relname string, mergedNames map[string]struct{}) error {
var rv2 interface{}
if mergeDepth > mergeDepthLimit {
err := fmt.Errorf("merge depth limit exceeded")
errFunc.add(err)
return err
}
err := json.Unmarshal([]byte(mergeValue), &rv2)
if err != nil {
err = fmt.Errorf("couldn't parse JSON to be merged: %v", err)
errFunc.add(err)
return err
}
parse(rv2, v, resolve, errFunc, depth, mergeDepth, subdomain, relname, mergedNames)
return nil
}
func parseIP(rv map[string]interface{}, v *Value, errFunc ErrorFunc, ipi interface{}, ipv6 bool) {
if ipv6 {
v.IP6 = nil
} else {
v.IP = nil
}
if ipi == nil {
return
}
if ipa, ok := ipi.([]interface{}); ok {
for _, ip := range ipa {
if ips, ok := ip.(string); ok {
addIP(rv, v, errFunc, ips, ipv6)
}
}
return
}
if ip, ok := ipi.(string); ok {
addIP(rv, v, errFunc, ip, ipv6)
}
}
func addIP(rv map[string]interface{}, v *Value, errFunc ErrorFunc, ips string, ipv6 bool) {
pip := net.ParseIP(ips)
if pip == nil || (pip.To4() == nil) != ipv6 {
errFunc.add(fmt.Errorf("malformed IP: %s", ips))
return
}
if ipv6 {
v.IP6 = append(v.IP6, pip)
} else {
v.IP = append(v.IP, pip)
}
}
func parseNS(rv map[string]interface{}, v *Value, errFunc ErrorFunc, relname string) {
// "dns" takes precedence
if dns, ok := rv["dns"]; ok && dns != nil {
rv["ns"] = dns
}
ns, ok := rv["ns"]
if !ok || ns == nil {
return
}
v.NS = nil
if _, ok := rv["_nsSet"]; !ok {
rv["_nsSet"] = map[string]struct{}{}
}
switch ns.(type) {
case []interface{}:
for _, si := range ns.([]interface{}) {
s, ok := si.(string)
if !ok {
continue
}
addNS(rv, v, errFunc, s, relname)
}
return
case string:
s := ns.(string)
addNS(rv, v, errFunc, s, relname)
return
default:
errFunc.add(fmt.Errorf("unknown NS field format"))
}
}
func addNS(rv map[string]interface{}, v *Value, errFunc ErrorFunc, s, relname string) {
if !util.ValidateOwnerName(s) {
errFunc.add(fmt.Errorf("malformed domain name in NS field"))
}
if _, ok := (rv["_nsSet"].(map[string]struct{}))[s]; !ok {
v.NS = append(v.NS, s)
(rv["_nsSet"].(map[string]struct{}))[s] = struct{}{}
}
}
func parseAlias(rv map[string]interface{}, v *Value, errFunc ErrorFunc, relname string) {
alias, ok := rv["alias"]
if !ok {
return
}
if alias == nil {
v.Alias = ""
v.HasAlias = false
return
}
if s, ok := alias.(string); ok {
if !util.ValidateOwnerName(s) {
errFunc.add(fmt.Errorf("malformed alias name"))
return
}
v.Alias = s
v.HasAlias = true
return
}
errFunc.add(fmt.Errorf("unknown alias field format"))
}
func parseTranslate(rv map[string]interface{}, v *Value, errFunc ErrorFunc, relname string) {
translate, ok := rv["translate"]
if !ok {
return
}
if translate == nil {
v.Translate = ""
v.HasTranslate = false
return
}
if s, ok := translate.(string); ok {
if !util.ValidateOwnerName(s) {
errFunc.add(fmt.Errorf("malformed translate name"))
return
}
v.Translate = s
v.HasTranslate = true
return
}
errFunc.add(fmt.Errorf("unknown translate field format"))
}
func isAllArray(x []interface{}) bool {
for _, v := range x {
if _, ok := v.([]interface{}); !ok {
return false
}
}
return true
}
func isAllString(x []interface{}) bool {
for _, v := range x {
if _, ok := v.(string); !ok {
return false
}
}
return true
}
func parseImportImpl(rv map[string]interface{}, val *Value, resolve ResolveFunc, errFunc ErrorFunc, depth, mergeDepth int, relname string, mergedNames map[string]struct{}, delegate bool) (bool, error) {
var err error
succeeded := false
xname := "import"
if delegate {
xname = "delegate"
}
src, ok := rv[xname]
if !ok || src == nil {
return false, nil
}
if s, ok := src.(string); ok {
src = []interface{}{s}
}
if a, ok := src.([]interface{}); ok {
// [..., ..., ...]
if isAllString(a) {
// ["s/somedomain"]
// ["s/somedomain", "sub.domain"]
a = []interface{}{a}
}
if isAllArray(a) {
// [ ["s/somedomain", "sub.domain"], ["s/somedomain", "sub.domain"] ]
for _, vx := range a {
v := vx.([]interface{})
if len(v) != 1 && len(v) != 2 {
continue
}
subs := ""
if k, ok := v[0].(string); ok {
if len(v) > 1 {
if sub, ok := v[1].(string); ok {
subs = sub
}
}
// ok
var dv string
dv, err = resolve(k)
if err != nil {
continue
}
if _, ok := mergedNames[k]; ok {
// already merged
continue
}
mergedNames[k] = struct{}{}
err = parseMerge(rv, dv, val, resolve, errFunc, depth, mergeDepth+1, subs, relname, mergedNames)
if err != nil {
errFunc.add(err)
continue
}
succeeded = true
}
}
// ...
return succeeded, nil
}
// malformed
}
if err == nil {
err = fmt.Errorf("unknown import/delegate field format")
}
errFunc.add(err)
return succeeded, err
}
func parseImport(rv map[string]interface{}, v *Value, resolve ResolveFunc, errFunc ErrorFunc, depth, mergeDepth int, relname string, mergedNames map[string]struct{}) error {
_, err := parseImportImpl(rv, v, resolve, errFunc, depth, mergeDepth, relname, mergedNames, false)
return err
}
func parseDelegate(rv map[string]interface{}, v *Value, resolve ResolveFunc, errFunc ErrorFunc, depth, mergeDepth int, relname string, mergedNames map[string]struct{}) (bool, error) {
return parseImportImpl(rv, v, resolve, errFunc, depth, mergeDepth, relname, mergedNames, true)
}
func parseHostmaster(rv map[string]interface{}, v *Value, errFunc ErrorFunc) {
hm, ok := rv["email"]
if !ok || hm == nil {
return
}
if s, ok := hm.(string); ok {
if !util.ValidateEmail(s) {
errFunc.add(fmt.Errorf("malformed e. mail address in email field"))
return
}
v.Hostmaster = s
return
}
errFunc.add(fmt.Errorf("unknown email field format"))
}
func parseDS(rv map[string]interface{}, v *Value, errFunc ErrorFunc) {
rds, ok := rv["ds"]
if !ok || rds == nil {
return
}
v.DS = nil
if dsa, ok := rds.([]interface{}); ok {
for _, ds1 := range dsa {
if ds, ok := ds1.([]interface{}); ok {
if len(ds) < 4 {
errFunc.add(fmt.Errorf("DS item must have four items"))
continue
}
a1, ok := ds[0].(float64)
if !ok {
errFunc.add(fmt.Errorf("First item in DS value must be an integer (key tag)"))
continue
}
a2, ok := ds[1].(float64)
if !ok {
errFunc.add(fmt.Errorf("Second item in DS value must be an integer (algorithm)"))
continue
}
a3, ok := ds[2].(float64)
if !ok {
errFunc.add(fmt.Errorf("Third item in DS value must be an integer (digest type)"))
continue
}
a4, ok := ds[3].(string)
if !ok {
errFunc.add(fmt.Errorf("Fourth item in DS value must be a string (digest)"))
continue
}
a4b, err := base64.StdEncoding.DecodeString(a4)
if err != nil {
errFunc.add(fmt.Errorf("Fourth item in DS value must be valid base64: %v", err))
continue
}
a4h := hex.EncodeToString(a4b)
v.DS = append(v.DS, &dns.DS{
Hdr: dns.RR_Header{Rrtype: dns.TypeDS, Class: dns.ClassINET, Ttl: defaultTTL},
KeyTag: uint16(a1),
Algorithm: uint8(a2),
DigestType: uint8(a3),
Digest: a4h,
})
} else {
errFunc.add(fmt.Errorf("DS item must be an array"))
}
}
return
}
errFunc.add(fmt.Errorf("malformed DS field format"))
}
func parseTLSA(rv map[string]interface{}, v *Value, errFunc ErrorFunc) {
tlsa, ok := rv["tls"]
if !ok || tlsa == nil {
return
}
v.TLSA = nil
if tlsaa, ok := tlsa.([]interface{}); ok {
for _, tlsa1 := range tlsaa {
if tlsa, ok := tlsa1.([]interface{}); ok {
// Format: ["443", "tcp", 1, 2, 3, "base64 certificate data"]
if len(tlsa) < 6 {
errFunc.add(fmt.Errorf("TLSA item must have six items"))
continue
}
ports, ok := tlsa[0].(string)
if !ok {
porti, ok := tlsa[0].(float64)
if !ok {
errFunc.add(fmt.Errorf("First item in TLSA value must be an integer or string (port number)"))
continue
}
ports = fmt.Sprintf("%d", int(porti))
}
transport, ok := tlsa[1].(string)
if !ok {
errFunc.add(fmt.Errorf("Second item in TLSA value must be a string (transport protocol name)"))
continue
}
a1, ok := tlsa[2].(float64)
if !ok {
errFunc.add(fmt.Errorf("Third item in TLSA value must be an integer (usage)"))
continue
}
a2, ok := tlsa[3].(float64)
if !ok {
errFunc.add(fmt.Errorf("Fourth item in TLSA value must be an integer (selector)"))
continue
}
a3, ok := tlsa[4].(float64)
if !ok {
errFunc.add(fmt.Errorf("Fifth item in TLSA value must be an integer (match type)"))
continue
}
a4, ok := tlsa[5].(string)
if !ok {
errFunc.add(fmt.Errorf("Sixth item in TLSA value must be a string (certificate)"))
continue
}
a4b, err := base64.StdEncoding.DecodeString(a4)
if err != nil {
errFunc.add(fmt.Errorf("Fourth item in DS value must be valid base64: %v", err))
continue
}
if len(ports) > 62 || len(transport) > 62 {
errFunc.add(fmt.Errorf("Application and transport names must not exceed 62 characters"))
continue
}
a4h := hex.EncodeToString(a4b)
name := "_" + ports + "._" + transport
v.TLSA = append(v.TLSA, &dns.TLSA{
Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeTLSA, Class: dns.ClassINET,
Ttl: defaultTTL},
Usage: uint8(a1),
Selector: uint8(a2),
MatchingType: uint8(a3),
Certificate: strings.ToUpper(a4h),
})
} else {
errFunc.add(fmt.Errorf("TLSA item must be an array"))
}
}
return
}
errFunc.add(fmt.Errorf("Malformed TLSA field format"))
}
func parseTXT(rv map[string]interface{}, v *Value, errFunc ErrorFunc) {
rtxt, ok := rv["txt"]
if !ok || rtxt == nil {
return
}
if txta, ok := rtxt.([]interface{}); ok {
// ["...", "..."] or [["...", "..."], ["...", "..."]]
for _, vv := range txta {
if sa, ok := vv.([]interface{}); ok {
// [["...", "..."], ["...", "..."]]
a := []string{}
for _, x := range sa {
if xs, ok := x.(string); ok && len(xs) <= 255 {
a = append(a, xs)
}
}
if len(a) > 0 {
v.TXT = append(v.TXT, a)
}
} else if s, ok := vv.(string); ok {
v.TXT = append(v.TXT, segmentizeTXT(s))
} else {
errFunc.add(fmt.Errorf("malformed TXT value"))
return
}
}
} else {
// "..."
if s, ok := rtxt.(string); ok {
v.TXT = append(v.TXT, segmentizeTXT(s))
} else {
errFunc.add(fmt.Errorf("malformed TXT value"))
return
}
}
// Make sure the content of each TXT record does not exceed 65535 bytes.
for i := range v.TXT {
for {
L := 0
for j := range v.TXT[i] {
L += len(v.TXT[i][j]) + 1
}
if L <= 65535 {
break
}
// Pop segments until under the limit.
v.TXT[i] = v.TXT[i][0 : len(v.TXT[i])-1]
}
}
return
}
func segmentizeTXT(txt string) (a []string) {
for len(txt) > 255 {
a = append(a, txt[0:255])
txt = txt[255:]
}
a = append(a, txt)
return
}
func parseMX(rv map[string]interface{}, v *Value, errFunc ErrorFunc, relname string) {
rmx, ok := rv["mx"]
if !ok || rmx == nil {
return
}
if sa, ok := rmx.([]interface{}); ok {
for _, s := range sa {
parseSingleMX(rv, s, v, errFunc, relname)
}
return
}
errFunc.add(fmt.Errorf("malformed MX value"))
}
func parseSingleMX(rv map[string]interface{}, s interface{}, v *Value, errFunc ErrorFunc, relname string) {
sa, ok := s.([]interface{})
if !ok {
errFunc.add(fmt.Errorf("malformed MX value"))
return
}
if len(sa) < 2 {
errFunc.add(fmt.Errorf("malformed MX value"))
return
}
prio, ok := sa[0].(float64)
if !ok || prio < 0 {
errFunc.add(fmt.Errorf("malformed MX value"))
return
}
hostname, ok := sa[1].(string)
if !ok {
errFunc.add(fmt.Errorf("malformed MX value"))
return
}
v.MX = append(v.MX, &dns.MX{
Hdr: dns.RR_Header{Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: defaultTTL},
Preference: uint16(prio),
Mx: hostname,
})
return
}
func parseService(rv map[string]interface{}, v *Value, errFunc ErrorFunc, relname string) {
rsvc, ok := rv["service"]
if !ok || rsvc == nil {
return
}
// We have to merge the services specified and those imported using an
// import statement.
servicesUsed := map[string]struct{}{}
oldServices := v.Service
v.Service = nil
if sa, ok := rsvc.([]interface{}); ok {
for _, s := range sa {
parseSingleService(rv, s, v, errFunc, relname, servicesUsed)
}
} else {
errFunc.add(fmt.Errorf("malformed service value"))
}
for _, svc := range oldServices {
if _, ok := servicesUsed[svc.Header().Name]; !ok {
v.Service = append(v.Service, svc)
}
}
}
func parseSingleService(rv map[string]interface{}, svc interface{}, v *Value, errFunc ErrorFunc, relname string, servicesUsed map[string]struct{}) {
svca, ok := svc.([]interface{})
if !ok {
errFunc.add(fmt.Errorf("malformed service value"))
return
}
if len(svca) < 6 {
errFunc.add(fmt.Errorf("malformed service value: must have six items"))
return
}
appProtoName, ok := svca[0].(string)
if !ok || !util.ValidateServiceName(appProtoName) {
errFunc.add(fmt.Errorf("malformed service value: first item must be a string (application protocol)"))
return
}
transportProtoName, ok := svca[1].(string)
if !ok || !util.ValidateServiceName(transportProtoName) {
errFunc.add(fmt.Errorf("malformed service value: second item must be a string (transport protocol)"))
return
}
priority, ok := svca[2].(float64)
if !ok {
errFunc.add(fmt.Errorf("malformed service value: third item must be an integer (priority)"))
return
}
weight, ok := svca[3].(float64)
if !ok {
errFunc.add(fmt.Errorf("malformed service value: fourth item must be an integer (weight)"))
return
}
port, ok := svca[4].(float64)
if !ok {
errFunc.add(fmt.Errorf("malformed service value: fifth item must be an integer (port number)"))
return
}
hostname, ok := svca[5].(string)
if !ok {
errFunc.add(fmt.Errorf("malformed service value: sixth item must be a string (target)"))
return
}
sname := "_" + appProtoName + "._" + transportProtoName
servicesUsed[sname] = struct{}{}
v.Service = append(v.Service, &dns.SRV{
Hdr: dns.RR_Header{
Name: sname,
Rrtype: dns.TypeSRV,
Class: dns.ClassINET,
Ttl: defaultTTL,
},
Priority: uint16(priority),
Weight: uint16(weight),
Port: uint16(port),
Target: hostname,
})
return
}
func parseMap(rv map[string]interface{}, v *Value, resolve ResolveFunc, errFunc ErrorFunc, depth, mergeDepth int, relname string) {
rmap, ok := rv["map"]
if !ok || rmap == nil {
return
}
m, ok := rmap.(map[string]interface{})
if !ok {
errFunc.add(fmt.Errorf("Map value must be an object"))
return
}
for mk, mv := range m {
if s, ok := mv.(string); ok {
// deprecated case: "map": { "": "127.0.0.1" }
mv = map[string]interface{}{"ip": []interface{}{s}}
m[mk] = mv
}
if mvm, ok := mv.(map[string]interface{}); ok {
v2 := &Value{}
mergedNames := map[string]struct{}{}
parse(mvm, v2, resolve, errFunc, depth+1, mergeDepth, "", relname, mergedNames)
if v.Map == nil {
v.Map = make(map[string]*Value)
}
v.Map[mk] = v2
} else {
errFunc.add(fmt.Errorf("Value in map object must be an object or string"))
continue
}
}
}
// Moves items in {"map": {"": ...}} to the object itself, then deletes the ""
// entry in the map object.
func (v *Value) moveEmptyMapItems() {
if ev, ok := v.Map[""]; ok {
if len(v.IP) == 0 {
v.IP = ev.IP
}
if len(v.IP6) == 0 {
v.IP6 = ev.IP6
}
if len(v.NS) == 0 {
v.NS = ev.NS
}
if len(v.DS) == 0 {
v.DS = ev.DS
}
if len(v.TXT) == 0 {
v.TXT = ev.TXT
}
if len(v.Service) == 0 {
v.Service = ev.Service
}
if len(v.MX) == 0 {
v.MX = ev.MX
}
if len(v.Alias) == 0 {
v.Alias = ev.Alias
}
if len(v.Translate) == 0 {
v.Translate = ev.Translate
}
if len(v.Hostmaster) == 0 {
v.Hostmaster = ev.Hostmaster
}
delete(v.Map, "")
if len(v.Map) == 0 {
v.Map = ev.Map
}
}
}