From 7ebf824e68a12328cbb01accfb8d8f996c10d291 Mon Sep 17 00:00:00 2001 From: Itsnotlupus Date: Sun, 10 Jul 2011 21:23:07 -0500 Subject: [PATCH] better draft spec support --- lib/nmcresolver.js | 262 ++++++++++++++++++++++++++++++++++----------- lib/nmcsocks.js | 4 +- 2 files changed, 200 insertions(+), 66 deletions(-) diff --git a/lib/nmcresolver.js b/lib/nmcresolver.js index cb94f13..fbaccbc 100644 --- a/lib/nmcresolver.js +++ b/lib/nmcresolver.js @@ -1,6 +1,7 @@ /** * The point of this module is to do exactly one thing: - * Given a hostname, massage it according to the + * + * Given a hostname, transform it according to the * Namecoin shared database. * * Ideally, whatever it returns can either be connected @@ -8,7 +9,7 @@ * DNS server. * * This code should obey the spec as best as possible, - * avoid infinite loops, and avoid hax.q + * avoid infinite loops, and avoid hax. * * */ @@ -21,8 +22,19 @@ var private_mode = false; var tor_mode = false; // if true, prefer i2p addresses over alternatives var i2p_mode = false; +// if true, load a test name file that override live namecoin values +var testing = false; +var testData = {}; + +// mostly here to break infinite delegate loops or similar shenanigans +const MAX_SCAN = 10; // tweak if we start to see legitimate cases for more record access for a single name resolution + +function resolveHostWithNamecoin(host, counter, callback) { + + if (counter > MAX_SCAN) { + return callback(new Error("Too many loops to resolve.")); + } -function resolveHostWithNamecoin(host, callback) { var chunks = host.toLowerCase().split(".").reverse(); var TLD = chunks.shift(); if (TLD==="") { TLD = chunks.shift(); } @@ -32,58 +44,171 @@ function resolveHostWithNamecoin(host, callback) { return; } var domain = chunks.shift(); // chunks now has sub-domains, if any - require("./nmcrpc").call("name_scan", ['d/'+domain, 1], function(err, data) { - if (err) { - callback(err); - return; + + getRecord("d/"+domain, "", function(err, data) { + if (err) { return callback(err); } + + resolveFromValue(host, null, chunks, data, counter, callback); + }); +} + +function getRecord(key, sub, callback) { + + if (testing) { + if (testData[key]!=null) { + return nameHandler(null, [{ + name:key, + value:JSON.stringify(testData[key]) + }]); + } else { } - if (data.length!=1 || data[0].name!='d/'+domain) { - callback(new Error("host not found")); - return; + } + + require("./nmcrpc").call("name_scan", [key, 1], nameHandler); + + function nameHandler(err, data) { + if (err) { return callback(err); } + + if (data.length != 1 || data[0].name!=key) { + return callback(new Error("Namecoin key not found")); } + try { + //console.log("Value for",key,"with sub=",sub,"is",data[0].value); var value = JSON.parse(data[0].value); - } catch (e) { - callback(new Error("Invalid namecoin value")); - return; + // crawl into the sub. + if (sub!="") { + var chunks = sub.split(".").reverse(); + for (var i=0,l=chunks.length;i MAX_SCAN) { + return callback(new Error("Too many loops to resolve.")); + } + + // 1. delegate processing + // we have a delegate situation if: we have a delegate key OR the whole value is an array. + var delegate = (value instanceof Array)?value:value.delegate; + if (delegate) { + if (typeof delegate == "string") { delegate = [ delegate, "" ]; } + return getRecord(delegate[0], delegate[1]||"", function(err, data) { + if (err) { return callback(err); } + + resolveFromValue(host, parent, chunks, data, counter + 1, callback); + }); + } + + // 2. import processing + while (value.import instanceof Array && value.import.length) { + // implemented by progressively modifying the current value. + var import = value.import.shift(); + if (import instanceof Array) { + return getRecord(import[0], import[1], function(err, data) { + if (err) { + // XXX is this too lax? + return resolveFromValue(host, parent, chunks, value, counter + 1, callback); + } + var subchunks = import[1].split(".").reverse(); + var base = value; + while(subchunks.length) { + var key = subchunks.shift(); + if (!base.map) { base.map = {}; } + if (!base.map.key) { base.map[key] = {}; } + base = base.map[key]; + } + // merge items from data into base. + mergeKeys(data, base); + // restart processing with our changes + resolveFromValue(host, parent, chunks, value, counter + 1, callback); + }); + } + } + + // 3. dns (/ns) resolvers are looked up here. var dns; if (value.ns&&!value.dns) { value.dns = value.ns; } if (value.dns) { - if (typeof value.dns == "string") { - dns = value.dns; - } else if (value.dns instanceof Array) { - dns = value.dns[~~(Math.random()*value.dns.length)]; - } + dns = oneOf(value.dns); } - - // 3. translate should happen here + // 4. translate should happen here if (value.translate) { // XXX the spec currently says it should only apply to subs. ignoring that part for now. - var new_host = chunks.reverse().join(".")+"."+value.translate; - return resolveHostWithNamecoin(new_host, callback); // XXX add something to prevent two domains from playing translate ping-pong. + var new_host = (chunks.length?chunks.reverse().join(".")+".":"")+value.translate; + return resolveHostWithNamecoin(new_host, counter+1, callback); } - // 4. alias check. - if (value.alias) { - // I don't keep enough state to implement that right now. XXX :'( + // 5. alias check. + if (value.alias != undefined) { + // 2 options: absolute (ends with "."), or relative to parent of current state + var alias=value.alias; + if (alias.substr(-1)==".") { + // absolute alias. just use that value directly + return resolveHostWithNamecoin(alias, counter+1, callback); + } + if (alias.substr(-1)=="@") { // something like "us.@".. + var domain=host.split(".").filter(function(e){return e!=""}).slice(-2).join("."); // XXX could probably be cached from somewhere. + return resolveHostWithNamecoin(alias.split("@")[0]+domain, counter + 1, callback); + } + // relative-to-parent alias.. + if (alias=="") { + if (parent) { + return resolveFromValue(host, null, [], parent, counter + 1, callback); + } + } else { + // crawl the parent's map to resolve this alias.. + try { + var aliasChunks = alias.split("."); + var data = parent; + while (aliasChunks.length) { + data = data.map[aliasChunks.shift()]; + } + return resolveFromValue(host, null, [], data, counter + 1, callback); + } catch (e) { + // XXX out-of-spec fallback. invalid relative alias, process as if it was an absolute alias. + return resolveHostWithNamecoin(alias, counter+1, callback); + } + } } - // 5. apply DNS if it is set. + // 6. apply DNS if it is set. // (note: if private_mode is set, avoid this path and keep going to allow // alternate resolution mechanisms to happen.) if (dns&&!private_mode) { // resolve host with @dns. + //console.log("resolving ",host,"through dns server",dns); return resolveWithDNS(host, dns, callback); } @@ -94,11 +219,11 @@ function resolveFromValue(host, chunks, value, callback) { if (!value.map || !value.map[sub]) { // check for a wildcard. if (value.map && value.map["*"]) { - return resolveFromValue(host, [], value.map["*"], callback); + return resolveFromValue(host, value, [], value.map["*"], counter, callback); } return callback(new Error("Host not found")); } - return resolveFromValue(host, chunks, value.map[sub], callback); + return resolveFromValue(host, value, chunks, value.map[sub], counter, callback); } // 1. legacy crap: if value a string? if (typeof value == "string") { @@ -115,25 +240,39 @@ function resolveFromValue(host, chunks, value, callback) { } // 2. else, find some other hardcoded value to use. + var ip; if (value.ip) { - if (typeof value.ip == "string") { - return callback(null, value.ip); + ip = oneOf(value.ip); + // if it's not an ipv4 address, we ignore it. + if (require("net").isIP(ip)==4) { + return callback(null, ip); } - if (value.ip instanceof Array) { - return callback(null, value.ip[~~(Math.random()*value.ip.length)]); + } + if (value.ip6) { + ip = oneOf(value.ip6); + // if it's not an ipv6 address, we ignore it. + if (require("net").isIP(ip)==6) { + return callback(null, ip); } } // do the map[""] special case if (value.map && value.map[""]) { - return resolveFromValue(host, chunks, value.map[""], callback); + return resolveFromValue(host, parent, chunks, value.map[""], counter, callback); } - // last-ditch attempts. Those are likely to fail. + // last-ditch attempts. Those are not likely to be useful. if (value.tor) { return callback(null, value.tor); } if (value.i2p) { return callback(null, value.i2p.b32); } callback(new Error("Host not found, or something's not implemented")); + + function oneOf(value) { + if (typeof value == "string") { return value; } + if (value instanceof Array) { + return value[~~(Math.random()*value.length)]; + } + } } function resolveWithDNS(host, server, callback) { @@ -141,41 +280,36 @@ function resolveWithDNS(host, server, callback) { // refuse to resolve return callback(new Error("Cannot use external DNS server in private mode.")); } - dns.resolve4(host, server, function(err, data) { + require("./ndns/ndns-client").resolve4(host, server, function(err, data) { if (err) { callback(err); } else { + if (!data.length) { return callback(new Error("DNS server returned no results.")); } callback(null, data[~~(Math.random()*data.length)]); } }); } -function setPrivateMode(flag) { - private_mode = !!flag; -} -function setTorMode(flag) { - tor_mode = !!flag; -} -function setI2PMode(flag) { - i2p_mode = !!flag; -} - // allow for easy command line testing. // # node resolve.js some.domain.name -if (process.argv[1].indexOf("resolve.js")>-1) { -resolveHostWithNamecoin(process.argv[2], function(err, data) { - if (err) { - console.log("ERROR: ", err.message); - } else { - console.log(data); - } -}); +if (process.argv[1].indexOf("nmcresolver.js")>-1) { + testing = true; + testData = require("./name_scan"); + resolveHostWithNamecoin(process.argv[2], 0, function(err, data) { + if (err) { + console.log("ERROR: ", err.message); + } else { + console.log("ANSWER:",data); + } + }); } module.exports = { - resolve: resolveHostWithNamecoin, - setPrivateMode: setPrivateMode, - setTorMode: setTorMode, - setI2PMode: setI2PMode + resolve: function(host, callback) { + return resolveHostWithNamecoin(host, 0, callback); + }, + setPrivateMode: function(flag) { private_mode = !!flag; }, + setTorMode: function(flag) { tor_mode = !!flag; }, + setI2PMode: function(flag) { i2p_mode = !!flag; } }; diff --git a/lib/nmcsocks.js b/lib/nmcsocks.js index 3947943..a9fe1c4 100755 --- a/lib/nmcsocks.js +++ b/lib/nmcsocks.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -const VERSION = "0.1"; +const VERSION = "0.2"; var net = require("net"); var binary = require("binary"); @@ -321,8 +321,8 @@ function socks_connect(client, host, port, response) { socketErrorHandler(new Error("Couldn't chain through "+argv.shost+":"+argv.sport+" and --chain="+argv.chain+". Dropping connection.")); } else { socket = new net.Socket({type: 'tcp4'}); - socket.connect(port, host, socketHandler); socket.once("error", socketErrorHandler); + socket.connect(port, host, socketHandler); } } else { socket = data;