Skip to content

Commit

Permalink
better draft spec support
Browse files Browse the repository at this point in the history
  • Loading branch information
itsnotlupus committed Jul 11, 2011
1 parent 302d717 commit 7ebf824
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 66 deletions.
262 changes: 198 additions & 64 deletions lib/nmcresolver.js
@@ -1,14 +1,15 @@
/**
* 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
* to directly, or can be resolved through an ordinary
* 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.
*
*
*/
Expand All @@ -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(); }
Expand All @@ -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<l;i++) {
value = value.map[chunks[i]];
}
}
} catch(e) {
return callback(new Error("Invalid namecoin value"));
}
if (value != null) {
callback(null, value);
} else {
callback(new Error("Empty namecoin value."));
}
}
}

// we're in business. Try to resolve to something useful.
resolveFromValue(host, chunks, value, callback);

});
function mergeKeys(from, to) {
for (var key in from) {
switch(typeof to[key]) {
case "undefined":
to[key] = from[key]; break;
case "object":
if (to[key] == null) {
to[key] = from[key];
} else {
mergeKeys(from[key], to[key]);
}
break;
default:
// the spec calls for not clobbering data with import, so do nothing.
}
}
}

function resolveFromValue(host, chunks, value, callback) {
// 1. delegate and import directives should be processed here. XXX :
function resolveFromValue(host, parent, chunks, value, counter, callback) {

// 2. dns (/ns) resolvers should be looked up here. XXX
//console.log("resolveFromValue(",host,",",parent,",",chunks,",",value,",",counter,")");

if (counter > 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);
}

Expand All @@ -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") {
Expand All @@ -115,67 +240,76 @@ 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) {
if (private_mode) {
// 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; }
};

4 changes: 2 additions & 2 deletions 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");
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 7ebf824

Please sign in to comment.