Skip to content
5 changes: 5 additions & 0 deletions lib/server/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ class AuthServer extends DNSServer {
this.initOptions(options);
}

setZSKFromString(str) {
this.zone.setZSKFromString(str);
return this;
}

setOrigin(name) {
this.zone.setOrigin(name);
return this;
Expand Down
189 changes: 135 additions & 54 deletions lib/zone.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ const fs = require('bfile');
const constants = require('./constants');
const util = require('./util');
const wire = require('./wire');
const dnssec = require('./dnssec');
const {keyFlags} = dnssec;
const {ZONE} = keyFlags;


const {
types,
Expand Down Expand Up @@ -46,8 +50,10 @@ class Zone {
this.origin = '.';
this.count = 0;
this.names = new Map();
this.wild = new RecordMap();
this.wild = new RecordMap(this);
this.nsec = new NameList();
this.zskpriv = null;
this.zskkey = null;
this.setOrigin(origin);
}

Expand All @@ -65,6 +71,12 @@ class Zone {
return this;
}

setZSKFromString(str) {
const [alg, zskpriv] = dnssec.decodePrivate(str);
this.zskpriv = zskpriv;
this.zskkey = dnssec.makeKey(this.origin, alg, zskpriv, ZONE);
}

setOrigin(origin) {
if (origin == null)
origin = '.';
Expand Down Expand Up @@ -95,7 +107,7 @@ class Zone {
this.wild.insert(rr);
} else {
if (!this.names.has(rr.name))
this.names.set(rr.name, new RecordMap());
this.names.set(rr.name, new RecordMap(this));

const map = this.names.get(rr.name);

Expand All @@ -121,8 +133,8 @@ class Zone {

if (map)
map.push(name, type, an);

this.wild.push(name, type, an);
else
this.wild.push(name, type, an);

return this;
}
Expand All @@ -145,27 +157,42 @@ class Zone {
return map.rrs.has(type);
}

glue(name, an) {
glue(name, an, type, ns) {
assert(util.isFQDN(name));
assert(Array.isArray(an));

this.push(name, types.A, an);
this.push(name, types.AAAA, an);
const initial = an.length;

if (!type) {
this.push(name, types.A, an);
this.push(name, types.AAAA, an);
} else {
this.push(name, type, an);
}

const final = an.length;

// If the only answer we have is a CNAME with no "glue",
// include an SOA in the authority section, just like
// if we had no answer for a name we're authoritative over.
if (initial === final)
this.push(name, types.SOA, ns);

return this;
}

find(name, type) {
const an = this.get(name, type);
const ar = [];
const ns = [];

for (const rr of an) {
switch (rr.type) {
case types.CNAME:
this.glue(rr.data.target, an);
this.glue(rr.data.target, an, type, ns);
break;
case types.DNAME:
this.glue(rr.data.target, an);
this.glue(rr.data.target, an, type, ns);
break;
case types.NS:
this.glue(rr.data.ns, ar);
Expand All @@ -182,7 +209,7 @@ class Zone {
}
}

return [an, ar];
return [an, ar, ns];
}

getHints() {
Expand Down Expand Up @@ -230,12 +257,17 @@ class Zone {
assert(util.isFQDN(name));
assert((type & 0xffff) === type);

const [an, ar] = this.find(name, type);
const labels = util.split(name);
const zone = util.from(name, labels, -this.count);
const authority = util.equal(zone, this.origin);

let [an, ar, ns] = this.find(name, type);
let glue;

// Do we have an answer?
if (an.length > 0) {
// Are we authoritative for this name?
if (!this.has(name, types.SOA)) {
if (!authority) {
// If we're not authoritative for this
// name, this is probably a request
// for a DS or NSEC record.
Expand All @@ -246,34 +278,17 @@ class Zone {
return [[], an, ar, false, true];
}

// Send the answer but do
// not set the `aa` bit.
return [an, [], ar, false, true];
}

// We're authoritative. Send the
// answer and set the `aa` bit.
return [an, [], ar, true, true];
}

const labels = util.split(name);

// Are they requesting a child of our
// origin? If not, handle the mishap
// gracefully.
if (this.origin !== '.') {
const zone = util.from(name, labels, -this.count);

// Refer them back to the root zone.
if (!util.equal(zone, this.origin)) {
const [ns, ar] = this.getHints();
return [[], ns, ar, false, true];
}
return [an, ns, ar, true, true];
}

// Couldn't find anything.
// Serve an SoA (no data).
if (labels.length === this.count) {
if (authority) {
const ns = this.get(this.origin, types.SOA);
this.proveNoData(ns);
return [[], ns, [], true, false];
Expand All @@ -284,13 +299,18 @@ class Zone {
// might have a referral for.
const index = this.count + 1;
const child = util.from(name, labels, -index);
const [ns, glue] = this.find(child, types.NS);
[ns, glue] = this.find(child, types.NS);

// Couldn't find any nameservers.
// Serve an SoA (nxdomain).
if (ns.length === 0) {
const ns = this.get(this.origin, types.SOA);
this.proveNameError(child, ns);
let ns = [];
// The root zone can prove the TLD doesn't exist with authority
// but regular authoritative name servers should be as quiet as possible.
if (this.origin === '.') {
ns = this.get(this.origin, types.SOA);
this.proveNameError(child, ns);
}
return [[], ns, [], false, false];
}

Expand Down Expand Up @@ -348,11 +368,12 @@ class Zone {
*/

class RecordMap {
constructor() {
constructor(zone) {
// type -> rrs
this.rrs = new Map();
// type covered -> sigs
this.sigs = new Map();
this.zone = zone;
}

clear() {
Expand Down Expand Up @@ -388,24 +409,94 @@ class RecordMap {
return this;
}

filterMatches(name, rrs) {
const ret = [];

for (const rr of rrs) {
if (!isWild(rr.name)) {
ret.push(rr);
continue;
}

const x = util.splitName(name);
const y = util.splitName(rr.name);

if (x.length < y.length)
continue;

// Remove '*' label and test remainder
y.shift();

let push = true;
for (let i = 1; i <= y.length; i++) {
if (y[y.length - i] !== x[x.length - i]) {
push = false;
break;
}
}
if (!push)
continue;

ret.push(rr);
}

return ret;
}

push(name, type, an) {
assert(util.isFQDN(name));
assert((type & 0xffff) === type);
assert(Array.isArray(an));

const rrs = this.rrs.get(type);
// If a name has a CNAME record, there should be no
// other records for that name in the zone.
// (RFC 1034 section 3.6.2, RFC 1912 section 2.4)
if (type !== types.CNAME) {
let rrs = this.rrs.get(types.CNAME);

if (!rrs || rrs.length === 0)
return this;
if (rrs && rrs.length > 0) {
rrs = this.filterMatches(name, rrs);
for (const rr of rrs)
an.push(convert(name, rr));

for (const rr of rrs)
an.push(convert(name, rr));
let sigs = this.sigs.get(types.CNAME);

const sigs = this.sigs.get(type);
if (sigs) {
sigs = this.filterMatches(name, sigs);
for (const rr of sigs)
an.push(convert(name, rr));
}

if (sigs) {
for (const rr of sigs)
if (!sigs && this.zone.zskkey && this.zone.zskpriv) {
// Create dnssec sig on the fly (especially useful for wildcard)
const sig = dnssec.sign(this.zone.zskkey, this.zone.zskpriv, an);
an.push(sig);
}

return this;
}
}

let rrs = this.rrs.get(type);

if (rrs && rrs.length > 0) {
rrs = this.filterMatches(name, rrs);
for (const rr of rrs)
an.push(convert(name, rr));

let sigs = this.sigs.get(type);

if (sigs) {
sigs = this.filterMatches(name, sigs);
for (const rr of sigs)
an.push(convert(name, rr));
}

if (!sigs && this.zone.zskkey && this.zone.zskpriv) {
// Create dnssec sig on the fly (especially useful for wildcard)
const sig = dnssec.sign(this.zone.zskkey, this.zone.zskpriv, an);
an.push(sig);
}
}

return this;
Expand Down Expand Up @@ -527,19 +618,9 @@ function convert(name, rr) {
if (!isWild(rr.name))
return rr;

const x = util.splitName(name);
const y = util.splitName(rr.name);

assert(y.length > 0);

if (x.length < y.length)
return rr;

rr = rr.clone();

y[0] = x[x.length - y.length];

rr.name = `${y.join('.')}.`;
rr.name = name;

return rr;
}
Expand Down
Loading