mirror of
https://github.com/namecoin/ncdns
synced 2024-11-10 01:10:31 +00:00
tests pass
This commit is contained in:
parent
8eecfd2b90
commit
9867373039
@ -118,7 +118,7 @@ func (b *Backend) getNamecoinEntryLL(name string) (*domain, error) {
|
|||||||
|
|
||||||
log.Info("namecoin query (", name, ") succeeded: ", v)
|
log.Info("namecoin query (", name, ") succeeded: ", v)
|
||||||
|
|
||||||
d, err := b.jsonToDomain(v)
|
d, err := b.jsonToDomain(name, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Infoe(err, "cannot convert JSON to domain")
|
log.Infoe(err, "cannot convert JSON to domain")
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -127,10 +127,10 @@ func (b *Backend) getNamecoinEntryLL(name string) (*domain, error) {
|
|||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) jsonToDomain(jsonValue string) (*domain, error) {
|
func (b *Backend) jsonToDomain(name, jsonValue string) (*domain, error) {
|
||||||
d := &domain{}
|
d := &domain{}
|
||||||
|
|
||||||
v, err := ncdomain.ParseValue(jsonValue, b.resolveExtraName)
|
v, err := ncdomain.ParseValue(name, jsonValue, b.resolveExtraName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -344,10 +344,7 @@ func (tx *btx) _findNCValue(ncv *ncdomain.Value, isubname, subname string, depth
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isubname != "" {
|
if isubname != "" {
|
||||||
head, rest, err := util.SplitDomainHead(isubname)
|
head, rest := util.SplitDomainHead(isubname)
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
sub, ok := ncv.Map[head]
|
sub, ok := ncv.Map[head]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -367,7 +364,7 @@ func (tx *btx) _findNCValue(ncv *ncdomain.Value, isubname, subname string, depth
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tx *btx) addAnswersUnderNCValueActual(ncv *ncdomain.Value, sn string) (rrs []dns.RR, err error) {
|
func (tx *btx) addAnswersUnderNCValueActual(ncv *ncdomain.Value, sn string) (rrs []dns.RR, err error) {
|
||||||
rrs, err = ncv.RRs(nil, dns.Fqdn(tx.qname)) //convertAt(nil, dns.Fqdn(tx.qname), ncv)
|
rrs, err = ncv.RRs(nil, dns.Fqdn(tx.qname), dns.Fqdn(tx.basename+"."+tx.rootname)) //convertAt(nil, dns.Fqdn(tx.qname), ncv)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,9 +196,81 @@ func syncReplyParser(m json.RawMessage) (interface{}, error) {
|
|||||||
return nsr, nil
|
return nsr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// name_filter
|
||||||
|
|
||||||
|
type NameFilterCmd struct {
|
||||||
|
id interface{}
|
||||||
|
Regexp string
|
||||||
|
MaxAge int
|
||||||
|
From int
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNameFilterCmd(id interface{}, regexp string, maxage, from, count int) (*NameFilterCmd, error) {
|
||||||
|
return &NameFilterCmd{
|
||||||
|
id: id,
|
||||||
|
Regexp: regexp,
|
||||||
|
MaxAge: maxage,
|
||||||
|
From: from,
|
||||||
|
Count: count,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NameFilterCmd) Id() interface{} {
|
||||||
|
return c.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NameFilterCmd) Method() string {
|
||||||
|
return "name_filter"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NameFilterCmd) MarshalJSON() ([]byte, error) {
|
||||||
|
params := []interface{}{
|
||||||
|
c.Regexp,
|
||||||
|
c.MaxAge,
|
||||||
|
c.From,
|
||||||
|
c.Count,
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := btcjson.NewRawCmd(c.id, c.Method(), params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NameFilterCmd) UnmarshalJSON(b []byte) error {
|
||||||
|
// We don't need to implement this as we are only ever the client.
|
||||||
|
panic("not implemented")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NameFilterReply []NameFilterItem
|
||||||
|
type NameFilterItem struct {
|
||||||
|
Name string // "d/example"
|
||||||
|
Value string // "..."
|
||||||
|
TxID string
|
||||||
|
Address string
|
||||||
|
Height int
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
Expired bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterReplyParser(m json.RawMessage) (interface{}, error) {
|
||||||
|
nsr := NameFilterReply{}
|
||||||
|
err := json.Unmarshal(m, &nsr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nsr, nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
btcjson.RegisterCustomCmd("name_show", nil, showReplyParser, "name_show <name>")
|
btcjson.RegisterCustomCmd("name_show", nil, showReplyParser, "name_show <name>")
|
||||||
btcjson.RegisterCustomCmd("name_sync", nil, syncReplyParser, "name_sync <block-hash> <count> <wait?>")
|
btcjson.RegisterCustomCmd("name_sync", nil, syncReplyParser, "name_sync <block-hash> <count> <wait?>")
|
||||||
|
btcjson.RegisterCustomCmd("name_filter", nil, filterReplyParser, "name_filter <regexp> <maxage> <from> <count>")
|
||||||
}
|
}
|
||||||
|
|
||||||
// © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later
|
// © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later
|
||||||
|
@ -119,4 +119,30 @@ func (nc *Conn) CurHeight() (int, error) {
|
|||||||
return 0, fmt.Errorf("bad reply")
|
return 0, fmt.Errorf("bad reply")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (nc *Conn) Filter(regexp string, maxage, from, count int) (names []extratypes.NameFilterItem, err error) {
|
||||||
|
cmd, err := extratypes.NewNameFilterCmd(newID(), regexp, maxage, from, count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := btcjson.RpcSend(nc.Username, nc.Password, nc.Server, cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Error != nil {
|
||||||
|
return nil, r.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Result == nil {
|
||||||
|
return nil, fmt.Errorf("got nil result")
|
||||||
|
}
|
||||||
|
|
||||||
|
if nsr, ok := r.Result.(extratypes.NameFilterReply); ok {
|
||||||
|
return []extratypes.NameFilterItem(nsr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("bad reply")
|
||||||
|
}
|
||||||
|
|
||||||
// © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later
|
// © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later
|
||||||
|
@ -8,48 +8,72 @@ import "encoding/base64"
|
|||||||
import "encoding/hex"
|
import "encoding/hex"
|
||||||
import "regexp"
|
import "regexp"
|
||||||
import "net/mail"
|
import "net/mail"
|
||||||
|
import "github.com/hlandau/ncdns/util"
|
||||||
|
import "strings"
|
||||||
|
|
||||||
const depthLimit = 16
|
const depthLimit = 16
|
||||||
const mergeDepthLimit = 4
|
const mergeDepthLimit = 4
|
||||||
|
|
||||||
|
// 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 {
|
type Value struct {
|
||||||
IP []net.IP
|
IP []net.IP
|
||||||
IP6 []net.IP
|
IP6 []net.IP
|
||||||
NS []string
|
NS []string
|
||||||
Alias string
|
Alias string
|
||||||
Translate string
|
HasAlias bool // True if Alias was specified. Necessary as "" is a valid relative alias.
|
||||||
DS []*dns.DS
|
Translate string
|
||||||
TXT [][]string
|
HasTranslate bool // True if Translate was specified. Necessary as "" is a valid relative value for Translate.
|
||||||
Service []*dns.SRV // header name contains e.g. "_http._tcp"
|
DS []*dns.DS
|
||||||
Hostmaster string // "hostmaster@example.com"
|
TXT [][]string
|
||||||
MX []*dns.MX // header name is left blank
|
Service []*dns.SRV // header name contains e.g. "_http._tcp"
|
||||||
Map map[string]*Value // may contain and "*", will not contain ""
|
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) RRs(out []dns.RR, suffix string) ([]dns.RR, error) {
|
func (v *Value) RRs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
|
||||||
il := len(out)
|
il := len(out)
|
||||||
suffix = dns.Fqdn(suffix)
|
suffix = dns.Fqdn(suffix)
|
||||||
|
apexSuffix = dns.Fqdn(apexSuffix)
|
||||||
|
|
||||||
out, _ = v.appendNSs(out, suffix)
|
out, _ = v.appendNSs(out, suffix, apexSuffix)
|
||||||
if len(v.NS) == 0 {
|
if len(v.NS) == 0 {
|
||||||
out, _ = v.appendTranslate(out, suffix)
|
out, _ = v.appendTranslate(out, suffix, apexSuffix)
|
||||||
if v.Translate == "" {
|
if !v.HasTranslate {
|
||||||
out, _ = v.appendAlias(out, suffix)
|
out, _ = v.appendAlias(out, suffix, apexSuffix)
|
||||||
if v.Alias == "" {
|
if !v.HasAlias {
|
||||||
out, _ = v.appendIPs(out, suffix)
|
out, _ = v.appendIPs(out, suffix, apexSuffix)
|
||||||
out, _ = v.appendIP6s(out, suffix)
|
out, _ = v.appendIP6s(out, suffix, apexSuffix)
|
||||||
out, _ = v.appendTXTs(out, suffix)
|
out, _ = v.appendTXTs(out, suffix, apexSuffix)
|
||||||
out, _ = v.appendServices(out, suffix)
|
out, _ = v.appendMXs(out, suffix, apexSuffix)
|
||||||
out, _ = v.appendMXs(out, suffix)
|
|
||||||
}
|
}
|
||||||
|
// 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)
|
out, _ = v.appendDSs(out, suffix, apexSuffix)
|
||||||
|
|
||||||
xout := out[il:]
|
xout := out[il:]
|
||||||
for i := range xout {
|
for i := range xout {
|
||||||
h := xout[i].Header()
|
h := xout[i].Header()
|
||||||
if h.Rrtype == dns.TypeSRV {
|
if rrtypeHasPrefix(h.Rrtype) {
|
||||||
h.Name += "." + suffix
|
h.Name += "." + suffix
|
||||||
} else {
|
} else {
|
||||||
h.Name = suffix
|
h.Name = suffix
|
||||||
@ -59,7 +83,11 @@ func (v *Value) RRs(out []dns.RR, suffix string) ([]dns.RR, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Value) appendIPs(out []dns.RR, suffix string) ([]dns.RR, error) {
|
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 {
|
for _, ip := range v.IP {
|
||||||
out = append(out, &dns.A{
|
out = append(out, &dns.A{
|
||||||
Hdr: dns.RR_Header{
|
Hdr: dns.RR_Header{
|
||||||
@ -75,7 +103,7 @@ func (v *Value) appendIPs(out []dns.RR, suffix string) ([]dns.RR, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Value) appendIP6s(out []dns.RR, suffix string) ([]dns.RR, error) {
|
func (v *Value) appendIP6s(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
|
||||||
for _, ip := range v.IP6 {
|
for _, ip := range v.IP6 {
|
||||||
out = append(out, &dns.AAAA{
|
out = append(out, &dns.AAAA{
|
||||||
Hdr: dns.RR_Header{
|
Hdr: dns.RR_Header{
|
||||||
@ -91,8 +119,13 @@ func (v *Value) appendIP6s(out []dns.RR, suffix string) ([]dns.RR, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Value) appendNSs(out []dns.RR, suffix string) ([]dns.RR, error) {
|
func (v *Value) appendNSs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
|
||||||
for _, ns := range v.NS {
|
for _, ns := range v.NS {
|
||||||
|
qn, ok := v.qualify(ns, suffix, apexSuffix)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
out = append(out, &dns.NS{
|
out = append(out, &dns.NS{
|
||||||
Hdr: dns.RR_Header{
|
Hdr: dns.RR_Header{
|
||||||
Name: suffix,
|
Name: suffix,
|
||||||
@ -100,14 +133,14 @@ func (v *Value) appendNSs(out []dns.RR, suffix string) ([]dns.RR, error) {
|
|||||||
Class: dns.ClassINET,
|
Class: dns.ClassINET,
|
||||||
Ttl: 600,
|
Ttl: 600,
|
||||||
},
|
},
|
||||||
Ns: dns.Fqdn(ns),
|
Ns: qn,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Value) appendTXTs(out []dns.RR, suffix string) ([]dns.RR, error) {
|
func (v *Value) appendTXTs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
|
||||||
for _, txt := range v.TXT {
|
for _, txt := range v.TXT {
|
||||||
out = append(out, &dns.TXT{
|
out = append(out, &dns.TXT{
|
||||||
Hdr: dns.RR_Header{
|
Hdr: dns.RR_Header{
|
||||||
@ -123,7 +156,7 @@ func (v *Value) appendTXTs(out []dns.RR, suffix string) ([]dns.RR, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Value) appendDSs(out []dns.RR, suffix string) ([]dns.RR, error) {
|
func (v *Value) appendDSs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
|
||||||
for _, ds := range v.DS {
|
for _, ds := range v.DS {
|
||||||
out = append(out, ds)
|
out = append(out, ds)
|
||||||
}
|
}
|
||||||
@ -131,7 +164,7 @@ func (v *Value) appendDSs(out []dns.RR, suffix string) ([]dns.RR, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Value) appendMXs(out []dns.RR, suffix string) ([]dns.RR, error) {
|
func (v *Value) appendMXs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
|
||||||
for _, mx := range v.MX {
|
for _, mx := range v.MX {
|
||||||
out = append(out, mx)
|
out = append(out, mx)
|
||||||
}
|
}
|
||||||
@ -139,7 +172,7 @@ func (v *Value) appendMXs(out []dns.RR, suffix string) ([]dns.RR, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Value) appendServices(out []dns.RR, suffix string) ([]dns.RR, error) {
|
func (v *Value) appendServices(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
|
||||||
for _, svc := range v.Service {
|
for _, svc := range v.Service {
|
||||||
out = append(out, svc)
|
out = append(out, svc)
|
||||||
}
|
}
|
||||||
@ -147,8 +180,20 @@ func (v *Value) appendServices(out []dns.RR, suffix string) ([]dns.RR, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Value) appendAlias(out []dns.RR, suffix string) ([]dns.RR, error) {
|
func (v *Value) appendTLSA(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
|
||||||
if v.Alias != "" {
|
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{
|
out = append(out, &dns.CNAME{
|
||||||
Hdr: dns.RR_Header{
|
Hdr: dns.RR_Header{
|
||||||
Name: suffix,
|
Name: suffix,
|
||||||
@ -156,15 +201,19 @@ func (v *Value) appendAlias(out []dns.RR, suffix string) ([]dns.RR, error) {
|
|||||||
Class: dns.ClassINET,
|
Class: dns.ClassINET,
|
||||||
Ttl: 600,
|
Ttl: 600,
|
||||||
},
|
},
|
||||||
Target: v.Alias,
|
Target: qn,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Value) appendTranslate(out []dns.RR, suffix string) ([]dns.RR, error) {
|
func (v *Value) appendTranslate(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
|
||||||
if v.Translate != "" {
|
if v.HasTranslate {
|
||||||
|
qn, ok := v.qualify(v.Translate, suffix, apexSuffix)
|
||||||
|
if !ok {
|
||||||
|
return out, fmt.Errorf("bad translate")
|
||||||
|
}
|
||||||
out = append(out, &dns.DNAME{
|
out = append(out, &dns.DNAME{
|
||||||
Hdr: dns.RR_Header{
|
Hdr: dns.RR_Header{
|
||||||
Name: suffix,
|
Name: suffix,
|
||||||
@ -172,15 +221,15 @@ func (v *Value) appendTranslate(out []dns.RR, suffix string) ([]dns.RR, error) {
|
|||||||
Class: dns.ClassINET,
|
Class: dns.ClassINET,
|
||||||
Ttl: 600,
|
Ttl: 600,
|
||||||
},
|
},
|
||||||
Target: v.Translate,
|
Target: qn,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Value) RRsRecursive(out []dns.RR, suffix string) ([]dns.RR, error) {
|
func (v *Value) RRsRecursive(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
|
||||||
out, err := v.RRs(out, suffix)
|
out, err := v.RRs(out, suffix, apexSuffix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -190,7 +239,7 @@ func (v *Value) RRsRecursive(out []dns.RR, suffix string) ([]dns.RR, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err = mv.RRsRecursive(out, mk+"."+suffix)
|
out, err = mv.RRsRecursive(out, mk+"."+suffix, apexSuffix)
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
// return nil, err
|
// return nil, err
|
||||||
//}
|
//}
|
||||||
@ -199,6 +248,24 @@ func (v *Value) RRsRecursive(out []dns.RR, suffix string) ([]dns.RR, error) {
|
|||||||
return out, nil
|
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 struct {
|
type rawValue struct {
|
||||||
IP interface{} `json:"ip"`
|
IP interface{} `json:"ip"`
|
||||||
IP6 interface{} `json:"ip6"`
|
IP6 interface{} `json:"ip6"`
|
||||||
@ -211,6 +278,7 @@ type rawValue struct {
|
|||||||
TXT interface{} `json:"txt"`
|
TXT interface{} `json:"txt"`
|
||||||
Hostmaster interface{} `json:"email"` // Hostmaster
|
Hostmaster interface{} `json:"email"` // Hostmaster
|
||||||
MX interface{} `json:"mx"`
|
MX interface{} `json:"mx"`
|
||||||
|
TLSA interface{} `json:"tlsa"`
|
||||||
|
|
||||||
Map json.RawMessage `json:"map"`
|
Map json.RawMessage `json:"map"`
|
||||||
|
|
||||||
@ -228,7 +296,7 @@ type ResolveFunc func(name string) (string, error)
|
|||||||
// Namecoin form (e.g. "d/example"). The JSON value or an error should be
|
// Namecoin form (e.g. "d/example"). The JSON value or an error should be
|
||||||
// returned. If no ResolveFunc is passed, "import" and "delegate" statements
|
// returned. If no ResolveFunc is passed, "import" and "delegate" statements
|
||||||
// always fail.
|
// always fail.
|
||||||
func ParseValue(jsonValue string, resolve ResolveFunc) (value *Value, err error) {
|
func ParseValue(name, jsonValue string, resolve ResolveFunc) (value *Value, err error) {
|
||||||
rv := &rawValue{}
|
rv := &rawValue{}
|
||||||
v := &Value{}
|
v := &Value{}
|
||||||
|
|
||||||
@ -243,40 +311,93 @@ func ParseValue(jsonValue string, resolve ResolveFunc) (value *Value, err error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rv.parse(v, resolve, 0, 0)
|
mergedNames := map[string]struct{}{}
|
||||||
|
mergedNames[name] = struct{}{}
|
||||||
|
|
||||||
|
rv.parse(v, resolve, 0, 0, "", "", mergedNames)
|
||||||
|
v.IsTopLevel = true
|
||||||
|
|
||||||
value = v
|
value = v
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parse(v *Value, resolve ResolveFunc, depth, mergeDepth int) error {
|
func (rv *rawValue) parse(v *Value, resolve ResolveFunc, depth, mergeDepth int, subdomain, relname string, mergedNames map[string]struct{}) error {
|
||||||
if depth > depthLimit {
|
if depth > depthLimit {
|
||||||
return fmt.Errorf("depth limit exceeded")
|
return fmt.Errorf("depth limit exceeded")
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, _ := rv.parseDelegate(v, resolve, depth, mergeDepth)
|
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, _ := rv.parseDelegate(v, resolve, depth, mergeDepth, relname, mergedNames)
|
||||||
if ok {
|
if ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rv.parseImport(v, resolve, depth, mergeDepth)
|
rv.parseImport(v, resolve, depth, mergeDepth, relname, mergedNames)
|
||||||
rv.parseIP(v, rv.IP, false)
|
rv.parseIP(v, rv.IP, false)
|
||||||
rv.parseIP(v, rv.IP6, true)
|
rv.parseIP(v, rv.IP6, true)
|
||||||
rv.parseNS(v)
|
rv.parseNS(v, relname)
|
||||||
rv.parseAlias(v)
|
rv.parseAlias(v, relname)
|
||||||
rv.parseTranslate(v)
|
rv.parseTranslate(v, relname)
|
||||||
rv.parseHostmaster(v)
|
rv.parseHostmaster(v)
|
||||||
rv.parseDS(v)
|
rv.parseDS(v)
|
||||||
rv.parseTXT(v)
|
rv.parseTXT(v)
|
||||||
rv.parseService(v)
|
rv.parseService(v, relname)
|
||||||
rv.parseMX(v)
|
rv.parseMX(v, relname)
|
||||||
rv.parseMap(v, resolve, depth, mergeDepth)
|
rv.parseTLSA(v)
|
||||||
|
rv.parseMap(v, resolve, depth, mergeDepth, relname)
|
||||||
v.moveEmptyMapItems()
|
v.moveEmptyMapItems()
|
||||||
|
|
||||||
|
if subdomain != "" {
|
||||||
|
subv, err := v.findSubdomainByName(subdomain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*realv = *subv
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseMerge(mergeValue string, v *Value, resolve ResolveFunc, depth, mergeDepth int) error {
|
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 !validateHostName(s) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rv *rawValue) parseMerge(mergeValue string, v *Value, resolve ResolveFunc, depth, mergeDepth int, subdomain, relname string, mergedNames map[string]struct{}) error {
|
||||||
rv2 := &rawValue{}
|
rv2 := &rawValue{}
|
||||||
|
|
||||||
if mergeDepth > mergeDepthLimit {
|
if mergeDepth > mergeDepthLimit {
|
||||||
@ -288,7 +409,7 @@ func (rv *rawValue) parseMerge(mergeValue string, v *Value, resolve ResolveFunc,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return rv2.parse(v, resolve, depth, mergeDepth)
|
return rv2.parse(v, resolve, depth, mergeDepth, subdomain, relname, mergedNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseIP(v *Value, ipi interface{}, ipv6 bool) {
|
func (rv *rawValue) parseIP(v *Value, ipi interface{}, ipv6 bool) {
|
||||||
@ -330,7 +451,7 @@ func (rv *rawValue) addIP(v *Value, ips string, ipv6 bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseNS(v *Value) error {
|
func (rv *rawValue) parseNS(v *Value, relname string) error {
|
||||||
// "dns" takes precedence
|
// "dns" takes precedence
|
||||||
if rv.DNS != nil {
|
if rv.DNS != nil {
|
||||||
rv.NS = rv.DNS
|
rv.NS = rv.DNS
|
||||||
@ -353,23 +474,19 @@ func (rv *rawValue) parseNS(v *Value) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rv.addNS(v, s)
|
rv.addNS(v, s, relname)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case string:
|
case string:
|
||||||
s := rv.NS.(string)
|
s := rv.NS.(string)
|
||||||
rv.addNS(v, s)
|
rv.addNS(v, s, relname)
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown NS field format")
|
return fmt.Errorf("unknown NS field format")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) addNS(v *Value, s string) error {
|
func (rv *rawValue) addNS(v *Value, s, relname string) error {
|
||||||
if !validateHostName(s) {
|
|
||||||
return fmt.Errorf("malformed NS hostname")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := rv.nsSet[s]; !ok {
|
if _, ok := rv.nsSet[s]; !ok {
|
||||||
v.NS = append(v.NS, s)
|
v.NS = append(v.NS, s)
|
||||||
rv.nsSet[s] = struct{}{}
|
rv.nsSet[s] = struct{}{}
|
||||||
@ -378,70 +495,133 @@ func (rv *rawValue) addNS(v *Value, s string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseAlias(v *Value) error {
|
func (rv *rawValue) parseAlias(v *Value, relname string) error {
|
||||||
if rv.Alias == nil {
|
if rv.Alias == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if s, ok := rv.Alias.(string); ok {
|
if s, ok := rv.Alias.(string); ok {
|
||||||
if !validateHostName(s) {
|
|
||||||
return fmt.Errorf("malformed hostname in alias field")
|
|
||||||
}
|
|
||||||
|
|
||||||
v.Alias = s
|
v.Alias = s
|
||||||
|
v.HasAlias = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("unknown alias field format")
|
return fmt.Errorf("unknown alias field format")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseTranslate(v *Value) error {
|
func (rv *rawValue) parseTranslate(v *Value, relname string) error {
|
||||||
if rv.Translate == nil {
|
if rv.Translate == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if s, ok := rv.Translate.(string); ok {
|
if s, ok := rv.Translate.(string); ok {
|
||||||
if !validateHostName(s) {
|
|
||||||
return fmt.Errorf("malformed hostname in translate field")
|
|
||||||
}
|
|
||||||
|
|
||||||
v.Translate = s
|
v.Translate = s
|
||||||
|
v.HasTranslate = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("unknown translate field format")
|
return fmt.Errorf("unknown translate field format")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseImport(v *Value, resolve ResolveFunc, depth, mergeDepth int) error {
|
func isAllArray(x []interface{}) bool {
|
||||||
if rv.Import == nil {
|
for _, v := range x {
|
||||||
return nil
|
if _, ok := v.([]interface{}); !ok {
|
||||||
}
|
return false
|
||||||
|
|
||||||
if s, ok := rv.Import.(string); ok {
|
|
||||||
dv, err := resolve(s)
|
|
||||||
if err == nil {
|
|
||||||
err = rv.parseMerge(dv, v, resolve, depth, mergeDepth+1)
|
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
return fmt.Errorf("unknown import field format")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseDelegate(v *Value, resolve ResolveFunc, depth, mergeDepth int) (bool, error) {
|
func isAllString(x []interface{}) bool {
|
||||||
if rv.Delegate == nil {
|
for _, v := range x {
|
||||||
|
if _, ok := v.(string); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rv *rawValue) parseImportImpl(val *Value, resolve ResolveFunc, depth, mergeDepth int, relname string, mergedNames map[string]struct{}, delegate bool) (bool, error) {
|
||||||
|
var err error
|
||||||
|
succeeded := false
|
||||||
|
src := rv.Import
|
||||||
|
if delegate {
|
||||||
|
src = rv.Delegate
|
||||||
|
}
|
||||||
|
|
||||||
|
if src == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if s, ok := rv.Delegate.(string); ok {
|
if s, ok := src.(string); ok {
|
||||||
dv, err := resolve(s)
|
src = []interface{}{s}
|
||||||
if err == nil {
|
|
||||||
err = rv.parseMerge(dv, v, resolve, depth, mergeDepth+1)
|
|
||||||
}
|
|
||||||
return true, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, fmt.Errorf("unknown delegate field format")
|
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 = rv.parseMerge(dv, val, resolve, depth, mergeDepth+1, subs, relname, mergedNames)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
succeeded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// malformed
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = fmt.Errorf("unknown import/delegate field format")
|
||||||
|
}
|
||||||
|
|
||||||
|
return succeeded, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rv *rawValue) parseImport(v *Value, resolve ResolveFunc, depth, mergeDepth int, relname string, mergedNames map[string]struct{}) error {
|
||||||
|
_, err := rv.parseImportImpl(v, resolve, depth, mergeDepth, relname, mergedNames, false)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rv *rawValue) parseDelegate(v *Value, resolve ResolveFunc, depth, mergeDepth int, relname string, mergedNames map[string]struct{}) (bool, error) {
|
||||||
|
return rv.parseImportImpl(v, resolve, depth, mergeDepth, relname, mergedNames, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseHostmaster(v *Value) error {
|
func (rv *rawValue) parseHostmaster(v *Value) error {
|
||||||
@ -511,9 +691,81 @@ func (rv *rawValue) parseDS(v *Value) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("malformed DS field format")
|
return fmt.Errorf("malformed DS field format")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rv *rawValue) parseTLSA(v *Value) error {
|
||||||
|
if rv.TLSA == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v.TLSA = nil
|
||||||
|
|
||||||
|
if tlsaa, ok := rv.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 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ports, ok := tlsa[0].(string)
|
||||||
|
if !ok {
|
||||||
|
porti, ok := tlsa[0].(float64)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ports = fmt.Sprintf("%d", int(porti))
|
||||||
|
}
|
||||||
|
|
||||||
|
transport, ok := tlsa[1].(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
a1, ok := tlsa[2].(float64)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
a2, ok := tlsa[3].(float64)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
a3, ok := tlsa[4].(float64)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
a4, ok := tlsa[5].(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
a4b, err := base64.StdEncoding.DecodeString(a4)
|
||||||
|
if err != nil {
|
||||||
|
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: 600},
|
||||||
|
Usage: uint8(a1),
|
||||||
|
Selector: uint8(a2),
|
||||||
|
MatchingType: uint8(a3),
|
||||||
|
Certificate: strings.ToUpper(a4h),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("malformed TLSA field format")
|
||||||
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseTXT(v *Value) error {
|
func (rv *rawValue) parseTXT(v *Value) error {
|
||||||
if rv.TXT == nil {
|
if rv.TXT == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -578,21 +830,21 @@ func segmentizeTXT(txt string) (a []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseMX(v *Value) error {
|
func (rv *rawValue) parseMX(v *Value, relname string) error {
|
||||||
if rv.MX == nil {
|
if rv.MX == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if sa, ok := rv.MX.([]interface{}); ok {
|
if sa, ok := rv.MX.([]interface{}); ok {
|
||||||
for _, s := range sa {
|
for _, s := range sa {
|
||||||
rv.parseSingleMX(s, v)
|
rv.parseSingleMX(s, v, relname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("malformed MX value")
|
return fmt.Errorf("malformed MX value")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseSingleMX(s interface{}, v *Value) error {
|
func (rv *rawValue) parseSingleMX(s interface{}, v *Value, relname string) error {
|
||||||
sa, ok := s.([]interface{})
|
sa, ok := s.([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("malformed MX value")
|
return fmt.Errorf("malformed MX value")
|
||||||
@ -608,7 +860,7 @@ func (rv *rawValue) parseSingleMX(s interface{}, v *Value) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hostname, ok := sa[1].(string)
|
hostname, ok := sa[1].(string)
|
||||||
if !ok || !validateHostName(hostname) {
|
if !ok {
|
||||||
return fmt.Errorf("malformed MX value")
|
return fmt.Errorf("malformed MX value")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -621,21 +873,33 @@ func (rv *rawValue) parseSingleMX(s interface{}, v *Value) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseService(v *Value) error {
|
func (rv *rawValue) parseService(v *Value, relname string) error {
|
||||||
if rv.Service == nil {
|
if rv.Service == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 := rv.Service.([]interface{}); ok {
|
if sa, ok := rv.Service.([]interface{}); ok {
|
||||||
for _, s := range sa {
|
for _, s := range sa {
|
||||||
rv.parseSingleService(s, v)
|
rv.parseSingleService(s, v, relname, servicesUsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, svc := range oldServices {
|
||||||
|
if _, ok := servicesUsed[svc.Header().Name]; !ok {
|
||||||
|
v.Service = append(v.Service, svc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("malformed service value")
|
return fmt.Errorf("malformed service value")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseSingleService(svc interface{}, v *Value) error {
|
func (rv *rawValue) parseSingleService(svc interface{}, v *Value, relname string, servicesUsed map[string]struct{}) error {
|
||||||
svca, ok := svc.([]interface{})
|
svca, ok := svc.([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("malformed service value")
|
return fmt.Errorf("malformed service value")
|
||||||
@ -671,13 +935,16 @@ func (rv *rawValue) parseSingleService(svc interface{}, v *Value) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hostname, ok := svca[5].(string)
|
hostname, ok := svca[5].(string)
|
||||||
if !ok || !validateHostName(hostname) {
|
if !ok {
|
||||||
return fmt.Errorf("malformed service value")
|
return fmt.Errorf("malformed service value")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sname := "_" + appProtoName + "._" + transportProtoName
|
||||||
|
servicesUsed[sname] = struct{}{}
|
||||||
|
|
||||||
v.Service = append(v.Service, &dns.SRV{
|
v.Service = append(v.Service, &dns.SRV{
|
||||||
Hdr: dns.RR_Header{
|
Hdr: dns.RR_Header{
|
||||||
Name: "_" + appProtoName + "._" + transportProtoName,
|
Name: sname,
|
||||||
Rrtype: dns.TypeSRV,
|
Rrtype: dns.TypeSRV,
|
||||||
Class: dns.ClassINET,
|
Class: dns.ClassINET,
|
||||||
Ttl: 600,
|
Ttl: 600,
|
||||||
@ -691,7 +958,7 @@ func (rv *rawValue) parseSingleService(svc interface{}, v *Value) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rawValue) parseMap(v *Value, resolve ResolveFunc, depth, mergeDepth int) error {
|
func (rv *rawValue) parseMap(v *Value, resolve ResolveFunc, depth, mergeDepth int, relname string) error {
|
||||||
m := map[string]json.RawMessage{}
|
m := map[string]json.RawMessage{}
|
||||||
|
|
||||||
err := json.Unmarshal(rv.Map, &m)
|
err := json.Unmarshal(rv.Map, &m)
|
||||||
@ -717,7 +984,8 @@ func (rv *rawValue) parseMap(v *Value, resolve ResolveFunc, depth, mergeDepth in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rv2.parse(v2, resolve, depth+1, mergeDepth)
|
mergedNames := map[string]struct{}{}
|
||||||
|
rv2.parse(v2, resolve, depth+1, mergeDepth, "", relname, mergedNames)
|
||||||
|
|
||||||
if v.Map == nil {
|
if v.Map == nil {
|
||||||
v.Map = make(map[string]*Value)
|
v.Map = make(map[string]*Value)
|
||||||
@ -776,7 +1044,7 @@ func (v *Value) moveEmptyMapItems() error {
|
|||||||
// This is used to validate NS records, targets in SRV records, etc. In these cases
|
// This is used to validate NS records, targets in SRV records, etc. In these cases
|
||||||
// an IP address is not allowed. Therefore this regex must exclude all-numeric domain names.
|
// an IP address is not allowed. Therefore this regex must exclude all-numeric domain names.
|
||||||
// This is done by requiring the final part to start with an alphabetic character.
|
// This is done by requiring the final part to start with an alphabetic character.
|
||||||
var re_hostName = regexp.MustCompilePOSIX(`^([a-z0-9_][a-z0-9_-]{0,62}\.)*[a-z_][a-z0-9_-]{0,62}\.?$`)
|
var re_hostName = regexp.MustCompilePOSIX(`^(([a-z0-9_][a-z0-9_-]{0,62}\.)*[a-z_][a-z0-9_-]{0,62}\.?|\.)$`)
|
||||||
var re_serviceName = regexp.MustCompilePOSIX(`^[a-z_][a-z0-9_-]*$`)
|
var re_serviceName = regexp.MustCompilePOSIX(`^[a-z_][a-z0-9_-]*$`)
|
||||||
var re_label = regexp.MustCompilePOSIX(`^[a-z0-9_][a-z0-9_-]*$`)
|
var re_label = regexp.MustCompilePOSIX(`^[a-z0-9_][a-z0-9_-]*$`)
|
||||||
|
|
||||||
|
@ -1,12 +1,208 @@
|
|||||||
package ncdomain_test
|
package ncdomain_test
|
||||||
|
|
||||||
import "github.com/hlandau/ncdns/ncdomain"
|
import "github.com/hlandau/ncdns/ncdomain"
|
||||||
import "github.com/miekg/dns"
|
import _ "github.com/hlandau/nctestsuite"
|
||||||
import "testing"
|
import "testing"
|
||||||
import "net"
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
import "strings"
|
import "strings"
|
||||||
|
import "os"
|
||||||
|
import "path/filepath"
|
||||||
|
import "bufio"
|
||||||
|
import "io"
|
||||||
|
import "sort"
|
||||||
|
import "unicode"
|
||||||
|
import "unicode/utf8"
|
||||||
|
|
||||||
|
//import "github.com/miekg/dns"
|
||||||
|
//import "net"
|
||||||
|
|
||||||
|
type testItem struct {
|
||||||
|
ID string
|
||||||
|
Names map[string]string
|
||||||
|
Records string
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripTag(L string) string {
|
||||||
|
if len(L) < 3 {
|
||||||
|
return L
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _ := utf8.DecodeRuneInString(L)
|
||||||
|
|
||||||
|
if unicode.IsUpper(r) {
|
||||||
|
x := strings.IndexRune(L, ' ')
|
||||||
|
L = L[x+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return L
|
||||||
|
}
|
||||||
|
|
||||||
|
func suiteReader(t *testing.T) <-chan testItem {
|
||||||
|
testItemChan := make(chan testItem, 20)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
gopath := os.Getenv("GOPATH")
|
||||||
|
if gopath == "" {
|
||||||
|
gopath = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
fpath := filepath.Join(gopath, "src/github.com/hlandau/nctestsuite/testsuite.txt")
|
||||||
|
f, err := os.Open(fpath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error: Couldn't open %s: %+v", fpath, err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
lineChan := make(chan string, 20)
|
||||||
|
syncChan := make(chan struct{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
reissue := false
|
||||||
|
var L string
|
||||||
|
var ok bool
|
||||||
|
for {
|
||||||
|
if reissue {
|
||||||
|
reissue = false
|
||||||
|
} else {
|
||||||
|
L, ok = <-lineChan
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]string{}
|
||||||
|
|
||||||
|
if L != "IN" && !strings.HasPrefix(L, "IN ") {
|
||||||
|
t.Fatalf("invalid test suite file")
|
||||||
|
}
|
||||||
|
|
||||||
|
id := ""
|
||||||
|
if len(L) > 2 {
|
||||||
|
id = L[3:]
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
name := <-lineChan
|
||||||
|
value := <-lineChan
|
||||||
|
m[name] = value
|
||||||
|
|
||||||
|
L = <-lineChan
|
||||||
|
if L != "IN" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if L != "OUT" {
|
||||||
|
t.Fatalf("invalid test suite file")
|
||||||
|
}
|
||||||
|
|
||||||
|
records := []string{}
|
||||||
|
for {
|
||||||
|
L, ok = <-lineChan
|
||||||
|
if !ok || L == "IN" || strings.HasPrefix(L, "IN ") {
|
||||||
|
reissue = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
L = stripTag(L)
|
||||||
|
records = append(records, L)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(records)
|
||||||
|
|
||||||
|
// process records
|
||||||
|
ti := testItem{
|
||||||
|
ID: id,
|
||||||
|
Names: m,
|
||||||
|
Records: strings.Join(records, "\n"),
|
||||||
|
}
|
||||||
|
|
||||||
|
testItemChan <- ti
|
||||||
|
}
|
||||||
|
|
||||||
|
close(testItemChan)
|
||||||
|
close(syncChan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
r := bufio.NewReader(f)
|
||||||
|
for {
|
||||||
|
L, err := r.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
t.Errorf("error while reading line: %+v", err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
L = strings.Trim(L, " \t\r\n")
|
||||||
|
if L == "" || (len(L) > 0 && L[0] == '#') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lineChan <- L
|
||||||
|
}
|
||||||
|
close(lineChan)
|
||||||
|
<-syncChan
|
||||||
|
}()
|
||||||
|
|
||||||
|
return testItemChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuite(t *testing.T) {
|
||||||
|
items := suiteReader(t)
|
||||||
|
for ti := range items {
|
||||||
|
resolve := func(name string) (string, error) {
|
||||||
|
v, ok := ti.Names[name]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, jsonValue := range ti.Names {
|
||||||
|
dnsName, err := convertName(k)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := ncdomain.ParseValue(k, jsonValue, resolve)
|
||||||
|
if err != nil {
|
||||||
|
// TODO
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rrstrs := []string{}
|
||||||
|
rrs, _ := v.RRsRecursive(nil, dnsName+".bit.", dnsName+".bit.")
|
||||||
|
for _, rr := range rrs {
|
||||||
|
s := rr.String()
|
||||||
|
s = strings.Replace(s, "\t600\t", "\t", -1) // XXX
|
||||||
|
rrstrs = append(rrstrs, strings.Replace(s, "\t", " ", -1))
|
||||||
|
}
|
||||||
|
sort.Strings(rrstrs)
|
||||||
|
rrstr := strings.Join(rrstrs, "\n")
|
||||||
|
|
||||||
|
// CHECK MATCH
|
||||||
|
if rrstr != ti.Records {
|
||||||
|
t.Errorf("Didn't match: %s\n%+v\n !=\n%+v\n\n%#v\n\n%#v", ti.ID, rrstr, ti.Records, v, rrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertName(n string) (string, error) {
|
||||||
|
if len(n) < 3 || len(n) > 65 {
|
||||||
|
return "", fmt.Errorf("invalid name")
|
||||||
|
}
|
||||||
|
if n[0:2] != "d/" {
|
||||||
|
return "", fmt.Errorf("not a domain")
|
||||||
|
}
|
||||||
|
return n[2:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
type item struct {
|
type item struct {
|
||||||
jsonValue string
|
jsonValue string
|
||||||
value *ncdomain.Value
|
value *ncdomain.Value
|
||||||
@ -195,4 +391,4 @@ func eqValueMap(a *ncdomain.Value, b *ncdomain.Value) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}*/
|
||||||
|
13
util/util.go
13
util/util.go
@ -3,7 +3,7 @@ package util
|
|||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
// Split a domain name a.b.c.d.e into parts e (the head) and a.b.c.d (the rest).
|
// Split a domain name a.b.c.d.e into parts e (the head) and a.b.c.d (the rest).
|
||||||
func SplitDomainHead(name string) (head string, rest string, err error) {
|
func SplitDomainHead(name string) (head, rest string) {
|
||||||
if len(name) > 0 && name[len(name)-1] == '.' {
|
if len(name) > 0 && name[len(name)-1] == '.' {
|
||||||
name = name[0 : len(name)-1]
|
name = name[0 : len(name)-1]
|
||||||
}
|
}
|
||||||
@ -19,4 +19,15 @@ func SplitDomainHead(name string) (head string, rest string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split a domain name a.b.c.d.e into parts a (the tail) and b.c.d.e (the rest).
|
||||||
|
func SplitDomainTail(name string) (tail, rest string) {
|
||||||
|
s := strings.SplitN(name, ".", 2)
|
||||||
|
|
||||||
|
if len(s) == 1 {
|
||||||
|
return s[0], ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return s[0], s[1]
|
||||||
|
}
|
||||||
|
|
||||||
// © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later
|
// © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later
|
||||||
|
Loading…
Reference in New Issue
Block a user