diff --git a/backend/backend.go b/backend/backend.go index 04f8c81..f92f650 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -1,4 +1,5 @@ package backend + import "github.com/golang/groupcache/lru" import "github.com/miekg/dns" import "github.com/hlandau/degoutils/log" @@ -14,570 +15,570 @@ import "github.com/hlandau/ncdns/util" import "sync" type Backend struct { - //s *Server - nc namecoin.NamecoinConn - cache lru.Cache // items are of type *Domain - cacheMutex sync.Mutex - cfg Config + //s *Server + nc namecoin.NamecoinConn + cache lru.Cache // items are of type *Domain + cacheMutex sync.Mutex + cfg Config } const ( - defaultMaxEntries = 100 + defaultMaxEntries = 100 ) type Config struct { - // Username and password to use for connecting to the Namecoin JSON-RPC interface. - RPCUsername string - RPCPassword string + // Username and password to use for connecting to the Namecoin JSON-RPC interface. + RPCUsername string + RPCPassword string - // hostname:port to use for connecting to the Namecoin JSON-RPC interface. - RPCAddress string + // hostname:port to use for connecting to the Namecoin JSON-RPC interface. + RPCAddress string - // Maximum entries to permit in name cache. If zero, a default value is used. - CacheMaxEntries int + // Maximum entries to permit in name cache. If zero, a default value is used. + CacheMaxEntries int - // The hostname which should be advertised as the primary nameserver for the zone. - // If left empty, a psuedo-hostname resolvable to SelfIP is used. - SelfName string + // The hostname which should be advertised as the primary nameserver for the zone. + // If left empty, a psuedo-hostname resolvable to SelfIP is used. + SelfName string - // Used only if SelfName is left blank. An IP which the internal psuedo-hostname - // should resolve to. This should be the public IP of the nameserver serving the - // zone expressed by this backend. - SelfIP string + // Used only if SelfName is left blank. An IP which the internal psuedo-hostname + // should resolve to. This should be the public IP of the nameserver serving the + // zone expressed by this backend. + SelfIP string } // Creates a new Namecoin backend. func New(cfg *Config) (backend *Backend, err error) { - b := &Backend{} + b := &Backend{} - b.cfg = *cfg - b.nc.Username = cfg.RPCUsername - b.nc.Password = cfg.RPCPassword - b.nc.Server = cfg.RPCAddress + b.cfg = *cfg + b.nc.Username = cfg.RPCUsername + b.nc.Password = cfg.RPCPassword + b.nc.Server = cfg.RPCAddress - b.cache.MaxEntries = cfg.CacheMaxEntries - if b.cache.MaxEntries == 0 { - b.cache.MaxEntries = defaultMaxEntries - } + b.cache.MaxEntries = cfg.CacheMaxEntries + if b.cache.MaxEntries == 0 { + b.cache.MaxEntries = defaultMaxEntries + } - backend = b + backend = b - return + return } // Keep domains in DNS format. type domain struct { - ncv *ncValue + ncv *ncValue } // Root of a domain JSON structure type ncValue struct { - IP interface{} `json:"ip"` - IP6 interface{} `json:"ip6"` - Service [][]interface{} `json:"service"` - Alias string `json:"alias"` - NS interface{} `json:"ns"` - Map map[string]*ncValue `json:"map"` // may contain "" and "*" - DS [][]interface{} `json:"ds"` - TXT interface{} `json:"txt"` + IP interface{} `json:"ip"` + IP6 interface{} `json:"ip6"` + Service [][]interface{} `json:"service"` + Alias string `json:"alias"` + NS interface{} `json:"ns"` + Map map[string]*ncValue `json:"map"` // may contain "" and "*" + DS [][]interface{} `json:"ds"` + TXT interface{} `json:"txt"` } func toNamecoinName(basename string) (string, error) { - return "d/" + basename, nil + return "d/" + basename, nil } func (b *Backend) getNamecoinEntry(name string) (*domain, error) { - d := b.getNamecoinEntryCache(name) - if d != nil { - return d, nil - } + d := b.getNamecoinEntryCache(name) + if d != nil { + return d, nil + } - d, err := b.getNamecoinEntryLL(name) - if err != nil { - return nil, err - } + d, err := b.getNamecoinEntryLL(name) + if err != nil { + return nil, err + } - b.addNamecoinEntryToCache(name, d) - return d, nil + b.addNamecoinEntryToCache(name, d) + return d, nil } func (b *Backend) getNamecoinEntryCache(name string) *domain { - b.cacheMutex.Lock() - defer b.cacheMutex.Unlock() + b.cacheMutex.Lock() + defer b.cacheMutex.Unlock() - if dd, ok := b.cache.Get(name); ok { - d := dd.(*domain) - return d - } + if dd, ok := b.cache.Get(name); ok { + d := dd.(*domain) + return d + } - return nil + return nil } func (b *Backend) addNamecoinEntryToCache(name string, d *domain) { - b.cacheMutex.Lock() - defer b.cacheMutex.Unlock() + b.cacheMutex.Lock() + defer b.cacheMutex.Unlock() - b.cache.Add(name, d) + b.cache.Add(name, d) } func (b *Backend) getNamecoinEntryLL(name string) (*domain, error) { - v, err := b.nc.Query(name) - if err != nil { - log.Infoe(err, "namecoin query failed: ", err) - return nil, err - } + v, err := b.nc.Query(name) + if err != nil { + log.Infoe(err, "namecoin query failed: ", err) + return nil, err + } - log.Info("namecoin query (", name, ") succeeded: ", v) + log.Info("namecoin query (", name, ") succeeded: ", v) - d, err := b.jsonToDomain(v) - if err != nil { - log.Infoe(err, "cannot convert JSON to domain") - return nil, err - } + d, err := b.jsonToDomain(v) + if err != nil { + log.Infoe(err, "cannot convert JSON to domain") + return nil, err + } - return d, nil + return d, nil } func (b *Backend) jsonToDomain(v string) (dd *domain, err error) { - d := &domain{} - ncv := &ncValue{} + d := &domain{} + ncv := &ncValue{} - err = json.Unmarshal([]byte(v), ncv) - if err != nil { - log.Infoe(err, fmt.Sprintf("cannot unmarshal JSON: %+v", v)) - return - } + err = json.Unmarshal([]byte(v), ncv) + if err != nil { + log.Infoe(err, fmt.Sprintf("cannot unmarshal JSON: %+v", v)) + return + } - d.ncv = ncv + d.ncv = ncv - dd = d - return + dd = d + return } type btx struct { - b *Backend - qname string + b *Backend + qname string - 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 + 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) { - tx.subname, tx.basename, tx.rootname, err = tx.determineDomain() - if err != nil { - log.Infoe(err, "couldn't determine domain") - return - } + tx.subname, tx.basename, tx.rootname, err = tx.determineDomain() + if err != nil { + log.Infoe(err, "couldn't determine domain") + return + } - log.Info("domain: sub=", tx.subname, " basename=", tx.basename, " rootname=", tx.rootname) + log.Info("domain: sub=", tx.subname, " basename=", tx.basename, " rootname=", tx.rootname) - if tx.rootname == "" { - // REFUSED - return nil, merr.ErrNotInZone - } + if tx.rootname == "" { + // REFUSED + return nil, merr.ErrNotInZone + } - if tx.subname == "" && tx.basename == "" { - return tx.doRootDomain() - } + if tx.subname == "" && tx.basename == "" { + return tx.doRootDomain() + } - if tx.basename == "x--nmc" && tx.b.cfg.SelfName == "" { - return tx.doMetaDomain() - } + if tx.basename == "x--nmc" && tx.b.cfg.SelfName == "" { + return tx.doMetaDomain() + } - rrs, err = tx.doUserDomain() + rrs, err = tx.doUserDomain() - log.Info("USER RECORDS YIELDED:") - for _, rr := range rrs { - log.Info(" ", rr.String()) - } + log.Info("USER RECORDS YIELDED:") + for _, rr := range rrs { + log.Info(" ", rr.String()) + } - return + return } func (tx *btx) doRootDomain() (rrs []dns.RR, err error) { - nsname := tx.b.cfg.SelfName - if nsname == "" { - nsname = "this.x--nmc." + tx.rootname - } - - soa := &dns.SOA { - Hdr: dns.RR_Header { - Name: dns.Fqdn(tx.rootname), - Ttl: 86400, - Class: dns.ClassINET, - Rrtype: dns.TypeSOA, - }, - Ns: dns.Fqdn(nsname), - Mbox: ".", - Serial: 1, - Refresh: 600, - Retry: 600, - Expire: 7200, - Minttl: 600, - } - - ns := &dns.NS { - Hdr: dns.RR_Header { - Name: dns.Fqdn(tx.rootname), - Ttl: 86400, - Class: dns.ClassINET, - Rrtype: dns.TypeNS, - }, - Ns: dns.Fqdn(nsname), - } - - rrs = []dns.RR{ soa, ns, } - return + nsname := tx.b.cfg.SelfName + if nsname == "" { + nsname = "this.x--nmc." + tx.rootname + } + + soa := &dns.SOA{ + Hdr: dns.RR_Header{ + Name: dns.Fqdn(tx.rootname), + Ttl: 86400, + Class: dns.ClassINET, + Rrtype: dns.TypeSOA, + }, + Ns: dns.Fqdn(nsname), + Mbox: ".", + Serial: 1, + Refresh: 600, + Retry: 600, + Expire: 7200, + Minttl: 600, + } + + ns := &dns.NS{ + Hdr: dns.RR_Header{ + Name: dns.Fqdn(tx.rootname), + Ttl: 86400, + Class: dns.ClassINET, + Rrtype: dns.TypeNS, + }, + Ns: dns.Fqdn(nsname), + } + + rrs = []dns.RR{soa, ns} + return } func (tx *btx) doMetaDomain() (rrs []dns.RR, err error) { - ip := net.ParseIP(tx.b.cfg.SelfIP) - if ip == nil || ip.To4() == nil { - return nil, fmt.Errorf("invalid value specified for SelfIP") - } - - switch tx.subname { - case "this": - rrs = []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: dns.Fqdn("this." + tx.basename + "." + tx.rootname), - Ttl: 86400, - Class: dns.ClassINET, - Rrtype: dns.TypeA, - }, - A: ip, - }, - } - - default: - } - - return + ip := net.ParseIP(tx.b.cfg.SelfIP) + if ip == nil || ip.To4() == nil { + return nil, fmt.Errorf("invalid value specified for SelfIP") + } + + switch tx.subname { + case "this": + rrs = []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{ + Name: dns.Fqdn("this." + tx.basename + "." + tx.rootname), + Ttl: 86400, + Class: dns.ClassINET, + Rrtype: dns.TypeA, + }, + A: ip, + }, + } + + default: + } + + return } func (tx *btx) doUserDomain() (rrs []dns.RR, err error) { - ncname, err := toNamecoinName(tx.basename) - if err != nil { - log.Infoe(err, "cannot determine namecoin name") - return - } + ncname, err := toNamecoinName(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 - } + 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 - } + rrs, err = tx.doUnderDomain(d) + if err != nil { + log.Infoe(err, "cannot process namecoin entry under domain") + return nil, err + } - return rrs, nil + return rrs, nil } func (tx *btx) doUnderDomain(d *domain) (rrs []dns.RR, err error) { - rrs, err = tx.addAnswersUnderNCValue(d.ncv, tx.subname) - if err == merr.ErrNoResults { - err = nil - } + rrs, err = tx.addAnswersUnderNCValue(d.ncv, tx.subname) + if err == merr.ErrNoResults { + err = nil + } - return + return } func (tx *btx) addAnswersUnderNCValue(rncv *ncValue, subname string) (rrs []dns.RR, err error) { - ncv, sn, err := tx.findNCValue(rncv, subname, nil /*hasNS*/) - if err != nil { - return - } + ncv, sn, err := tx.findNCValue(rncv, subname, nil /*hasNS*/) + if err != nil { + return + } - log.Info("ncv actual: ", sn) - return tx.addAnswersUnderNCValueActual(ncv, sn) + log.Info("ncv actual: ", sn) + return tx.addAnswersUnderNCValueActual(ncv, sn) } func hasNS(ncv *ncValue) bool { - nss, err := ncv.GetNSs() - return err == nil && len(nss) > 0 + nss, err := ncv.GetNSs() + return err == nil && len(nss) > 0 } func (tx *btx) findNCValue(ncv *ncValue, subname string, shortCircuitFunc func(curNCV *ncValue) bool) (xncv *ncValue, sn string, err error) { - return tx._findNCValue(ncv, subname, "", 0, shortCircuitFunc) + return tx._findNCValue(ncv, subname, "", 0, shortCircuitFunc) } func (tx *btx) _findNCValue(ncv *ncValue, isubname, subname string, depth int, - shortCircuitFunc func(curNCV *ncValue) bool) (xncv *ncValue, sn string, err error) { + shortCircuitFunc func(curNCV *ncValue) bool) (xncv *ncValue, sn string, err error) { - if shortCircuitFunc != nil && shortCircuitFunc(ncv) { - return ncv, subname, nil - } + if shortCircuitFunc != nil && shortCircuitFunc(ncv) { + return ncv, subname, nil + } - if isubname != "" { - head, rest, err := util.SplitDomainHead(isubname) - if err != nil { - return nil, "", err - } + if isubname != "" { + head, rest, err := util.SplitDomainHead(isubname) + if err != nil { + return nil, "", err + } - sub, ok := ncv.Map[head] - if !ok { - sub, ok = ncv.Map["*"] - if !ok { - return nil, "", merr.ErrNoSuchDomain - } - } - return tx._findNCValue(sub, rest, head + "." + subname, depth+1, shortCircuitFunc) - } + sub, ok := ncv.Map[head] + if !ok { + sub, ok = ncv.Map["*"] + if !ok { + return nil, "", merr.ErrNoSuchDomain + } + } + return tx._findNCValue(sub, rest, head+"."+subname, depth+1, shortCircuitFunc) + } - if shortCircuitFunc != nil { - return nil, subname, merr.ErrNoSuchDomain - } + if shortCircuitFunc != nil { + return nil, subname, merr.ErrNoSuchDomain + } - return ncv, subname, nil + return ncv, subname, nil } func (tx *btx) addAnswersUnderNCValueActual(ncv *ncValue, sn string) (rrs []dns.RR, err error) { - // A - ips, err := ncv.GetIPs() - if err != nil { - return - } - - for _, ip := range ips { - pip := net.ParseIP(ip) - if pip == nil || pip.To4() == nil { - continue - } - rrs = append(rrs, &dns.A { - Hdr: dns.RR_Header { Name: dns.Fqdn(tx.qname), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 600, }, - A: pip }) - } - - // AAAA - ips, err = ncv.GetIP6s() - if err != nil { - return - } - - for _, ip := range ips { - pip := net.ParseIP(ip) - if pip == nil || pip.To4() != nil { - continue - } - rrs = append(rrs, &dns.AAAA { - Hdr: dns.RR_Header { Name: dns.Fqdn(tx.qname), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 600, }, - AAAA: pip }) - } - - // NS - nss, err := ncv.GetNSs() - if err != nil { - return - } - - for _, ns := range nss { - ns = dns.Fqdn(ns) - rrs = append(rrs, &dns.NS { - Hdr: dns.RR_Header { Name: dns.Fqdn(tx.qname), Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 600, }, - Ns: ns }) - } - - // TXT - txts, err := ncv.GetTXTs() - if err != nil { - return - } - - for _, txt := range txts { - rrs = append(rrs, &dns.TXT { - Hdr: dns.RR_Header { Name: dns.Fqdn(tx.qname), Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 600, }, - Txt: txt }) - } - - // TODO: MX - // TODO: SRV - - // DS - dss, err := ncv.GetDSs() - if err != nil { - return - } - - for i := range dss { - dss[i].Hdr.Name = dns.Fqdn(tx.qname) - rrs = append(rrs, &dss[i]) - } - - if len(rrs) == 0 { - if m, ok := ncv.Map[""]; ok { - return tx.addAnswersUnderNCValueActual(m, sn) - } - } - - return + // A + ips, err := ncv.GetIPs() + if err != nil { + return + } + + for _, ip := range ips { + pip := net.ParseIP(ip) + if pip == nil || pip.To4() == nil { + continue + } + rrs = append(rrs, &dns.A{ + Hdr: dns.RR_Header{Name: dns.Fqdn(tx.qname), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 600}, + A: pip}) + } + + // AAAA + ips, err = ncv.GetIP6s() + if err != nil { + return + } + + for _, ip := range ips { + pip := net.ParseIP(ip) + if pip == nil || pip.To4() != nil { + continue + } + rrs = append(rrs, &dns.AAAA{ + Hdr: dns.RR_Header{Name: dns.Fqdn(tx.qname), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 600}, + AAAA: pip}) + } + + // NS + nss, err := ncv.GetNSs() + if err != nil { + return + } + + for _, ns := range nss { + ns = dns.Fqdn(ns) + rrs = append(rrs, &dns.NS{ + Hdr: dns.RR_Header{Name: dns.Fqdn(tx.qname), Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 600}, + Ns: ns}) + } + + // TXT + txts, err := ncv.GetTXTs() + if err != nil { + return + } + + for _, txt := range txts { + rrs = append(rrs, &dns.TXT{ + Hdr: dns.RR_Header{Name: dns.Fqdn(tx.qname), Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 600}, + Txt: txt}) + } + + // TODO: MX + // TODO: SRV + + // DS + dss, err := ncv.GetDSs() + if err != nil { + return + } + + for i := range dss { + dss[i].Hdr.Name = dns.Fqdn(tx.qname) + rrs = append(rrs, &dss[i]) + } + + if len(rrs) == 0 { + if m, ok := ncv.Map[""]; ok { + return tx.addAnswersUnderNCValueActual(m, sn) + } + } + + return } func (ncv *ncValue) getArray(a interface{}) (ips []string, err error) { - if a == nil { - return - } - - ipa, ok := a.([]interface{}) - if ok { - for _, v := range ipa { - s, ok := v.(string) - if ok { - ips = append(ips, s) - } - } - } else { - s, ok := a.(string) - if ok { - ips = []string{s} - } else { - err = fmt.Errorf("malformed IP value") - } - } - return + if a == nil { + return + } + + ipa, ok := a.([]interface{}) + if ok { + for _, v := range ipa { + s, ok := v.(string) + if ok { + ips = append(ips, s) + } + } + } else { + s, ok := a.(string) + if ok { + ips = []string{s} + } else { + err = fmt.Errorf("malformed IP value") + } + } + return } func (ncv *ncValue) GetIPs() (ips []string, err error) { - return ncv.getArray(ncv.IP) + return ncv.getArray(ncv.IP) } func (ncv *ncValue) GetIP6s() (ips []string, err error) { - return ncv.getArray(ncv.IP6) + return ncv.getArray(ncv.IP6) } func (ncv *ncValue) GetNSs() (nss []string, err error) { - return ncv.getArray(ncv.NS) + return ncv.getArray(ncv.NS) } func (ncv *ncValue) getArrayTXT(a interface{}) (txts [][]string, err error) { - if a == nil { - return - } - - if txta, ok := a.([]interface{}); ok { - // ["...", "..."] or [["...","..."], ["...","..."]] - for _, v := range txta { - if sa, ok := v.([]string); ok { - // [["...", "..."], ["...","..."]] - txts = append(txts, sa) - } else if s, ok := v.(string); ok { - // ["...", "..."] - txts = append(txts, segmentizeTXT(s)) - } else { - err = fmt.Errorf("malformed TXT value") - return - } - } - } else { - // "..." - if s, ok := a.(string); ok { - txts = append(txts, segmentizeTXT(s)) - } else { - err = fmt.Errorf("malformed TXT value") - } - } - return + if a == nil { + return + } + + if txta, ok := a.([]interface{}); ok { + // ["...", "..."] or [["...","..."], ["...","..."]] + for _, v := range txta { + if sa, ok := v.([]string); ok { + // [["...", "..."], ["...","..."]] + txts = append(txts, sa) + } else if s, ok := v.(string); ok { + // ["...", "..."] + txts = append(txts, segmentizeTXT(s)) + } else { + err = fmt.Errorf("malformed TXT value") + return + } + } + } else { + // "..." + if s, ok := a.(string); ok { + txts = append(txts, segmentizeTXT(s)) + } else { + err = fmt.Errorf("malformed TXT value") + } + } + return } func (ncv *ncValue) GetTXTs() (txts [][]string, err error) { - return ncv.getArrayTXT(ncv.TXT) + return ncv.getArrayTXT(ncv.TXT) } func segmentizeTXT(txt string) (a []string) { - for len(txt) > 255 { - a = append(a, txt[0:255]) - txt = txt[255:] - } - a = append(a, txt) - return + for len(txt) > 255 { + a = append(a, txt[0:255]) + txt = txt[255:] + } + a = append(a, txt) + return } func (ncv *ncValue) GetDSs() (dss []dns.DS, err error) { - for _, ds := range ncv.DS { - //log.Info(" - DS: ", ds) - if len(ds) != 4 { - log.Info(" DS is bad len") - continue - } - - a1, ok := ds[0].(float64) - if !ok { - log.Info(" DS[0]") - continue - } - a2, ok := ds[1].(float64) - if !ok { - log.Info(" DS[1]") - continue - } - a3, ok := ds[2].(float64) - if !ok { - log.Info(" DS[2]") - continue - } - a4, ok := ds[3].(string) - if !ok { - log.Info(" DS[3]") - continue - } - - a4b, err := base64.StdEncoding.DecodeString(a4) - if err != nil { - log.Info("can't decode: ", err) - err = nil - continue - } - - a4h := hex.EncodeToString(a4b) - - d := dns.DS { - Hdr: dns.RR_Header { Rrtype: dns.TypeDS, Class: dns.ClassINET, Ttl: 60, }, - KeyTag: uint16(a1), - Algorithm: uint8(a2), - DigestType: uint8(a3), - Digest: a4h, - } - dss = append(dss, d) - } - return + for _, ds := range ncv.DS { + //log.Info(" - DS: ", ds) + if len(ds) != 4 { + log.Info(" DS is bad len") + continue + } + + a1, ok := ds[0].(float64) + if !ok { + log.Info(" DS[0]") + continue + } + a2, ok := ds[1].(float64) + if !ok { + log.Info(" DS[1]") + continue + } + a3, ok := ds[2].(float64) + if !ok { + log.Info(" DS[2]") + continue + } + a4, ok := ds[3].(string) + if !ok { + log.Info(" DS[3]") + continue + } + + a4b, err := base64.StdEncoding.DecodeString(a4) + if err != nil { + log.Info("can't decode: ", err) + err = nil + continue + } + + a4h := hex.EncodeToString(a4b) + + d := dns.DS{ + Hdr: dns.RR_Header{Rrtype: dns.TypeDS, Class: dns.ClassINET, Ttl: 60}, + KeyTag: uint16(a1), + Algorithm: uint8(a2), + DigestType: uint8(a3), + Digest: a4h, + } + dss = append(dss, d) + } + return } // a.b.c.d.e.f.g.zzz.bit @@ -592,10 +593,10 @@ func (ncv *ncValue) GetDSs() (dss []dns.DS, err error) { // 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() + btx := &btx{} + btx.b = b + btx.qname = qname + return btx.Do() } // © 2014 Hugo Landau GPLv3 or later diff --git a/main.go b/main.go index 27c24e9..65cf8a2 100644 --- a/main.go +++ b/main.go @@ -1,32 +1,33 @@ package main + import "github.com/hlandau/degoutils/config" import "github.com/hlandau/degoutils/log" import "github.com/hlandau/degoutils/daemon" import "github.com/hlandau/ncdns/server" func main() { - cfg := server.ServerConfig{} - config := config.Configurator{ - ProgramName: "ncdns", - ConfigFilePaths: []string { "etc/ncdns.conf", "/etc/ncdns/ncdns.conf", }, - } - config.ParseFatal(&cfg) + cfg := server.ServerConfig{} + config := config.Configurator{ + ProgramName: "ncdns", + ConfigFilePaths: []string{"etc/ncdns.conf", "/etc/ncdns/ncdns.conf"}, + } + config.ParseFatal(&cfg) - err := daemon.Init() - log.Fatale(err) + err := daemon.Init() + log.Fatale(err) - if cfg.Daemonize { - err := daemon.Daemonize() - log.Fatale(err) - } + if cfg.Daemonize { + err := daemon.Daemonize() + log.Fatale(err) + } - err = daemon.DropPrivileges(cfg.UID, cfg.GID) - log.Fatale(err, "can't drop privileges") + err = daemon.DropPrivileges(cfg.UID, cfg.GID) + log.Fatale(err, "can't drop privileges") - s, err := server.NewServer(&cfg) - log.Fatale(err) + s, err := server.NewServer(&cfg) + log.Fatale(err) - s.Run() + s.Run() } // © 2014 Hugo Landau GPLv3 or later diff --git a/namecoin/extratypes/extratypes.go b/namecoin/extratypes/extratypes.go index eb69699..f750b4a 100644 --- a/namecoin/extratypes/extratypes.go +++ b/namecoin/extratypes/extratypes.go @@ -6,62 +6,62 @@ import "github.com/hlandauf/btcjson" import "encoding/json" type NameShowCmd struct { - id interface{} - Name string `json:"name"` + id interface{} + Name string `json:"name"` } func NewNameShowCmd(id interface{}, name string) (*NameShowCmd, error) { - return &NameShowCmd { - id: id, - Name: name, - }, nil + return &NameShowCmd{ + id: id, + Name: name, + }, nil } func (c *NameShowCmd) Id() interface{} { - return c.id + return c.id } func (c *NameShowCmd) Method() string { - return "name_show" + return "name_show" } func (c *NameShowCmd) MarshalJSON() ([]byte, error) { - params := []interface{}{ - c.Name, - } + params := []interface{}{ + c.Name, + } - raw, err := btcjson.NewRawCmd(c.id, c.Method(), params) - if err != nil { - return nil, err - } + raw, err := btcjson.NewRawCmd(c.id, c.Method(), params) + if err != nil { + return nil, err + } - return json.Marshal(raw) + return json.Marshal(raw) } func (c *NameShowCmd) UnmarshalJSON(b []byte) error { - // We don't need to implement this as we are only ever the client. - panic("not implemented") - return nil + // We don't need to implement this as we are only ever the client. + panic("not implemented") + return nil } type NameShowReply struct { - Name string `json:"name"` - Value string `json:"value"` - ExpiresIn int `json:"expires_in"` + Name string `json:"name"` + Value string `json:"value"` + ExpiresIn int `json:"expires_in"` } func replyParser(m json.RawMessage) (interface{}, error) { - nsr := &NameShowReply{} - err := json.Unmarshal(m, nsr) - if err != nil { - return nil, err - } + nsr := &NameShowReply{} + err := json.Unmarshal(m, nsr) + if err != nil { + return nil, err + } - return nsr, nil + return nsr, nil } func init() { - btcjson.RegisterCustomCmd("name_show", nil, replyParser, "name_show ") + btcjson.RegisterCustomCmd("name_show", nil, replyParser, "name_show ") } // © 2014 Hugo Landau GPLv3 or later diff --git a/namecoin/namecoin.go b/namecoin/namecoin.go index 59a57bc..c4a870f 100644 --- a/namecoin/namecoin.go +++ b/namecoin/namecoin.go @@ -12,52 +12,52 @@ import "fmt" var idCounter int32 = 0 func newID() int32 { - return atomic.AddInt32(&idCounter, 1) + return atomic.AddInt32(&idCounter, 1) } // Used to query a Namecoin JSON-RPC interface. Initialize the struct with a // username, password, and address (hostname:port). type NamecoinConn struct { - Username string - Password string - Server string + Username string + Password string + Server string } // Query the Namecoin daemon for a Namecoin domain (e.g. d/example). // If the domain exists, returns the value stored in Namecoin, which should be JSON. // Note that this will return domain data even if the domain is expired. func (nc *NamecoinConn) Query(name string) (v string, err error) { - cmd, err := extratypes.NewNameShowCmd(newID(), name) - if err != nil { - //log.Info("NC NEWCMD ", err) - return "", err - } - - r, err := btcjson.RpcSend(nc.Username, nc.Password, nc.Server, cmd) - if err != nil { - return "", err - } - - if r.Error != nil { - //log.Info("RPC error: ", r.Error) - if r.Error.Code == -4 { - return "", merr.ErrNoSuchDomain - } - return "", r.Error - } - - if r.Result == nil { - //log.Info("NC NILRESULT") - return "", fmt.Errorf("got nil result") - } - - if nsr, ok := r.Result.(*extratypes.NameShowReply); ok { - //log.Info("NC OK") - return nsr.Value, nil - } else { - //log.Info("NC BADREPLY") - return "", fmt.Errorf("bad reply") - } + cmd, err := extratypes.NewNameShowCmd(newID(), name) + if err != nil { + //log.Info("NC NEWCMD ", err) + return "", err + } + + r, err := btcjson.RpcSend(nc.Username, nc.Password, nc.Server, cmd) + if err != nil { + return "", err + } + + if r.Error != nil { + //log.Info("RPC error: ", r.Error) + if r.Error.Code == -4 { + return "", merr.ErrNoSuchDomain + } + return "", r.Error + } + + if r.Result == nil { + //log.Info("NC NILRESULT") + return "", fmt.Errorf("got nil result") + } + + if nsr, ok := r.Result.(*extratypes.NameShowReply); ok { + //log.Info("NC OK") + return nsr.Value, nil + } else { + //log.Info("NC BADREPLY") + return "", fmt.Errorf("bad reply") + } } // © 2014 Hugo Landau GPLv3 or later diff --git a/server/server.go b/server/server.go index 878b002..ff3eadd 100644 --- a/server/server.go +++ b/server/server.go @@ -1,4 +1,5 @@ package server + import "github.com/hlandau/madns" import "github.com/hlandau/degoutils/log" import "github.com/hlandau/ncdns/backend" @@ -9,148 +10,148 @@ import "os/signal" import "syscall" type Server struct { - cfg ServerConfig + cfg ServerConfig - engine madns.Engine + engine madns.Engine - mux *dns.ServeMux - udpListener *dns.Server - tcpListener *dns.Server + mux *dns.ServeMux + udpListener *dns.Server + tcpListener *dns.Server } type ServerConfig struct { - Bind string `default:":53" usage:"Address to bind to (e.g. 0.0.0.0:53)"` - PublicKey string `default:"ncdns.key" usage:"Path to the DNSKEY KSK public key file"` - PrivateKey string `default:"ncdns.private" usage:"Path to the KSK's corresponding private key file"` - ZonePublicKey string `default:"" usage:"Path to the DNSKEY ZSK public key file; if one is not specified, a temporary one is generated on startup and used only for the duration of that process"` - ZonePrivateKey string `default:"" usage:"Path to the ZSK's corresponding private key file"` - - NamecoinRPCUsername string `default:"" usage:"Namecoin RPC username"` - NamecoinRPCPassword string `default:"" usage:"Namecoin RPC password"` - NamecoinRPCAddress string `default:"localhost:8336" usage:"Namecoin RPC server address"` - CacheMaxEntries int `default:"1000" usage:"Maximum name cache entries"` - SelfName string `default:"" usage:"Canonical name for this nameserver (default: autogenerated psuedo-hostname resolving to SelfIP; SelfIP is not used if this is set)"` - SelfIP string `default:"127.127.127.127" usage:"The canonical IP address for this service"` - - Daemonize bool `default:"false" usage:"Daemonize (doesn't fork)"` - UID int `default:"0" usage:"UID to drop privileges to if run as root"` - GID int `default:"0" usage:"GID to drop privileges to if run as root"` + Bind string `default:":53" usage:"Address to bind to (e.g. 0.0.0.0:53)"` + PublicKey string `default:"ncdns.key" usage:"Path to the DNSKEY KSK public key file"` + PrivateKey string `default:"ncdns.private" usage:"Path to the KSK's corresponding private key file"` + ZonePublicKey string `default:"" usage:"Path to the DNSKEY ZSK public key file; if one is not specified, a temporary one is generated on startup and used only for the duration of that process"` + ZonePrivateKey string `default:"" usage:"Path to the ZSK's corresponding private key file"` + + NamecoinRPCUsername string `default:"" usage:"Namecoin RPC username"` + NamecoinRPCPassword string `default:"" usage:"Namecoin RPC password"` + NamecoinRPCAddress string `default:"localhost:8336" usage:"Namecoin RPC server address"` + CacheMaxEntries int `default:"1000" usage:"Maximum name cache entries"` + SelfName string `default:"" usage:"Canonical name for this nameserver (default: autogenerated psuedo-hostname resolving to SelfIP; SelfIP is not used if this is set)"` + SelfIP string `default:"127.127.127.127" usage:"The canonical IP address for this service"` + + Daemonize bool `default:"false" usage:"Daemonize (doesn't fork)"` + UID int `default:"0" usage:"UID to drop privileges to if run as root"` + GID int `default:"0" usage:"GID to drop privileges to if run as root"` } func NewServer(cfg *ServerConfig) (s *Server, err error) { - s = &Server{} - s.cfg = *cfg - - bcfg := &backend.Config { - RPCUsername: cfg.NamecoinRPCUsername, - RPCPassword: cfg.NamecoinRPCPassword, - RPCAddress: cfg.NamecoinRPCAddress, - CacheMaxEntries: cfg.CacheMaxEntries, - SelfName: cfg.SelfName, - SelfIP: cfg.SelfIP, - } - - b, err := backend.New(bcfg) - if err != nil { - return - } - - ecfg := &madns.EngineConfig{ - Backend: b, - } - - // key setup - if cfg.PublicKey != "" { - ksk, kskPrivate, err := s.loadKey(cfg.PublicKey, cfg.PrivateKey) - if err != nil { - return nil, err - } - - ecfg.KSK = ksk - ecfg.KSKPrivate = kskPrivate - } - - if cfg.ZonePublicKey != "" { - zsk, zskPrivate, err := s.loadKey(cfg.ZonePublicKey, cfg.ZonePrivateKey) - if err != nil { - return nil, err - } - - ecfg.ZSK = zsk - ecfg.ZSKPrivate = zskPrivate - } - - if ecfg.KSK != nil && ecfg.ZSK == nil { - return nil, fmt.Errorf("Must specify ZSK if KSK is specified") - } - - e, err := madns.NewEngine(ecfg) - if err != nil { - return - } - - s.engine = e - return + s = &Server{} + s.cfg = *cfg + + bcfg := &backend.Config{ + RPCUsername: cfg.NamecoinRPCUsername, + RPCPassword: cfg.NamecoinRPCPassword, + RPCAddress: cfg.NamecoinRPCAddress, + CacheMaxEntries: cfg.CacheMaxEntries, + SelfName: cfg.SelfName, + SelfIP: cfg.SelfIP, + } + + b, err := backend.New(bcfg) + if err != nil { + return + } + + ecfg := &madns.EngineConfig{ + Backend: b, + } + + // key setup + if cfg.PublicKey != "" { + ksk, kskPrivate, err := s.loadKey(cfg.PublicKey, cfg.PrivateKey) + if err != nil { + return nil, err + } + + ecfg.KSK = ksk + ecfg.KSKPrivate = kskPrivate + } + + if cfg.ZonePublicKey != "" { + zsk, zskPrivate, err := s.loadKey(cfg.ZonePublicKey, cfg.ZonePrivateKey) + if err != nil { + return nil, err + } + + ecfg.ZSK = zsk + ecfg.ZSKPrivate = zskPrivate + } + + if ecfg.KSK != nil && ecfg.ZSK == nil { + return nil, fmt.Errorf("Must specify ZSK if KSK is specified") + } + + e, err := madns.NewEngine(ecfg) + if err != nil { + return + } + + s.engine = e + return } func (s *Server) loadKey(fn, privateFn string) (k *dns.DNSKEY, privatek dns.PrivateKey, err error) { - f, err := os.Open(fn) - if err != nil { - return - } - - rr, err := dns.ReadRR(f, fn) - if err != nil { - return - } - - k, ok := rr.(*dns.DNSKEY) - if !ok { - err = fmt.Errorf("Loaded record from key file, but it wasn't a DNSKEY") - return - } - - privatef, err := os.Open(privateFn) - if err != nil { - return - } - - privatek, err = k.ReadPrivateKey(privatef, privateFn) - log.Fatale(err) - - return + f, err := os.Open(fn) + if err != nil { + return + } + + rr, err := dns.ReadRR(f, fn) + if err != nil { + return + } + + k, ok := rr.(*dns.DNSKEY) + if !ok { + err = fmt.Errorf("Loaded record from key file, but it wasn't a DNSKEY") + return + } + + privatef, err := os.Open(privateFn) + if err != nil { + return + } + + privatek, err = k.ReadPrivateKey(privatef, privateFn) + log.Fatale(err) + + return } func (s *Server) Run() { - s.mux = dns.NewServeMux() - s.mux.Handle(".", s.engine) - - s.udpListener = s.runListener("udp") - s.tcpListener = s.runListener("tcp") - - log.Info("Ready.") - - // wait - sig := make(chan os.Signal) - signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) - for { - s := <-sig - fmt.Printf("Signal %v received, stopping.", s) - break - } + s.mux = dns.NewServeMux() + s.mux.Handle(".", s.engine) + + s.udpListener = s.runListener("udp") + s.tcpListener = s.runListener("tcp") + + log.Info("Ready.") + + // 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) { - err := ds.ListenAndServe() - log.Fatale(err) + err := ds.ListenAndServe() + log.Fatale(err) } func (s *Server) runListener(net string) *dns.Server { - ds := &dns.Server { - Addr: s.cfg.Bind, - Net: net, - Handler: s.mux, - } - go s.doRunListener(ds) - return ds + ds := &dns.Server{ + Addr: s.cfg.Bind, + Net: net, + Handler: s.mux, + } + go s.doRunListener(ds) + return ds } diff --git a/util/util.go b/util/util.go index ad95210..ffe52a2 100644 --- a/util/util.go +++ b/util/util.go @@ -1,17 +1,18 @@ package util + import "strings" // Split a domain name a.b.c.d.e into parts a (the head) and b.c.d.e (the rest). func SplitDomainHead(name string) (head string, rest string, err error) { - parts := strings.Split(name, ".") + parts := strings.Split(name, ".") - head = parts[len(parts)-1] + head = parts[len(parts)-1] - if len(parts) >= 2 { - rest = strings.Join(parts[0:len(parts)-1], ".") - } + if len(parts) >= 2 { + rest = strings.Join(parts[0:len(parts)-1], ".") + } - return + return } // © 2014 Hugo Landau GPLv3 or later