web server

pull/18/head
Hugo Landau 10 years ago
parent d6c632fed5
commit ec41469d58

@ -9,6 +9,8 @@ import "github.com/hlandau/ncdns/ncdomain"
import "sync"
import "fmt"
import "net"
import "net/mail"
import "strings"
// Provides an abstract zone file for the Namecoin .bit TLD.
type Backend struct {
@ -23,25 +25,33 @@ const defaultMaxEntries = 100
// Backend configuration.
type Config struct {
NamecoinConn namecoin.Conn
// Username and password to use for connecting to the Namecoin JSON-RPC interface.
RPCUsername string
RPCPassword string
//RPCUsername string
//RPCPassword string
// hostname:port to use for connecting to the Namecoin JSON-RPC interface.
RPCAddress string
//RPCAddress string
// 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
// Nameservers to advertise at zone apex. The first is considered the primary.
// If empty, a psuedo-hostname resolvable to SelfIP is used.
CanonicalNameservers []string
// Vanity IPs to place at the zone apex.
VanityIPs []net.IP
// 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.
// Used only if CanonicalNameservers 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
// Hostmaster in e. mail form (e.g. "hostmaster@example.com").
Hostmaster string
// Map names (like "d/example") to strings containing JSON values. Used to provide
// fake names for testing purposes. You don't need to use this.
FakeNames map[string]string
@ -52,20 +62,50 @@ func New(cfg *Config) (backend *Backend, err error) {
b := &Backend{}
b.cfg = *cfg
b.nc.Username = cfg.RPCUsername
b.nc.Password = cfg.RPCPassword
b.nc.Server = cfg.RPCAddress
b.nc = b.cfg.NamecoinConn
//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
}
hostmaster, err := convertEmail(b.cfg.Hostmaster)
if err != nil {
return
}
b.cfg.Hostmaster = hostmaster
backend = b
return
}
func convertEmail(email string) (string, error) {
if email == "" {
return ".", nil
}
if util.ValidateHostName(email) {
return email, nil
}
addr, err := mail.ParseAddress(email)
if err != nil {
return "", err
}
email = addr.Address
parts := strings.SplitN(email, "@", 2)
if len(parts) < 2 {
return "", fmt.Errorf("invalid e. mail address specified")
}
return dns.Fqdn(parts[0] + "." + parts[1]), nil
}
// 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) {
@ -111,7 +151,7 @@ func (tx *btx) Do() (rrs []dns.RR, err error) {
// 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" && len(tx.b.cfg.CanonicalNameservers) == 0 {
return tx.doMetaDomain()
}
@ -125,9 +165,9 @@ func (tx *btx) determineDomain() (subname, basename, rootname string, err error)
}
func (tx *btx) doRootDomain() (rrs []dns.RR, err error) {
nsname := tx.b.cfg.SelfName
if nsname == "" {
nsname = "this.x--nmc." + tx.rootname
nss := tx.b.cfg.CanonicalNameservers
if len(tx.b.cfg.CanonicalNameservers) == 0 {
nss = []string{dns.Fqdn("this.x--nmc." + tx.rootname)}
}
soa := &dns.SOA{
@ -137,8 +177,8 @@ func (tx *btx) doRootDomain() (rrs []dns.RR, err error) {
Class: dns.ClassINET,
Rrtype: dns.TypeSOA,
},
Ns: dns.Fqdn(nsname),
Mbox: ".",
Ns: nss[0],
Mbox: tx.b.cfg.Hostmaster,
Serial: 1,
Refresh: 600,
Retry: 600,
@ -146,17 +186,48 @@ func (tx *btx) doRootDomain() (rrs []dns.RR, err error) {
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 = make([]dns.RR, 0, 1+len(nss)+len(tx.b.cfg.VanityIPs))
rrs = append(rrs, soa)
for _, cn := range nss {
ns := &dns.NS{
Hdr: dns.RR_Header{
Name: dns.Fqdn(tx.rootname),
Ttl: 86400,
Class: dns.ClassINET,
Rrtype: dns.TypeNS,
},
Ns: dns.Fqdn(cn),
}
rrs = append(rrs, ns)
}
for _, ip := range tx.b.cfg.VanityIPs {
if ip.To4() != nil {
a := &dns.A{
Hdr: dns.RR_Header{
Name: dns.Fqdn(tx.rootname),
Ttl: 86400,
Class: dns.ClassINET,
Rrtype: dns.TypeA,
},
A: ip,
}
rrs = append(rrs, a)
} else {
a := &dns.AAAA{
Hdr: dns.RR_Header{
Name: dns.Fqdn(tx.rootname),
Ttl: 86400,
Class: dns.ClassINET,
Rrtype: dns.TypeAAAA,
},
AAAA: ip,
}
rrs = append(rrs, a)
}
}
rrs = []dns.RR{soa, ns}
return
}
@ -277,9 +348,9 @@ func (b *Backend) resolveName(name string) (jsonValue string, err error) {
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
v := ncdomain.ParseValue(name, jsonValue, b.resolveExtraName, nil)
if v == nil {
return nil, fmt.Errorf("couldn't parse value")
}
d.ncv = v

@ -7,6 +7,12 @@ import "github.com/hlandau/ncdns/namecoin/extratypes"
import "sync/atomic"
import "fmt"
import "expvar"
var cQueryCalls = expvar.NewInt("ncdns.namecoin.numQueryCalls")
var cSyncCalls = expvar.NewInt("ncdns.namecoin.numSyncCalls")
var cFilterCalls = expvar.NewInt("ncdns.namecoin.numFilterCalls")
var cCurHeightCalls = expvar.NewInt("ncdns.namecoin.numCurHeightCalls")
// Used for generating IDs for JSON-RPC requests.
var idCounter int32
@ -27,6 +33,8 @@ type Conn struct {
// 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 *Conn) Query(name string) (v string, err error) {
cQueryCalls.Add(1)
cmd, err := extratypes.NewNameShowCmd(newID(), name)
if err != nil {
//log.Info("NC NEWCMD ", err)
@ -65,6 +73,8 @@ var ErrSyncNoSuchBlock = fmt.Errorf("no block exists with given hash")
const rpcInvalidAddressOrKey = -5
func (nc *Conn) Sync(hash string, count int, wait bool) ([]extratypes.NameSyncEvent, error) {
cSyncCalls.Add(1)
cmd, err := extratypes.NewNameSyncCmd(newID(), hash, count, wait)
if err != nil {
return nil, err
@ -94,6 +104,8 @@ func (nc *Conn) Sync(hash string, count int, wait bool) ([]extratypes.NameSyncEv
}
func (nc *Conn) CurHeight() (int, error) {
cCurHeightCalls.Add(1)
cmd, err := btcjson.NewGetInfoCmd(newID())
if err != nil {
return 0, err
@ -120,6 +132,8 @@ func (nc *Conn) CurHeight() (int, error) {
}
func (nc *Conn) Filter(regexp string, maxage, from, count int) (names []extratypes.NameFilterItem, err error) {
cFilterCalls.Add(1)
cmd, err := extratypes.NewNameFilterCmd(newID(), regexp, maxage, from, count)
if err != nil {
return nil, err

@ -46,6 +46,56 @@ type Value struct {
IsTopLevel bool
}
func (v *Value) mkString(i string) string {
s := i[1:] + "Value:"
i += " "
if v.HasAlias {
s += i + "CNAME: \"" + v.Alias + "\""
}
if v.HasTranslate {
s += i + "DNAME: \"" + v.Translate + "\""
}
if v.Hostmaster != "" {
s += i + "Hostmaster: " + v.Hostmaster
}
for _, ip := range v.IP {
s += i + "IPv4 Address: " + ip.String()
}
for _, ip := range v.IP6 {
s += i + "IPv6 Address: " + ip.String()
}
for _, ns := range v.NS {
s += i + "Nameserver: " + ns
}
for _, ds := range v.DS {
s += i + "DS Record: " + ds.String()
}
for _, txt := range v.TXT {
s += i + "TXT Record:"
for _, txtc := range txt {
s += i + " " + txtc
}
}
for _, srv := range v.Service {
s += i + "SRV Record: " + srv.String()
}
for _, tlsa := range v.TLSA {
s += i + "TLSA Record: " + tlsa.String()
}
if len(v.Map) > 0 {
s += i + "Subdomains:"
for k, v := range v.Map {
s += i + " " + k + ":"
s += v.mkString(i + " ")
}
}
return s
}
func (v *Value) String() string {
return v.mkString("\n")
}
func (v *Value) RRs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
il := len(out)
suffix = dns.Fqdn(suffix)
@ -287,6 +337,19 @@ type rawValue struct {
}
type ResolveFunc func(name string) (string, error)
type ErrorFunc func(err error, isWarning bool)
func (ef ErrorFunc) add(err error) {
if ef != nil && err != nil {
ef(err, false)
}
}
func (ef ErrorFunc) addWarning(err error) {
if ef != nil && err != nil {
ef(err, true)
}
}
// Call to convert a given JSON value to a parsed Namecoin domain value.
//
@ -295,12 +358,17 @@ type ResolveFunc func(name string) (string, error)
// Namecoin form (e.g. "d/example"). The JSON value or an error should be
// returned. If no ResolveFunc is passed, "import" and "delegate" statements
// always fail.
func ParseValue(name, jsonValue string, resolve ResolveFunc) (value *Value, err error) {
//
// Returns nil if the JSON could not be parsed. For all other errors processing
// continues and recovers as much as possible; errFunc is called for all errors
// and warnings if specified.
func ParseValue(name, jsonValue string, resolve ResolveFunc, errFunc ErrorFunc) (value *Value) {
rv := &rawValue{}
v := &Value{}
err = json.Unmarshal([]byte(jsonValue), rv)
err := json.Unmarshal([]byte(jsonValue), rv)
if err != nil {
errFunc.add(err)
return
}
@ -313,16 +381,17 @@ func ParseValue(name, jsonValue string, resolve ResolveFunc) (value *Value, err
mergedNames := map[string]struct{}{}
mergedNames[name] = struct{}{}
rv.parse(v, resolve, 0, 0, "", "", mergedNames)
rv.parse(v, resolve, errFunc, 0, 0, "", "", mergedNames)
v.IsTopLevel = true
value = v
return
}
func (rv *rawValue) parse(v *Value, resolve ResolveFunc, depth, mergeDepth int, subdomain, relname string, mergedNames map[string]struct{}) error {
func (rv *rawValue) parse(v *Value, resolve ResolveFunc, errFunc ErrorFunc, depth, mergeDepth int, subdomain, relname string, mergedNames map[string]struct{}) {
if depth > depthLimit {
return fmt.Errorf("depth limit exceeded")
errFunc.add(fmt.Errorf("depth limit exceeded"))
return
}
realv := v
@ -332,35 +401,34 @@ func (rv *rawValue) parse(v *Value, resolve ResolveFunc, depth, mergeDepth int,
v = &Value{}
}
ok, _ := rv.parseDelegate(v, resolve, depth, mergeDepth, relname, mergedNames)
ok, _ := rv.parseDelegate(v, resolve, errFunc, depth, mergeDepth, relname, mergedNames)
if ok {
return nil
}
rv.parseImport(v, resolve, depth, mergeDepth, relname, mergedNames)
rv.parseIP(v, rv.IP, false)
rv.parseIP(v, rv.IP6, true)
rv.parseNS(v, relname)
rv.parseAlias(v, relname)
rv.parseTranslate(v, relname)
rv.parseHostmaster(v)
rv.parseDS(v)
rv.parseTXT(v)
rv.parseService(v, relname)
rv.parseMX(v, relname)
rv.parseTLSA(v)
rv.parseMap(v, resolve, depth, mergeDepth, relname)
return
}
rv.parseImport(v, resolve, errFunc, depth, mergeDepth, relname, mergedNames)
rv.parseIP(v, errFunc, rv.IP, false)
rv.parseIP(v, errFunc, rv.IP6, true)
rv.parseNS(v, errFunc, relname)
rv.parseAlias(v, errFunc, relname)
rv.parseTranslate(v, errFunc, relname)
rv.parseHostmaster(v, errFunc)
rv.parseDS(v, errFunc)
rv.parseTXT(v, errFunc)
rv.parseService(v, errFunc, relname)
rv.parseMX(v, errFunc, relname)
rv.parseTLSA(v, errFunc)
rv.parseMap(v, resolve, errFunc, depth, mergeDepth, relname)
v.moveEmptyMapItems()
if subdomain != "" {
subv, err := v.findSubdomainByName(subdomain)
if err != nil {
return err
errFunc.add(fmt.Errorf("couldn't find subdomain by name in import or delegate item: %v", err))
return
}
*realv = *subv
}
return nil
}
func (v *Value) qualifyIntl(name, suffix, apexSuffix string) string {
@ -396,22 +464,27 @@ func (v *Value) qualify(name, suffix, apexSuffix string) (string, bool) {
return s, true
}
func (rv *rawValue) parseMerge(mergeValue string, v *Value, resolve ResolveFunc, depth, mergeDepth int, subdomain, relname string, mergedNames map[string]struct{}) error {
func (rv *rawValue) parseMerge(mergeValue string, v *Value, resolve ResolveFunc, errFunc ErrorFunc, depth, mergeDepth int, subdomain, relname string, mergedNames map[string]struct{}) error {
rv2 := &rawValue{}
if mergeDepth > mergeDepthLimit {
return fmt.Errorf("merge depth limit exceeded")
err := fmt.Errorf("merge depth limit exceeded")
errFunc.add(err)
return err
}
err := json.Unmarshal([]byte(mergeValue), rv2)
if err != nil {
err = fmt.Errorf("couldn't parse JSON to be merged: %v", err)
errFunc.add(err)
return err
}
return rv2.parse(v, resolve, depth, mergeDepth, subdomain, relname, mergedNames)
rv2.parse(v, resolve, errFunc, depth, mergeDepth, subdomain, relname, mergedNames)
return nil
}
func (rv *rawValue) parseIP(v *Value, ipi interface{}, ipv6 bool) {
func (rv *rawValue) parseIP(v *Value, errFunc ErrorFunc, ipi interface{}, ipv6 bool) {
if ipi != nil {
if ipv6 {
v.IP6 = nil
@ -423,7 +496,7 @@ func (rv *rawValue) parseIP(v *Value, ipi interface{}, ipv6 bool) {
if ipa, ok := ipi.([]interface{}); ok {
for _, ip := range ipa {
if ips, ok := ip.(string); ok {
rv.addIP(v, ips, ipv6)
rv.addIP(v, errFunc, ips, ipv6)
}
}
@ -431,14 +504,15 @@ func (rv *rawValue) parseIP(v *Value, ipi interface{}, ipv6 bool) {
}
if ip, ok := ipi.(string); ok {
rv.addIP(v, ip, ipv6)
rv.addIP(v, errFunc, ip, ipv6)
}
}
func (rv *rawValue) addIP(v *Value, ips string, ipv6 bool) error {
func (rv *rawValue) addIP(v *Value, errFunc ErrorFunc, ips string, ipv6 bool) {
pip := net.ParseIP(ips)
if pip == nil || (pip.To4() == nil) != ipv6 {
return fmt.Errorf("malformed IP")
errFunc.add(fmt.Errorf("malformed IP: %s", ips))
return
}
if ipv6 {
@ -446,18 +520,16 @@ func (rv *rawValue) addIP(v *Value, ips string, ipv6 bool) error {
} else {
v.IP = append(v.IP, pip)
}
return nil
}
func (rv *rawValue) parseNS(v *Value, relname string) error {
func (rv *rawValue) parseNS(v *Value, errFunc ErrorFunc, relname string) {
// "dns" takes precedence
if rv.DNS != nil {
rv.NS = rv.DNS
}
if rv.NS == nil {
return nil
return
}
v.NS = nil
@ -475,51 +547,49 @@ func (rv *rawValue) parseNS(v *Value, relname string) error {
}
rv.addNS(v, s, relname)
}
return nil
return
case string:
s := rv.NS.(string)
rv.addNS(v, s, relname)
return nil
return
default:
return fmt.Errorf("unknown NS field format")
errFunc.add(fmt.Errorf("unknown NS field format"))
}
}
func (rv *rawValue) addNS(v *Value, s, relname string) error {
func (rv *rawValue) addNS(v *Value, s, relname string) {
if _, ok := rv.nsSet[s]; !ok {
v.NS = append(v.NS, s)
rv.nsSet[s] = struct{}{}
}
return nil
}
func (rv *rawValue) parseAlias(v *Value, relname string) error {
func (rv *rawValue) parseAlias(v *Value, errFunc ErrorFunc, relname string) {
if rv.Alias == nil {
return nil
return
}
if s, ok := rv.Alias.(string); ok {
v.Alias = s
v.HasAlias = true
return nil
return
}
return fmt.Errorf("unknown alias field format")
errFunc.add(fmt.Errorf("unknown alias field format"))
}
func (rv *rawValue) parseTranslate(v *Value, relname string) error {
func (rv *rawValue) parseTranslate(v *Value, errFunc ErrorFunc, relname string) {
if rv.Translate == nil {
return nil
return
}
if s, ok := rv.Translate.(string); ok {
v.Translate = s
v.HasTranslate = true
return nil
return
}
return fmt.Errorf("unknown translate field format")
errFunc.add(fmt.Errorf("unknown translate field format"))
}
func isAllArray(x []interface{}) bool {
@ -540,7 +610,7 @@ func isAllString(x []interface{}) bool {
return true
}
func (rv *rawValue) parseImportImpl(val *Value, resolve ResolveFunc, depth, mergeDepth int, relname string, mergedNames map[string]struct{}, delegate bool) (bool, error) {
func (rv *rawValue) parseImportImpl(val *Value, resolve ResolveFunc, errFunc ErrorFunc, depth, mergeDepth int, relname string, mergedNames map[string]struct{}, delegate bool) (bool, error) {
var err error
succeeded := false
src := rv.Import
@ -595,14 +665,18 @@ func (rv *rawValue) parseImportImpl(val *Value, resolve ResolveFunc, depth, merg
mergedNames[k] = struct{}{}
err = rv.parseMerge(dv, val, resolve, depth, mergeDepth+1, subs, relname, mergedNames)
err = rv.parseMerge(dv, val, resolve, errFunc, depth, mergeDepth+1, subs, relname, mergedNames)
if err != nil {
errFunc.add(err)
continue
}
succeeded = true
}
}
// ...
return succeeded, nil
}
// malformed
}
@ -611,38 +685,41 @@ func (rv *rawValue) parseImportImpl(val *Value, resolve ResolveFunc, depth, merg
err = fmt.Errorf("unknown import/delegate field format")
}
errFunc.add(err)
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)
func (rv *rawValue) parseImport(v *Value, resolve ResolveFunc, errFunc ErrorFunc, depth, mergeDepth int, relname string, mergedNames map[string]struct{}) error {
_, err := rv.parseImportImpl(v, resolve, errFunc, 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) parseDelegate(v *Value, resolve ResolveFunc, errFunc ErrorFunc, depth, mergeDepth int, relname string, mergedNames map[string]struct{}) (bool, error) {
return rv.parseImportImpl(v, resolve, errFunc, depth, mergeDepth, relname, mergedNames, true)
}
func (rv *rawValue) parseHostmaster(v *Value) error {
func (rv *rawValue) parseHostmaster(v *Value, errFunc ErrorFunc) {
if rv.Hostmaster == nil {
return nil
return
}
if s, ok := rv.Hostmaster.(string); ok {
if !util.ValidateEmail(s) {
return fmt.Errorf("malformed e. mail address in email field")
errFunc.add(fmt.Errorf("malformed e. mail address in email field"))
return
}
v.Hostmaster = s
return nil
return
}
return fmt.Errorf("unknown email field format")
errFunc.add(fmt.Errorf("unknown email field format"))
}
func (rv *rawValue) parseDS(v *Value) error {
func (rv *rawValue) parseDS(v *Value, errFunc ErrorFunc) {
if rv.DS == nil {
return nil
return
}
v.DS = nil
@ -650,32 +727,38 @@ func (rv *rawValue) parseDS(v *Value) error {
if dsa, ok := rv.DS.([]interface{}); ok {
for _, ds1 := range dsa {
if ds, ok := ds1.([]interface{}); ok {
if len(ds) != 4 {
if len(ds) < 4 {
errFunc.add(fmt.Errorf("DS item must have four items"))
continue
}
a1, ok := ds[0].(float64)
if !ok {
errFunc.add(fmt.Errorf("First item in DS value must be an integer (key tag)"))
continue
}
a2, ok := ds[1].(float64)
if !ok {
errFunc.add(fmt.Errorf("Second item in DS value must be an integer (algorithm)"))
continue
}
a3, ok := ds[2].(float64)
if !ok {
errFunc.add(fmt.Errorf("Third item in DS value must be an integer (digest type)"))
continue
}
a4, ok := ds[3].(string)
if !ok {
errFunc.add(fmt.Errorf("Fourth item in DS value must be a string (digest)"))
continue
}
a4b, err := base64.StdEncoding.DecodeString(a4)
if err != nil {
errFunc.add(fmt.Errorf("Fourth item in DS value must be valid base64: %v", err))
continue
}
@ -687,16 +770,19 @@ func (rv *rawValue) parseDS(v *Value) error {
DigestType: uint8(a3),
Digest: a4h,
})
} else {
errFunc.add(fmt.Errorf("DS item must be an array"))
}
}
return
}
return fmt.Errorf("malformed DS field format")
errFunc.add(fmt.Errorf("malformed DS field format"))
}
func (rv *rawValue) parseTLSA(v *Value) error {
func (rv *rawValue) parseTLSA(v *Value, errFunc ErrorFunc) {
if rv.TLSA == nil {
return nil
return
}
v.TLSA = nil
@ -706,6 +792,7 @@ func (rv *rawValue) parseTLSA(v *Value) error {
if tlsa, ok := tlsa1.([]interface{}); ok {
// Format: ["443", "tcp", 1, 2, 3, "base64 certificate data"]
if len(tlsa) < 6 {
errFunc.add(fmt.Errorf("TLSA item must have six items"))
continue
}
@ -713,6 +800,7 @@ func (rv *rawValue) parseTLSA(v *Value) error {
if !ok {
porti, ok := tlsa[0].(float64)
if !ok {
errFunc.add(fmt.Errorf("First item in TLSA value must be an integer or string (port number)"))
continue
}
ports = fmt.Sprintf("%d", int(porti))
@ -720,31 +808,42 @@ func (rv *rawValue) parseTLSA(v *Value) error {
transport, ok := tlsa[1].(string)
if !ok {
errFunc.add(fmt.Errorf("Second item in TLSA value must be a string (transport protocol name)"))
continue
}
a1, ok := tlsa[2].(float64)
if !ok {
errFunc.add(fmt.Errorf("Third item in TLSA value must be an integer (usage)"))
continue
}
a2, ok := tlsa[3].(float64)
if !ok {
errFunc.add(fmt.Errorf("Fourth item in TLSA value must be an integer (selector)"))
continue
}
a3, ok := tlsa[4].(float64)
if !ok {
errFunc.add(fmt.Errorf("Fifth item in TLSA value must be an integer (match type)"))
continue
}
a4, ok := tlsa[5].(string)
if !ok {
errFunc.add(fmt.Errorf("Sixth item in TLSA value must be a string (certificate)"))
continue
}
a4b, err := base64.StdEncoding.DecodeString(a4)
if err != nil {
errFunc.add(fmt.Errorf("Fourth item in DS value must be valid base64: %v", err))
continue
}
if len(ports) > 62 || len(transport) > 62 {
errFunc.add(fmt.Errorf("Application and transport names must not exceed 62 characters"))
continue
}
@ -759,16 +858,19 @@ func (rv *rawValue) parseTLSA(v *Value) error {
MatchingType: uint8(a3),
Certificate: strings.ToUpper(a4h),
})
} else {
errFunc.add(fmt.Errorf("TLSA item must be an array"))
}
}
return
}
return fmt.Errorf("malformed TLSA field format")
errFunc.add(fmt.Errorf("Malformed TLSA field format"))
}
func (rv *rawValue) parseTXT(v *Value) error {
func (rv *rawValue) parseTXT(v *Value, errFunc ErrorFunc) {
if rv.TXT == nil {
return nil
return
}
if txta, ok := rv.TXT.([]interface{}); ok {
@ -788,7 +890,8 @@ func (rv *rawValue) parseTXT(v *Value) error {
} else if s, ok := vv.(string); ok {
v.TXT = append(v.TXT, segmentizeTXT(s))
} else {
return fmt.Errorf("malformed TXT value")
errFunc.add(fmt.Errorf("malformed TXT value"))
return
}
}
} else {
@ -796,7 +899,8 @@ func (rv *rawValue) parseTXT(v *Value) error {
if s, ok := rv.TXT.(string); ok {
v.TXT = append(v.TXT, segmentizeTXT(s))
} else {
return fmt.Errorf("malformed TXT value")
errFunc.add(fmt.Errorf("malformed TXT value"))
return
}
}
@ -818,7 +922,7 @@ func (rv *rawValue) parseTXT(v *Value) error {
}
}
return nil
return
}
func segmentizeTXT(txt string) (a []string) {
@ -830,38 +934,43 @@ func segmentizeTXT(txt string) (a []string) {
return
}
func (rv *rawValue) parseMX(v *Value, relname string) error {
func (rv *rawValue) parseMX(v *Value, errFunc ErrorFunc, relname string) {
if rv.MX == nil {
return nil
return
}
if sa, ok := rv.MX.([]interface{}); ok {
for _, s := range sa {
rv.parseSingleMX(s, v, relname)
rv.parseSingleMX(s, v, errFunc, relname)
}
return
}
return fmt.Errorf("malformed MX value")
errFunc.add(fmt.Errorf("malformed MX value"))
}
func (rv *rawValue) parseSingleMX(s interface{}, v *Value, relname string) error {
func (rv *rawValue) parseSingleMX(s interface{}, v *Value, errFunc ErrorFunc, relname string) {
sa, ok := s.([]interface{})
if !ok {
return fmt.Errorf("malformed MX value")
errFunc.add(fmt.Errorf("malformed MX value"))
return
}
if len(sa) < 2 {
return fmt.Errorf("malformed MX value")
errFunc.add(fmt.Errorf("malformed MX value"))
return
}
prio, ok := sa[0].(float64)
if !ok || prio < 0 {
return fmt.Errorf("malformed MX value")
errFunc.add(fmt.Errorf("malformed MX value"))
return
}
hostname, ok := sa[1].(string)
if !ok {
return fmt.Errorf("malformed MX value")
errFunc.add(fmt.Errorf("malformed MX value"))
return
}
v.MX = append(v.MX, &dns.MX{
@ -870,12 +979,12 @@ func (rv *rawValue) parseSingleMX(s interface{}, v *Value, relname string) error
Mx: hostname,
})
return nil
return
}
func (rv *rawValue) parseService(v *Value, relname string) error {
func (rv *rawValue) parseService(v *Value, errFunc ErrorFunc, relname string) {
if rv.Service == nil {
return nil
return
}
// We have to merge the services specified and those imported using an
@ -886,8 +995,10 @@ func (rv *rawValue) parseService(v *Value, relname string) error {
if sa, ok := rv.Service.([]interface{}); ok {
for _, s := range sa {
rv.parseSingleService(s, v, relname, servicesUsed)
rv.parseSingleService(s, v, errFunc, relname, servicesUsed)
}
} else {
errFunc.add(fmt.Errorf("malformed service value"))
}
for _, svc := range oldServices {
@ -895,48 +1006,54 @@ func (rv *rawValue) parseService(v *Value, relname string) error {
v.Service = append(v.Service, svc)
}
}
return fmt.Errorf("malformed service value")
}
func (rv *rawValue) parseSingleService(svc interface{}, v *Value, relname string, servicesUsed map[string]struct{}) error {
func (rv *rawValue) parseSingleService(svc interface{}, v *Value, errFunc ErrorFunc, relname string, servicesUsed map[string]struct{}) {
svca, ok := svc.([]interface{})
if !ok {
return fmt.Errorf("malformed service value")
errFunc.add(fmt.Errorf("malformed service value"))
return
}
if len(svca) < 6 {
return fmt.Errorf("malformed service value")
errFunc.add(fmt.Errorf("malformed service value: must have six items"))
return
}
appProtoName, ok := svca[0].(string)
if !ok || !util.ValidateServiceName(appProtoName) {
return fmt.Errorf("malformed service value")
errFunc.add(fmt.Errorf("malformed service value: first item must be a string (application protocol)"))
return
}
transportProtoName, ok := svca[1].(string)
if !ok || !util.ValidateServiceName(transportProtoName) {
return fmt.Errorf("malformed service value")
errFunc.add(fmt.Errorf("malformed service value: second item must be a string (transport protocol)"))
return
}
priority, ok := svca[2].(float64)
if !ok {
return fmt.Errorf("malformed service value")
errFunc.add(fmt.Errorf("malformed service value: third item must be an integer (priority)"))
return
}
weight, ok := svca[3].(float64)
if !ok {
return fmt.Errorf("malformed service value")
errFunc.add(fmt.Errorf("malformed service value: fourth item must be an integer (weight)"))
return
}
port, ok := svca[4].(float64)
if !ok {
return fmt.Errorf("malformed service value")
errFunc.add(fmt.Errorf("malformed service value: fifth item must be an integer (port number)"))
return
}
hostname, ok := svca[5].(string)
if !ok {
return fmt.Errorf("malformed service value")
errFunc.add(fmt.Errorf("malformed service value: sixth item must be a string (target)"))
return
}
sname := "_" + appProtoName + "._" + transportProtoName
@ -955,15 +1072,20 @@ func (rv *rawValue) parseSingleService(svc interface{}, v *Value, relname string
Target: hostname,
})
return nil
return
}
func (rv *rawValue) parseMap(v *Value, resolve ResolveFunc, depth, mergeDepth int, relname string) error {
func (rv *rawValue) parseMap(v *Value, resolve ResolveFunc, errFunc ErrorFunc, depth, mergeDepth int, relname string) {
if rv.Map == nil {
return
}
m := map[string]json.RawMessage{}
err := json.Unmarshal(rv.Map, &m)
if err != nil {
return err
errFunc.add(fmt.Errorf("Couldn't unmarshal map: %v", err))
return
}
for mk, mv := range m {
@ -980,12 +1102,13 @@ func (rv *rawValue) parseMap(v *Value, resolve ResolveFunc, depth, mergeDepth in
// normal case: "map": { "": { ... } }
err = json.Unmarshal(mv, rv2)
if err != nil {
errFunc.add(fmt.Errorf("Couldn't unmarshal map: %v", err))
continue
}
}
mergedNames := map[string]struct{}{}
rv2.parse(v2, resolve, depth+1, mergeDepth, "", relname, mergedNames)
rv2.parse(v2, resolve, errFunc, depth+1, mergeDepth, "", relname, mergedNames)
if v.Map == nil {
v.Map = make(map[string]*Value)
@ -993,13 +1116,11 @@ func (rv *rawValue) parseMap(v *Value, resolve ResolveFunc, depth, mergeDepth in
v.Map[mk] = v2
}
return nil
}
// Moves items in {"map": {"": ...}} to the object itself, then deletes the ""
// entry in the map object.
func (v *Value) moveEmptyMapItems() error {
func (v *Value) moveEmptyMapItems() {
if ev, ok := v.Map[""]; ok {
if len(v.IP) == 0 {
v.IP = ev.IP
@ -1036,5 +1157,4 @@ func (v *Value) moveEmptyMapItems() error {
v.Map = ev.Map
}
}
return nil
}

@ -26,8 +26,8 @@ func TestSuite(t *testing.T) {
continue
}
v, err := ncdomain.ParseValue(k, jsonValue, resolve)
if err != nil {
v := ncdomain.ParseValue(k, jsonValue, resolve, nil)
if v == nil {
// TODO
continue
}

@ -3,9 +3,12 @@ package server
import "github.com/hlandau/madns"
import "github.com/hlandau/degoutils/log"
import "github.com/hlandau/ncdns/backend"
import "github.com/hlandau/ncdns/namecoin"
import "github.com/miekg/dns"
import "os"
import "net"
import "fmt"
import "strings"
import "path/filepath"
const version = "1.0"
@ -13,7 +16,8 @@ const version = "1.0"
type Server struct {
cfg ServerConfig
engine madns.Engine
engine madns.Engine
namecoinConn namecoin.Conn
mux *dns.ServeMux
udpListener *dns.Server
@ -31,9 +35,18 @@ type ServerConfig struct {
NamecoinRPCPassword string `default:"" usage:"Namecoin RPC password"`
NamecoinRPCAddress string `default:"localhost:8336" usage:"Namecoin RPC server address"`
CacheMaxEntries int `default:"100" 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)"`
SelfName string `default:"" usage:"The FQDN of this nameserver. If empty, a psuedo-hostname is generated."`
SelfIP string `default:"127.127.127.127" usage:"The canonical IP address for this service"`
HTTPListenAddr string `default:"" usage:"Address for webserver to listen at (default: disabled)"`
CanonicalSuffix string `default:"bit" usage:"Suffix to advertise via HTTP"`
CanonicalNameservers string `default:"" usage:"Comma-separated list of nameservers to use for NS records. If blank, SelfName (or autogenerated psuedo-hostname) is used."`
canonicalNameservers []string
Hostmaster string `default:"" usage:"Hostmaster e. mail address"`
VanityIPs string `default:"" usage:"Comma separated list of IP addresses to place in A/AAAA records at the zone apex (default: don't add any records)"`
vanityIPs []net.IP
ConfigDir string // path to interpret filenames relative to
}
@ -45,13 +58,33 @@ func NewServer(cfg *ServerConfig) (s *Server, err error) {
s = &Server{}
s.cfg = *cfg
s.cfg.canonicalNameservers = strings.Split(s.cfg.CanonicalNameservers, ",")
for i := range s.cfg.canonicalNameservers {
s.cfg.canonicalNameservers[i] = dns.Fqdn(s.cfg.canonicalNameservers[i])
}
vanityIPs := strings.Split(s.cfg.VanityIPs, ",")
for _, ips := range vanityIPs {
ip := net.ParseIP(ips)
if ip == nil {
return nil, fmt.Errorf("Couldn't parse IP: %s", ips)
}
s.cfg.vanityIPs = append(s.cfg.vanityIPs, ip)
}
s.namecoinConn = namecoin.Conn{
Username: cfg.NamecoinRPCUsername,
Password: cfg.NamecoinRPCPassword,
Server: cfg.NamecoinRPCAddress,
}
bcfg := &backend.Config{
RPCUsername: cfg.NamecoinRPCUsername,
RPCPassword: cfg.NamecoinRPCPassword,
RPCAddress: cfg.NamecoinRPCAddress,
CacheMaxEntries: cfg.CacheMaxEntries,
SelfName: cfg.SelfName,
SelfIP: cfg.SelfIP,
NamecoinConn: s.namecoinConn,
CacheMaxEntries: cfg.CacheMaxEntries,
SelfIP: cfg.SelfIP,
Hostmaster: cfg.Hostmaster,
CanonicalNameservers: s.cfg.canonicalNameservers,
VanityIPs: s.cfg.vanityIPs,
}
b, err := backend.New(bcfg)
@ -89,6 +122,14 @@ func NewServer(cfg *ServerConfig) (s *Server, err error) {
}
s.engine = e
if cfg.HTTPListenAddr != "" {
err = webStart(cfg.HTTPListenAddr, s)
if err != nil {
return
}
}
return
}

@ -0,0 +1,28 @@
package server
import "os"
import "net"
var hostname string
func init() {
names, err := net.LookupAddr("127.0.0.1")
if err != nil || len(names) == 0 {
hn, err := os.Hostname()
if err != nil {
panic(err)
}
hostname = hn
return
}
hostname = names[0]
}
func (s *Server) ServerName() string {
n := s.cfg.SelfName
if n == "" {
n = hostname
}
return n
}

@ -0,0 +1,222 @@
package server
import "net/http"
import "html/template"
import "github.com/hlandau/degoutils/log"
import "github.com/hlandau/ncdns/util"
import "github.com/hlandau/ncdns/ncdomain"
import "github.com/miekg/dns"
import "github.com/kr/pretty"
import "time"
import "strings"
import "fmt"
import "flag"
var layoutTpl *template.Template
var mainPageTpl *template.Template
var lookupPageTpl *template.Template
var tplSetFlag = flag.String("tplset", "std", "Subdirectory of tpl/ to look for templates in (default: std)")
func initTemplates() error {
if lookupPageTpl != nil {
return nil
}
if *tplSetFlag == "" {
*tplSetFlag = "std"
}
var err error
layoutTpl, err = template.ParseFiles(tplFilename("layout"))
if err != nil {
return err
}
mainPageTpl, err = deriveTemplate(tplFilename("main"))
if err != nil {
return err
}
lookupPageTpl, err = deriveTemplate(tplFilename("lookup"))
if err != nil {
return err
}
return nil
}
func deriveTemplate(filename string) (*template.Template, error) {
cl, err := layoutTpl.Clone()
if err != nil {
return nil, err
}
return cl.ParseFiles(filename)
}
func tplFilename(filename string) string {
return "tpl/" + *tplSetFlag + "/" + filename + ".tpl"
}
type webServer struct {
s *Server
sm *http.ServeMux
}
type layoutInfo struct {
SelfName string
Time string
CanonicalSuffix string
CanonicalNameservers []string
Hostmaster string
CanonicalSuffixHTML template.HTML
TLD string
HasDNSSEC bool
}
func (ws *webServer) layoutInfo() *layoutInfo {
csparts := strings.SplitN(ws.s.cfg.CanonicalSuffix, ".", 2)
cshtml := `<span id="logo1">` + csparts[0] + `</span>`
if len(csparts) > 1 {
cshtml = `<span id="logo1">` + csparts[0] + `</span><span id="logo2">.</span><span id="logo3">` + csparts[1] + `</span>`
}
li := &layoutInfo{
SelfName: ws.s.ServerName(),
Time: time.Now().Format("2006-01-02 15:04:05"),
CanonicalSuffix: ws.s.cfg.CanonicalSuffix,
CanonicalNameservers: ws.s.cfg.canonicalNameservers,
Hostmaster: ws.s.cfg.Hostmaster,
CanonicalSuffixHTML: template.HTML(cshtml),
TLD: "." + csparts[1],
HasDNSSEC: ws.s.cfg.ZonePublicKey != "",
}
return li
}
func (ws *webServer) handleRoot(rw http.ResponseWriter, req *http.Request) {
err := mainPageTpl.Execute(rw, ws.layoutInfo())
log.Infoe(err, "tpl")
}
func (ws *webServer) handleLookup(rw http.ResponseWriter, req *http.Request) {
info := struct {
layoutInfo
JSONMode bool
JSONValue string
Query string
Advanced bool
NamecoinName string
DomainName string
BareName string
NameParseError error
ExistenceError error
Expired bool
Value string
NCValue *ncdomain.Value
NCValueFmt fmt.Formatter
ParseErrors []error
ParseWarnings []error
RRs []dns.RR
RRError error
Valid bool
}{layoutInfo: *ws.layoutInfo()}
defer func() {
err := lookupPageTpl.Execute(rw, &info)
log.Infoe(err, "lookup page tpl")
}()
q := req.FormValue("q")
info.Query = q
info.BareName, info.NamecoinName, info.NameParseError = util.ParseFuzzyDomainNameNC(q)
if info.NameParseError != nil {
return
}
info.Advanced = (req.FormValue("adv") != "")
info.DomainName = info.BareName + ".bit."
info.JSONValue = req.FormValue("value")
info.Value = strings.Trim(info.JSONValue, " \t\r\n")
if info.Value == "" {
info.Value, info.ExistenceError = ws.s.namecoinConn.Query(info.NamecoinName)
if info.ExistenceError != nil {
return
}
} else {
info.JSONMode = true
}
errorFunc := func(e error, isWarning bool) {
if isWarning {
info.ParseWarnings = append(info.ParseWarnings, e)
} else {
info.ParseErrors = append(info.ParseErrors, e)
}
}
info.NCValue = ncdomain.ParseValue(info.NamecoinName, info.Value, ws.resolveFunc, errorFunc)
if info.NCValue == nil {
return
}
info.NCValueFmt = pretty.Formatter(info.NCValue)
info.RRs, info.RRError = info.NCValue.RRsRecursive(nil, info.DomainName, "bit.")
if len(info.ParseErrors) == 0 && info.RRError == nil {
info.Valid = true
}
}
func (ws *webServer) resolveFunc(name string) (string, error) {
return ws.s.namecoinConn.Query(name)
}
func (ws *webServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline';")
rw.Header().Set("X-Frame-Options", "DENY")
rw.Header().Set("X-Content-Type-Options", "nosniff")
rw.Header().Set("Server", "ncdns")
//req.Header.Set("Strict-Transport-Security", "max-age=259200")
//req.Header.Set("X-Download-Options", "noopen")
//req.Header.Set("X-XSS-Protection", "0")
//req.Header.Set("X-Permitted-Cross-Domain-Policies", "none")
clearAllCookies(rw, req)
ws.sm.ServeHTTP(rw, req)
}
func clearAllCookies(rw http.ResponseWriter, req *http.Request) {
for _, ck := range req.Cookies() {
ck2 := http.Cookie{
Name: ck.Name,
MaxAge: -1,
}
rw.Header().Add("Set-Cookie", ck2.String())
}
}
func webStart(listenAddr string, server *Server) error {
err := initTemplates()
if err != nil {
return err
}
ws := &webServer{
s: server,
sm: http.NewServeMux(),
}
ws.sm.HandleFunc("/", ws.handleRoot)
ws.sm.HandleFunc("/lookup", ws.handleLookup)
s := http.Server{
Addr: listenAddr,
Handler: ws,
}
go s.ListenAndServe()
// TODO: error handling
return nil
}

@ -94,17 +94,29 @@ func SplitDomainByFloatingAnchor(qname, anchor string) (subname, basename, rootn
// 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
if !ValidateDomainNameLabel(basename) {
return "", fmt.Errorf("invalid domain name")
}
return basenameToNamecoinKey(basename), nil
}
func basenameToNamecoinKey(basename string) string {
return "d/" + basename
}
// 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
if !strings.HasPrefix(key, "d/") {
return "", fmt.Errorf("not a valid domain name key")
}
key = key[2:]
if !ValidateDomainNameLabel(key) {
return "", fmt.Errorf("not a valid domain name key")
}
return "", fmt.Errorf("not a domain name key")
return key, nil
}
// This is used to validate NS records, targets in SRV records, etc. In these cases
@ -113,6 +125,7 @@ func NamecoinKeyToBasename(key string) (string, error) {
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_-]*$`)
var re_domainNameLabel = regexp.MustCompilePOSIX(`^(xn--)?[a-z0-9]+(-[a-z0-9]+)*$`)
func ValidateHostName(name string) bool {
name = dns.Fqdn(name)
@ -127,6 +140,10 @@ func ValidateServiceName(name string) bool {
return len(name) < 63 && re_serviceName.MatchString(name)
}
func ValidateDomainNameLabel(name string) bool {
return len(name) <= 63 && re_domainNameLabel.MatchString(name)
}
func ValidateEmail(email string) bool {
addr, err := mail.ParseAddress(email)
if addr == nil || err != nil {
@ -135,4 +152,32 @@ func ValidateEmail(email string) bool {
return addr.Name == ""
}
// Takes a name in the form "d/example" or "example.bit" and converts it to the
// bareword "example". Returns an error if the input is in neither form.
func ParseFuzzyDomainName(name string) (string, error) {
if strings.HasPrefix(name, "d/") {
return NamecoinKeyToBasename(name)
}
if len(name) > 0 && name[len(name)-1] == '.' {
name = name[0 : len(name)-1]
}
if strings.HasSuffix(name, ".bit") {
name = name[0 : len(name)-4]
if !ValidateDomainNameLabel(name) {
return "", fmt.Errorf("invalid domain name")
}
return name, nil
}
return "", fmt.Errorf("invalid domain name")
}
func ParseFuzzyDomainNameNC(name string) (bareName string, namecoinKey string, err error) {
name, err = ParseFuzzyDomainName(name)
if err != nil {
return "", "", err
}
return name, basenameToNamecoinKey(name), nil
}
// © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later

Loading…
Cancel
Save