pull/18/head
Hugo Landau 10 years ago
parent d8c033a4df
commit 1896399ba8

@ -1,16 +1,14 @@
package backend package backend
import "github.com/golang/groupcache/lru"
import "github.com/miekg/dns" import "github.com/miekg/dns"
import "github.com/hlandau/degoutils/log" import "github.com/golang/groupcache/lru"
import "fmt"
import "strings"
import "net"
import "github.com/hlandau/ncdns/namecoin"
import "github.com/hlandau/madns/merr" import "github.com/hlandau/madns/merr"
import "github.com/hlandau/ncdns/namecoin"
import "github.com/hlandau/ncdns/util" import "github.com/hlandau/ncdns/util"
import "github.com/hlandau/ncdns/ncdomain" import "github.com/hlandau/ncdns/ncdomain"
import "sync" import "sync"
import "fmt"
import "net"
// Provides an abstract zone file for the Namecoin .bit TLD. // Provides an abstract zone file for the Namecoin .bit TLD.
type Backend struct { type Backend struct {
@ -21,9 +19,7 @@ type Backend struct {
cfg Config cfg Config
} }
const ( const defaultMaxEntries = 100
defaultMaxEntries = 100
)
// Backend configuration. // Backend configuration.
type Config struct { type Config struct {
@ -65,108 +61,21 @@ func New(cfg *Config) (backend *Backend, err error) {
b.cache.MaxEntries = defaultMaxEntries b.cache.MaxEntries = defaultMaxEntries
} }
if b.cfg.FakeNames == nil {
b.cfg.FakeNames = map[string]string{}
}
backend = b backend = b
return return
} }
// Keep domains in parsed format. // Do low-level queries against an abstract zone file. This is the per-query
type domain struct { // entrypoint from madns.
ncv *ncdomain.Value func (b *Backend) Lookup(qname string) (rrs []dns.RR, err error) {
} btx := &btx{}
btx.b = b
func toNamecoinName(basename string) (string, error) { btx.qname = qname
return "d/" + basename, nil return btx.Do()
}
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)
} }
// Things to keep track of while processing a query.
type btx struct { type btx struct {
b *Backend b *Backend
qname string qname string
@ -174,73 +83,47 @@ type btx struct {
subname, basename, rootname string 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) { 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() tx.subname, tx.basename, tx.rootname, err = tx.determineDomain()
if err != nil { 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 return
} }
log.Info("domain: sub=", tx.subname, " basename=", tx.basename, " rootname=", tx.rootname)
if tx.rootname == "" { if tx.rootname == "" {
// REFUSED // REFUSED
return nil, merr.ErrNotInZone 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 == "" { if tx.subname == "" && tx.basename == "" {
return tx.doRootDomain() 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 == "" { if tx.basename == "x--nmc" && tx.b.cfg.SelfName == "" {
return tx.doMetaDomain() return tx.doMetaDomain()
} }
// If we have reached this point the query must be a normal user query.
rrs, err = tx.doUserDomain() rrs, err = tx.doUserDomain()
log.Info("USER RECORDS YIELDED:")
for _, rr := range rrs {
log.Info(" ", rr.String())
}
return 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) { func (tx *btx) doRootDomain() (rrs []dns.RR, err error) {
nsname := tx.b.cfg.SelfName nsname := tx.b.cfg.SelfName
if nsname == "" { if nsname == "" {
@ -304,27 +187,110 @@ func (tx *btx) doMetaDomain() (rrs []dns.RR, err error) {
} }
func (tx *btx) doUserDomain() (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 { if err != nil {
log.Infoe(err, "cannot determine namecoin name")
return return
} }
d, err := tx.b.getNamecoinEntry(ncname) d, err := tx.b.getNamecoinEntry(ncname)
if err != nil { if err != nil {
log.Infoe(err, "cannot get namecoin entry")
return nil, err return nil, err
} }
rrs, err = tx.doUnderDomain(d) rrs, err = tx.doUnderDomain(d)
if err != nil { if err != nil {
log.Infoe(err, "cannot process namecoin entry under domain")
return nil, err return nil, err
} }
return rrs, nil 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) { func (tx *btx) doUnderDomain(d *domain) (rrs []dns.RR, err error) {
rrs, err = tx.addAnswersUnderNCValue(d.ncv, tx.subname) rrs, err = tx.addAnswersUnderNCValue(d.ncv, tx.subname)
if err == merr.ErrNoResults { if err == merr.ErrNoResults {
@ -340,15 +306,9 @@ func (tx *btx) addAnswersUnderNCValue(rncv *ncdomain.Value, subname string) (rrs
return return
} }
log.Info("ncv actual: ", sn)
return tx.addAnswersUnderNCValueActual(ncv, 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) { 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) 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) { 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 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[b]("a", "b.c.d.e.f.g.zzz.bit")
// f[a]("", "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 // © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later

@ -6,13 +6,12 @@ import "fmt"
import "github.com/miekg/dns" import "github.com/miekg/dns"
import "encoding/base64" import "encoding/base64"
import "encoding/hex" import "encoding/hex"
import "regexp"
import "net/mail"
import "github.com/hlandau/ncdns/util" import "github.com/hlandau/ncdns/util"
import "strings" import "strings"
const depthLimit = 16 const depthLimit = 16
const mergeDepthLimit = 4 const mergeDepthLimit = 4
const defaultTTL = 600
// Note: Name values in Value (e.g. those in Alias and Target, Services, MXs, // 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 // etc.) are not necessarily fully qualified and must be fully qualified before
@ -94,7 +93,7 @@ func (v *Value) appendIPs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, er
Name: suffix, Name: suffix,
Rrtype: dns.TypeA, Rrtype: dns.TypeA,
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: 600, Ttl: defaultTTL,
}, },
A: ip, A: ip,
}) })
@ -110,7 +109,7 @@ func (v *Value) appendIP6s(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, e
Name: suffix, Name: suffix,
Rrtype: dns.TypeAAAA, Rrtype: dns.TypeAAAA,
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: 600, Ttl: defaultTTL,
}, },
AAAA: ip, AAAA: ip,
}) })
@ -131,7 +130,7 @@ func (v *Value) appendNSs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, er
Name: suffix, Name: suffix,
Rrtype: dns.TypeNS, Rrtype: dns.TypeNS,
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: 600, Ttl: defaultTTL,
}, },
Ns: qn, Ns: qn,
}) })
@ -147,7 +146,7 @@ func (v *Value) appendTXTs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, e
Name: suffix, Name: suffix,
Rrtype: dns.TypeTXT, Rrtype: dns.TypeTXT,
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: 600, Ttl: defaultTTL,
}, },
Txt: txt, Txt: txt,
}) })
@ -199,7 +198,7 @@ func (v *Value) appendAlias(out []dns.RR, suffix, apexSuffix string) ([]dns.RR,
Name: suffix, Name: suffix,
Rrtype: dns.TypeCNAME, Rrtype: dns.TypeCNAME,
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: 600, Ttl: defaultTTL,
}, },
Target: qn, Target: qn,
}) })
@ -219,7 +218,7 @@ func (v *Value) appendTranslate(out []dns.RR, suffix, apexSuffix string) ([]dns.
Name: suffix, Name: suffix,
Rrtype: dns.TypeDNAME, Rrtype: dns.TypeDNAME,
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: 600, Ttl: defaultTTL,
}, },
Target: qn, Target: qn,
}) })
@ -235,7 +234,7 @@ func (v *Value) RRsRecursive(out []dns.RR, suffix, apexSuffix string) ([]dns.RR,
} }
for mk, mv := range v.Map { for mk, mv := range v.Map {
if !validateLabel(mk) && mk != "" && mk != "*" { if !util.ValidateLabel(mk) && mk != "" && mk != "*" {
continue continue
} }
@ -390,7 +389,7 @@ func (v *Value) qualifyIntl(name, suffix, apexSuffix string) string {
func (v *Value) qualify(name, suffix, apexSuffix string) (string, bool) { func (v *Value) qualify(name, suffix, apexSuffix string) (string, bool) {
s := v.qualifyIntl(name, suffix, apexSuffix) s := v.qualifyIntl(name, suffix, apexSuffix)
if !validateHostName(s) { if !util.ValidateHostName(s) {
return "", false return "", false
} }
@ -630,7 +629,7 @@ func (rv *rawValue) parseHostmaster(v *Value) error {
} }
if s, ok := rv.Hostmaster.(string); ok { if s, ok := rv.Hostmaster.(string); ok {
if !validateEmail(s) { if !util.ValidateEmail(s) {
return fmt.Errorf("malformed e. mail address in email field") return fmt.Errorf("malformed e. mail address in email field")
} }
@ -682,7 +681,7 @@ func (rv *rawValue) parseDS(v *Value) error {
a4h := hex.EncodeToString(a4b) a4h := hex.EncodeToString(a4b)
v.DS = append(v.DS, &dns.DS{ v.DS = append(v.DS, &dns.DS{
Hdr: dns.RR_Header{Rrtype: dns.TypeDS, Class: dns.ClassINET, Ttl: 600}, Hdr: dns.RR_Header{Rrtype: dns.TypeDS, Class: dns.ClassINET, Ttl: defaultTTL},
KeyTag: uint16(a1), KeyTag: uint16(a1),
Algorithm: uint8(a2), Algorithm: uint8(a2),
DigestType: uint8(a3), DigestType: uint8(a3),
@ -753,7 +752,8 @@ func (rv *rawValue) parseTLSA(v *Value) error {
name := "_" + ports + "._" + transport name := "_" + ports + "._" + transport
v.TLSA = append(v.TLSA, &dns.TLSA{ v.TLSA = append(v.TLSA, &dns.TLSA{
Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeTLSA, Class: dns.ClassINET, Ttl: 600}, Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeTLSA, Class: dns.ClassINET,
Ttl: defaultTTL},
Usage: uint8(a1), Usage: uint8(a1),
Selector: uint8(a2), Selector: uint8(a2),
MatchingType: uint8(a3), MatchingType: uint8(a3),
@ -865,7 +865,7 @@ func (rv *rawValue) parseSingleMX(s interface{}, v *Value, relname string) error
} }
v.MX = append(v.MX, &dns.MX{ v.MX = append(v.MX, &dns.MX{
Hdr: dns.RR_Header{Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 600}, Hdr: dns.RR_Header{Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: defaultTTL},
Preference: uint16(prio), Preference: uint16(prio),
Mx: hostname, Mx: hostname,
}) })
@ -910,12 +910,12 @@ func (rv *rawValue) parseSingleService(svc interface{}, v *Value, relname string
} }
appProtoName, ok := svca[0].(string) appProtoName, ok := svca[0].(string)
if !ok || !validateServiceName(appProtoName) { if !ok || !util.ValidateServiceName(appProtoName) {
return fmt.Errorf("malformed service value") return fmt.Errorf("malformed service value")
} }
transportProtoName, ok := svca[1].(string) transportProtoName, ok := svca[1].(string)
if !ok || !validateServiceName(transportProtoName) { if !ok || !util.ValidateServiceName(transportProtoName) {
return fmt.Errorf("malformed service value") return fmt.Errorf("malformed service value")
} }
@ -947,7 +947,7 @@ func (rv *rawValue) parseSingleService(svc interface{}, v *Value, relname string
Name: sname, Name: sname,
Rrtype: dns.TypeSRV, Rrtype: dns.TypeSRV,
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: 600, Ttl: defaultTTL,
}, },
Priority: uint16(priority), Priority: uint16(priority),
Weight: uint16(weight), Weight: uint16(weight),
@ -1038,33 +1038,3 @@ func (v *Value) moveEmptyMapItems() error {
} }
return nil return nil
} }
// Validation functions
// 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.
// 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_serviceName = regexp.MustCompilePOSIX(`^[a-z_][a-z0-9_-]*$`)
var re_label = regexp.MustCompilePOSIX(`^[a-z0-9_][a-z0-9_-]*$`)
func validateHostName(name string) bool {
name = dns.Fqdn(name)
return len(name) <= 255 && re_hostName.MatchString(name)
}
func validateServiceName(name string) bool {
return len(name) < 63 && re_serviceName.MatchString(name)
}
func validateLabel(name string) bool {
return len(name) <= 63 && re_label.MatchString(name)
}
func validateEmail(email string) bool {
addr, err := mail.ParseAddress(email)
if addr == nil || err != nil {
return false
}
return addr.Name == ""
}

@ -6,8 +6,6 @@ import "github.com/hlandau/ncdns/backend"
import "github.com/miekg/dns" import "github.com/miekg/dns"
import "os" import "os"
import "fmt" import "fmt"
import "os/signal"
import "syscall"
import "path/filepath" import "path/filepath"
const version = "1.0" const version = "1.0"
@ -68,23 +66,17 @@ func NewServer(cfg *ServerConfig) (s *Server, err error) {
// key setup // key setup
if cfg.PublicKey != "" { if cfg.PublicKey != "" {
ksk, kskPrivate, err := s.loadKey(cfg.PublicKey, cfg.PrivateKey) ecfg.KSK, ecfg.KSKPrivate, err = s.loadKey(cfg.PublicKey, cfg.PrivateKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ecfg.KSK = ksk
ecfg.KSKPrivate = kskPrivate
} }
if cfg.ZonePublicKey != "" { if cfg.ZonePublicKey != "" {
zsk, zskPrivate, err := s.loadKey(cfg.ZonePublicKey, cfg.ZonePrivateKey) ecfg.ZSK, ecfg.ZSKPrivate, err = s.loadKey(cfg.ZonePublicKey, cfg.ZonePrivateKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ecfg.ZSK = zsk
ecfg.ZSKPrivate = zskPrivate
} }
if ecfg.KSK != nil && ecfg.ZSK == nil { if ecfg.KSK != nil && ecfg.ZSK == nil {
@ -141,19 +133,6 @@ func (s *Server) Start() error {
return nil return nil
} }
func (s *Server) Run() {
s.Start()
// wait
sig := make(chan os.Signal)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
for {
s := <-sig
fmt.Printf("Signal %v received, stopping.", s)
break
}
}
func (s *Server) doRunListener(ds *dns.Server) { func (s *Server) doRunListener(ds *dns.Server) {
err := ds.ListenAndServe() err := ds.ListenAndServe()
log.Fatale(err) log.Fatale(err)

@ -1,6 +1,11 @@
package util package util
import "strings" import "strings"
import "github.com/miekg/dns"
import "github.com/hlandau/madns/merr"
import "fmt"
import "regexp"
import "net/mail"
// 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, rest string) { func SplitDomainHead(name string) (head, rest string) {
@ -30,4 +35,104 @@ func SplitDomainTail(name string) (tail, rest string) {
return s[0], s[1] return s[0], s[1]
} }
// For domains of the form "Y1.Y2...Yn.ANCHOR.X1.X2...Xn.", returns the following values:
//
// basename is the name appearing directly beneath the anchor (Yn).
//
// subname contains any additional labels appearing beneath the anchor (Y1 through Y(n-1)),
// separated by dots.
//
// rootname contains ANCHOR through Xn inclusive, separated by dots.
//
// If no label corresponds to ANCHOR, an error is returned.
// If ANCHOR is the first label, basename is an empty string.
//
// Examples, where anchor="bit":
// "a.b.c.d." -> merr.ErrNotInZone
// "a.b.c.d.bit." -> subname="a.b.c", basename="d", rootname="bit"
// "d.bit." -> subname="", basename="d", rootname="bit"
// "bit." -> subname="", basename="", rootname="bit"
// "bit.x.y.z." -> subname="", basename="", rootname="bit.x.y.z"
// "d.bit.x.y.z." -> subname="", basename="d", rootname="bit.x.y.z"
// "c.d.bit.x.y.z." -> subname="c", basename="d", rootname="bit.x.y.z"
// "a.b.c.d.bit.x.y.z." -> subname="a.b.c", basename="d", rootname="bit.x.y.z"
func SplitDomainByFloatingAnchor(qname, anchor string) (subname, basename, rootname string, err error) {
qname = strings.TrimRight(qname, ".")
parts := strings.Split(qname, ".")
if len(parts) < 2 {
if parts[0] != anchor {
err = merr.ErrNotInZone
return
}
rootname = parts[0]
return
}
for i := len(parts) - 1; i >= 0; i-- {
v := parts[i]
// scanning for rootname
if v == anchor {
if i == 0 {
// i is alreay 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
}
// Convert a domain name basename (e.g. "example") to a Namecoin domain name
// key name ("d/example").
func BasenameToNamecoinKey(basename string) (string, error) {
return "d/" + basename, nil
}
// Convert a Namecoin domain name key name (e.g. "d/example") to a domain name
// basename ("example").
func NamecoinKeyToBasename(key string) (string, error) {
if strings.HasPrefix(key, "d/") {
return key[2:], nil
}
return "", fmt.Errorf("not a domain name key")
}
// 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.
// 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_label = regexp.MustCompilePOSIX(`^[a-z_][a-z0-9_-]*$`)
var re_serviceName = regexp.MustCompilePOSIX(`^[a-z_][a-z0-9_-]*$`)
func ValidateHostName(name string) bool {
name = dns.Fqdn(name)
return len(name) <= 255 && re_hostName.MatchString(name)
}
func ValidateLabel(name string) bool {
return len(name) <= 63 && re_label.MatchString(name)
}
func ValidateServiceName(name string) bool {
return len(name) < 63 && re_serviceName.MatchString(name)
}
func ValidateEmail(email string) bool {
addr, err := mail.ParseAddress(email)
if addr == nil || err != nil {
return false
}
return addr.Name == ""
}
// © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later // © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later

@ -2,6 +2,7 @@ package util_test
import "testing" import "testing"
import "github.com/hlandau/ncdns/util" import "github.com/hlandau/ncdns/util"
import "github.com/hlandau/madns/merr"
type item struct { type item struct {
input string input string
@ -39,3 +40,44 @@ func TestSplitDomainHead(t *testing.T) {
} }
} }
} }
type aitem struct {
input string
anchor string
expectedSubname string
expectedBasename string
expectedRootname string
expectedError error
}
var aitems = []aitem{
aitem{"", "bit", "", "", "", merr.ErrNotInZone},
aitem{".", "bit", "", "", "", merr.ErrNotInZone},
aitem{"d.", "bit", "", "", "", merr.ErrNotInZone},
aitem{"a.b.c.d.", "bit", "", "", "", merr.ErrNotInZone},
aitem{"a.b.c.d.bit.", "bit", "a.b.c", "d", "bit", nil},
aitem{"d.bit.", "bit", "", "d", "bit", nil},
aitem{"bit.", "bit", "", "", "bit", nil},
aitem{"bit.x.y.z.", "bit", "", "", "bit.x.y.z", nil},
aitem{"d.bit.x.y.z.", "bit", "", "d", "bit.x.y.z", nil},
aitem{"c.d.bit.x.y.z.", "bit", "c", "d", "bit.x.y.z", nil},
aitem{"a.b.c.d.bit.x.y.z.", "bit", "a.b.c", "d", "bit.x.y.z", nil},
}
func TestSplitDomainByFloatingAnchor(t *testing.T) {
for i, it := range aitems {
subname, basename, rootname, err := util.SplitDomainByFloatingAnchor(it.input, it.anchor)
if subname != it.expectedSubname {
t.Errorf("Item %d: subname \"%s\" does not equal expected value \"%s\"", i, subname, it.expectedSubname)
}
if basename != it.expectedBasename {
t.Errorf("Item %d: basename \"%s\" does not equal expected value \"%s\"", i, basename, it.expectedBasename)
}
if rootname != it.expectedRootname {
t.Errorf("Item %d: rootname \"%s\" does not equal expected value \"%s\"", i, basename, it.expectedRootname)
}
if err != it.expectedError {
t.Errorf("Item %d: error \"%s\" does not equal expected error \"%s\"", i, err, it.expectedError)
}
}
}

Loading…
Cancel
Save