2
0
mirror of https://github.com/namecoin/ncdns synced 2024-11-10 01:10:31 +00:00

tests pass

This commit is contained in:
Hugo Landau 2014-12-01 00:44:06 +00:00
parent 8eecfd2b90
commit 9867373039
6 changed files with 687 additions and 117 deletions

View File

@ -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
} }

View File

@ -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

View File

@ -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

View File

@ -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_-]*$`)

View File

@ -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
} }*/

View File

@ -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