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 |