|
|
|
@ -1,16 +1,14 @@
|
|
|
|
|
package backend
|
|
|
|
|
|
|
|
|
|
import "github.com/golang/groupcache/lru"
|
|
|
|
|
import "github.com/miekg/dns"
|
|
|
|
|
import "github.com/hlandau/degoutils/log"
|
|
|
|
|
import "fmt"
|
|
|
|
|
import "strings"
|
|
|
|
|
import "net"
|
|
|
|
|
import "github.com/hlandau/ncdns/namecoin"
|
|
|
|
|
import "github.com/golang/groupcache/lru"
|
|
|
|
|
import "github.com/hlandau/madns/merr"
|
|
|
|
|
import "github.com/hlandau/ncdns/namecoin"
|
|
|
|
|
import "github.com/hlandau/ncdns/util"
|
|
|
|
|
import "github.com/hlandau/ncdns/ncdomain"
|
|
|
|
|
import "sync"
|
|
|
|
|
import "fmt"
|
|
|
|
|
import "net"
|
|
|
|
|
|
|
|
|
|
// Provides an abstract zone file for the Namecoin .bit TLD.
|
|
|
|
|
type Backend struct {
|
|
|
|
@ -21,9 +19,7 @@ type Backend struct {
|
|
|
|
|
cfg Config
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
defaultMaxEntries = 100
|
|
|
|
|
)
|
|
|
|
|
const defaultMaxEntries = 100
|
|
|
|
|
|
|
|
|
|
// Backend configuration.
|
|
|
|
|
type Config struct {
|
|
|
|
@ -65,108 +61,21 @@ func New(cfg *Config) (backend *Backend, err error) {
|
|
|
|
|
b.cache.MaxEntries = defaultMaxEntries
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if b.cfg.FakeNames == nil {
|
|
|
|
|
b.cfg.FakeNames = map[string]string{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
backend = b
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Keep domains in parsed format.
|
|
|
|
|
type domain struct {
|
|
|
|
|
ncv *ncdomain.Value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func toNamecoinName(basename string) (string, error) {
|
|
|
|
|
return "d/" + basename, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) getNamecoinEntry(name string) (*domain, error) {
|
|
|
|
|
d := b.getNamecoinEntryCache(name)
|
|
|
|
|
if d != nil {
|
|
|
|
|
return d, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d, err := b.getNamecoinEntryLL(name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
b.addNamecoinEntryToCache(name, d)
|
|
|
|
|
return d, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) getNamecoinEntryCache(name string) *domain {
|
|
|
|
|
b.cacheMutex.Lock()
|
|
|
|
|
defer b.cacheMutex.Unlock()
|
|
|
|
|
|
|
|
|
|
if dd, ok := b.cache.Get(name); ok {
|
|
|
|
|
d := dd.(*domain)
|
|
|
|
|
return d
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) addNamecoinEntryToCache(name string, d *domain) {
|
|
|
|
|
b.cacheMutex.Lock()
|
|
|
|
|
defer b.cacheMutex.Unlock()
|
|
|
|
|
|
|
|
|
|
b.cache.Add(name, d)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) resolveName(name string) (jsonValue string, err error) {
|
|
|
|
|
if fv, ok := b.cfg.FakeNames[name]; ok {
|
|
|
|
|
if fv == "NX" {
|
|
|
|
|
return "", merr.ErrNoSuchDomain
|
|
|
|
|
}
|
|
|
|
|
return fv, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v, err := b.nc.Query(name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return v, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) getNamecoinEntryLL(name string) (*domain, error) {
|
|
|
|
|
v, err := b.resolveName(name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Info("namecoin query (", name, ") succeeded: ", v)
|
|
|
|
|
|
|
|
|
|
d, err := b.jsonToDomain(name, v)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Infoe(err, "cannot convert JSON to domain")
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return d, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) jsonToDomain(name, jsonValue string) (*domain, error) {
|
|
|
|
|
d := &domain{}
|
|
|
|
|
|
|
|
|
|
v, err := ncdomain.ParseValue(name, jsonValue, b.resolveExtraName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d.ncv = v
|
|
|
|
|
|
|
|
|
|
return d, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) resolveExtraName(name string) (jsonValue string, err error) {
|
|
|
|
|
return b.resolveName(name)
|
|
|
|
|
// Do low-level queries against an abstract zone file. This is the per-query
|
|
|
|
|
// entrypoint from madns.
|
|
|
|
|
func (b *Backend) Lookup(qname string) (rrs []dns.RR, err error) {
|
|
|
|
|
btx := &btx{}
|
|
|
|
|
btx.b = b
|
|
|
|
|
btx.qname = qname
|
|
|
|
|
return btx.Do()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Things to keep track of while processing a query.
|
|
|
|
|
type btx struct {
|
|
|
|
|
b *Backend
|
|
|
|
|
qname string
|
|
|
|
@ -174,73 +83,47 @@ type btx struct {
|
|
|
|
|
subname, basename, rootname string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tx *btx) determineDomain() (subname, basename, rootname string, err error) {
|
|
|
|
|
qname := tx.qname
|
|
|
|
|
qname = strings.TrimRight(qname, ".")
|
|
|
|
|
parts := strings.Split(qname, ".")
|
|
|
|
|
if len(parts) < 2 {
|
|
|
|
|
if parts[0] != "bit" {
|
|
|
|
|
err = merr.ErrNotInZone
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rootname = parts[0]
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := len(parts) - 1; i >= 0; i-- {
|
|
|
|
|
v := parts[i]
|
|
|
|
|
|
|
|
|
|
// scanning for rootname
|
|
|
|
|
if v == "bit" {
|
|
|
|
|
if i == 0 {
|
|
|
|
|
// i is already zero, so we have something like bit.x.y.z.
|
|
|
|
|
rootname = qname
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
rootname = strings.Join(parts[i:len(parts)], ".")
|
|
|
|
|
basename = parts[i-1]
|
|
|
|
|
subname = strings.Join(parts[0:i-1], ".")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = merr.ErrNotInZone
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tx *btx) Do() (rrs []dns.RR, err error) {
|
|
|
|
|
// Split the domain up. 'rootname' is the TLD and everything after it,
|
|
|
|
|
// basename is the name directly before that, and subname is every name
|
|
|
|
|
// before that. So "a.b.example.bit.suffix.xyz." would have a subname
|
|
|
|
|
// of "a.b", a basename of "example" and a rootname of "bit.suffix.xyz".
|
|
|
|
|
tx.subname, tx.basename, tx.rootname, err = tx.determineDomain()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Infoe(err, "couldn't determine domain")
|
|
|
|
|
// We get an error if '.bit.' does not appear anywhere. In that case
|
|
|
|
|
// we're not authoritative for the query in question and error out.
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Info("domain: sub=", tx.subname, " basename=", tx.basename, " rootname=", tx.rootname)
|
|
|
|
|
|
|
|
|
|
if tx.rootname == "" {
|
|
|
|
|
// REFUSED
|
|
|
|
|
return nil, merr.ErrNotInZone
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If subname and basename are "", this means the query was for a root name
|
|
|
|
|
// such as "bit." or "bit.suffix.xyz." directly. Serve SOA, NS records, etc.
|
|
|
|
|
// as requested.
|
|
|
|
|
if tx.subname == "" && tx.basename == "" {
|
|
|
|
|
return tx.doRootDomain()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Where ncdns has not been configured with a hostname to identify itself by,
|
|
|
|
|
// it generates one under a special meta domain "x--nmc". This domain is not
|
|
|
|
|
// a valid Namecoin domain name, so it does not confict with the Namecoin
|
|
|
|
|
// domain name namespace.
|
|
|
|
|
if tx.basename == "x--nmc" && tx.b.cfg.SelfName == "" {
|
|
|
|
|
return tx.doMetaDomain()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we have reached this point the query must be a normal user query.
|
|
|
|
|
rrs, err = tx.doUserDomain()
|
|
|
|
|
|
|
|
|
|
log.Info("USER RECORDS YIELDED:")
|
|
|
|
|
for _, rr := range rrs {
|
|
|
|
|
log.Info(" ", rr.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tx *btx) determineDomain() (subname, basename, rootname string, err error) {
|
|
|
|
|
return util.SplitDomainByFloatingAnchor(tx.qname, "bit")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tx *btx) doRootDomain() (rrs []dns.RR, err error) {
|
|
|
|
|
nsname := tx.b.cfg.SelfName
|
|
|
|
|
if nsname == "" {
|
|
|
|
@ -304,27 +187,110 @@ func (tx *btx) doMetaDomain() (rrs []dns.RR, err error) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tx *btx) doUserDomain() (rrs []dns.RR, err error) {
|
|
|
|
|
ncname, err := toNamecoinName(tx.basename)
|
|
|
|
|
ncname, err := util.BasenameToNamecoinKey(tx.basename)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Infoe(err, "cannot determine namecoin name")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d, err := tx.b.getNamecoinEntry(ncname)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Infoe(err, "cannot get namecoin entry")
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rrs, err = tx.doUnderDomain(d)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Infoe(err, "cannot process namecoin entry under domain")
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rrs, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Keep domains in parsed format.
|
|
|
|
|
type domain struct {
|
|
|
|
|
ncv *ncdomain.Value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) getNamecoinEntry(name string) (*domain, error) {
|
|
|
|
|
d := b.getNamecoinEntryCache(name)
|
|
|
|
|
if d != nil {
|
|
|
|
|
return d, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d, err := b.getNamecoinEntryLL(name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
b.addNamecoinEntryToCache(name, d)
|
|
|
|
|
return d, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) getNamecoinEntryCache(name string) *domain {
|
|
|
|
|
b.cacheMutex.Lock()
|
|
|
|
|
defer b.cacheMutex.Unlock()
|
|
|
|
|
|
|
|
|
|
if dd, ok := b.cache.Get(name); ok {
|
|
|
|
|
d := dd.(*domain)
|
|
|
|
|
return d
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) addNamecoinEntryToCache(name string, d *domain) {
|
|
|
|
|
b.cacheMutex.Lock()
|
|
|
|
|
defer b.cacheMutex.Unlock()
|
|
|
|
|
|
|
|
|
|
b.cache.Add(name, d)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) getNamecoinEntryLL(name string) (*domain, error) {
|
|
|
|
|
v, err := b.resolveName(name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d, err := b.jsonToDomain(name, v)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return d, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) resolveName(name string) (jsonValue string, err error) {
|
|
|
|
|
if fv, ok := b.cfg.FakeNames[name]; ok {
|
|
|
|
|
if fv == "NX" {
|
|
|
|
|
return "", merr.ErrNoSuchDomain
|
|
|
|
|
}
|
|
|
|
|
return fv, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v, err := b.nc.Query(name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return v, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) jsonToDomain(name, jsonValue string) (*domain, error) {
|
|
|
|
|
d := &domain{}
|
|
|
|
|
|
|
|
|
|
v, err := ncdomain.ParseValue(name, jsonValue, b.resolveExtraName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d.ncv = v
|
|
|
|
|
|
|
|
|
|
return d, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Backend) resolveExtraName(name string) (jsonValue string, err error) {
|
|
|
|
|
return b.resolveName(name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tx *btx) doUnderDomain(d *domain) (rrs []dns.RR, err error) {
|
|
|
|
|
rrs, err = tx.addAnswersUnderNCValue(d.ncv, tx.subname)
|
|
|
|
|
if err == merr.ErrNoResults {
|
|
|
|
@ -340,15 +306,9 @@ func (tx *btx) addAnswersUnderNCValue(rncv *ncdomain.Value, subname string) (rrs
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Info("ncv actual: ", sn)
|
|
|
|
|
return tx.addAnswersUnderNCValueActual(ncv, sn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*func hasNS(ncv *ncdomain.Value) bool {
|
|
|
|
|
nss, err := ncv.GetNSs()
|
|
|
|
|
return err == nil && len(nss) > 0
|
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
func (tx *btx) findNCValue(ncv *ncdomain.Value, subname string, shortCircuitFunc func(curNCV *ncdomain.Value) bool) (xncv *ncdomain.Value, sn string, err error) {
|
|
|
|
|
return tx._findNCValue(ncv, subname, "", 0, shortCircuitFunc)
|
|
|
|
|
}
|
|
|
|
@ -381,7 +341,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) {
|
|
|
|
|
rrs, err = ncv.RRs(nil, dns.Fqdn(tx.qname), dns.Fqdn(tx.basename+"."+tx.rootname)) //convertAt(nil, dns.Fqdn(tx.qname), ncv)
|
|
|
|
|
rrs, err = ncv.RRs(nil, dns.Fqdn(tx.qname), dns.Fqdn(tx.basename+"."+tx.rootname))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -395,12 +355,4 @@ func (tx *btx) addAnswersUnderNCValueActual(ncv *ncdomain.Value, sn string) (rrs
|
|
|
|
|
// f[b]("a", "b.c.d.e.f.g.zzz.bit")
|
|
|
|
|
// f[a]("", "a.b.c.d.e.f.g.zzz.bit")
|
|
|
|
|
|
|
|
|
|
// Do low-level queries against an abstract zone file.
|
|
|
|
|
func (b *Backend) Lookup(qname string) (rrs []dns.RR, err error) {
|
|
|
|
|
btx := &btx{}
|
|
|
|
|
btx.b = b
|
|
|
|
|
btx.qname = qname
|
|
|
|
|
return btx.Do()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later
|
|
|
|
|