UNPKG

dnschain

Version:

A blockchain-based DNS + HTTPS server that fixes HTTPS security, and more!

270 lines (228 loc) 12.8 kB
### dnschain http://dnschain.net Copyright (c) 2014 okTurtles Foundation This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. ### ### INSTRUCTIONS: 1. Copy this file and rename it to your blockchain's name. The name you choose will also be your metaTLD (e.g. namecoin.coffee => namecoin.dns) 2. Rename the class (following the same naming convention as shown in `blockchains/namecoin.coffee`) and `extend BlockchainResolver` 3. Uncomment and edit the code as appropriate. Look at how the other blockchains do it (especially namecoin.coffee) REMEMBER: When in doubt, look at `blockchains/namecoin.coffee` ! ### module.exports = (dnschain) -> # expose these into our namespace for k of dnschain.globals eval "var #{k} = dnschain.globals.#{k};" # Uncomment this: # BlockchainResolver = require('../blockchain.coffee')(dnschain) # For `dnsHandler:` ResolverStream = require('./resolver-stream')(dnschain) NAME_RCODE = dns2.consts.NAME_TO_RCODE RCODE_NAME = dns2.consts.RCODE_TO_NAME # This class is partially annotated using flowtate # http://flowtype.org/blog/2015/02/20/Flow-Comments.html # https://github.com/jareware/flotate ### @flow ### # Uncomment the 'extends' comment below: class BlockchainResolver # extends BlockchainResolver # Do you initialization in here. ### (dnschain: DNSChain): BlockchainResolver ### constructor: (@dnschain) -> # Fill these in as appropriate: @log = gNewLogger 'YourChainName' @tld = 'chn' # Your chain's TLD @name = 'templatechain' # Your chain's name (lowercase, no spaces) # Optionally specify how long Redis should cache entries in seconds # @cacheTTL = 600 # 0 == no cache, override here # Fills this object with `startCheck` and `shutdownCheck` methods gFillWithRunningChecks @ # This is the default TTL value for all blockchains. # Override it above in the constructor for your blockchain. cacheTTL: gConf.get 'redis:blockchain:ttl' # Return `this` upon successful load, falsy otherwise # If you return `this`, this chain will be included in the parent # DNSChain instance's `@servers` list, and will have its `start:` # method called. ### : ?(BlockchainResolver | boolean) ### config: -> @log.debug "Loading resolver config" @ # Fill this in with code to load your config. # We recommend copying and editing the stuff from namecoin.coffee # # if "loaded successfully" # return this # else # return false # Connect to your blockchain. Return a Promise ### : Promise ### start: -> # Replace this with something useful. # 'cb' is of the form (err, args...) -> @startCheck (cb) => cb null # Close connection to your blockchain and do any other cleanup. Return a Promise. ### : Promise ### shutdown: -> # Replace this with something useful. # 'cb' is of the form (err, args...) -> @shutdownCheck (cb) => cb null # Do not modify the result template itself. Instead, set its .data property # in your resources.key function.See how other blockchains do it. ### : object ### resultTemplate: -> version: '0.0.1' header: datastore: @name data: {} # <== Your `resources.key` function *must* set this to a JSON # object that contains the output from your blockchain. standardizers: # These Functions convert data in the `data` object (see `resultTemplate` above) # into a standard form (primarily for processing by `dnsHandler`s. # See nxt.coffee for example of how to override. dnsInfo: (data) -> data.value # Value sould conform to Namecoin's d/ spec. ttlInfo: (data) -> 600 # 600 seconds for Namecoin (override if necessary) # Any valid resource for a blockchain should be set here. # All keys of this object must be functions of this form: # (property: string, operation: string, fmt: string, args: object, cb: function(err: object, result: object)) -> # # For more information, see: # https://github.com/okTurtles/openname-specifications/blob/resolvers/resolvers.md resources: key: (property, operation, fmt, args, cb) -> cb new Error "Not Implemented" # Example of what this function should do. # Uncomment and edit: #result = @resultTemplate() # myBlockchain.resolve path, (err, answer) => # if err # cb err # else # result.data = JSON.parse answer # cb null, result dnsHandler: # A dictionary of functions corresponding to traditional DNS. # You DO NOT need to copy these functions over unless you want to customize # them for your blockchain. By default, these are designed for Namecoin's d/ spec. # Value of `this` is bound to the class instance (i.e. you blockchain). # TODO: handle all the types specified in the specification! # https://wiki.namecoin.info/index.php?title=Domain_Name_Specification#Value_field # # TODO: handle other info outside of the specification! # - GNS support # - DNSSEC support? # Functions must be of this function signature ### (object, object, number, object, function): any ### A: (req, res, qIdx, data, cb) -> q = req.question[qIdx] @log.debug gLineInfo "A handler for #{@name}", {q:q} info = @standardizers.dnsInfo data ttl = @standardizers.ttlInfo data # According to NMC specification, specifying 'ns' # overrules 'ip' value, so check it here and resolve using # old-style DNS. if info.ns?.length > 0 # 1. Create a stream of nameserver IP addresses out of info.ns # 2. Send request to each of the servers, separated by `stackedDelay`. # On receiving the first answer from any of them, cancel all other # pending requests and respond to our client. # # TODO: handle ns = IPv6 addr! [nsIPs, nsCNAMEs] = [[],[]] for ip in info.ns (if net.isIP(ip) then nsIPs else nsCNAMEs).push(ip) if @method is gConsts.oldDNS.NO_OLD_DNS_EVER nsCNAMEs = [] # IMPORTANT! This DNSChain server might have a bi-directional relationship # with another local resolver for oldDNS (like PowerDNS). We # don't want malicious data in the blockchain to result in # queries being sent back and forth between them ad-infinitum! # Namecoin's specification states that these should only be # oldDNS TLDs anyway. Use 'delegate', 'import', or 'map' to # refer to other blockchain locations: # https://wiki.namecoin.info/index.php?title=Domain_Name_Specification#Value_field # WARNING! Because of this issue, it's probably best to not create a # bi-directional relationship like this between two resolvers. # It's far safer to tell DNSChain to use a different resolver # that won't re-ask DNSChain any questions. nsCNAMEs = _.reject nsCNAMEs, (ns)->/\.(bit|dns)$/.test ns # IPs like 127.0.0.1 are checked below against gConf.localhosts array if nsIPs.length == nsCNAMEs.length == 0 return cb NAME_RCODE.REFUSED # TODO: use these statically instead of creating new instances for each request # See: https://github.com/okTurtles/dnschain/issues/11 nsCNAME2IP = new ResolverStream name : 'nsCNAME2IP' # +'-'+(instanceNum++) stackedDelay: 100 stackedQuery = new ResolverStream name : 'stackedQuery' #+'-'+(instanceNum-1) stackedDelay: 1000 reqMaker : (nsIP) => dns2.Request question: q server: address: nsIP nsIPs = gES.merge(gES.readArray(nsIPs), gES.readArray(nsCNAMEs).pipe(nsCNAME2IP)) stopRequests = (code) => if code @log.warn gLineInfo("errors on all NS!"), {q:q, code:RCODE_NAME[code]} else @log.debug gLineInfo('ending async requests'), {q:q} rs.cancelRequests(true) for rs in [nsCNAME2IP, stackedQuery] cb code nsIPs.on 'data', (nsIP) => if _.find(gConf.localhosts, (ip)->S(nsIP).startsWith ip) # avoid the possible infinite-loop on some (perhaps poorly) configured systems @log.warn gLineInfo('dropping query, NMC NS ~= localhost!'), {q:q, nsIP:nsIP, info:info} else stackedQuery.write(nsIP) nsCNAME2IP.on 'failed', (err) => @log.warn gLineInfo('nsCNAME2IP error'), {error:err?.message, q:q} if nsCNAME2IP.errCount == info.ns.length stopRequests err.code ? NAME_RCODE.NOTFOUND stackedQuery.on 'failed', (err) => @log.warn gLineInfo('stackedQuery error'), {error:err?.message, q:q} if stackedQuery.errCount == info.ns.length stopRequests err.code ? NAME_RCODE.SERVFAIL stackedQuery.on 'answers', (answers) => @log.debug gLineInfo('stackedQuery answers'), {answers:answers} res.answer.push answers... stopRequests() else if info.ip # we have its IP! send reply to client # TODO: handle more info! send the rest of the # stuff in 'info', and all the IPs! info.ip = [info.ip] if typeof info.ip is 'string' # info.ip.forEach (a)-> res.answer.push gIP2type(q.name, ttl)(a) res.answer.push (info.ip.map gIP2type(q.name, ttl))... cb() else @log.warn gLineInfo('no useful data from nmc_show'), {q:q} cb NAME_RCODE.NOTFOUND # /end 'A' # TODO: implement this! AAAA: (req, res, qIdx, data, cb) -> @log.debug gLineInfo "AAAA handler for #{@name}" cb NAME_RCODE.NOTIMP TLSA: (req, res, qIdx, data, cb) -> q = req.question[qIdx] @log.debug gLineInfo "TLSA handler for #{@name}", {q:q} ttl = @standardizers.ttlInfo data len = res.answer.length if info = @standardizers.dnsInfo data res.answer.push gTls2tlsa(info.tls, ttl, q.name)... # check if any records were added if res.answer.length - len is 0 @log.warn gLineInfo('no TLSA found'), {q:q, data:data} cb NAME_RCODE.NOTFOUND else cb() ANY: -> @log.debug gLineInfo "ANY handler for #{@name}" # TODO: loop through dnsInfo and call approrpriate handlers # TODO: enable EDNS reply @dnsHandler.A.apply @, [].slice.call arguments