| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.net | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | ### | |
| 15 | ||
| 16 | INSTRUCTIONS: | |
| 17 | ||
| 18 | 1. Copy this file and rename it to your blockchain's name. | |
| 19 | The name you choose will also be your metaTLD (e.g. namecoin.coffee => namecoin.dns) | |
| 20 | 2. Rename the class (following the same naming convention as shown in | |
| 21 | `blockchains/namecoin.coffee`) and `extend BlockchainResolver` | |
| 22 | 3. Uncomment and edit the code as appropriate. | |
| 23 | Look at how the other blockchains do it (especially namecoin.coffee) | |
| 24 | ||
| 25 | REMEMBER: When in doubt, look at `blockchains/namecoin.coffee` ! | |
| 26 | ||
| 27 | ### | |
| 28 | ||
| 29 | 1 | module.exports = (dnschain) -> |
| 30 | # expose these into our namespace | |
| 31 | 24 | for k of dnschain.globals |
| 32 | 672 | eval "var #{k} = dnschain.globals.#{k};" |
| 33 | ||
| 34 | # Uncomment this: | |
| 35 | # BlockchainResolver = require('../blockchain.coffee')(dnschain) | |
| 36 | ||
| 37 | # For `dnsHandler:` | |
| 38 | 24 | ResolverStream = require('./resolver-stream')(dnschain) |
| 39 | 24 | NAME_RCODE = dns2.consts.NAME_TO_RCODE |
| 40 | 24 | RCODE_NAME = dns2.consts.RCODE_TO_NAME |
| 41 | ||
| 42 | # This class is partially annotated using flowtate | |
| 43 | # http://flowtype.org/blog/2015/02/20/Flow-Comments.html | |
| 44 | # https://github.com/jareware/flotate | |
| 45 | ### @flow ### | |
| 46 | ||
| 47 | # Uncomment the 'extends' comment below: | |
| 48 | 24 | class BlockchainResolver # extends BlockchainResolver |
| 49 | # Do you initialization in here. | |
| 50 | 24 | ### (dnschain: DNSChain): BlockchainResolver ### |
| 51 | constructor: (@dnschain) -> | |
| 52 | # Fill these in as appropriate: | |
| 53 | 0 | @log = gNewLogger 'YourChainName' |
| 54 | 0 | @tld = 'chn' # Your chain's TLD |
| 55 | 0 | @name = 'templatechain' # Your chain's name (lowercase, no spaces) |
| 56 | ||
| 57 | # Optionally specify how long Redis should cache entries in seconds | |
| 58 | # @cacheTTL = 600 # 0 == no cache, override here | |
| 59 | ||
| 60 | # Fills this object with `startCheck` and `shutdownCheck` methods | |
| 61 | 0 | gFillWithRunningChecks @ |
| 62 | ||
| 63 | # This is the default TTL value for all blockchains. | |
| 64 | # Override it above in the constructor for your blockchain. | |
| 65 | cacheTTL: gConf.get 'redis:blockchain:ttl' | |
| 66 | ||
| 67 | # Return `this` upon successful load, falsy otherwise | |
| 68 | # If you return `this`, this chain will be included in the parent | |
| 69 | # DNSChain instance's `@servers` list, and will have its `start:` | |
| 70 | # method called. | |
| 71 | ### : ?(BlockchainResolver | boolean) ### | |
| 72 | config: -> | |
| 73 | 6 | @log.debug "Loading resolver config" |
| 74 | 6 | @ |
| 75 | # Fill this in with code to load your config. | |
| 76 | # We recommend copying and editing the stuff from namecoin.coffee | |
| 77 | # | |
| 78 | # if "loaded successfully" | |
| 79 | # return this | |
| 80 | # else | |
| 81 | # return false | |
| 82 | ||
| 83 | # Connect to your blockchain. Return a Promise | |
| 84 | ### : Promise ### | |
| 85 | start: -> | |
| 86 | # Replace this with something useful. | |
| 87 | # 'cb' is of the form (err, args...) -> | |
| 88 | 12 | @startCheck (cb) => cb null |
| 89 | ||
| 90 | # Close connection to your blockchain and do any other cleanup. Return a Promise. | |
| 91 | ### : Promise ### | |
| 92 | shutdown: -> | |
| 93 | # Replace this with something useful. | |
| 94 | # 'cb' is of the form (err, args...) -> | |
| 95 | 24 | @shutdownCheck (cb) => cb null |
| 96 | ||
| 97 | ||
| 98 | # Do not modify the result template itself. Instead, set its .data property | |
| 99 | # in your resources.key function.See how other blockchains do it. | |
| 100 | ### : object ### | |
| 101 | resultTemplate: -> | |
| 102 | 13 | version: '0.0.1' |
| 103 | header: | |
| 104 | datastore: @name | |
| 105 | data: {} # <== Your `resources.key` function *must* set this to a JSON | |
| 106 | # object that contains the output from your blockchain. | |
| 107 | ||
| 108 | standardizers: | |
| 109 | # These Functions convert data in the `data` object (see `resultTemplate` above) | |
| 110 | # into a standard form (primarily for processing by `dnsHandler`s. | |
| 111 | # See nxt.coffee for example of how to override. | |
| 112 | 6 | dnsInfo: (data) -> data.value # Value sould conform to Namecoin's d/ spec. |
| 113 | 7 | ttlInfo: (data) -> 600 # 600 seconds for Namecoin (override if necessary) |
| 114 | ||
| 115 | # Any valid resource for a blockchain should be set here. | |
| 116 | # All keys of this object must be functions of this form: | |
| 117 | # (property: string, operation: string, fmt: string, args: object, cb: function(err: object, result: object)) -> | |
| 118 | # | |
| 119 | # For more information, see: | |
| 120 | # https://github.com/okTurtles/openname-specifications/blob/resolvers/resolvers.md | |
| 121 | resources: | |
| 122 | key: (property, operation, fmt, args, cb) -> | |
| 123 | 0 | cb new Error "Not Implemented" |
| 124 | # Example of what this function should do. | |
| 125 | # Uncomment and edit: | |
| 126 | #result = @resultTemplate() | |
| 127 | ||
| 128 | # myBlockchain.resolve path, (err, answer) => | |
| 129 | # if err | |
| 130 | # cb err | |
| 131 | # else | |
| 132 | # result.data = JSON.parse answer | |
| 133 | # cb null, result | |
| 134 | ||
| 135 | dnsHandler: # A dictionary of functions corresponding to traditional DNS. | |
| 136 | # You DO NOT need to copy these functions over unless you want to customize | |
| 137 | # them for your blockchain. By default, these are designed for Namecoin's d/ spec. | |
| 138 | # Value of `this` is bound to the class instance (i.e. you blockchain). | |
| 139 | # TODO: handle all the types specified in the specification! | |
| 140 | # https://wiki.namecoin.info/index.php?title=Domain_Name_Specification#Value_field | |
| 141 | # | |
| 142 | # TODO: handle other info outside of the specification! | |
| 143 | # - GNS support | |
| 144 | # - DNSSEC support? | |
| 145 | # Functions must be of this function signature | |
| 146 | ### (object, object, number, object, function): any ### | |
| 147 | A: (req, res, qIdx, data, cb) -> | |
| 148 | 7 | q = req.question[qIdx] |
| 149 | 7 | @log.debug gLineInfo "A handler for #{@name}", {q:q} |
| 150 | 7 | info = @standardizers.dnsInfo data |
| 151 | 7 | ttl = @standardizers.ttlInfo data |
| 152 | ||
| 153 | # According to NMC specification, specifying 'ns' | |
| 154 | # overrules 'ip' value, so check it here and resolve using | |
| 155 | # old-style DNS. | |
| 156 | 7 | if info.ns?.length > 0 |
| 157 | # 1. Create a stream of nameserver IP addresses out of info.ns | |
| 158 | # 2. Send request to each of the servers, separated by `stackedDelay`. | |
| 159 | # On receiving the first answer from any of them, cancel all other | |
| 160 | # pending requests and respond to our client. | |
| 161 | # | |
| 162 | # TODO: handle ns = IPv6 addr! | |
| 163 | 1 | [nsIPs, nsCNAMEs] = [[],[]] |
| 164 | ||
| 165 | 1 | for ip in info.ns |
| 166 | 2 | (if net.isIP(ip) then nsIPs else nsCNAMEs).push(ip) |
| 167 | ||
| 168 | 1 | if @method is gConsts.oldDNS.NO_OLD_DNS_EVER |
| 169 | 0 | nsCNAMEs = [] |
| 170 | ||
| 171 | # IMPORTANT! This DNSChain server might have a bi-directional relationship | |
| 172 | # with another local resolver for oldDNS (like PowerDNS). We | |
| 173 | # don't want malicious data in the blockchain to result in | |
| 174 | # queries being sent back and forth between them ad-infinitum! | |
| 175 | # Namecoin's specification states that these should only be | |
| 176 | # oldDNS TLDs anyway. Use 'delegate', 'import', or 'map' to | |
| 177 | # refer to other blockchain locations: | |
| 178 | # https://wiki.namecoin.info/index.php?title=Domain_Name_Specification#Value_field | |
| 179 | # WARNING! Because of this issue, it's probably best to not create a | |
| 180 | # bi-directional relationship like this between two resolvers. | |
| 181 | # It's far safer to tell DNSChain to use a different resolver | |
| 182 | # that won't re-ask DNSChain any questions. | |
| 183 | 1 | nsCNAMEs = _.reject nsCNAMEs, (ns)->/\.(bit|dns)$/.test ns |
| 184 | # IPs like 127.0.0.1 are checked below against gConf.localhosts array | |
| 185 | ||
| 186 | 1 | if nsIPs.length == nsCNAMEs.length == 0 |
| 187 | 0 | return cb NAME_RCODE.REFUSED |
| 188 | ||
| 189 | # TODO: use these statically instead of creating new instances for each request | |
| 190 | # See: https://github.com/okTurtles/dnschain/issues/11 | |
| 191 | 1 | nsCNAME2IP = new ResolverStream |
| 192 | name : 'nsCNAME2IP' # +'-'+(instanceNum++) | |
| 193 | stackedDelay: 100 | |
| 194 | 1 | stackedQuery = new ResolverStream |
| 195 | name : 'stackedQuery' #+'-'+(instanceNum-1) | |
| 196 | stackedDelay: 1000 | |
| 197 | reqMaker : (nsIP) => | |
| 198 | 1 | dns2.Request |
| 199 | question: q | |
| 200 | server: address: nsIP | |
| 201 | ||
| 202 | 1 | nsIPs = gES.merge(gES.readArray(nsIPs), gES.readArray(nsCNAMEs).pipe(nsCNAME2IP)) |
| 203 | ||
| 204 | 1 | stopRequests = (code) => |
| 205 | 1 | if code |
| 206 | 0 | @log.warn gLineInfo("errors on all NS!"), {q:q, code:RCODE_NAME[code]} |
| 207 | else | |
| 208 | 1 | @log.debug gLineInfo('ending async requests'), {q:q} |
| 209 | 1 | rs.cancelRequests(true) for rs in [nsCNAME2IP, stackedQuery] |
| 210 | 1 | cb code |
| 211 | ||
| 212 | 1 | nsIPs.on 'data', (nsIP) => |
| 213 | 2 | if _.find(gConf.localhosts, (ip)->S(nsIP).startsWith ip) |
| 214 | # avoid the possible infinite-loop on some (perhaps poorly) configured systems | |
| 215 | 0 | @log.warn gLineInfo('dropping query, NMC NS ~= localhost!'), {q:q, nsIP:nsIP, info:info} |
| 216 | else | |
| 217 | 2 | stackedQuery.write(nsIP) |
| 218 | ||
| 219 | 1 | nsCNAME2IP.on 'failed', (err) => |
| 220 | 0 | @log.warn gLineInfo('nsCNAME2IP error'), {error:err?.message, q:q} |
| 221 | 0 | if nsCNAME2IP.errCount == info.ns.length |
| 222 | 0 | stopRequests err.code ? NAME_RCODE.NOTFOUND |
| 223 | ||
| 224 | 1 | stackedQuery.on 'failed', (err) => |
| 225 | 0 | @log.warn gLineInfo('stackedQuery error'), {error:err?.message, q:q} |
| 226 | 0 | if stackedQuery.errCount == info.ns.length |
| 227 | 0 | stopRequests err.code ? NAME_RCODE.SERVFAIL |
| 228 | ||
| 229 | 1 | stackedQuery.on 'answers', (answers) => |
| 230 | 1 | @log.debug gLineInfo('stackedQuery answers'), {answers:answers} |
| 231 | 1 | res.answer.push answers... |
| 232 | 1 | stopRequests() |
| 233 | ||
| 234 | 6 | else if info.ip |
| 235 | # we have its IP! send reply to client | |
| 236 | # TODO: handle more info! send the rest of the | |
| 237 | # stuff in 'info', and all the IPs! | |
| 238 | 6 | info.ip = [info.ip] if typeof info.ip is 'string' |
| 239 | # info.ip.forEach (a)-> res.answer.push gIP2type(q.name, ttl)(a) | |
| 240 | 6 | res.answer.push (info.ip.map gIP2type(q.name, ttl))... |
| 241 | 6 | cb() |
| 242 | else | |
| 243 | 0 | @log.warn gLineInfo('no useful data from nmc_show'), {q:q} |
| 244 | 0 | cb NAME_RCODE.NOTFOUND |
| 245 | # /end 'A' | |
| 246 | ||
| 247 | # TODO: implement this! | |
| 248 | AAAA: (req, res, qIdx, data, cb) -> | |
| 249 | 0 | @log.debug gLineInfo "AAAA handler for #{@name}" |
| 250 | 0 | cb NAME_RCODE.NOTIMP |
| 251 | ||
| 252 | TLSA: (req, res, qIdx, data, cb) -> | |
| 253 | 0 | q = req.question[qIdx] |
| 254 | 0 | @log.debug gLineInfo "TLSA handler for #{@name}", {q:q} |
| 255 | 0 | ttl = @standardizers.ttlInfo data |
| 256 | 0 | len = res.answer.length |
| 257 | 0 | if info = @standardizers.dnsInfo data |
| 258 | 0 | res.answer.push gTls2tlsa(info.tls, ttl, q.name)... |
| 259 | # check if any records were added | |
| 260 | 0 | if res.answer.length - len is 0 |
| 261 | 0 | @log.warn gLineInfo('no TLSA found'), {q:q, data:data} |
| 262 | 0 | cb NAME_RCODE.NOTFOUND |
| 263 | else | |
| 264 | 0 | cb() |
| 265 | ||
| 266 | ANY: -> | |
| 267 | 0 | @log.debug gLineInfo "ANY handler for #{@name}" |
| 268 | # TODO: loop through dnsInfo and call approrpriate handlers | |
| 269 | # TODO: enable EDNS reply | |
| 270 | 0 | @dnsHandler.A.apply @, [].slice.call arguments |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.net | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | 1 | Packet = require('native-dns-packet') |
| 15 | ||
| 16 | 1 | module.exports = (dnschain) -> |
| 17 | # expose these into our namespace | |
| 18 | 6 | for k of dnschain.globals |
| 19 | 168 | eval "var #{k} = dnschain.globals.#{k};" |
| 20 | ||
| 21 | 6 | BlockchainResolver = require('../blockchain.coffee')(dnschain) |
| 22 | ||
| 23 | 6 | QTYPE_NAME = dns2.consts.QTYPE_TO_NAME |
| 24 | 6 | NAME_QTYPE = dns2.consts.NAME_TO_QTYPE |
| 25 | 6 | NAME_RCODE = dns2.consts.NAME_TO_RCODE |
| 26 | 6 | RCODE_NAME = dns2.consts.RCODE_TO_NAME |
| 27 | ||
| 28 | 6 | class IcannResolver extends BlockchainResolver |
| 29 | 6 | constructor: (@dnschain) -> |
| 30 | 6 | @log = gNewLogger 'ICANN' |
| 31 | 6 | @name = 'icann' |
| 32 | 6 | gFillWithRunningChecks @ |
| 33 | ||
| 34 | resources: | |
| 35 | key: (property, operation, fmt, args, cb) -> | |
| 36 | 3 | req = new Packet() |
| 37 | 3 | result = @resultTemplate() |
| 38 | 3 | @log.debug gLineInfo("#{@name} resolve"), {property:property, args:args} |
| 39 | 3 | req.question.push dns2.Question {name: property, type: args.type if args?.type? and _.has NAME_QTYPE,args.type} |
| 40 | 3 | @dnschain.dns.oldDNSLookup req, (code, packet) => |
| 41 | 3 | if code |
| 42 | 0 | cb {code:code, name:RCODE_NAME[code]} |
| 43 | else | |
| 44 | 3 | result.data = packet |
| 45 | 3 | cb null, result |
| 46 |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.net | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | 1 | module.exports = (dnschain) -> |
| 15 | # expose these into our namespace | |
| 16 | 6 | for k of dnschain.globals |
| 17 | 168 | eval "var #{k} = dnschain.globals.#{k};" |
| 18 | ||
| 19 | 6 | BlockchainResolver = require('../blockchain.coffee')(dnschain) |
| 20 | ||
| 21 | 6 | class KeyidResolver extends BlockchainResolver |
| 22 | 6 | constructor: (@dnschain) -> |
| 23 | 6 | @log = gNewLogger 'KeyID' |
| 24 | 6 | @tld = 'p2p' |
| 25 | 6 | @name = 'keyid' |
| 26 | 6 | gFillWithRunningChecks @ |
| 27 | ||
| 28 | config: -> | |
| 29 | 6 | @log.debug "Loading #{@name} resolver" |
| 30 | ||
| 31 | 6 | return unless gConf.add @name, _.map(_.filter([ |
| 32 | [process.env.APPDATA, 'KeyID', 'config.json'], | |
| 33 | [process.env.HOME, '.KeyID', 'config.json'], | |
| 34 | [process.env.HOME, 'Library', 'Application Support', 'KeyID', 'config.json']] | |
| 35 | 18 | , (x) -> !!x[0]) |
| 36 | 12 | , (x) -> path.join x...) |
| 37 | ||
| 38 | 6 | @params = _.transform ["httpd_endpoint", "rpc_user", "rpc_password"], (o,v) => |
| 39 | 18 | o[v] = gConf.chains[@name].get 'rpc:'+v |
| 40 | , {} | |
| 41 | ||
| 42 | 6 | unless _(@params).values().every() |
| 43 | 0 | missing = _.transform @params, ((o,v,k)->if !v then o.push 'rpc:'+k), [] |
| 44 | 0 | @log.info "Disabled. Missing params:", missing |
| 45 | 0 | return |
| 46 | ||
| 47 | 6 | [@params.host, @params.port] = @params.httpd_endpoint.split ':' |
| 48 | 6 | @params.port = parseInt @params.port |
| 49 | 6 | gConf.chains[@name].set 'host', @params.host |
| 50 | 6 | @ |
| 51 | ||
| 52 | start: -> | |
| 53 | 6 | @startCheck (success) => |
| 54 | 6 | params = _.at @params, ["port", "host", "rpc_user", "rpc_password"] |
| 55 | # TODO: $create doesn't actually connect. you need to open a raw socket | |
| 56 | # or an http socket and see if that works before declaring it works | |
| 57 | 6 | @peer = rpc.Client.$create(params...) or gErr "rpc create" |
| 58 | 6 | @log.info "rpc to bitshares_client on: %s:%d/rpc", @params.host, @params.port |
| 59 | 6 | success() |
| 60 | ||
| 61 | resources: | |
| 62 | key: (property, operation, fmt, args, cb) -> | |
| 63 | 0 | result = @resultTemplate() |
| 64 | 0 | @log.debug gLineInfo("#{@name} resolve"), {property:property} |
| 65 | 0 | @peer.call 'dotp2p_show', [property], property:'/rpc', (err, ans) -> |
| 66 | 0 | return cb(err) if err |
| 67 | 0 | if _.isString ans |
| 68 | 0 | try |
| 69 | 0 | result.data = JSON.parse ans |
| 70 | catch e | |
| 71 | 0 | @log.error glineInfo(e.message) |
| 72 | 0 | return cb e |
| 73 | 0 | else if not _.isObject ans |
| 74 | 0 | @log.warn gLineInfo('type not string or object!'), {json: ans, type: typeof(ans)} |
| 75 | 0 | result.data = {} |
| 76 | 0 | cb null, result |
| 77 |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.net | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | 1 | module.exports = (dnschain) -> |
| 15 | # expose these into our namespace | |
| 16 | 6 | for k of dnschain.globals |
| 17 | 168 | eval "var #{k} = dnschain.globals.#{k};" |
| 18 | ||
| 19 | 6 | BlockchainResolver = require('../blockchain.coffee')(dnschain) |
| 20 | ||
| 21 | # https://wiki.namecoin.info/index.php?title=Domain_Name_Specification#Regular_Expression | |
| 22 | 6 | VALID_NMC_DOMAINS = /^[a-z]([a-z0-9-]{0,62}[a-z0-9])?$/ |
| 23 | ||
| 24 | 6 | class NamecoinResolver extends BlockchainResolver |
| 25 | 6 | constructor: (@dnschain) -> |
| 26 | 6 | @log = gNewLogger 'NMC' |
| 27 | 6 | @name = 'namecoin' |
| 28 | 6 | @tld = 'bit' |
| 29 | 6 | gFillWithRunningChecks @ |
| 30 | ||
| 31 | config: -> | |
| 32 | 6 | @log.debug "Loading #{@name} resolver" |
| 33 | ||
| 34 | 6 | return unless gConf.add @name, _.map(_.filter([ |
| 35 | [process.env.APPDATA, 'Namecoin', 'namecoin.conf'] | |
| 36 | [process.env.HOME, '.namecoin', 'namecoin.conf'] | |
| 37 | [process.env.HOME, 'Library', 'Application Support', 'Namecoin', 'namecoin.conf'] | |
| 38 | ['/etc/namecoin/namecoin.conf']] | |
| 39 | 24 | , (x) -> !!x[0]) |
| 40 | 18 | , (x) -> path.join x...), 'INI' |
| 41 | ||
| 42 | 6 | @params = _.transform ["port", "connect", "user", "password"], (o,v) => |
| 43 | 24 | o[v] = gConf.chains[@name].get 'rpc'+v |
| 44 | , {} | |
| 45 | 6 | @params.connect ?= "127.0.0.1" |
| 46 | ||
| 47 | 6 | unless _(@params).values().every() |
| 48 | 0 | missing = _.transform @params, ((o,v,k)->if !v then o.push 'rpc'+k), [] |
| 49 | 0 | @log.info "Disabled. Missing params:", missing |
| 50 | 0 | return |
| 51 | ||
| 52 | 6 | gConf.chains[@name].set 'host', @params.connect |
| 53 | 6 | @ |
| 54 | ||
| 55 | start: -> | |
| 56 | 6 | @startCheck (cb) => |
| 57 | 6 | params = _.at @params, ["port", "connect", "user", "password"] |
| 58 | # TODO: $create doesn't actually connect. you need to open a raw socket | |
| 59 | # or an http socket and see if that works before declaring it works | |
| 60 | 6 | @peer = rpc.Client.$create(params...) or gErr "rpc create" |
| 61 | 6 | @log.info "rpc to namecoind on: %s:%d", @params.connect, @params.port |
| 62 | 6 | cb null |
| 63 | ||
| 64 | resources: | |
| 65 | key: (property, operation, fmt, args, cb) -> | |
| 66 | 8 | if not operation? |
| 67 | 8 | result = @resultTemplate() |
| 68 | 8 | if S(property).endsWith(".#{@tld}") # namecoinize Domain |
| 69 | 5 | property = S(property).chompRight(".#{@tld}").s |
| 70 | 5 | if (dotIdx = property.lastIndexOf('.')) != -1 |
| 71 | 0 | property = property.slice(dotIdx+1) #rm subdomain |
| 72 | 5 | if not VALID_NMC_DOMAINS.test property |
| 73 | 0 | err = new Error "Invalid Domain: #{property}" |
| 74 | 0 | err.httpCode = 400 |
| 75 | 0 | return cb(err) |
| 76 | 5 | property = 'd/' + property |
| 77 | 8 | @log.debug gLineInfo("#{@name} resolve"), {property:property} |
| 78 | 8 | @peer.call 'name_show', [property], (err, ans) => |
| 79 | 8 | if err |
| 80 | 0 | @log.error gLineInfo('name_show failed'), {property:property, errMsg:err.message} |
| 81 | 0 | cb err |
| 82 | else | |
| 83 | 8 | try |
| 84 | 8 | ans.value = JSON.parse ans.value |
| 85 | catch e | |
| 86 | 1 | @log.debug gLineInfo('bad JSON'), {value:ans.value, errMsg:e.message} |
| 87 | 8 | result.data = ans |
| 88 | 8 | cb null, result |
| 89 | else | |
| 90 | 0 | @log.debug gLineInfo "unsupported op #{operation} for prop: #{property}" |
| 91 | 0 | cb new Error "Not Implemented" |
| 92 |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.net | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | 1 | request = require 'superagent' |
| 15 | ||
| 16 | 1 | module.exports = (dnschain) -> |
| 17 | # expose these into our namespace | |
| 18 | 6 | for k of dnschain.globals |
| 19 | 168 | eval "var #{k} = dnschain.globals.#{k};" |
| 20 | ||
| 21 | 6 | BlockchainResolver = require('../blockchain.coffee')(dnschain) |
| 22 | 6 | ResolverStream = require('../resolver-stream')(dnschain) |
| 23 | ||
| 24 | 6 | getAsync = Promise.promisify request.get |
| 25 | ||
| 26 | 6 | QTYPE_NAME = dns2.consts.QTYPE_TO_NAME |
| 27 | 6 | NAME_QTYPE = dns2.consts.NAME_TO_QTYPE |
| 28 | 6 | NAME_RCODE = dns2.consts.NAME_TO_RCODE |
| 29 | 6 | RCODE_NAME = dns2.consts.RCODE_TO_NAME |
| 30 | 6 | BLOCKS2SEC = 60 |
| 31 | ||
| 32 | 6 | class NxtResolver extends BlockchainResolver |
| 33 | 6 | constructor: (@dnschain) -> |
| 34 | 6 | @log = gNewLogger 'NXT' |
| 35 | 6 | @name = 'nxt' |
| 36 | 6 | @tld = 'nxt' |
| 37 | 6 | @standardizers.dnsInfo = (data) -> data.aliasURI |
| 38 | 6 | gFillWithRunningChecks @ |
| 39 | ||
| 40 | config: -> | |
| 41 | 6 | @log.debug "Loading #{@name} resolver" |
| 42 | ||
| 43 | 6 | @params = _.transform ["port", "connect"], (o,v) => |
| 44 | 12 | o[v] = gConf.get 'nxt:'+v |
| 45 | , {} | |
| 46 | ||
| 47 | 6 | unless _(@params).values().every() |
| 48 | 0 | missing = _.transform @params, ((o,v,k)->if !v then o.push 'nxt:'+k), [] |
| 49 | 0 | @log.info "Disabled. Missing params:", missing |
| 50 | 0 | return |
| 51 | ||
| 52 | 6 | @peer = "http://#{@params.connect}:#{@params.port}/nxt?requestType=getAlias&aliasName=" |
| 53 | ||
| 54 | 6 | @log.info "Nxt API on:", @params |
| 55 | 6 | @ |
| 56 | ||
| 57 | resources: | |
| 58 | key: (property, operation, fmt, args, cb) -> | |
| 59 | 2 | result = @resultTemplate() |
| 60 | ||
| 61 | 2 | if S(property).endsWith(".#{@tld}") |
| 62 | 1 | property = S(property).chompRight(".#{@tld}").s |
| 63 | 1 | if (dotIdx = property.lastIndexOf('.')) != -1 |
| 64 | 0 | property = property.slice(dotIdx+1) #rm subdomain |
| 65 | ||
| 66 | 2 | @log.debug gLineInfo("#{@name} resolve"), {property:property} |
| 67 | ||
| 68 | 2 | getAsync(@peer + encodeURIComponent(property)).then (res) => |
| 69 | 2 | try |
| 70 | 2 | json = JSON.parse res.text |
| 71 | 2 | if json.aliasURI? |
| 72 | 2 | try json.aliasURI = JSON.parse json.aliasURI |
| 73 | 2 | result.data = json |
| 74 | 2 | @log.debug gLineInfo('resolved OK!'), {result:result} |
| 75 | 2 | cb null, result |
| 76 | catch e | |
| 77 | 0 | @log.warn gLineInfo('server did not respond with valid json!'), {err:e.message, url:res.request.url, response:res.text} |
| 78 | 0 | cb e |
| 79 | .catch (e) => | |
| 80 | 0 | @log.error gLineInfo('error contacting NXT server!'), {err:e.message} |
| 81 | 0 | cb e |
| 82 |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.net | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | 1 | if process.env.TEST_DNSCHAIN and not process.env.TEST_REAL_REDIS |
| 15 | 1 | redis = require 'fakeredis' |
| 16 | else | |
| 17 | 0 | redis = require 'redis' |
| 18 | ||
| 19 | 1 | module.exports = (dnschain) -> |
| 20 | # expose these into our namespace | |
| 21 | 1 | for k of dnschain.globals |
| 22 | 28 | eval "var #{k} = dnschain.globals.#{k};" |
| 23 | ||
| 24 | 1 | class ResolverCache |
| 25 | 1 | constructor: (@dnschain) -> |
| 26 | 6 | @log = gNewLogger 'Redis' |
| 27 | 6 | gFillWithRunningChecks @ |
| 28 | ||
| 29 | start: -> | |
| 30 | 6 | @startCheck (cb) => |
| 31 | 6 | @blockchainEnabled = gConf.get 'redis:blockchain:enabled' |
| 32 | 6 | @oldDNSEnabled = gConf.get 'redis:oldDNS:enabled' |
| 33 | 6 | @oldDNSTTL = gConf.get 'redis:oldDNS:ttl' |
| 34 | ||
| 35 | 6 | if !@blockchainEnabled and !@oldDNSEnabled |
| 36 | 5 | @log.info "cache not enabled".bold.yellow |
| 37 | else | |
| 38 | 1 | @log.info "cache is enabled".bold |
| 39 | 1 | [host,port] = gConf.get('redis:socket').split(':') |
| 40 | 1 | @cache = |
| 41 | if port? | |
| 42 | 1 | if isNaN(portNum = parseInt port) |
| 43 | 0 | gErr "Redis port is NaN: #{port}" |
| 44 | 1 | redis.createClient portNum, host |
| 45 | else | |
| 46 | 0 | redis.createClient host |
| 47 | 1 | @cache.on 'error', (err) => |
| 48 | 0 | @log.error "cache errored: #{err.message}", err |
| 49 | 0 | @shutdown() |
| 50 | 6 | cb() |
| 51 | ||
| 52 | shutdown: -> | |
| 53 | 6 | @shutdownCheck (cb) => |
| 54 | 6 | @cache?.end() |
| 55 | 6 | cb() |
| 56 | ||
| 57 | get: (key, valueRetriever, valueDoer) -> | |
| 58 | 50 | @cache.get key, (err, result) => |
| 59 | 50 | if err |
| 60 | 0 | @log.error gLineInfo('caching error'), {err: err} |
| 61 | 50 | if result? |
| 62 | 25 | @log.debug gLineInfo('resolved from cache'), {key: key} |
| 63 | 25 | valueDoer null, key, JSON.parse result |
| 64 | 25 | return |
| 65 | 25 | valueRetriever key, (err2, ttl, value) => |
| 66 | 25 | @cache.setex(key, ttl, JSON.stringify value) if not err2 |
| 67 | 25 | valueDoer err, key, value |
| 68 | ||
| 69 | resolveResource: (datastore, requestFn, serialization, cb) -> | |
| 70 | 14 | if @blockchainEnabled and datastore.cacheTTL? |
| 71 | 2 | retriever = (key, callback) => |
| 72 | 1 | requestFn (err, result) => |
| 73 | 1 | callback err, datastore.cacheTTL, result |
| 74 | 2 | doer = (err, key, result) => |
| 75 | 2 | cb err, result |
| 76 | 2 | @get serialization, retriever, doer |
| 77 | else | |
| 78 | 12 | requestFn cb |
| 79 | ||
| 80 | resolveOldDNS: (req, cb) -> | |
| 81 | 104 | if @oldDNSEnabled |
| 82 | 48 | q = req.question[0] |
| 83 | 48 | retriever = (key, callback) => |
| 84 | 24 | f = (err, result) => |
| 85 | 24 | ttl = |
| 86 | if result.answer[0]?.ttl? | |
| 87 | 23 | Math.min result.answer[0].ttl, @oldDNSTTL |
| 88 | else | |
| 89 | 1 | @oldDNSTTL |
| 90 | 24 | callback err, ttl, result |
| 91 | 24 | @dnschain.dns.oldDNSLookup req, f |
| 92 | 48 | doer = (err, key, result) => |
| 93 | 48 | cb err, result |
| 94 | 48 | @get "oldDNS:#{q.name}:#{q.type}", retriever, doer |
| 95 | else | |
| 96 | 56 | @dnschain.dns.oldDNSLookup req, cb |
| 97 |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.net | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | ### | |
| 15 | All configuration options can be overwritten using command line args | |
| 16 | and/or environment variables. | |
| 17 | ||
| 18 | Below you will see the available options and their defaults. | |
| 19 | ||
| 20 | - The top-level options map to sections in the config file. | |
| 21 | I.e. `dns` and `log` designate the sections `[dns]` and `[log]` | |
| 22 | - All non top-level options are respresented via dot notation. | |
| 23 | I.e. to set the `oldDNS` `address`, you'd do: | |
| 24 | ||
| 25 | [dns] | |
| 26 | oldDNS.address = 8.8.4.4 | |
| 27 | ||
| 28 | - For each blockchain, you can specify its configuration file | |
| 29 | by specifying the blockchain name as a section, and then | |
| 30 | setting the config variable. | |
| 31 | Example: | |
| 32 | ||
| 33 | [namecoin] | |
| 34 | config = /home/namecoin/.namecoin/namecoin.conf | |
| 35 | ||
| 36 | See also: | |
| 37 | <https://github.com/okTurtles/dnschain/blob/master/docs/How-do-I-run-my-own.md#Configuration> | |
| 38 | ### | |
| 39 | ||
| 40 | 1 | nconf = require 'nconf' |
| 41 | 1 | props = require 'properties' |
| 42 | 1 | fs = require 'fs' |
| 43 | 1 | tty = require 'tty' |
| 44 | ||
| 45 | 1 | module.exports = (dnschain) -> |
| 46 | # expose these into our namespace | |
| 47 | 1 | for k of dnschain.globals |
| 48 | 26 | eval "var #{k} = dnschain.globals.#{k};" |
| 49 | ||
| 50 | # TODO: add path to our private key for signing answers | |
| 51 | 1 | amRoot = process.getuid() is 0 |
| 52 | ||
| 53 | # ================================================= | |
| 54 | # BEGIN DNSCHAIN CONFIGURATION OPTIONS AND DEFAULTS | |
| 55 | # ================================================= | |
| 56 | 1 | defaults = { |
| 57 | log: | |
| 58 | 0 | level: if process.env.DNS_EXAMPLE then 'debug' else 'info' |
| 59 | colors: true | |
| 60 | pretty: tty.isatty process.stdout | |
| 61 | timestamp: tty.isatty process.stdout | |
| 62 | dns: | |
| 63 | 0 | port: if amRoot then 53 else 5333 |
| 64 | host: '0.0.0.0' # what we bind to | |
| 65 | externalIP: gExternalIP() # Advertised IP for .dns metaTLD (ex: namecoin.dns) | |
| 66 | oldDNSMethod: 'NATIVE_DNS' # see 'globals.coffee' for possible values | |
| 67 | oldDNS: | |
| 68 | address: '8.8.8.8' # Google (we recommend running PowerDNS yourself and sending it there) | |
| 69 | port: 53 | |
| 70 | type: 'udp' | |
| 71 | http: | |
| 72 | 0 | port: if amRoot then 80 else 8088 # Standard HTTP port |
| 73 | 0 | tlsPort: if amRoot then 443 else 4443 # Standard HTTPS port |
| 74 | 1 | tlsKey: "#{process.env.HOME}/.dnschain/key.pem" |
| 75 | 1 | tlsCert: "#{process.env.HOME}/.dnschain/cert.pem" |
| 76 | internalTLSPort: 2500 # Not accessible from the internet, used internally only | |
| 77 | internalAdminPort: 3000 # Not accessible from the internet, used internally only | |
| 78 | host: '0.0.0.0' # what we bind to. 0.0.0.0 for the whole internet | |
| 79 | redis: | |
| 80 | socket: '127.0.0.1:6379' # or UNIX domain socket path | |
| 81 | oldDNS: | |
| 82 | enabled: false | |
| 83 | ttl: 600 # Maximum time to keep DNS records in cache, regardless of TTL | |
| 84 | blockchain: | |
| 85 | enabled: false | |
| 86 | ttl: 600 | |
| 87 | ||
| 88 | unblock: # The options in this section are only for when Unblock is enabled. | |
| 89 | enabled: false | |
| 90 | acceptApiCallsTo: ["localhost"] # Add your public facing domain here if you want to accept calls to the RESTful API when Unblock is enabled. | |
| 91 | routeDomains: { # If traffic coming in on the tlsPort needs to be redirected to another application on the server then add it here | |
| 92 | # Example: "mywebsite.com" : 9000 # This tells the server to send traffic meant to "mywebsite.com" to port 9000. It'll still be encrypted when it reaches port 9000 | |
| 93 | } | |
| 94 | ||
| 95 | # WARNING: Do not change these settings unless you know exactly what you're doing. | |
| 96 | # Read the source code, read the Bottleneck docs, | |
| 97 | # make sure you understand how it might make your server complicit in DNS Amplification Attacks and your server might be taken down as a result. | |
| 98 | rateLimiting: | |
| 99 | dns: | |
| 100 | maxConcurrent: 1 | |
| 101 | minTime: 200 | |
| 102 | highWater: 2 | |
| 103 | strategy: Bottleneck.strategy.BLOCK | |
| 104 | penalty: 7000 | |
| 105 | http: | |
| 106 | maxConcurrent: 2 | |
| 107 | minTime: 150 | |
| 108 | highWater: 10 | |
| 109 | strategy: Bottleneck.strategy.OVERFLOW | |
| 110 | https: | |
| 111 | maxConcurrent: 2 | |
| 112 | minTime: 150 | |
| 113 | highWater: 10 | |
| 114 | strategy: Bottleneck.strategy.OVERFLOW | |
| 115 | } | |
| 116 | # =============================================== | |
| 117 | # END DNSCHAIN CONFIGURATION OPTIONS AND DEFAULTS | |
| 118 | # =============================================== | |
| 119 | ||
| 120 | 1 | fileFormatOpts = |
| 121 | comments: ['#', ';'] | |
| 122 | sections: true | |
| 123 | namespaces: true | |
| 124 | ||
| 125 | 1 | props.parse = _.partialRight props.parse, fileFormatOpts |
| 126 | 1 | props.stringify = _.partialRight props.stringify, fileFormatOpts |
| 127 | ||
| 128 | 1 | confTypes = |
| 129 | INI: props | |
| 130 | JSON: JSON | |
| 131 | ||
| 132 | # load our config | |
| 133 | 1 | nconf.argv().env('__') |
| 134 | 1 | dnscConfLocs = [ |
| 135 | 1 | "#{process.env.HOME}/.dnschain/dnschain.conf", # the default |
| 136 | 1 | "#{process.env.HOME}/.dnschain.conf", |
| 137 | "/etc/dnschain/dnschain.conf" | |
| 138 | ] | |
| 139 | 1 | dnscConf = _.find dnscConfLocs, (x) -> fs.existsSync x |
| 140 | ||
| 141 | 1 | if process.env.HOME and not fs.existsSync "#{process.env.HOME}/.dnschain" |
| 142 | # create this folder on UNIX based systems so that https.coffee | |
| 143 | # can autogen the private/public key if they don't exist | |
| 144 | 0 | fs.mkdirSync "#{process.env.HOME}/.dnschain", 0o710 |
| 145 | ||
| 146 | # we can't access `dnschain.globals.gLogger` here because it hasn't | |
| 147 | # been defined yet unfortunately. | |
| 148 | 1 | if dnscConf |
| 149 | 1 | console.info "[INFO] Loading DNSChain config from: #{dnscConf}" |
| 150 | 1 | nconf.file 'user', {file: dnscConf, format: props} |
| 151 | else | |
| 152 | 0 | console.warn "[WARN] No DNSChain configuration file found. Using defaults!".bold.yellow |
| 153 | 0 | nconf.file 'user', {file: dnscConfLocs[0], format: props} |
| 154 | ||
| 155 | 1 | config = |
| 156 | 639 | get: (key, store="dnschain") -> config.chains[store].get key |
| 157 | 5 | set: (key, value, store="dnschain") -> config.chains[store].set key, value |
| 158 | chains: | |
| 159 | dnschain: nconf.defaults defaults | |
| 160 | add: (name, paths, type) -> | |
| 161 | 12 | log = dnschain.globals.gLogger |
| 162 | 12 | gLineInfo = dnschain.globals.gLineInfo |
| 163 | 12 | if config.chains[name]? |
| 164 | 10 | log.warn gLineInfo "Not overwriting existing #{name} configuration" |
| 165 | 10 | return config.chains[name] |
| 166 | ||
| 167 | 2 | paths = [paths] unless Array.isArray(paths) |
| 168 | 2 | type = confTypes[type] || confTypes['JSON'] |
| 169 | ||
| 170 | # if dnschain's config specifies this chain's config path, prioritize it | |
| 171 | # fixes: https://github.com/okTurtles/dnschain/issues/60 | |
| 172 | 2 | customConfigPath = config.chains.dnschain.get "#{name}:config" |
| 173 | 2 | if customConfigPath? |
| 174 | 0 | paths = [customConfigPath] |
| 175 | 0 | log.info "custom config path for #{name}: #{paths[0]}" |
| 176 | ||
| 177 | 2 | confFile = _.find paths, (x) -> fs.existsSync x |
| 178 | ||
| 179 | 2 | unless confFile |
| 180 | 0 | log.warn "Couldn't find #{name} configuration:".bold.yellow, paths |
| 181 | 0 | return |
| 182 | ||
| 183 | 2 | conf = (new nconf.Provider()).argv().env() |
| 184 | 2 | log.info "#{name} configuration path: #{confFile}" |
| 185 | 2 | conf.file 'user', {file: confFile, format: type} |
| 186 | # if dnschain's config specifies this chain's config information, use it as default | |
| 187 | 2 | if config.chains.dnschain.get("#{name}")? |
| 188 | 1 | conf.defaults config.chains.dnschain.get "#{name}" |
| 189 | 2 | config.chains[name] = conf |
| 190 |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.net | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | # TODO: go through 'TODO's! | |
| 15 | ||
| 16 | 1 | Packet = require('native-dns-packet') |
| 17 | ||
| 18 | 1 | module.exports = (dnschain) -> |
| 19 | # expose these into our namespace | |
| 20 | 1 | for k of dnschain.globals |
| 21 | 28 | eval "var #{k} = dnschain.globals.#{k};" |
| 22 | ||
| 23 | 1 | QTYPE_NAME = dns2.consts.QTYPE_TO_NAME |
| 24 | 1 | NAME_QTYPE = dns2.consts.NAME_TO_QTYPE |
| 25 | 1 | NAME_RCODE = dns2.consts.NAME_TO_RCODE |
| 26 | 1 | RCODE_NAME = dns2.consts.RCODE_TO_NAME |
| 27 | ||
| 28 | 1 | class DNSServer |
| 29 | 1 | constructor: (@dnschain) -> |
| 30 | 6 | @log = gNewLogger 'DNS' |
| 31 | 6 | @log.debug "Loading DNSServer..." |
| 32 | 6 | @method = gConf.get 'dns:oldDNSMethod' |
| 33 | 6 | @rateLimiting = gConf.get 'rateLimiting:dns' |
| 34 | ||
| 35 | # this is just for development testing of NODE_DNS method | |
| 36 | # dns.setServers ['8.8.8.8'] | |
| 37 | ||
| 38 | 6 | if @method is gConsts.oldDNS.NODE_DNS |
| 39 | 0 | @log.warn "Using".bold.red, "oldDNSMethod = NODE_DNS".bold, "method is strongly discouraged!".bold.red |
| 40 | 0 | if dns.getServers? |
| 41 | 0 | blacklist = _.intersection ['127.0.0.1', '::1', 'localhost'], dns.getServers() |
| 42 | 0 | if blacklist.length > 0 |
| 43 | 0 | gErr "Cannot use NODE_DNS method when system DNS lists %j as a resolver! Would lead to infinite loop!", blacklist |
| 44 | else | |
| 45 | 0 | gErr "Node's DNS module doesn't have 'getServers'. Please upgrade NodeJS." |
| 46 | 6 | else if @method is gConsts.oldDNS.NO_OLD_DNS |
| 47 | 0 | @log.warn "oldDNSMethod is set to refuse queries for traditional DNS!".bold |
| 48 | 6 | else if @method is gConsts.oldDNS.NO_OLD_DNS_EVER |
| 49 | 0 | @log.warn "oldDNSMethod is set to refuse *ALL* queries for traditional DNS (even if the blockchain wants us to)!".bold.red |
| 50 | 6 | else if @method isnt gConsts.oldDNS.NATIVE_DNS |
| 51 | 0 | gErr "No such oldDNSMethod: #{@method}" |
| 52 | ||
| 53 | 6 | gFillWithRunningChecks @ |
| 54 | ||
| 55 | start: -> | |
| 56 | 6 | @startCheck (cb) => |
| 57 | 6 | @server = dns2.createServer() or gErr "dns2 create" |
| 58 | 6 | @server.on 'socketError', (err) -> gErr err |
| 59 | 6 | @server.on 'request', (req, res) => |
| 60 | 120 | domain = req.question[0]?.name |
| 61 | 120 | if domain |
| 62 | 120 | domain = domain.split(".") |
| 63 | 120 | if domain.length > 3 |
| 64 | # if there are more than 3 parts to the domain, we use the last | |
| 65 | # letter of the fourth, and the full parts of the last three | |
| 66 | # This isn't perfect, especially because of: | |
| 67 | # https://publicsuffix.org/list/effective_tld_names.dat | |
| 68 | # | |
| 69 | # See: https://github.com/okTurtles/dnschain/issues/107 ! | |
| 70 | 13 | domain = [domain[-4..][0][-1..]].concat(domain[-3..]).join '.' |
| 71 | else | |
| 72 | 107 | domain = domain[-3..].join '.' |
| 73 | ||
| 74 | 120 | key = "dns-#{req.address.address}-#{domain}" |
| 75 | 120 | @log.debug gLineInfo("creating bottleneck on: #{key}") |
| 76 | 120 | limiter = gThrottle key, => new Bottleneck _.at(@rateLimiting, ['maxConcurrent', 'minTime', 'highWater', 'strategy'])... |
| 77 | 120 | limiter.changePenalty(@rateLimiting.penalty).submit (@callback.bind @), req, res, null |
| 78 | else | |
| 79 | 0 | @log.warn gLineInfo('received empty request!'), {req:req} |
| 80 | # // end on 'request' | |
| 81 | 6 | @server.on 'listening', => |
| 82 | 6 | @log.info 'started DNS', gConf.get 'dns' |
| 83 | 6 | cb() |
| 84 | 6 | @server.serve gConf.get('dns:port'), gConf.get('dns:host') |
| 85 | ||
| 86 | shutdown: -> | |
| 87 | 6 | @shutdownCheck (cb) => |
| 88 | 6 | if @server |
| 89 | 6 | @server.on 'close', cb |
| 90 | 6 | @server.close() |
| 91 | else | |
| 92 | 0 | @log.warn gLineInfo '@server not defined!' |
| 93 | 0 | cb() |
| 94 | ||
| 95 | # (Notes on 'native-dns' version <=0.6.x, which I'd like to see changed.) | |
| 96 | # | |
| 97 | # Both `req` and `res` are of type `Packet` (the subclass, as explained next). | |
| 98 | # | |
| 99 | # The packet that's inside of 'native-dns' inherits from the one inside of 'native-dns-packet'. | |
| 100 | # It adds two extra fields (at this time of writing): | |
| 101 | # | |
| 102 | # - address: added by Server.prototype.handleMessage and the Packet subclass constructor | |
| 103 | # - _socket: added by the Packet subclass constructor in lib/packet.js | |
| 104 | # | |
| 105 | # `req` and `res` are both instances of this subclass of 'Packet'. | |
| 106 | # They also have the same 'question' field. | |
| 107 | # | |
| 108 | # See also: | |
| 109 | # - native-dns/lib/server.js | |
| 110 | # - native-dns/lib/packet.js | |
| 111 | # - native-dns-packet/packet.js | |
| 112 | # | |
| 113 | # Separately, there is a 'Request' class defined in 'native-dns/lib/client.js'. | |
| 114 | # Like 'Packet', it has a 'send' method. | |
| 115 | # To understand it see these functions in 'lib/pending.js': | |
| 116 | # | |
| 117 | # - SocketQueue.prototype._dequeue (sending) | |
| 118 | # - SocketQueue.prototype._onmessage (receiving) | |
| 119 | # | |
| 120 | # When you create a 'new Request' and send it, it will first create a 'new Packet' and copy | |
| 121 | # some of the values from the request into it, and then call 'send' on that. | |
| 122 | # Similarly, in receiving a reply from a Request instance, handle the 'message' event, which | |
| 123 | # will create a new Packet (the subclass, with the _socket field) from the received data. | |
| 124 | # | |
| 125 | # See also: | |
| 126 | # - native-dns/lib/client.js | |
| 127 | # - native-dns/lib/pending.js | |
| 128 | # | |
| 129 | # Ideally we want to be able to reuse the 'req' received here and pass it along | |
| 130 | # to oldDNSLookup without having to recreate or copy any information. | |
| 131 | # See: https://github.com/tjfontaine/node-dns/issues/69 | |
| 132 | # | |
| 133 | # Even more ideally we want to be able to simply pass along the raw data without having to parse it. | |
| 134 | # See: https://github.com/okTurtles/dnschain/issues/6 | |
| 135 | # | |
| 136 | callback: (req, res, cb) -> | |
| 137 | # answering multiple questions in a query appears to be problematic, | |
| 138 | # and few servers do it, so we only answer the first question: | |
| 139 | # https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query | |
| 140 | # At some point we may still want to support this though. | |
| 141 | 112 | q = req.question[qIdx=0] |
| 142 | 112 | q.name = q.name.toLowerCase() |
| 143 | ||
| 144 | 112 | ttl = Math.floor(Math.random() * 3600) + 30 # TODO: pick an appropriate TTL value! |
| 145 | 112 | @log.debug "received question", q |
| 146 | ||
| 147 | 112 | if (datastore = @dnschain.chainsTLDs[q.name.split('.').pop()]) |
| 148 | 7 | @log.debug gLineInfo("resolving via #{datastore.name}..."), {domain:q.name, q:q} |
| 149 | ||
| 150 | 7 | if not datastore.resources.key? |
| 151 | 0 | @log.error gLineInfo "#{datastore.name} does not implement `key` resource!".bold |
| 152 | 0 | return @sendErr(res, NAME_RCODE.SERVFAIL, cb) |
| 153 | 7 | args = [datastore.name , "key", q.name, null, null, {}] # args conform to the datastore API |
| 154 | 7 | resourceRequest = (cb) => |
| 155 | 6 | datastore.resources.key.call datastore, args[2..]..., cb |
| 156 | 7 | @dnschain.cache.resolveResource datastore, resourceRequest, JSON.stringify(args), (err, result) => |
| 157 | 7 | if err? or !result |
| 158 | 0 | @log.error gLineInfo("#{datastore.name} failed to resolve"), {err:err?.message, result:result, q:q} |
| 159 | 0 | @sendErr res, null, cb |
| 160 | else | |
| 161 | 7 | @log.debug gLineInfo("#{datastore.name} resolved query"), {q:q, d:q.name, result:result} |
| 162 | ||
| 163 | 7 | if not (handler = datastore.dnsHandler[QTYPE_NAME[q.type]]) |
| 164 | 0 | @log.warn gLineInfo("no such DNS handler!"), {datastore: datastore.name, q:q, type: QTYPE_NAME[q.type]} |
| 165 | 0 | return @sendErr res, NAME_RCODE.NOTIMP, cb |
| 166 | ||
| 167 | 7 | handler.call datastore, req, res, qIdx, result.data, (errCode) => |
| 168 | 7 | try |
| 169 | 7 | if errCode |
| 170 | 0 | @sendErr res, errCode, cb |
| 171 | else | |
| 172 | 7 | @sendRes res, cb |
| 173 | catch e | |
| 174 | 0 | @log.error e.stack |
| 175 | 0 | @log.error gLineInfo("exception in handler"), {q:q, result:result} |
| 176 | 0 | return @sendErr res, NAME_RCODE.SERVFAIL, cb |
| 177 | ||
| 178 | 105 | else if S(q.name).endsWith '.dns' |
| 179 | 1 | res.answer.push gIP2type(q.name,ttl,QTYPE_NAME[q.type])(gConf.get 'dns:externalIP') |
| 180 | 1 | @log.debug gLineInfo('cb|.dns'), {q:q, answer:res.answer} |
| 181 | 1 | @sendRes res, cb |
| 182 | else | |
| 183 | 104 | @log.debug gLineInfo("resolving #{q.name} via oldDNS"), {q:q} |
| 184 | 104 | @dnschain.cache.resolveOldDNS req, (code, packet) => |
| 185 | 104 | _.assign res, packet |
| 186 | 104 | if code |
| 187 | 0 | @sendErr res, code, cb |
| 188 | else | |
| 189 | 104 | @sendRes res, cb |
| 190 | # / end callback | |
| 191 | ||
| 192 | oldDNSLookup: (req, cb) -> | |
| 193 | 83 | res = new Packet() |
| 194 | 83 | sig = "oldDNS{#{@method}}" |
| 195 | 83 | q = req.question[0] |
| 196 | 83 | filterRes = (p) -> |
| 197 | 83 | _.pick p, ['edns_version', 'edns_options', 'edns', 'answer', 'authority', 'additional'] |
| 198 | ||
| 199 | 83 | @log.debug {fn:sig+':start', q:q} |
| 200 | ||
| 201 | 83 | if @method is gConsts.oldDNS.NATIVE_DNS |
| 202 | 83 | success = false |
| 203 | # TODO: retry in TCP-mode on truncated response (like `dig`) | |
| 204 | # See: https://github.com/tjfontaine/node-dns/issues/70 | |
| 205 | 83 | req2 = new dns2.Request |
| 206 | question: q | |
| 207 | server : gConf.get 'dns:oldDNS' | |
| 208 | try_edns: q.type is NAME_QTYPE.ANY or req.edns? | |
| 209 | ||
| 210 | # 'answer' is a Packet subclass with the .address and ._socket fields | |
| 211 | 83 | req2.on 'message', (err, answer) => |
| 212 | 83 | if err? |
| 213 | 0 | @log.error gLineInfo("should not have an error here!"), {err:err?.message, answer:answer} |
| 214 | 0 | req2.DNSErr ?= err |
| 215 | else | |
| 216 | 83 | @log.debug gLineInfo('message'), {answer:answer} |
| 217 | 83 | success = true |
| 218 | 83 | res = answer |
| 219 | ||
| 220 | 83 | req2.on 'error', (err={message:'unknown error'}) => |
| 221 | 0 | @log.error gLineInfo('oldDNS lookup error'), {err:err?.message} |
| 222 | 0 | req2.DNSErr = err |
| 223 | ||
| 224 | 83 | req2.on 'timeout', (err={message:'timeout'}) => |
| 225 | 0 | @log.warn gLineInfo('oldDNS timeout'), {err:err} |
| 226 | 0 | req2.DNSErr = err |
| 227 | ||
| 228 | 83 | req2.on 'end', => |
| 229 | 83 | if success |
| 230 | 83 | @log.debug gLineInfo('success!'), {q:q, res: _.omit(res, '_socket')} |
| 231 | 83 | cb null, filterRes(res) |
| 232 | else | |
| 233 | # TODO: this is noisy. | |
| 234 | # also make log output look good in journalctl | |
| 235 | # you can log IP with: res._socket.remote.address | |
| 236 | 0 | @log.warn gLineInfo('oldDNS lookup failed'), {q:q, err:req2.DNSErr} |
| 237 | 0 | cb NAME_RCODE.SERVFAIL, filterRes(res) |
| 238 | # @log.debug {fn:"beforesend", req:req2} | |
| 239 | 83 | req2.send() |
| 240 | 0 | else if @method is gConsts.oldDNS.NODE_DNS |
| 241 | 0 | dns.resolve q.name, QTYPE_NAME[q.type], (err, addrs) => |
| 242 | 0 | if err |
| 243 | 0 | @log.debug {fn:sig+':fail', q:q, err:err?.message} |
| 244 | 0 | cb NAME_RCODE.SERVFAIL, filterRes(res) |
| 245 | else | |
| 246 | # USING THIS METHOD IS DISCOURAGED BECAUSE IT DOESN'T | |
| 247 | # PROVIDE US WITH CORRECT TTL VALUES!! | |
| 248 | # TODO: pick an appropriate TTL value! | |
| 249 | 0 | ttl = Math.floor(Math.random() * 3600) + 30 |
| 250 | 0 | res.answer.push (addrs.map gIP2type(q.name, ttl, QTYPE_NAME[q.type]))... |
| 251 | 0 | @log.debug {fn:sig+':success', answer:res.answer, q:q.name} |
| 252 | 0 | cb null, filterRes(res) |
| 253 | else | |
| 254 | # refuse all such queries | |
| 255 | 0 | cb NAME_RCODE.REFUSED, res |
| 256 | ||
| 257 | sendRes: (res, cb) -> | |
| 258 | 112 | try |
| 259 | 112 | @log.debug gLineInfo("sending response!"), {res:_.omit(res, '_socket')} |
| 260 | 112 | res.send() |
| 261 | 112 | cb() |
| 262 | catch e | |
| 263 | 0 | @log.error gLineInfo('error trying send response back!'), {msg:e.message, res:_.omit(res, '_socket'), stack:e.stack} |
| 264 | 0 | cb e |
| 265 | ||
| 266 | sendErr: (res, code=NAME_RCODE.SERVFAIL, cb) -> | |
| 267 | 0 | try |
| 268 | 0 | res.header.rcode = code |
| 269 | 0 | @log.debug gLineInfo(), {code:code, name:RCODE_NAME[code]} |
| 270 | 0 | res.send() |
| 271 | catch e | |
| 272 | 0 | @log.error gLineInfo('exception sending error back!'), e.stack |
| 273 | 0 | cb() |
| 274 | 0 | false # helps other functions pass back an error value |
| 275 |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.net | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | # we don't 'use strict' because i really want to be able to use 'eval' to declare variables | |
| 15 | ||
| 16 | # expose global dependencies, functions, and constants into our namespace | |
| 17 | 1 | for k of require('./globals')(exports) |
| 18 | 28 | eval "var #{k} = exports.globals.#{k};" |
| 19 | ||
| 20 | 1 | exports.createServer = (a...) -> new DNSChain a... |
| 21 | ||
| 22 | 1 | DNSServer = require('./dns')(exports) |
| 23 | 1 | HTTPServer = require('./http')(exports) |
| 24 | 1 | EncryptedServer = require('./https')(exports) |
| 25 | 1 | ResolverCache = require('./cache')(exports) |
| 26 | ||
| 27 | 1 | localhosts = -> |
| 28 | 6 | _.uniq [ |
| 29 | "127.0.0.", "10.0.0.", "192.168.", "::1", "fe80::" | |
| 30 | gConf.get('dns:host'), gConf.get('http:host') | |
| 31 | gConf.get('dns:externalIP'), gExternalIP() | |
| 32 | 18 | _.map(gConf.chains, (c) -> c.get('host'))... |
| 33 | 72 | ].filter (o)-> typeof(o) is 'string' |
| 34 | ||
| 35 | 1 | exports.DNSChain = class DNSChain |
| 36 | 1 | constructor: -> |
| 37 | 6 | @log = gNewLogger 'DNSChain' |
| 38 | 6 | chainDir = path.join __dirname, 'blockchains' |
| 39 | 6 | @chains = _.omit(_.mapValues(_.indexBy(fs.readdirSync(chainDir), (file) => |
| 40 | 24 | S(file).chompRight('.coffee').s |
| 41 | ), (file) => | |
| 42 | 24 | chain = new (require('./blockchains/'+file)(exports)) @ |
| 43 | 24 | chain.config() |
| 44 | ), (chain) => | |
| 45 | 24 | not chain |
| 46 | ) | |
| 47 | 6 | @chainsTLDs = _.indexBy _.compact(_.map(@chains, (chain) -> |
| 48 | 24 | chain if chain.tld? |
| 49 | )), 'tld' | |
| 50 | 6 | gConf.localhosts = localhosts.call @ |
| 51 | 6 | @dns = new DNSServer @ |
| 52 | 6 | @http = new HTTPServer @ |
| 53 | 6 | @encryptedserver = new EncryptedServer @ |
| 54 | 6 | @cache = new ResolverCache @ |
| 55 | # ordered this way to start all external facing servers last | |
| 56 | 6 | @servers = _.values(@chains).concat [@cache, @http, @encryptedserver, @dns] |
| 57 | 6 | gFillWithRunningChecks @ |
| 58 | ||
| 59 | start: -> | |
| 60 | 6 | @startCheck (cb) => |
| 61 | 6 | Promise.all(@servers.map (s)-> s.start()).then => |
| 62 | 6 | [host, port] = ['dns:externalIP', 'dns:port'].map (x)-> gConf.get x |
| 63 | 6 | @log.info "DNSChain started and advertising DNS on: #{host}:#{port}" |
| 64 | ||
| 65 | 6 | if process.getuid() isnt 0 and port isnt 53 and require('tty').isatty process.stdout |
| 66 | 6 | @log.warn "DNS port isn't 53!".bold.red, "While testing you should either run me as root or make sure to set standard ports in the configuration!".bold |
| 67 | 6 | cb null |
| 68 | 0 | .catch (e) -> cb e |
| 69 | .catch (e) => | |
| 70 | 0 | @log.error "DNSChain failed to start:", e.stack |
| 71 | 0 | @shutdown() |
| 72 | 0 | throw e # re-throw to indicate that this promise failed |
| 73 | ||
| 74 | shutdown: -> | |
| 75 | 6 | @shutdownCheck (cb) => |
| 76 | # shutdown servers in the opposite order they were started | |
| 77 | 6 | reversedServers = @servers[..].reverse() |
| 78 | 6 | Promise.settle reversedServers.map (s, idx) => |
| 79 | 48 | name = s?.name || s?.log?.transports.console?.label |
| 80 | 48 | @log.debug "Shutting down server at idx:#{idx}: #{name}" |
| 81 | 48 | s?.shutdown() |
| 82 | 6 | .then -> cb null |
| 83 | 0 | .catch (e) -> cb e |
| 84 |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.net | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | 1 | module.exports = (dnschain) -> |
| 15 | ||
| 16 | # 1. global dependencies | |
| 17 | # | |
| 18 | # IMPORTANT: *ALL* DNSChain globals *MUST* be prefixed with a 'g' | |
| 19 | # *EXCEPT* some of the commonly used global module dependencies below. | |
| 20 | ||
| 21 | 1 | dnschain.globals = |
| 22 | rpc : 'json-rpc2' | |
| 23 | _ : 'lodash' | |
| 24 | S : 'string' | |
| 25 | dns2 : 'native-dns' | |
| 26 | gES : 'event-stream' | |
| 27 | Bottleneck : 'bottleneck' | |
| 28 | Promise : 'bluebird' | |
| 29 | ||
| 30 | # Initialize the global rate limiter pool, only an interface will be visible from the outside | |
| 31 | 1 | limiters = {} |
| 32 | # Garbage collect the unused limiters every minute. | |
| 33 | #This is necessary or else the server will run out of RAM after a few days | |
| 34 | 1 | setInterval -> |
| 35 | 0 | time = Date.now() |
| 36 | 0 | for key,limiter of limiters |
| 37 | # Unused in the last 5 minutes | |
| 38 | 0 | if (limiter._nextRequest+(60*1000*5)) < time |
| 39 | 0 | delete limiters[key] |
| 40 | , 60*1000 # Every minute | |
| 41 | ||
| 42 | # no renaming done for these | |
| 43 | 1 | for d in ['dns', 'fs', 'http','net', 'os', 'path', 'tls', 'url', 'util', 'winston'] |
| 44 | 10 | dnschain.globals[d] = d |
| 45 | ||
| 46 | # replace all the string values in dnschain.globals with the module they represent | |
| 47 | # and expose them into this function's namespace | |
| 48 | 1 | for k,v of dnschain.globals |
| 49 | 17 | eval "var #{k} = dnschain.globals.#{k} = require('#{v}');" |
| 50 | ||
| 51 | # 2. global constants | |
| 52 | 1 | _.assign dnschain.globals, gConsts: |
| 53 | # for questions that the blockchain cannot answer (hopefully these will disappear with time) | |
| 54 | # >> Use the keys (as string values) in your config! << | |
| 55 | # >> Do *NOT* use the numerical values in your config! << | |
| 56 | # For now they're supported but their use is DEPRECATED and they *WILL* be removed! | |
| 57 | oldDNS: | |
| 58 | # Recommended method | |
| 59 | NATIVE_DNS: 0 # Use 'native-dns' module (current default). Hopefully merged into NodeJS in the future: https://github.com/joyent/node/issues/6864#issuecomment-32229852 | |
| 60 | ||
| 61 | # !! WARNING !! | |
| 62 | # > ! USING 'NODE_DNS' IS __STRONGLY DISCOURAGED__ ! < | |
| 63 | # > ! BECAUSE IT DOES NOT PROVIDE PROVIDE TTL VALUES ! < | |
| 64 | # > ! ITS USE MAY BE DEPRECATED IN THE FUTURE ! < | |
| 65 | NODE_DNS: 1 # Prior to node 0.11.x will ignore dnsOpts.oldDNS and use OS-specified DNS. Currently ignores 'dnsOpts.oldDNS' in favor of OS-specified DNS even in node 0.11.x (simply needs to be implemented). TODO: <- this! | |
| 66 | ||
| 67 | # Refuse to resolve domains in the canonical DNS system | |
| 68 | # unless the blockchain tells us to, in which case the | |
| 69 | # NATIVE_DNS method will be used for that purpose. | |
| 70 | NO_OLD_DNS: 2 | |
| 71 | ||
| 72 | # Never resolve domains in canonical DNS. Return REFUSED for all such requests. | |
| 73 | NO_OLD_DNS_EVER: 3 | |
| 74 | ||
| 75 | # 3. create global functions, and then return the entire globals object | |
| 76 | 1 | _.assign dnschain.globals, { |
| 77 | gExternalIP: do -> | |
| 78 | 1 | cachedIP = null |
| 79 | 1 | faces = os.networkInterfaces() |
| 80 | 1 | default_iface = switch |
| 81 | 1 | when os.type() is 'Darwin' and faces.en0? then 'en0' |
| 82 | 0 | when os.type() is 'Linux' and faces.eth0? then 'eth0' |
| 83 | 0 | else _(faces).keys().find (x)-> !x.match /lo/ |
| 84 | 1 | (iface=default_iface, cached=true,fam='IPv4',internal=false) -> |
| 85 | 7 | cachedIP = switch |
| 86 | 6 | when cached and cachedIP then cachedIP |
| 87 | else | |
| 88 | 1 | unless ips = (faces = os.networkInterfaces())[iface] |
| 89 | 0 | throw new Error util.format("No such interface '%s'. Available: %j", iface, faces) |
| 90 | 1 | if (address = _.find(ips, {family:fam, internal:internal})?.address) |
| 91 | 1 | address |
| 92 | else | |
| 93 | 0 | console.warn "Couldn't find 'address' in:".bold.red, ips |
| 94 | 0 | console.warn "Couldn't figure out external IPv4 IP! Make SURE to manually set it in your configuration!".bold.red |
| 95 | 0 | "NOT AN IP! SEE: https://github.com/okTurtles/dnschain/issues/111#issuecomment-71958236" |
| 96 | ||
| 97 | gNewLogger: (name) -> | |
| 98 | 61 | new winston.Logger |
| 99 | levels: winston.config.cli.levels | |
| 100 | colors: winston.config.cli.lcolors | |
| 101 | transports: [ | |
| 102 | new winston.transports.Console | |
| 103 | label:name | |
| 104 | level: gConf.get 'log:level' | |
| 105 | colorize: gConf.get 'log:colors' | |
| 106 | prettyPrint: gConf.get 'log:pretty' | |
| 107 | timestamp: gConf.get 'log:timestamp' | |
| 108 | ] | |
| 109 | ||
| 110 | gErr: (args...) -> | |
| 111 | 1 | e = new Error util.format args... |
| 112 | 1 | gLogger.error e.stack |
| 113 | 1 | throw e |
| 114 | ||
| 115 | gFillWithRunningChecks: (server) -> | |
| 116 | 55 | gLineInfo = dnschain.globals.gLineInfo |
| 117 | 55 | server.startCheck = (cb) -> |
| 118 | 55 | if @running |
| 119 | 0 | @log.warn gLineInfo "Already running!" |
| 120 | 0 | Promise.reject() |
| 121 | else | |
| 122 | 55 | @log.debug "Starting up..." |
| 123 | 55 | new Promise (resolve, reject) => |
| 124 | 55 | cb (err, args...) => |
| 125 | 55 | if err |
| 126 | 0 | @log.error gLineInfo("failed to start"), err |
| 127 | 0 | reject err |
| 128 | else | |
| 129 | 55 | @log.info "Server started.", args |
| 130 | 55 | @running = true |
| 131 | 55 | resolve args |
| 132 | 55 | server.shutdownCheck = (cb) -> |
| 133 | 55 | if @running |
| 134 | 55 | @log.debug "Shutting down..." |
| 135 | 55 | new Promise (resolve, reject) => |
| 136 | 55 | cb (err, args...) => |
| 137 | 55 | if err |
| 138 | 0 | @log.error gLineInfo("failed to shutdown"), err |
| 139 | 0 | reject err |
| 140 | else | |
| 141 | 55 | @log.info "Server shutdown successfully.", args |
| 142 | 55 | @running = false |
| 143 | 55 | resolve args |
| 144 | else | |
| 145 | 0 | @log.warn gLineInfo "Shutdown called when not running!" |
| 146 | 0 | Promise.reject() |
| 147 | 55 | server # as a convenience, return the server instance |
| 148 | ||
| 149 | 131 | gThrottle: (key, makeLimiter) -> limiters[key] ? (limiters[key] = makeLimiter()) |
| 150 | ||
| 151 | # TODO: this function should take one parameter: an IP string | |
| 152 | # and return either 'A' or 'AAAA' | |
| 153 | # A separate function called type2rec should do what the inner fn does | |
| 154 | # https://github.com/okTurtles/dnschain/issues/7 | |
| 155 | gIP2type: (d, ttl, type='A') -> | |
| 156 | 7 | (ip)-> dns2[type] {name:d, address:ip, ttl:ttl} |
| 157 | ||
| 158 | # Currently this function is namecoin-specific. | |
| 159 | # DANE/TLSA info: https://tools.ietf.org/html/rfc6698 | |
| 160 | # TODO: implement RRSIG from http://tools.ietf.org/html/rfc4034 | |
| 161 | # as in: dig @8.8.4.4 +dnssec TLSA _443._tcp.good.dane.verisignlabs.com | |
| 162 | gTls2tlsa: (tls, ttl, queryname) -> | |
| 163 | 0 | [port, protocol] = (queryname.split '.')[0..1].map (o)-> o.replace '_','' |
| 164 | 0 | if !tls?[protocol]?[port]? |
| 165 | 0 | return [] |
| 166 | 0 | tls[protocol][port].map (certinfo) -> |
| 167 | 0 | dns2.TLSA |
| 168 | name: queryname | |
| 169 | ttl: ttl | |
| 170 | usage: 3 # 3 = "Fuck CAs" :P | |
| 171 | selector: 1 # SubjectPublicKeyInfo: DER-encoded [RFC5280] | |
| 172 | matchingtype: certinfo[0] | |
| 173 | buff: new Buffer certinfo[1], 'hex' | |
| 174 | ||
| 175 | gLineInfo: (prefix='') -> | |
| 176 | 625 | stack = new Error().stack |
| 177 | # console.log stack.split('\n')[2] | |
| 178 | 625 | [file, line] = stack.split('\n')[2].split ':' |
| 179 | 625 | [func, file] = file.split ' (' |
| 180 | 625 | [func, file] = ['??', func] unless file # sometimes the function isn't specified |
| 181 | 625 | [func, file] = [func.split(' ').pop(), path.basename(file)] |
| 182 | 625 | [junk, func] = func.split('.') |
| 183 | 625 | func = junk unless func |
| 184 | 625 | func = if func is '??' or func is '<anonymous>' then ' (' else " (<#{func}> " |
| 185 | 625 | prefix + func + file + ':' + line + ')' |
| 186 | } | |
| 187 | ||
| 188 | # 4. vars for use within the map above and elsewhere | |
| 189 | 1 | gConf = dnschain.globals.gConf = require('./config')(dnschain) |
| 190 | 1 | gLogger = dnschain.globals.gLogger = dnschain.globals.gNewLogger 'Global' |
| 191 | 1 | gConsts = dnschain.globals.gConsts |
| 192 | ||
| 193 | # handle DEPRECATED numerical oldDNSMethod values | |
| 194 | 1 | method = gConf.get 'dns:oldDNSMethod' |
| 195 | 1 | if typeof method isnt 'string' |
| 196 | 0 | method = _.keys(_.pick gConsts.oldDNS, (v,k)-> v is method)[0] |
| 197 | 0 | gLogger.warn "Specifying 'oldDNSMethod' as a number is DEPRECATED!".bold.red |
| 198 | 0 | gLogger.warn "Please specify the string value instead:".bold.red, "#{method}".bold |
| 199 | else | |
| 200 | 1 | if _.isNumber (method_num = gConsts.oldDNS[method]) |
| 201 | # kinda hackish... but makes for easy and quick comparisons | |
| 202 | 1 | gConf.set 'dns:oldDNSMethod', method_num |
| 203 | else | |
| 204 | 0 | gLogger.error "No such oldDNS method:".bold.red, method.bold |
| 205 | 0 | process.exit 1 |
| 206 | ||
| 207 | ||
| 208 | # 5. return the globals object | |
| 209 | 1 | dnschain.globals |
| 210 |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.net | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | 1 | express = require 'express' |
| 15 | ||
| 16 | 1 | module.exports = (dnschain) -> |
| 17 | # expose these into our namespace | |
| 18 | 1 | for k of dnschain.globals |
| 19 | 28 | eval "var #{k} = dnschain.globals.#{k};" |
| 20 | ||
| 21 | 1 | class HTTPServer |
| 22 | 1 | constructor: (@dnschain) -> |
| 23 | 6 | @log = gNewLogger 'HTTP' |
| 24 | 6 | @log.debug "Loading HTTPServer..." |
| 25 | 6 | @rateLimiting = gConf.get 'rateLimiting:http' |
| 26 | 6 | app = express() |
| 27 | ||
| 28 | # Openname spec defined here: | |
| 29 | # - https://github.com/okTurtles/openname-specifications/blob/resolvers/resolvers.md | |
| 30 | # - https://github.com/openname/openname-specifications/blob/master/resolvers.md | |
| 31 | ||
| 32 | 6 | opennameRoute = express.Router() |
| 33 | ||
| 34 | # Resolver specific API | |
| 35 | 6 | opennameRoute.get /\/(?:resolver|dnschain)\/([^\/\.]+)(?:\.([a-z]+))?/, (req, res) => |
| 36 | 1 | @log.debug gLineInfo("resolver API called"), {params: req.params} |
| 37 | 1 | [resource, format] = req.params |
| 38 | 1 | if resource == "fingerprint" |
| 39 | 1 | if !format or format is 'json' |
| 40 | 1 | res.json {fingerprint: @dnschain.encryptedserver.getFingerprint()} |
| 41 | else | |
| 42 | 0 | @sendErr req, res, 400, "Unsupported format: #{format}" |
| 43 | else | |
| 44 | 0 | @sendErr req, res, 400, "Bad resource: #{resource}" |
| 45 | ||
| 46 | # Datastore API | |
| 47 | # Note: JavaScript doesn't have negative lookbehind support | |
| 48 | # so we use the negative lookahead hacks mentioned here: | |
| 49 | # https://stackoverflow.com/questions/977251/regular-expressions-and-negating-a-whole-character-group | |
| 50 | 6 | opennameRoute.route(/// ^ |
| 51 | \/(\w+) # the datastore name | |
| 52 | \/(\w+) # the corresponding resource | |
| 53 | (?:\/((?:(?!\.json|\.xml)[^\/])+))? # optional property (or action on resource) | |
| 54 | (?:\/((?:(?!\.json|\.xml)[^\/])+))? # optional action on property | |
| 55 | (?:\.(json|xml))? # optional response format | |
| 56 | $ /// | |
| 57 | ).get (req, res) => | |
| 58 | 6 | @log.debug gLineInfo("get v1"), {params: req.params, queryArgs: req.query} |
| 59 | 6 | @callback req, res, [_.values(req.params)..., req.query] |
| 60 | ||
| 61 | 6 | opennameRoute.use (req, res) => |
| 62 | 0 | @sendErr req, res, 400, "Bad v1 request" |
| 63 | ||
| 64 | 6 | app.use "/v1", opennameRoute |
| 65 | 6 | app.get "*", (req, res) => # Old, deprecated API usage. |
| 66 | 2 | path = S(url.parse(req.originalUrl).pathname).chompLeft('/').s |
| 67 | 2 | options = url.parse(req.originalUrl, true).query |
| 68 | 2 | @log.debug gLineInfo('deprecated request'), {path:path, options:options, url:req.originalUrl} |
| 69 | ||
| 70 | 2 | [...,datastoreName] = |
| 71 | if S(header = req.headers.blockchain || req.headers.host).endsWith('.dns') | |
| 72 | 2 | S(header).chompRight('.dns').s.split('.') |
| 73 | else | |
| 74 | 0 | ['none'] |
| 75 | ||
| 76 | 2 | @callback req, res, [datastoreName, "key", path, null, null, options] |
| 77 | ||
| 78 | 6 | app.use (err, req, res, next) => |
| 79 | 0 | @log.warn gLineInfo('error handler triggered'), |
| 80 | errMessage: err?.message | |
| 81 | stack: err?.stack | |
| 82 | req: _.at(req, ['originalUrl','ip','ips','protocol','hostname','headers']) | |
| 83 | 0 | res.status(500).send "Internal Error: #{err?.message}" |
| 84 | ||
| 85 | 6 | @server = http.createServer (req, res) => |
| 86 | 9 | key = "http-#{req.connection?.remoteAddress}" |
| 87 | 9 | @log.debug gLineInfo("creating bottleneck on: #{key}") |
| 88 | 9 | limiter = gThrottle key, => new Bottleneck _.at(@rateLimiting, ['maxConcurrent','minTime','highWater','strategy'])... |
| 89 | ||
| 90 | # Since Express doesn't take a callback function | |
| 91 | # we capture the callback that Bottleneck requires | |
| 92 | # in `bottleCB` and call it by hooking into `res.end` | |
| 93 | 9 | savedEnd = res.end.bind(res) |
| 94 | 9 | bottleCB = null |
| 95 | 9 | res.end = (args...) => |
| 96 | 9 | savedEnd args... |
| 97 | 9 | bottleCB() |
| 98 | ||
| 99 | 9 | limiter.submit (cb) -> |
| 100 | 9 | bottleCB = cb |
| 101 | 9 | app req, res |
| 102 | , null | |
| 103 | ||
| 104 | 6 | gErr("http create") unless @server |
| 105 | ||
| 106 | 6 | @server.on 'error', (err) -> gErr err |
| 107 | 6 | gFillWithRunningChecks @ |
| 108 | ||
| 109 | start: -> | |
| 110 | 6 | @startCheck (cb) => |
| 111 | 6 | @server.listen gConf.get('http:port'), gConf.get('http:host'), => |
| 112 | 6 | cb null, gConf.get 'http' |
| 113 | ||
| 114 | shutdown: -> | |
| 115 | 6 | @shutdownCheck (cb) => |
| 116 | 6 | @log.debug 'shutting down!' |
| 117 | 6 | @server.close cb |
| 118 | ||
| 119 | callback: (req, res, args) -> | |
| 120 | 8 | [datastoreName, resourceName, propOrAction] = args |
| 121 | 8 | if not (datastore = @dnschain.chains[datastoreName]) |
| 122 | 0 | return @sendErr req, res, 400, "Unsupported datastore: #{datastoreName}" |
| 123 | 8 | if not (resourceFn = datastore.resources[resourceName]) |
| 124 | 1 | return @sendErr req, res, 400, "Unsupported resource: #{resourceName}" |
| 125 | 7 | resourceRequest = (cb) => |
| 126 | 7 | resourceFn.call datastore, args[2..]..., cb |
| 127 | 7 | @dnschain.cache.resolveResource datastore, resourceRequest, JSON.stringify(args), (err,result) => |
| 128 | 7 | if err |
| 129 | 0 | err.httpCode ?= 404 |
| 130 | 0 | @log.debug gLineInfo('resolver failed'), {err:err.message} |
| 131 | 0 | @sendErr req, res, err.httpCode, "Not Found: #{propOrAction}" |
| 132 | else | |
| 133 | 7 | @log.debug gLineInfo('postResolve'), {path:propOrAction, result:result} |
| 134 | 7 | res.json result |
| 135 | ||
| 136 | sendErr: (req, res, code=404, comment="Not Found") -> | |
| 137 | 1 | if req.originalUrl != '/favicon.ico' # avoid logging these |
| 138 | 1 | @log.warn gLineInfo('sendErr'), |
| 139 | comment: comment | |
| 140 | code: code | |
| 141 | req: _.at(req, ['originalUrl','protocol','hostname']) | |
| 142 | 1 | res.status(code).send comment |
| 143 |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.org | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | ### | |
| 15 | This file contains the logic to handle connections on port 443 | |
| 16 | These connections can be naked HTTPS or wrapped inside of TLS | |
| 17 | ### | |
| 18 | ||
| 19 | ### | |
| 20 | NOTE: There can be any number of EncryptedServers. A good example of that is when running the tests. | |
| 21 | The TLSServers are shared between EncryptedServers. | |
| 22 | ||
| 23 | __________________________ ________________________ | |
| 24 | 443 traffic | | *--->| TLSServer | ______________ | |
| 25 | ----------->| EncryptedServer |--* | (Dumb decrypter) |---->| HTTPServer |----> Multiple destinations | |
| 26 | |(Categorization/Routing)| * | (One of many) | ______________ | |
| 27 | __________________________ * | (Unique destination) | | |
| 28 | * _______________________| | |
| 29 | * _____________ Soon | |
| 30 | *-->| TLSServer |----------> Unblock (Vastly simplified) | |
| 31 | _____________ | |
| 32 | ### | |
| 33 | ||
| 34 | 1 | module.exports = (dnschain) -> |
| 35 | # expose these into our namespace | |
| 36 | 3 | for k of dnschain.globals |
| 37 | 84 | eval "var #{k} = dnschain.globals.#{k};" |
| 38 | ||
| 39 | 3 | libHTTPS = new ((require "./httpsUtils")(dnschain)) # TODO: httpsUtils doesn't need to be a class |
| 40 | 3 | pem = (require './pem')(dnschain) |
| 41 | 3 | httpSettings = gConf.get "http" |
| 42 | 3 | unblockSettings = gConf.get "unblock" |
| 43 | 3 | tlsLog = gNewLogger "TLSServer" |
| 44 | ||
| 45 | 3 | httpsVars = tls: Promise.resolve() |
| 46 | ||
| 47 | 3 | keyMaterial = _(httpSettings).pick(['tlsKey', 'tlsCert']).transform((o, v, k)-> |
| 48 | 6 | o[k] = { key:k, path:v, exists: fs.existsSync(v) } |
| 49 | ).value() | |
| 50 | ||
| 51 | # Auto-generate public/private key pair if they don't exist | |
| 52 | 3 | if _.some(keyMaterial, exists:false) |
| 53 | 2 | missing = _.find(keyMaterial, exists:false) |
| 54 | 2 | tlsLog.warn "File for http:#{missing.key} does not exist: #{missing.path}".bold.red |
| 55 | 2 | tlsLog.warn "Vist this link for information on how to generate this file:".bold |
| 56 | 2 | tlsLog.warn "https://github.com/okTurtles/dnschain/blob/master/docs/How-do-I-run-my-own.md#getting-started".bold |
| 57 | ||
| 58 | # In the case where one file exists but the other does not | |
| 59 | # we do not auto-generate them for the user (so as to not overwrite anything) | |
| 60 | 2 | if exists = _.find(keyMaterial, exists:true) |
| 61 | 1 | tlsLog.error "\nhttp:#{exists.key} exists at:\n\t".bold.yellow, exists.path.bold, "\nbut http:#{missing.key} does not exist at:\n\t".bold.red, missing.path.bold |
| 62 | 1 | gErr "Missing file for http:#{missing.key}" |
| 63 | ||
| 64 | 1 | tlsLog.warn "Auto-generating private key and certificate for you...".bold.yellow |
| 65 | ||
| 66 | 1 | {tlsKey, tlsCert} = gConf.chains.dnschain.stores.defaults.get('http') |
| 67 | 1 | unless httpSettings.tlsKey is tlsKey and httpSettings.tlsCert is tlsCert |
| 68 | 1 | msg = "Can't autogen keys for you because you've customized their paths" |
| 69 | 1 | if process.env.TEST_DNSCHAIN |
| 70 | 1 | tlsLog.warn "Test detected. Not throwing error:".bold, msg.bold.yellow |
| 71 | else | |
| 72 | 0 | gErr msg |
| 73 | 1 | [tlsKey, tlsCert] = [httpSettings.tlsKey, httpSettings.tlsCert] |
| 74 | 1 | httpsVars.tls = pem.genKeyCertPair(tlsKey, tlsCert).then -> |
| 75 | 1 | tlsLog.info "Successfully autogenerated", {key:tlsKey, cert:tlsCert} |
| 76 | ||
| 77 | # Fetch the public key fingerprint of the cert we're using and log to console | |
| 78 | 2 | httpsVars.tls = httpsVars.tls.then -> |
| 79 | 2 | httpsVars.tlsOptions = |
| 80 | key: fs.readFileSync httpSettings.tlsKey | |
| 81 | cert: fs.readFileSync httpSettings.tlsCert | |
| 82 | ||
| 83 | 2 | pem.certFingerprint(httpSettings.tlsCert).then (f) -> |
| 84 | 2 | httpsVars.fingerprint = f |
| 85 | 2 | tlsLog.info "Your certificate fingerprint is:", f.bold |
| 86 | ||
| 87 | 2 | class EncryptedServer |
| 88 | 2 | constructor: (@dnschain) -> |
| 89 | 7 | @log = gNewLogger "HTTPS" |
| 90 | 7 | @log.debug gLineInfo "Loading HTTPS..." |
| 91 | 7 | @rateLimiting = gConf.get 'rateLimiting:https' |
| 92 | ||
| 93 | 7 | @server = net.createServer (c) => |
| 94 | 2 | key = "https-#{c.remoteAddress}" |
| 95 | 2 | limiter = gThrottle key, => new Bottleneck @rateLimiting.maxConcurrent, @rateLimiting.minTime, @rateLimiting.highWater, @rateLimiting.strategy |
| 96 | 2 | limiter.submit (@callback.bind @), c, null |
| 97 | 7 | @server.on "error", (err) -> gErr err |
| 98 | 7 | @server.on "close", => @log.info "HTTPS server received close event." |
| 99 | 7 | gFillWithRunningChecks @ |
| 100 | ||
| 101 | start: -> | |
| 102 | 7 | @startCheck (cb) => |
| 103 | 7 | listen = => |
| 104 | 7 | @server.listen httpSettings.tlsPort, httpSettings.host, => |
| 105 | 7 | cb null, httpSettings |
| 106 | ||
| 107 | 7 | if httpsVars.tls.then |
| 108 | 7 | httpsVars.tls.then => |
| 109 | 7 | httpsVars.tls = tls.createServer httpsVars.tlsOptions, (c) => |
| 110 | 2 | libHTTPS.getStream "127.0.0.1", httpSettings.port, (err, stream) -> |
| 111 | 2 | if err? |
| 112 | 0 | tlsLog.error gLineInfo "Tunnel failed: Could not connect to HTTP Server" |
| 113 | 0 | c?.destroy() |
| 114 | 0 | return stream?.destroy() |
| 115 | 2 | c.pipe(stream).pipe(c) |
| 116 | 7 | httpsVars.tls.on "error", (err) -> |
| 117 | 0 | tlsLog.error gLineInfo(), err |
| 118 | 0 | gErr err.message |
| 119 | 7 | httpsVars.tls.listen httpSettings.internalTLSPort, "127.0.0.1", -> |
| 120 | 7 | tlsLog.info "Listening" |
| 121 | 7 | listen() |
| 122 | else | |
| 123 | 0 | listen() |
| 124 | ||
| 125 | shutdown: -> | |
| 126 | 7 | @shutdownCheck (cb) => |
| 127 | 7 | httpsVars.tls.close() # node docs don't indicate this takes a callback |
| 128 | 7 | httpsVars.tls = Promise.resolve() # TODO: Simon, this hack is necessary |
| 129 | # because without it test/https.coffee | |
| 130 | # breaks if it is not run first. | |
| 131 | # This happens because of the | |
| 132 | # `if httpsVars.tls.then` above | |
| 133 | # in `start:`. | |
| 134 | 7 | @server.close cb |
| 135 | ||
| 136 | callback: (c, cb) -> | |
| 137 | 2 | libHTTPS.getClientHello c, (err, category, host, buf) => |
| 138 | 2 | @log.debug err, category, host, buf?.length |
| 139 | 2 | if err? |
| 140 | 0 | @log.debug gLineInfo "TCP handling: "+err.message |
| 141 | 0 | cb() |
| 142 | 0 | return c?.destroy() |
| 143 | ||
| 144 | # UNBLOCK: Check if needs to be hijacked | |
| 145 | ||
| 146 | 2 | isRouted = false # unblockSettings.enabled and unblockSettings.routeDomains[host]? |
| 147 | 2 | isDNSChain = ( |
| 148 | 2 | (category == libHTTPS.categories.NO_SNI) or |
| 149 | 0 | ((not unblockSettings.enabled) and category == libHTTPS.categories.SNI) or |
| 150 | 0 | (unblockSettings.enabled and (host in unblockSettings.acceptApiCallsTo)) or |
| 151 | 0 | ((host?.split(".")[-1..][0]) == "dns") |
| 152 | ) | |
| 153 | 2 | isUnblock = false |
| 154 | ||
| 155 | 2 | [destination, port, error] = if isRouted |
| 156 | 0 | ["127.0.0.1", unblockSettings.routeDomains[host], false] |
| 157 | 2 | else if isDNSChain |
| 158 | 2 | ["127.0.0.1", httpSettings.internalTLSPort, false] |
| 159 | 0 | else if isUnblock |
| 160 | 0 | [host, 443, false] |
| 161 | else | |
| 162 | 0 | ["", -1, true] |
| 163 | ||
| 164 | 2 | if error |
| 165 | 0 | @log.error "Illegal domain (#{host})" |
| 166 | 0 | cb() |
| 167 | 0 | return c?.destroy() |
| 168 | ||
| 169 | 2 | libHTTPS.getStream destination, port, (err, stream) => |
| 170 | 2 | if err? |
| 171 | 0 | @log.error gLineInfo "Tunnel failed: Could not connect to internal TLS Server" |
| 172 | 0 | c?.destroy() |
| 173 | 0 | cb() |
| 174 | 0 | return stream?.destroy() |
| 175 | 2 | stream.write buf |
| 176 | 2 | c.pipe(stream).pipe(c) |
| 177 | 2 | c.resume() |
| 178 | 2 | @log.debug gLineInfo "Tunnel: #{host}" |
| 179 | 2 | cb() |
| 180 | ||
| 181 | 1 | getFingerprint: -> httpsVars.fingerprint |
| 182 |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.org | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | 1 | module.exports = (dnschain) -> |
| 14 | # expose these into our namespace | |
| 15 | 3 | for k of dnschain.globals |
| 16 | 84 | eval "var #{k} = dnschain.globals.#{k};" |
| 17 | ||
| 18 | # http://tools.ietf.org/html/rfc5246#section-7.4.1 | |
| 19 | # http://stackoverflow.com/questions/17832592/extract-server-name-indication-sni-from-tls-client-hello | |
| 20 | 3 | class HTTPSUtils |
| 21 | 3 | categories: { |
| 22 | SNI : 0 | |
| 23 | NO_SNI : 1 | |
| 24 | NOT_HTTPS : 2 | |
| 25 | INCOMPLETE : 3 | |
| 26 | } | |
| 27 | ||
| 28 | parseHTTPS: (packet) -> | |
| 29 | 2 | res = {} |
| 30 | 2 | try |
| 31 | 2 | res.contentType = packet.readUInt8 0 |
| 32 | 2 | if res.contentType != 22 then return [@categories.NOT_HTTPS, {}] |
| 33 | ||
| 34 | 2 | res.recordVersionMajor = packet.readUInt8 1 |
| 35 | 2 | if res.recordVersionMajor >= 7 then return [@categories.NOT_HTTPS, {}] |
| 36 | ||
| 37 | 2 | res.recordVersionMinor = packet.readUInt8 2 |
| 38 | 2 | if res.recordVersionMinor >= 7 then return [@categories.NOT_HTTPS, {}] |
| 39 | ||
| 40 | 2 | res.recordLength = packet.readUInt16BE 3 |
| 41 | ||
| 42 | 2 | res.handshakeType = packet.readUInt8 5 |
| 43 | 2 | if res.handshakeType != 1 then return [@categories.NOT_HTTPS, {}] |
| 44 | ||
| 45 | 2 | res.handshakeLength = packet[6..8] |
| 46 | 2 | res.handshakeVersion = packet.readUInt16BE 9 |
| 47 | 2 | res.random = packet[11..42] |
| 48 | ||
| 49 | 2 | res.sessionIDlength = packet.readUInt8 43 |
| 50 | 2 | pos = res.sessionIDlength + 43 + 1 |
| 51 | ||
| 52 | 2 | res.cipherSuitesLength = packet.readUInt16BE pos |
| 53 | 2 | pos += res.cipherSuitesLength + 2 |
| 54 | ||
| 55 | 2 | res.compressionMethodsLength = packet.readUInt8 pos |
| 56 | 2 | pos += res.compressionMethodsLength + 1 |
| 57 | ||
| 58 | 2 | res.extensionsLength = packet.readUInt16BE pos |
| 59 | 2 | pos += 2 |
| 60 | ||
| 61 | 2 | extensionsEnd = pos + res.extensionsLength - 1 |
| 62 | 2 | jump = 0 |
| 63 | ||
| 64 | 2 | res.extensions = {} |
| 65 | ||
| 66 | 2 | while pos < extensionsEnd |
| 67 | 6 | ext = {} |
| 68 | 6 | ext.type = packet.readUInt16BE pos |
| 69 | 6 | ext.length = packet.readUInt16BE (pos+2) |
| 70 | 6 | jump = ext.length+4 |
| 71 | 6 | ext.body = packet[pos..(pos+jump-1)] |
| 72 | 6 | res.extensions[ext.type] = ext |
| 73 | 6 | pos += jump |
| 74 | ||
| 75 | 2 | if res.extensions["0"]? |
| 76 | 0 | sniPos = 0 |
| 77 | 0 | sni = res.extensions["0"] |
| 78 | 0 | sni.sniType = sni.body.readUInt16BE 0 |
| 79 | 0 | sni.sniLength = sni.body.readUInt16BE 2 |
| 80 | 0 | sni.sniList = sni.body.readUInt16BE 4 |
| 81 | 0 | sni.sniNameType = sni.body.readUInt8 6 |
| 82 | 0 | sni.sniNameLength = sni.body.readUInt16BE 7 |
| 83 | 0 | sni.sniName = sni.body[9..(9+sni.sniNameLength)] |
| 84 | 0 | res.host = sni.sniName.toString "utf8" |
| 85 | 0 | if res.host.length != sni.sniNameLength then return [@categories.NOT_HTTPS, {}] |
| 86 | 0 | return [@categories.SNI, res] |
| 87 | else | |
| 88 | 2 | return [@categories.NO_SNI, {}] |
| 89 | catch ex | |
| 90 | 0 | return [@categories.INCOMPLETE, {}] |
| 91 | ||
| 92 | ||
| 93 | # Open a TCP socket to a remote host. | |
| 94 | getStream: (host, port, cb) -> | |
| 95 | 4 | try |
| 96 | 4 | done = (err, s) -> |
| 97 | 4 | done = -> |
| 98 | 4 | cb err, s |
| 99 | 4 | s = net.createConnection {host, port}, -> |
| 100 | 4 | done null, s |
| 101 | 4 | s.on "error", (err) -> s.destroy(); done err |
| 102 | 4 | s.on "close", -> s.destroy() |
| 103 | 4 | s.on "timeout", -> s.destroy(); done new Error "getStream timed out" |
| 104 | catch err | |
| 105 | 0 | done err |
| 106 | ||
| 107 | # Received raw TCP data in chunked mode and attempt to extract Hello data | |
| 108 | # after every chunk. Return as soon as the Hello data has been obtained. | |
| 109 | getClientHello: (c, cb) -> | |
| 110 | 2 | received = [] |
| 111 | 2 | buf = new Buffer [] |
| 112 | 2 | done = (err, category, host, buf) -> |
| 113 | 2 | c.removeAllListeners("data") |
| 114 | 2 | done = -> |
| 115 | 2 | cb err, category, host, buf |
| 116 | 2 | c.on "data", (data) => |
| 117 | 2 | c.pause() |
| 118 | 2 | received.push data |
| 119 | 2 | buf = Buffer.concat received |
| 120 | ||
| 121 | 2 | [category, parsed] = @parseHTTPS buf |
| 122 | 2 | switch category |
| 123 | when @categories.SNI | |
| 124 | 0 | done null, category, parsed.host, buf |
| 125 | when @categories.NO_SNI | |
| 126 | 2 | done null, category, null, buf |
| 127 | when @categories.NOT_HTTPS | |
| 128 | 0 | done new Error "NOT HTTPS", category, null, buf |
| 129 | when @categories.INCOMPLETE | |
| 130 | 0 | c.resume() |
| 131 | else | |
| 132 | 0 | done new Error "Unimplemented", category, null, buf |
| 133 | 2 | c.on "timeout", -> |
| 134 | 0 | c.destroy() |
| 135 | 0 | done new Error "HTTPS getClientHello timeout" |
| 136 | 2 | c.on "error", (err) -> |
| 137 | 0 | c.destroy() |
| 138 | 0 | done err |
| 139 | 2 | c.on "close", -> |
| 140 | 2 | c.destroy() |
| 141 | 2 | done new Error "HTTPS socket closed" |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.org | |
| 5 | ||
| 6 | Copyright (c) 2015 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | 1 | module.exports = (dnschain) -> |
| 15 | # expose these into our namespace | |
| 16 | 3 | for k of dnschain.globals |
| 17 | 84 | eval "var #{k} = dnschain.globals.#{k};" |
| 18 | ||
| 19 | 3 | execAsync = Promise.promisify require('child_process').exec |
| 20 | ||
| 21 | 3 | pem = |
| 22 | certFingerprint: (cert, opts={}) -> | |
| 23 | 2 | opts = _.defaults opts, {timeout:1000, encoding:'utf8'} |
| 24 | 2 | cmd = "openssl x509 -fingerprint -sha256 -text -noout -in \"#{cert}\"" |
| 25 | 2 | gLogger.debug "running: #{cmd}" |
| 26 | 2 | execAsync(cmd, opts).spread (stdout, stderr) -> |
| 27 | 2 | stdout.match(/SHA256 Fingerprint=([0-9A-F:]{95})$/m)[1] |
| 28 | .catch (err) -> | |
| 29 | 0 | gErr "Failed to read public key fingerprint: #{err?.message}" |
| 30 | ||
| 31 | genKeyCertPair: (key, cert, opts={}) -> | |
| 32 | 1 | opts = _.defaults opts, {timeout:10000, encoding:'utf8'} |
| 33 | 1 | cmd = """ |
| 34 | openssl req -new -newkey rsa:4096 -days 730 -nodes -sha256 -x509 \ | |
| 35 | 1 | -subj "/CN=#{os.hostname()}" \ |
| 36 | 1 | -keyout "#{key}" -out "#{cert}" |
| 37 | """ | |
| 38 | 1 | gLogger.debug "running: #{cmd}" |
| 39 | 1 | execAsync(cmd, opts).spread (stdout, stderr) -> |
| 40 | 1 | fs.chmodSync key, 0o600 # rw for owner, 0 for everyone else |
| 41 | .catch (err) -> | |
| 42 | 0 | gErr "Failed to generate key material: #{err?.message}" |
| 43 |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.net | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | 1 | Transform = require('stream').Transform |
| 15 | ||
| 16 | # objectMode is on by default | |
| 17 | ||
| 18 | 1 | module.exports = (dnschain) -> |
| 19 | 30 | StackedScheduler = require('./stacked-scheduler')(dnschain) |
| 20 | ||
| 21 | # expose these into our namespace | |
| 22 | 30 | for k of dnschain.globals |
| 23 | 840 | eval "var #{k} = dnschain.globals.#{k};" |
| 24 | ||
| 25 | 30 | NAME_RCODE = dns2.consts.NAME_TO_RCODE |
| 26 | 30 | RCODE_NAME = dns2.consts.RCODE_TO_NAME |
| 27 | ||
| 28 | 30 | defaults = |
| 29 | name : 'RS' | |
| 30 | stackedDelay: 0 | |
| 31 | resolver : gConf.get 'dns:oldDNS' | |
| 32 | 3 | answerFilter: (a) -> a.address |
| 33 | reqMaker : (cname) -> | |
| 34 | 2 | dns2.Request |
| 35 | question: dns2.Question {name:cname, type:'A'} # TODO: is type=A always correct? | |
| 36 | server: @resolver | |
| 37 | ||
| 38 | 30 | class ResolverStream extends Transform |
| 39 | 30 | constructor: (@opts) -> |
| 40 | 2 | @opts = _.cloneDeep @opts # clone these for safety in case outside code updates them |
| 41 | 2 | @opts.objectMode ?= true |
| 42 | 2 | defaultProps = _.keys defaults |
| 43 | # copy property values in @opts for those keys in 'defaults' into this object | |
| 44 | 2 | _.assign @, _.pick(_.defaults(@opts, defaults), defaultProps) |
| 45 | 2 | super _.omit @opts, defaultProps |
| 46 | ||
| 47 | 2 | @scheduler = new StackedScheduler @opts |
| 48 | 2 | @requests = {} |
| 49 | 2 | @reqCounter = 0 |
| 50 | 2 | @errCount = 0 |
| 51 | 2 | @log = gNewLogger @name |
| 52 | ||
| 53 | cancelRequests: (andStop=false) -> | |
| 54 | 2 | @log.debug 'cancelling requests%s', if andStop then ' and stopping' else '' |
| 55 | ||
| 56 | # cancell all active requests (those that were sent) | |
| 57 | 2 | for id,req of @requests |
| 58 | 0 | @log.debug "cancelling req-#{req.rsReqID} (should have same id: #{id})" |
| 59 | 0 | req.cancel() |
| 60 | 0 | delete @requests[id] |
| 61 | ||
| 62 | # cancell all pending requests (those to be sent) | |
| 63 | 2 | @scheduler.cancelAll() |
| 64 | 2 | @stopped = andStop |
| 65 | ||
| 66 | # 'callback' should only be called in req.on 'end' | |
| 67 | _transform: (cname, encoding, callback) -> | |
| 68 | 4 | if typeof cname != 'string' |
| 69 | 0 | gErr "cname isn't a string!", cname |
| 70 | ||
| 71 | 4 | if @stopped |
| 72 | 1 | @log.debug gLineInfo("stopped. not scheduling req for '#{cname}'") |
| 73 | 1 | return callback() |
| 74 | ||
| 75 | 3 | req = @reqMaker(cname) |
| 76 | 3 | req.rsReqID = @reqCounter++ |
| 77 | 3 | q = req.question |
| 78 | 3 | answers = [] |
| 79 | 3 | success = false |
| 80 | 3 | reqErr = undefined |
| 81 | ||
| 82 | 3 | reqErrFn = (code, msg...) => |
| 83 | 0 | reqErr = new Error util.format msg... |
| 84 | 0 | reqErr.code = code |
| 85 | 0 | @log.debug gLineInfo('reqErrfn'), reqErr.message |
| 86 | 0 | @errCount += 1 |
| 87 | ||
| 88 | 3 | req.on 'message', (err, answer) => |
| 89 | 3 | if err |
| 90 | 0 | @log.error gLineInfo("should not have an error here!"), {err:err?.message, answer:answer, q:q} |
| 91 | 0 | reqErrFn NAME_RCODE.SERVFAIL, "msg err %j: %j", q, err.message ? err |
| 92 | else | |
| 93 | 3 | @log.debug gLineInfo("req-#{req.rsReqID} message"), {resolved_q:q, to:answer.answer} |
| 94 | 3 | if answer.answer.length > 0 |
| 95 | 3 | try |
| 96 | 3 | answers.push answer.answer... |
| 97 | 3 | answer.answer.forEach (a) => @push @answerFilter a |
| 98 | 3 | success = true |
| 99 | catch e | |
| 100 | # See: https://github.com/okTurtles/dnschain/issues/43 | |
| 101 | 0 | @log.warn gLineInfo("couldn't push answer"), {error: e.message, q:q, cname:cname, answer:answer} |
| 102 | 0 | reqErrFn NAME_RCODE.SERVFAIL, "push after EOF for '%j'?: %j", cname, e.message |
| 103 | else | |
| 104 | 0 | reqErrFn NAME_RCODE.NOTFOUND, "cname not found: %j", cname |
| 105 | ||
| 106 | 3 | req.on 'timeout', => |
| 107 | 0 | @log.debug gLineInfo("req-#{req.rsReqID} timeout"), {q:q} |
| 108 | # TODO: better rcode value? am not aware of a timeout value | |
| 109 | # https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml | |
| 110 | 0 | reqErrFn NAME_RCODE.SERVFAIL, "timeout for '%j': %j", cname, req |
| 111 | ||
| 112 | 3 | req.on 'cancelled', => |
| 113 | 0 | @log.debug gLineInfo("req-#{req.rsReqID} cancelled"), {q:q} |
| 114 | 0 | success = false |
| 115 | ||
| 116 | 3 | req.on 'error', (err) => |
| 117 | 0 | @log.warn gLineInfo("req-#{req.rsReqID} error"), {q:q, err:err?.message} |
| 118 | 0 | reqErrFn NAME_RCODE.SERVFAIL, "error for '%j': %j", cname, err |
| 119 | ||
| 120 | 3 | req.on 'end', => |
| 121 | 3 | @log.debug gLineInfo("req-#{req.rsReqID} end"), {q:q, answers:answers, success:success} |
| 122 | 3 | delete @requests[req.rsReqID] |
| 123 | # it is possible that neither 'success' is true | |
| 124 | # nor is 'reqError' defined. This can happen | |
| 125 | # when 'cancelRequests' is called on us. | |
| 126 | 3 | if reqErr |
| 127 | 0 | @log.debug gLineInfo("req-#{req.rsReqID} failed"), {err:reqErr.message, q:q} |
| 128 | # we emit 'failed' instead of 'error' so that we can continue | |
| 129 | # processing other `cname`s in the pipeline. | |
| 130 | 0 | @emit 'failed', reqErr |
| 131 | 3 | else if success |
| 132 | 3 | @emit 'answers', answers |
| 133 | ||
| 134 | # we always call `callback` without an error value for the same reason that | |
| 135 | # that we emit 'failed' instead of 'error' (to continue processing) | |
| 136 | 3 | callback() |
| 137 | ||
| 138 | 3 | @log.debug gLineInfo("scheduling req-#{req.rsReqID}"), {q:q, cname:cname} |
| 139 | ||
| 140 | 3 | @scheduler.schedule => |
| 141 | # add it to the active requests when it's actually sent | |
| 142 | 3 | @requests[req.rsReqID] = req |
| 143 | 3 | @log.debug gLineInfo("sending req-#{req.rsReqID}"), {q:q} |
| 144 | 3 | req.send() |
| 145 |
| Line | Hits | Source |
|---|---|---|
| 1 | ### | |
| 2 | ||
| 3 | dnschain | |
| 4 | http://dnschain.net | |
| 5 | ||
| 6 | Copyright (c) 2014 okTurtles Foundation | |
| 7 | ||
| 8 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 9 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| 11 | ||
| 12 | ### | |
| 13 | ||
| 14 | # TODO: go through 'TODO's! | |
| 15 | ||
| 16 | 1 | module.exports = (dnschain) -> |
| 17 | # expose these into our namespace | |
| 18 | 30 | for k of dnschain.globals |
| 19 | 840 | eval "var #{k} = dnschain.globals.#{k};" |
| 20 | ||
| 21 | 30 | class StackedScheduler |
| 22 | 30 | constructor: ({@stackedDelay}) -> |
| 23 | 2 | @stackedDelay ?= 2000 # 2 seconds by default |
| 24 | 2 | @tasks = {} |
| 25 | 2 | @nextRunTime = Date.now() |
| 26 | 2 | @taskCounter = 0 |
| 27 | ||
| 28 | cancelAll: (runCallback=false)-> | |
| 29 | 2 | for key, task of @tasks |
| 30 | 0 | clearTimeout(task.tid) |
| 31 | 0 | task.callback() if runCallback |
| 32 | 0 | delete @tasks[key] |
| 33 | ||
| 34 | schedule: (callback) -> | |
| 35 | 3 | diffMillis = @nextRunTime - Date.now() |
| 36 | 3 | @nextRunTime += @stackedDelay - Math.min(diffMillis, 0) |
| 37 | 3 | nonce = @taskCounter++ |
| 38 | 3 | @tasks[nonce] = |
| 39 | callback: callback # for 'cancelAll' | |
| 40 | tid: setTimeout => | |
| 41 | 3 | delete @tasks[nonce] |
| 42 | 3 | callback() |
| 43 | , Math.max diffMillis, 0 | |
| 44 |