Compare commits

...

36 Commits

Author SHA1 Message Date
AJ ONeal a4a3a89fd2 merge with upstream 2018-05-22 17:16:11 +00:00
AJ ONeal 1517ba0a4b handle bad packet more correctly 2018-02-21 11:39:59 -07:00
AJ ONeal d6f0dc83d3 cleanup 2018-02-01 15:20:46 -07:00
AJ ONeal 9ca66313ec cleanup 2018-02-01 15:15:07 -07:00
AJ ONeal b0a12260ea WIP authority vs regular records, fix query param 2018-02-01 11:10:55 -07:00
AJ ONeal 0e74e2db18 can delete zone 2018-01-31 19:19:11 -07:00
AJ ONeal 16bceb8a8a can delete zone 2018-01-31 19:09:26 -07:00
AJ ONeal 92ad26d18e WIP create new zone 2018-01-30 22:03:05 -07:00
AJ ONeal 49b1aa0ce7 WIP create new zone 2018-01-30 20:42:14 -07:00
AJ ONeal 98a64a3d34 WIP get peer ns addresses 2018-01-30 12:03:25 -07:00
AJ ONeal d5d1cf5782 WIP create new zone 2018-01-30 01:33:19 -07:00
AJ ONeal ae1720bed3 minor fix 2018-01-29 15:54:56 -07:00
AJ ONeal 04057aa94a migrate from v1.2 2018-01-29 15:17:55 -07:00
AJ ONeal f479c55501 better error handling 2018-01-29 15:01:10 -07:00
AJ ONeal 9dfa8ed323 delete existing record 2018-01-29 14:54:03 -07:00
AJ ONeal 656c803720 add new record 2018-01-29 11:35:34 -07:00
AJ ONeal 92056f6d55 better organization, 'new' button 2018-01-26 03:23:12 -07:00
AJ ONeal 77b69d59a1 update existing records 2018-01-26 01:42:24 -07:00
AJ ONeal 77e3096cd3 update existing records 2018-01-24 03:06:32 -07:00
AJ ONeal d586e011e2 WIP update existing records 2018-01-23 15:21:20 -07:00
AJ ONeal 574b7472a2 WIP save records 2018-01-21 01:47:37 -07:00
AJ ONeal 8ecd7a8ebf WIP save records 2018-01-21 01:04:39 -07:00
AJ ONeal 2aa57739ed WIP edit records 2018-01-20 21:23:59 -07:00
AJ ONeal 4612f2cbad WIP edit records 2018-01-20 21:08:29 -07:00
AJ ONeal 7118cb6852 WIP edit records 2018-01-17 17:01:44 -07:00
AJ ONeal 4f5f330862 WIP edit record form 2018-01-17 02:26:07 -07:00
AJ ONeal 7a36e5f685 show multiple record types 2018-01-17 02:17:39 -07:00
AJ ONeal fa45842f9f small refactor 2018-01-17 01:03:05 -07:00
AJ ONeal 8d329b93b2 WIP explore records 2018-01-12 02:46:17 -07:00
AJ ONeal 72e920c1dd WIP add jwt auth, myjqlite 2018-01-12 02:03:41 -07:00
AJ ONeal b2ced1a492 WIP show ADNS data 2018-01-10 14:54:08 -07:00
AJ ONeal 96dd311fc5 add http listing 2018-01-10 01:25:56 -07:00
AJ ONeal 66a267090b WIP add http rest interface 2018-01-10 08:14:10 +00:00
AJ ONeal 94cf2aa4bc WIP add http rest interface 2018-01-10 08:14:05 +00:00
AJ ONeal abaed056e5 v1.2.2 2017-12-14 21:32:20 -07:00
AJ ONeal e2393d965a don't throw on error 2017-12-15 04:20:16 +00:00
13 changed files with 2578 additions and 225 deletions

View File

@ -34,6 +34,7 @@ cli.parse({
//, 'serve': [ 's', 'path to json file with array of responses to issue for given queries', 'string' ]
//, 'type': [ 't', 'type (defaults to ANY for dns and PTR for mdns)', 'string' ]
//, 'query': [ 'q', 'a superfluous explicit option to set the query as a command line flag' ]
, 'http': [ false, 'enable http on the specified port', 'int' ]
});
cli.main(function (args, cli) {
@ -95,6 +96,9 @@ cli.main(function (args, cli) {
}
}
var engine;
var path = require('path');
var engineOpts = { filepath: path.resolve(cli.input) };
var dnsd = {};
dnsd.onMessage = function (nb, cb) {
var byteOffset = nb._dnsByteOffset || nb.byteOffset;
@ -364,7 +368,6 @@ cli.main(function (args, cli) {
}
// TODO get local answer first, if available
var path = require('path');
if (!cli.input) {
console.warn('[WARN] no db path given, must recurse if enabled');
recurse();
@ -383,9 +386,8 @@ cli.main(function (args, cli) {
sendResponse(resp);
}
var engine;
try {
engine = require('../lib/store.json.js').create({ filepath: path.resolve(cli.input) });
engine = engine || require('../lib/store.json.js').create(engineOpts);
} catch(e) {
respondWithResults(e);
return;
@ -409,6 +411,15 @@ cli.main(function (args, cli) {
if (cli.tcp /* TODO v1.3 !cli.notcp */) {
require('../lib/tcpd.js').create(cli, dnsd);
}
if (cli.http) {
try {
engine = engine || require('../lib/store.json.js').create(engineOpts);
} catch(e) {
console.error(e);
return;
}
require('../lib/httpd.js').create(cli, engine, dnsd);
}
console.log('');
if (!cli.nocmd) {

View File

@ -16,7 +16,7 @@ function getRecords(engine, qname, cb) {
// SECURITY XXX TODO var dig = require('dig.js/dns-request');
var count;
return engine.getRecords({ name: qname }, function (err, myRecords) {
return engine.records.get({ name: qname }, function (err, myRecords) {
if (err) { cb(err); return; }
function checkCount() {
@ -67,7 +67,6 @@ function getRecords(engine, qname, cb) {
// TODO allow multiple records to be returned(?)
return function (err, addresses) {
if (err || !addresses.length) {
r.id = r.id || Math.random();
delMe[r.id] = true;
} else if (addresses.length > 1) {
r._address = addresses[Math.floor(Math.random() * addresses.length)];
@ -139,19 +138,19 @@ function dbToResourceRecord(r) {
};
}
function getNs(engine, ds, results, cb) {
console.log('[DEV] getNs entered with domains', ds);
function getNs(engine, zs, results, cb) {
console.log('[DEV] getNs entered with domains', zs);
var d = ds.shift();
console.log('[DEV] trying another one', d);
var z = zs.shift();
console.log('[DEV] trying another one', z);
if (!d) {
if (!z) {
results.header.rcode = NXDOMAIN;
cb(null, results);
return;
}
var qn = d.id.toLowerCase();
var qn = z.name.toLowerCase();
return getRecords(engine, qn, function (err, records) {
if (err) { cb(err); return; }
@ -179,16 +178,16 @@ function getNs(engine, ds, results, cb) {
});
if (!results.authority.length) {
return getNs(engine, ds, results, cb);
return getNs(engine, zs, results, cb);
}
// d.vanityNs should only be vanity nameservers (pointing to this same server)
if (d.vanityNs || results.authority.some(function (ns) {
if (z.vanityNs || results.authority.some(function (ns) {
console.log('[debug] ns', ns);
return -1 !== engine.primaryNameservers.indexOf(ns.data.toLowerCase());
})) {
results.authority.length = 0;
results.authority.push(domainToSoa(engine.primaryNameservers, d));
results.authority.push(engine.zones._toSoa(z));
results.header.rcode = NXDOMAIN;
}
cb(null, results);
@ -196,54 +195,13 @@ function getNs(engine, ds, results, cb) {
});
}
function domainToSoa(primaryNameservers, domain) {
var nameservers = domain.vanityNs || primaryNameservers;
var index = Math.floor(Math.random() * nameservers.length) % nameservers.length;
var nameserver = nameservers[index];
return {
name: domain.id
, typeName: 'SOA'
, className: 'IN'
, ttl: domain.ttl || 60
// nameserver -- select an NS at random if they're all in sync
, primary: nameserver
, name_server: nameserver
// admin -- email address or domain for admin
, admin: domain.admin || ('admin.' + domain.id)
, email_addr: domain.admin || ('admin.' + domain.id)
// serial -- the version, for cache-busting of secondary nameservers. suggested format: YYYYMMDDnn
, serial: domain.serial || Math.round((domain.updatedAt || domain.createdAt || 0) / 1000)
, sn: domain.serial || Math.round((domain.updatedAt || domain.createdAt || 0) / 1000)
// refresh -- only used when nameservers following the DNS NOTIFY spec talk
, refresh: domain.refresh || 1800
, ref: domain.refresh || 1800
// retry -- only used when nameservers following the DNS NOTIFY spec talk
, retry: domain.retry || 600
, ret: domain.retry || 600
// expiration -- how long other nameservers should continue when the primary goes down
, expiration: domain.expiration || 2419200
, ex: domain.expiration || 2419200
// minimum -- how long to cache a non-existent domain (also the default ttl for BIND)
, minimum: domain.minimum || 5
, nx: domain.minimum || 5
};
}
function getSoa(primaryNameservers, domain, results, cb, answerSoa) {
function getSoa(engine, domain, results, cb, answerSoa) {
console.log('[DEV] getSoa entered');
if (!answerSoa) {
results.authority.push(domainToSoa(primaryNameservers, domain));
results.authority.push(engine.zones._toSoa(domain));
} else {
results.answer.push(domainToSoa(primaryNameservers, domain));
results.answer.push(engine.zones._toSoa(domain));
}
cb(null, results);
@ -317,7 +275,7 @@ module.exports.query = function (engine, query, cb) {
var qarr = qname.split('.');
var qnames = [];
while (qarr.length) {
qnames.push(qarr.join('.').toLowerCase());
qnames.push({ name: qarr.join('.').toLowerCase() });
qarr.shift(); // first
}
@ -326,7 +284,8 @@ module.exports.query = function (engine, query, cb) {
console.log('[DEV] qnames');
console.log(qnames);
return engine.getSoas({ names: qnames}, function (err, myDomains) {
// getSoas
return engine.zones.get(qnames, function (err, myDomains) {
console.log('[SOA] looking for', qnames, 'and proudly serving', err, myDomains);
if (err) { cb(err); return; }
@ -350,10 +309,10 @@ module.exports.query = function (engine, query, cb) {
//console.log('sorted domains', myDomains);
if (!getNsAlso) {
return getSoa(engine.primaryNameservers, myDomains[0], results, cb, answerSoa);
return getSoa(engine, myDomains[0], results, cb, answerSoa);
}
return getNs(engine, /*myDomains.slice(0)*/qnames.map(function (qn) { return { id: qn }; }), results, function (err, results) {
return getNs(engine, /*myDomains.slice(0)*/qnames, results, function (err, results) {
//console.log('[DEV] getNs complete');
if (err) { cb(err, results); return; }
@ -364,7 +323,7 @@ module.exports.query = function (engine, query, cb) {
}
// myDomains was sorted such that the longest was first
return getSoa(engine.primaryNameservers, myDomains[0], results, cb);
return getSoa(engine, myDomains[0], results, cb);
});
});

325
lib/httpd.js Normal file
View File

@ -0,0 +1,325 @@
'use strict';
module.exports.create = function (cli, engine/*, dnsd*/) {
var subparts = (cli.subject || '').split('@');
/*
{
"kty": "EC",
"use": "sig",
"crv": "P-256",
"x": "ogbK2nP6SiEIIp4w8oXBn3dcs6kljFfTbgZYG591tUU",
"y": "sB0AekMYwpvbQfAoW-2LlEWdapNhxynfj1zBtWpE9lo",
"alg": "ES256"
}
*/
var jwt;
var jwk;
var privpem;
var pubpem;
if (!subparts[1]) {
subparts = [ 'root', 'localhost' ];
// TODO generate new random key and store it
jwk = cli.privkey || require('../samples/privkey.js');
jwt = require('jsonwebtoken');
privpem = require('jwk-to-pem')(jwk, { private: true });
pubpem = require('jwk-to-pem')(jwk, { private: false });
console.info("====================");
console.info(" EC256 Private PEM: ");
console.info("====================");
console.info(privpem);
console.info();
console.info("================================");
console.info(" JWT Write Authorization Token: ");
console.info("================================");
console.info(jwt.sign(
{ sub: subparts[0]
, iss: subparts[1]
, aud: 'localhost'
, scp: '+rw@adns.org'
}
, privpem
, { notBefore: 0 // from now
, expiresIn: '2h'
, algorithm: 'ES256'
}
));
// expressed as "from now"
/*
{ NotBeforeError: jwt not active
at Object.module.exports [as verify] (digd.js/node_modules/jsonwebtoken/verify.js:117:19)
at digd.js/lib/httpd.js:112:15
at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13)
at digd.js/node_modules/express/lib/router/index.js:284:7
at Function.process_params (digd.js/node_modules/express/lib/router/index.js:335:12)
at next (digd.js/node_modules/express/lib/router/index.js:275:10)
at expressInit (digd.js/node_modules/express/lib/middleware/init.js:40:5)
at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13)
name: 'NotBeforeError',
message: 'jwt not active',
date: +050046-12-28T01:12:58.000Z }
*/
console.info("===============================");
console.info(" JWT Read Authorization Token: ");
console.info("===============================");
console.info(jwt.sign(
{ sub: subparts[0]
, iss: subparts[1]
, aud: 'localhost'
, scp: '+r@adns.org'
}
, privpem
, { notBefore: 0 // from now
, algorithm: 'ES256'
}
));
console.info("==========================");
}
function runHttp() {
var path = require('path');
var express = require('express');
var jsonParser = require('body-parser').json({ strict: true, limit: '100kb' });
var app = express();
var httpServer = require('http').createServer(app);
function hasClaim(claim) {
return function (req, res, next) {
if ((req.token.scp||'').split(/[\s,]/g).some(function (c) {
return claim === c;
})) {
next();
} else {
res.send({ error: { message: "no claim to '" + claim + "' in token" } });
return;
}
};
}
app.use('/api', function (req, res, next) {
var auth = ((req.headers.authorization || '').split(/\s+/)[1] || req.query.access_token);
var token;
if (!auth) {
res.statusCode = 403;
res.send({ error: { message: "need authorization" } });
return;
}
jwt = jwt || require('jsonwebtoken');
try {
token = jwt.decode(auth);
} catch (e) {
token = null;
}
if (!token || !token.iss) {
res.statusCode = 403;
res.send({ error: { message: "need jwt-format authorization" } });
return;
}
if (subparts[0] === token.sub && subparts[1] === token.iss) {
try {
/*
// { algorithm: 'ES256' }
{ JsonWebTokenError: invalid algorithm
at Object.module.exports [as verify] (digd.js/node_modules/jsonwebtoken/verify.js:90:17)
at digd.js/lib/httpd.js:82:15
at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13)
at digd.js/node_modules/express/lib/router/index.js:284:7
at Function.process_params (digd.js/node_modules/express/lib/router/index.js:335:12)
at next (digd.js/node_modules/express/lib/router/index.js:275:10)
at expressInit (digd.js/node_modules/express/lib/middleware/init.js:40:5)
at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13) name: 'JsonWebTokenError', message: 'invalid algorithm' }
*/
// could be that it's private but expecting public, or public but expecting private
/*
Error: error:0906D06C:PEM routines:PEM_read_bio:no start line
at Verify.verify (crypto.js:381:23)
at verify (digd.js/node_modules/jwa/index.js:68:21)
at Object.verify (digd.js/node_modules/jwa/index.js:85:18)
at Object.jwsVerify [as verify] (digd.js/node_modules/jws/lib/verify-stream.js:54:15)
at Object.module.exports [as verify] (digd.js/node_modules/jsonwebtoken/verify.js:96:17)
at digd.js/lib/httpd.js:82:15
at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13)
at digd.js/node_modules/express/lib/router/index.js:284:7
at Function.process_params (digd.js/node_modules/express/lib/router/index.js:335:12)
*/
jwt.verify(auth, pubpem, { algorithms: [ 'ES256' ] });
} catch(e) {
res.statusCode = 403;
console.error(e);
console.log(auth);
console.log(jwt.decode(auth, { complete: true }));
res.send({ error: { message: "jwt was not verified authorization" } });
return;
}
}
req.auth = auth;
req.token = token;
next();
});
app.get('/api/verify-auth', function (req, res) {
res.send({ success: true });
});
app.get('/api/peers', function (req, res) {
engine.peers.all(function (err, peers) {
res.send({ peers: peers });
});
});
app.get('/api/zones', function (req, res) {
engine.zones.all(function (err, zones) {
res.send({ zones: zones });
});
});
app.post('/api/zones', jsonParser, hasClaim('+rw@adns.org'), function (req, res) {
var upzone = req.body || {};
console.log('create zone', upzone);
engine.zones.save(upzone, function (err, zone) {
if (err) { res.send({ error: { message: err.message } }); return; }
console.log('create zone result', zone);
res.send(zone);
});
});
app.delete('/api/zones/:id', hasClaim('+rw@adns.org'), function (req, res) {
var zoneId = req.params.id;
engine.zones.destroy(zoneId, function (err, zone) {
if (err) { res.send({ error: { message: err.message } }); return; }
// zone + records
res.send(zone);
});
});
function mapRecord(r) {
return {
id: r.id
, soa: r.soa
, zone: r.zone
, name: r.name
, tld: r.tld
, type: r.type
, class: r.class
, ttl: r.ttl
, data: r.data
, address: r.address
, exchange: r.exchange
, priority: r.priority
, value: r.value
, aname: r.aname
, flag: r.flag
, tag: r.tag
, weight: r.weight
, port: r.port
, target: r.target
};
}
app.get('/api/zones/:zone/records', function (req, res) {
var zonename = req.params.zone;
// [{ name: zonename }]
engine.zones.get({ names: [ zonename ] }, function (err, zones) {
console.log('zone:');
console.log(zones[0]);
var zone = engine.zones._toSoa(zones[0]);
zone.class = zone.className;
zone.type = zone.typeName;
zone.soa = true;
engine.records.all(function (err, records) {
records = records.filter(function (r) {
return r.zone === zonename;
}).map(mapRecord);
records.unshift(zone);
res.send({ records: records });
});
});
});
app.get('/api/records', function (req, res) {
engine.records.all(function (err, records) {
res.send({ records: records.map(mapRecord) });
});
});
app.get('/api/records/:name', function (req, res) {
engine.records.all(function (err, records) {
res.send({ records: records.filter(function (r) {
if (r.name === req.params.name) {
return true;
}
var parts = req.params.name.split('.');
parts.shift();
if ('*.' + parts.join('.') === r.name) {
return true;
}
}).map(mapRecord) });
});
});
app.post('/api/records/:id?', jsonParser, hasClaim('+rw@adns.org'), function (req, res) {
var record = req.body || {};
record.id = req.params.id || record.id;
if ('SOA' === record.type) {
// TODO be strict about what can be edited
engine.zones.save(record, function (err, zone) {
if (err) {
res.send({ error: { message: err.message } });
return;
}
// { success: true }
res.send(zone);
});
} else {
engine.records.save(record, function (err, record) {
res.send({ success: true, id: record.id });
});
}
});
app.delete('/api/records/:id', jsonParser, hasClaim('+rw@adns.org'), function (req, res) {
var id = req.params.id;
engine.records.one(id, function (err, record) {
if (err) {
res.send({ error: { message: err.message } });
return;
}
if (!record) {
res.send({ error: { message: "no record with id '" + id + "' found" } });
return;
}
if ('SOA' === record.type) {
// TODO be strict about what can be edited
engine.zones.destroy(id, function (err, record) {
if (!err) {
res.send(record);
return;
}
res.send({ error: { message: err.message } });
});
} else {
engine.records.destroy(id, function (err, record) {
if (!err) {
res.send(record || { error: { message: "no record with id '" + id + "' found" } });
return;
}
res.send({ error: { message: err.message } });
});
}
});
});
app.use('/', express.static(path.join(__dirname, 'public')));
httpServer.listen(cli.http, function () {
console.info(httpServer.address().address + '#' + httpServer.address().port + ' (http)');
});
}
runHttp();
};

3
lib/public/css/style.css Normal file
View File

@ -0,0 +1,3 @@
input[class*="js-record-"] {
text-align: right
}

274
lib/public/index.html Normal file
View File

@ -0,0 +1,274 @@
<!DOCTYPE html>
<html>
<head>
<title>ADNS</title>
<link href="css/style.css" rel="stylesheet" />
</head>
<body>
<h1>ADNS Zones and Records</h1>
<p>
<label>API JWT:</label> <input class="js-jwt" type="text" placeholder="paste the api token here" />
<button class="js-jwt" type="button">Authorize</button>
</p>
<p><a class="js-peers" data-href="/api/peers?access_token=:token" target="adns-json">/api/peers</a></p>
<p><a class="js-zones" data-href="/api/zones?access_token=:token" target="adns-json">/api/zones</a></p>
<p><a data-href="/api/zones/:zone/records?access_token=:token" class="js-zone" target="adns-json">/api/zones/<code
class="js-zone">:zone</code>/records</a>
<input class="js-zone"
type="text" placeholder="example.com" /></p>
<p><a class="js-records" data-href="/api/records?access_token=:token" target="adns-json">/api/records</a></p>
<p><a data-href="/api/records/:name?access_token=:token" class="js-name" target="adns-json">/api/records/<code class="js-name">:name</code></a>
<input class="js-name"
type="text" placeholder="example.com"/></p>
<h3>Peers:</h3>
<ul class="js-peer-tpl">
<li class="js-peer">
<span class="js-peer-name">nsx.example.com</span>
<span class="js-peer-address">127.0.0.1</span>
</li>
</ul>
<h3>Zones:</h3>
<form class="js-zone-form-create">
<input type="text" class="js-zone-form-name" placeholder="ex: example.com" />
<button type="button" class="js-zone-new">Create Zone</button>
</form>
<ul class="js-zone-tpl">
<li class="js-zone">
<span class="js-zone-name">example.com</span>
<button type="button" class="js-zone-view">view</button>
<button type="button" class="js-zone-edit">edit</button>
<button type="button" class="js-zone-destroy">X</button>
</li>
</ul>
<div class="js-zone-form-tpl">
<form class="js-zone-form-soa">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">SOA</span>
<input type="text" class="js-record-name" placeholder="[zone name] example.com" disabled>
<input type="text" class="js-recordx-primary" placeholder="[primary] ns{{x}}.example.com" disabled>
<input type="text" class="js-record-admin" placeholder="[admin] admin.example.com">
<input type="text" class="js-record-expiration" placeholder="[expire] 2419200 (how long to rely on non-primary)">
<input type="text" class="js-record-minimum" placeholder="[minimum nxdomain] 5">
<input type="text" class="js-recordx-serial" placeholder="[serial] YYYYMMDDxx" disabled>
<input type="text" class="js-record-retry" placeholder="[retry] 1800 (how quickly to retry on failed update)">
<input type="text" class="js-record-refresh" placeholder="[refresh] 7200 (how often to check for updates)">
<input type="text" class="js-record-ttl" placeholder="[default record ttl] 86400">
</form>
<span class="js-record-type">Vanity Nameservers:</span>
<label><input type="checkbox" class="js-zone-form-vanityns" value=""> use vanity nameservers (requires glue records to be set at your domain reseller/registrar)</label>
<div class="js-zone-form-vns-tpl">
<form class="js-zone-form-vns">
<input type="hidden" class="js-record-id" />
<input type="text" class="js-record-host" placeholder="nsx">.<span class="js-record-zone">example.com</span>
<input type="text" class="js-record-address" placeholder="aname, ipv4, or ipv6 address">
<input type="text" class="js-record-ttl" placeholder="86400">
<!--
<button type="button" class="js-record-remove">-</button>
<button type="button" class="js-record-add">+</button>
-->
</form>
</div>
<button type="button" class="js-zone-save">Save Zone</button>
</div>
<h3>Records:</h3>
<ul class="js-record-tpl">
<li class="js-record-soa">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">SOA</span>
<span class="js-record-name">example.com</span>
<span class="js-record-primary">{{ primary }}</span>
<span class="js-record-admin">admin.example.com</span>
<span class="js-record-expiration">{{ seconds until expiration }}</span>
<span class="js-record-minimum">{{ minimum }}</span>
<span class="js-record-serial">{{ serial }}</span>
<span class="js-record-retry">{{ retry }}</span>
<span class="js-record-refresh">{{ refresh }}</span>
<span class="js-record-ttl">{{ ttl }}</span>
<button type="button" class="js-record-edit">Edit</button>
<button type="button" class="js-record-destroy">X</button>
</li>
<li class="js-record-a js-record-aaaa">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">A / AAAA</span>
<span class="js-record-name">example.com</span>
<span class="js-record-address">{{ addr }}</span>
<span class="js-record-ttl">{{ ttl }}</span>
<button type="button" class="js-record-edit">Edit</button>
<button type="button" class="js-record-destroy">X</button>
</li>
<li class="js-record-aname js-record-cname js-record-ns">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">ANAME / CNAME</span>
<span class="js-record-name">example.com</span>
<span class="js-record-data">{{ target }}</span>
<span class="js-record-ttl">{{ ttl }}</span>
<button type="button" class="js-record-edit">Edit</button>
<button type="button" class="js-record-destroy">X</button>
</li>
<li class="js-record-caa">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">CAA</span>
<span class="js-record-name">example.com</span>
<span class="js-record-flag">{{ flag }}</span>
<span class="js-record-value">{{ value }}</span>
<span class="js-record-ttl">{{ ttl }}</span>
<button type="button" class="js-record-edit">Edit</button>
<button type="button" class="js-record-destroy">X</button>
</li>
<li class="js-record-mx">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">MX</span>
<span class="js-record-name">example.com</span>
<span class="js-record-exchange">{{ target }}</span>
<span class="js-record-priority">{{ priority }}</span>
<span class="js-record-ttl">{{ ttl }}</span>
<button type="button" class="js-record-edit">Edit</button>
<button type="button" class="js-record-destroy">X</button>
</li>
<li class="js-record-ptr">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">PTR</span>
<span class="js-record-name">example.com</span>
<span class="js-record-ttl">{{ ttl }}</span>
<button type="button" class="js-record-edit">Edit</button>
<button type="button" class="js-record-destroy">X</button>
</li>
<li class="js-record-srv">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">SRV</span>
<span class="js-record-name">example.com</span>
<span class="js-record-port">{{ port }}</span>
<span class="js-record-priority">{{ priority }}</span>
<span class="js-record-ttl">{{ ttl }}</span>
<button type="button" class="js-record-edit">Edit</button>
<button type="button" class="js-record-destroy">X</button>
</li>
<li class="js-record-txt">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">TXT</span>
<span class="js-record-name">example.com</span>
<span class="js-record-data">{{ text data }}</span>
<span class="js-record-ttl">{{ ttl }}</span>
<button type="button" class="js-record-edit">Edit</button>
<button type="button" class="js-record-destroy">X</button>
</li>
<li class="js-record-typex">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">type000</span>
<span class="js-record-name">example.com</span>
<span class="js-record-rr">{{ hex }}</span>
<span class="js-record-ttl">{{ ttl }}</span>
<button type="button" class="js-record-edit">Edit</button>
<button type="button" class="js-record-destroy">X</button>
</li>
</ul>
<label>Select Type:</label>
<select class="js-record-form-type">
<option value="" selected disabled>Record Type</option>
<option value="SOA" disabled>SOA</option>
<option value="NS">NS (delegation)</option>
<option value="A">A</option>
<option value="AAAA">AAAA</option>
<option value="ANAME">ANAME</option>
<option value="CNAME">CNAME</option>
<option value="CAA">CAA</option>
<option value="MX">MX</option>
<option value="PTR">PTR</option>
<option value="SRV">SRV</option>
<option value="TXT">TXT</option>
<option value="typeX">typeX</option>
</select>
<button type="button" class="js-record-form-new">Create Record</button>
<div class="js-record-form-tpl">
<form class="js-record-form-soa">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">SOA</span>
<input type="text" class="js-record-name">
<input type="text" class="js-recordx-primary" placeholder="ns{{rnd}}.example.com" disabled>
<input type="text" class="js-record-admin">
<input type="text" class="js-record-expiration">
<input type="text" class="js-record-minimum">
<input type="text" class="js-record-serial" disabled>
<input type="text" class="js-record-retry">
<input type="text" class="js-record-refresh">
<input type="text" class="js-record-ttl">
<button type="button" class="js-record-save">save</button>
</form>
<form class="js-record-form-a js-record-form-aaaa">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">A / AAAA</span>
<input type="text" class="js-record-host">.<span class="js-record-zone">example.com</span>
<input type="text" class="js-record-address" placeholder="ex: 127.0.0.1">
<input type="text" class="js-record-ttl" placeholder="ex: 300">
<button type="button" class="js-record-save">save</button>
</form>
<form class="js-record-form-aname js-record-form-cname js-record-form-ns">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">ANAME / CNAME</span>
<input type="text" class="js-record-host">.<span class="js-record-zone">example.com</span>
<input type="text" class="js-record-data">
<input type="text" class="js-record-ttl">
<button type="button" class="js-record-save">save</button>
</form>
<form class="js-record-form-caa">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">CAA</span>
<input type="text" class="js-record-host">.<span class="js-record-zone">example.com</span>
<input type="text" class="js-record-flag">
<input type="text" class="js-record-value">
<input type="text" class="js-record-ttl">
<button type="button" class="js-record-save">save</button>
</form>
<form class="js-record-form-mx">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">MX</span>
<input type="text" class="js-record-host">.<span class="js-record-zone">example.com</span>
<input type="text" class="js-record-exchange">
<input type="text" class="js-record-priority">
<input type="text" class="js-record-ttl">
<button type="button" class="js-record-save">save</button>
</form>
<form class="js-record-form-ptr">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">PTR</span>
<input type="text" class="js-record-host">.<span class="js-record-zone">example.com</span>
<input type="text" class="js-record-ttl">
<button type="button" class="js-record-save">save</button>
</form>
<form class="js-record-form-srv">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">SRV</span>
<input type="text" class="js-record-host">.<span class="js-record-zone">example.com</span>
<input type="text" class="js-record-port">
<input type="text" class="js-record-priority">
<input type="text" class="js-record-ttl">
<button type="button" class="js-record-save">save</button>
</form>
<form class="js-record-form-txt">
<input type="hidden" class="js-record-id" />
<span class="js-record-type">TXT</span>
<input type="text" class="js-record-host">.<span class="js-record-zone">example.com</span>
<input type="text" class="js-record-data">
<input type="text" class="js-record-ttl">
<button type="button" class="js-record-save">save</button>
</form>
<form class="js-record-form-typex">
<input type="hidden" class="js-record-id" />
<label>typeX</label>
<input type="number" class="js-record-type" value="0" />
<input type="text" class="js-record-host">.<span class="js-record-zone">example.com</span>
<input type="text" class="js-record-rr">
<input type="text" class="js-record-ttl">
<button type="button" class="js-record-save">save</button>
</form>
</div>
<script src="/js/app.js"></script>
</body>
</html>

636
lib/public/js/app.js Normal file
View File

@ -0,0 +1,636 @@
(function () {
'use strict';
var cache = { recordsMap: {} };
var myZone;
window.ADNS = { cache: cache, $qs: $qs };
if (!Element.prototype.matches) {
Element.prototype.matches = Element.prototype.msMatchesSelector;
}
function $qs(qs, el) {
return (el||document).querySelector(qs);
}
function $qsa(qs, el) {
return Array.prototype.slice.call((el||document).querySelectorAll(qs));
}
function $on(selector, eventname, cb) {
if (!$on._events[eventname]) {
$on._events[eventname] = $on._dispatcher(eventname);
document.addEventListener(eventname, $on._events[eventname]);
}
if (!$on._handlers[eventname]) {
$on._handlers[eventname] = {};
}
if (!$on._handlers[eventname][selector]) {
$on._handlers[eventname][selector] = [];
}
$on._handlers[eventname][selector].push(cb);
}
$on._events = {};
$on._handlers = {};
$on._dispatcher = function (eventname) {
return function (ev) {
//console.log('event: ' + ev.type);
if (!$on._handlers[eventname]) {
console.warn('no handlers for event');
return;
}
var matches = Object.keys($on._handlers[eventname]).some(function (selector) {
if (ev.target.matches(selector)) {
$on._handlers[eventname][selector].forEach(function (cb) { cb(ev); });
return true;
}
});
if (matches) {
console.warn("no handlers for selector");
}
};
};
function verifyAuth(/*ev*/) {
auth = $qs('input.js-jwt').value;
return window.fetch(
'/api/verify-auth'
, { method: 'GET'
, headers: new window.Headers({ 'Authorization': 'Bearer ' + auth })
}
).then(function (resp) {
return resp.json().then(function (data) {
if (data.error) {
localStorage.removeItem('auth');
console.error(data.error);
window.alert('Bad HTTP Response: ' + data.error.message);
throw new Error('Bad HTTP Response: ' + data.error.message);
}
console.log('verify-auth:');
console.log(data);
localStorage.setItem('auth', auth);
changeAuth();
return fetchPeers(auth).then(function () {
return fetchZones(auth);
});
});
});
}
function fetchPeers(auth) {
return window.fetch('/api/peers', {
method: 'GET'
, headers: new window.Headers({ 'Authorization': 'Bearer ' + auth })
}).then(function (resp) {
return resp.json().then(function (data) {
var tpl = '';
var el;
cache.peers = data.peers;
cache.peers.forEach(function (peer) {
el = document.createElement('div');
el.innerHTML = tpls.peer;
console.log(el);
console.log($qs('.js-peer-name', el));
$qs('.js-peer-name', el).innerText = peer.name;
$qs('.js-peer-name', el).dataset.id = peer.name;
$qs('.js-peer', el).dataset.id = peer.name;
$qs('.js-peer-address', el).innerText = peer.address || '';
console.log(el.innerHTML);
tpl += el.innerHTML;
console.log(tpl);
});
$qs('.js-peer-tpl').innerHTML = tpl;
console.log($qs('.js-peer-tpl').innerHTML);
});
});
}
function renderZones() {
var tpl = '';
var el;
cache.zones.forEach(function (zone) {
el = document.createElement('div');
el.innerHTML = tpls.zone;
console.log(el);
console.log($qs('.js-zone', el));
$qs('.js-zone-name', el).innerText = zone.name;
$qs('.js-zone', el).dataset.id = zone.id;
$qs('.js-zone', el).dataset.name = zone.name;
console.log(el.innerHTML);
tpl += el.innerHTML;
console.log(tpl);
});
$qs('.js-zone-tpl').innerHTML = tpl;
console.log($qs('.js-zone-tpl').innerHTML);
}
function fetchZones(auth) {
return window.fetch('/api/zones', {
method: 'GET'
, headers: new window.Headers({ 'Authorization': 'Bearer ' + auth })
}).then(function (resp) {
return resp.json().then(function (data) {
if (!tpls.zone) {
tpls.zone = $qs('.js-zone-tpl').innerHTML;
}
cache.zones = data.zones;
renderZones();
});
});
}
function renderRecords() {
var tpl = '';
var el;
console.log('tpls.recordsMap:');
console.log(tpls.recordsMap);
cache.records.forEach(function (record) {
if (record.soa) {
return;
}
el = document.createElement('div');
console.log('record.type:', record.type);
el.innerHTML = tpls.recordsMap[record.type.toLowerCase()];
console.log(el);
console.log($qs('.js-record-name', el));
Object.keys(record).sort().forEach(function (key) {
if ('id' === key) { return; }
var x = $qs('.js-record-' + key, el);
if (x) {
x.innerText = record[key];
}
});
el.dataset.recordId = record.id;
$qs('input.js-record-id', el).value = record.id;
cache.recordsMap[record.id] = record;
//$qs('.js-record-type', el).innerText = record.type;
//$qs('.js-record-name', el).innerText = record.name;
//$qs('.js-record-address', el).innerText = record.address;
console.log('el.innerHTML:');
console.log(el.innerHTML);
tpl += el.innerHTML;
console.log(tpl);
});
$qs('.js-record-tpl').innerHTML = tpl;
console.log($qs('.js-record-tpl').innerHTML);
}
function fetchRecords(zone) {
return window.fetch('/api/zones/' + zone + '/records', {
method: 'GET'
, headers: new window.Headers({ 'Authorization': 'Bearer ' + auth })
}).then(function (resp) {
return resp.json().then(function (data) {
cache.records = data.records;
renderRecords();
});
});
}
// BEGIN
var tpls = {};
var auth = localStorage.getItem('auth');
if (auth) {
$qs('input.js-jwt').value = auth;
verifyAuth();
}
$on('body', 'click', function () {
console.log('woo-hoo, that tickles my body!');
});
$on('button.js-jwt', 'click', verifyAuth);
function changeAuth() {
$qs('a.js-peers').href =
$qs('a.js-peers').dataset.href.replace(/:token/, auth);
$qs('a.js-zones').href =
$qs('a.js-zones').dataset.href.replace(/:token/, auth);
$qs('a.js-records').href =
$qs('a.js-records').dataset.href.replace(/:token/, auth);
changeZone();
changeRecord();
}
function changeZone(ev) {
var val = (ev && ev.target.value || $qs('input.js-zone').value) || ':zone';
$qs('code.js-zone').innerHTML = val;
// $qs('a.js-zone').setAttribute('data-href', ...)
$qs('a.js-zone').href =
$qs('a.js-zone').dataset.href.replace(/:zone/, val).replace(/:token/, auth);
}
$on('input.js-zone', 'keyup', changeZone);
function changeRecord(ev) {
var val = (ev && ev.target.value || $qs('input.js-name').value) || ':name';
$qs('code.js-name').innerHTML = val;
$qs('a.js-name').href =
$qs('a.js-name').dataset.href.replace(/:name/, val).replace(/:token/, auth);
}
$on('input.js-name', 'keyup', changeRecord);
$on('button.js-zone-destroy', 'click', function (ev) {
var zoneId = ev.target.parentElement.dataset.id;
var zoneName = ev.target.parentElement.dataset.name;
if (!window.confirm("Remove zone '" + zoneName + "' and all associated records?")) {
return;
}
return window.fetch(
'/api/zones/' + zoneId
, { method: 'DELETE'
, headers: new window.Headers({
'Authorization': 'Bearer ' + auth
, 'Content-Type': 'application/json;charset=UTF-8'
})
}
).then(function (resp) {
return resp.json().then(function (data) {
var zone;
var records = [];
if (data.error) {
console.error(data);
window.alert(data.error.message);
return;
}
console.log('zone undo data:');
console.log(data);
cache.zones.some(function (z, i) {
if (z.id === zoneId) {
zone = cache.zones.splice(i, 1)[0];
return true;
}
});
function removeRecord(r, i) {
if (r.zone === zone.name || 'SOA' === r.type && r.name === zone.name) {
records.push(cache.records.splice(i, 1)[0]);
return true;
}
}
while (cache.records.some(removeRecord)) { continue; }
renderZones();
renderRecords();
console.log('result:', data);
});
});
});
$on('button.js-zone-view', 'click', function (ev) {
var zone = ev.target.parentElement.dataset.name;
myZone = zone;
return fetchRecords(zone);/*.then(function () {
});*/
});
function setZoneNs(zone) {
cache.peers.forEach(function (p, i) {
var $vns = $qsa('.js-zone-form-tpl .js-zone-form-vns')[i];
$qs('.js-record-zone', $vns).innerText = zone;
if (p.address) {
$qs('.js-record-address', $vns).value = p.address;
}
});
$qs('.js-zone-form-tpl .js-recordx-primary').value = 'nsx.' + zone;
}
function openZoneForm(myZone, ns) {
var d = new Date();
var zone = {
name: myZone.name || myZone
, admin: myZone.admin || ('admin.' + myZone)
, expiration: myZone.expiration || 2419200 // 4 weeks
, minimum: myZone.expiration || 5
, serial: myZone.serial || (d.getFullYear()
+ '' + (d.getMonth() + 1)
+ '' + d.getDate()
+ '' + (Math.round(Math.random() * 99)))
, retry: myZone.retry || 1800
, refresh: myZone.refresh || 7200
, ttl: myZone.ttl || 300
};
$qs('.js-zone-form-tpl').innerHTML = tpls.newZone;
ns.forEach(function () {
$qs('.js-zone-form-vns-tpl').innerHTML += tpls.newNs;
});
ns.forEach(function (p, i) {
var $vns = $qsa('.js-zone-form-tpl .js-zone-form-vns')[i];
$qs('.js-record-host', $vns).value = 'ns' + (i + 1);
//$qs('.js-record-name', $vns).value = 'ns' + (i + 1) + '.' + myZone;
$qs('.js-record-zone', $vns).innerText = myZone;
if (p.address) {
$qs('.js-record-address', $vns).value = p.address;
}
$qs('.js-record-ttl', $vns).value = 7200;
});
[ 'name', 'admin', 'expiration', 'minimum', 'retry', 'refresh', 'ttl' ].forEach(function (key) {
$qs('.js-zone-form-tpl .js-record-' + key).value = zone[key];
});
[ 'primary', 'serial' ].forEach(function (key) {
$qs('.js-zone-form-tpl .js-recordx-' + key).value = zone[key];
});
$qs('input.js-zone-form-vanityns').checked = zone.vanity;
$qs('input.js-zone-form-vanityns').dispatchEvent(new Event('change', { bubbles: true }));
console.log('val:', $qs('.js-zone-form-tpl .js-record-ttl').value);
console.log('x', $qs('.js-zone-form-tpl'));
console.log('val:', $qs('.js-zone-form-tpl .js-record-ttl').value);
}
$on('input.js-zone-form-vanityns', 'change', function (ev) {
console.log('checked:', ev.target.checked);
if (ev.target.checked) {
setZoneNs(myZone);
} else {
setZoneNs(cache.peers[0].name.split('.').slice(1).join('.'));
}
});
$on('form.js-zone-form-create', 'submit', function (ev) {
ev.preventDefault();
ev.stopPropagation();
console.log('form submit enter');
var myZone = $qs('.js-zone-form-name').value;
openZoneForm(myZone, cache.peers);
});
$on('button.js-zone-new', 'click', function (/*ev*/) {
//$qs('select.js-record-form-type').value = 'SOA';
//$qs('select.js-record-form-type').dispatchEvent(new Event('change', { bubbles: true }));
console.log('form submit click');
var myZone = $qs('.js-zone-form-name').value;
openZoneForm(myZone, cache.peers);
});
$on('button.js-zone-edit', 'click', function (ev) {
//var zoneId = ev.target.parentElement.dataset.id;
var zoneName = ev.target.parentElement.dataset.name;
myZone = zoneName;
openZoneForm(zoneName, cache.peers);
});
$on('button.js-zone-save', 'click', function (/*ev*/) {
var zone = {};
var nss = [];
[ 'id', 'name', 'admin', 'expiration', 'minimum', 'retry', 'refresh', 'ttl' ].forEach(function (key) {
zone[key] = $qs('.js-zone-form-tpl .js-record-' + key).value;
});
$qsa('.js-zone-form-tpl .js-zone-form-vns').forEach(function ($el) {
console.log('$el:');
console.log($el);
var ns = {};
[ 'id', 'host'/*, 'name'*/, 'ttl', 'address' ].forEach(function (key) {
ns[key] = $qs('.js-record-' + key, $el).value;
});
nss.push(ns);
});
console.log('zone:');
console.log(zone);
console.log('nss:');
console.log(nss);
zone.vanity = false;
if ($qs('input.js-zone-form-vanityns').checked) {
zone.vanity = true;
zone.vanityNs = nss;
}
$qs('.js-zone-form-tpl').innerHTML = '';
return window.fetch(
'/api/zones/' //+ zone.name
, { method: 'POST'
, headers: new window.Headers({
'Authorization': 'Bearer ' + auth
, 'Content-Type': 'application/json;charset=UTF-8'
})
, body: JSON.stringify(zone)
}
).then(function (resp) {
return resp.json().then(function (data) {
if (data.error) {
console.error(data);
window.alert(data.error.message);
return;
}
console.log('result:', data);
if (!zone.id) {
zone.id = data.id;
cache.zones.push(data);
renderZones();
}
});
});
});
$on('select.js-record-form-type', 'change', function (ev) {
var type = ev.target.value;
var $tpl;
console.log("form type:", type);
$tpl = $qs('.js-record-form-tpl');
$tpl.innerHTML = tpls.formsMap[type] || '';
if (!tpls.formsMap[type]) {
console.warn("no tpl for type '" + type + "'");
return;
}
$qs('.js-record-type', $tpl).innerText = type;
$qs('.js-record-zone', $tpl).innerText = myZone;
});
$on('button.js-record-form-new', 'click', function (/*ev*/) {
var type = $qs('select.js-record-form-type').value;
if (!type) {
$qs('select.js-record-form-type').value = 'A';
}
$qs('select.js-record-form-type').dispatchEvent(new Event('change', { bubbles: true }));
});
$on('button.js-record-edit', 'click', function (ev) {
var id = ev.target.parentElement.querySelector('.js-record-id').value;
var record = cache.recordsMap[id];
var formTpl = tpls.formsMap[record.type];
var $tpl;
if (!formTpl) {
formTpl = tpls.formsMap.typeX;
}
console.log(ev.target);
console.log(id);
console.log(record);
formTpl = tpls.formsMap[(record.type||'typeX')];
$qs('select.js-record-form-type').value = (record.type||'typeX');
$qs('select.js-record-form-type').dispatchEvent(new Event('change', { bubbles: true }));
$tpl = $qs('.js-record-form-tpl');
$tpl.innerHTML = formTpl || '';
record.host = record.name.replace(new RegExp('\\.?' + (record.zone || record.name).replace(/\./g, '\\.') + '$'), '');
console.log('record.type:');
console.log(record.type);
Object.keys(record).forEach(function (key) {
var $el = $qs('.js-record-' + key, $tpl);
if (!$el) {
return;
}
$el.value = record[key];
});
if ('SOA' === record.type) {
$qs('.js-record-name').disabled = 'disabled';
} else {
if (!record.host) { $qs('.js-record-host', $tpl).placeholder = '@'; }
}
$qs('.js-record-type', $tpl).innerHTML = record.type;
$qs('.js-record-zone', $tpl).innerText = myZone;
});
$on('button.js-record-save', 'click', function (ev) {
console.log('save');
var $pel = ev.target.parentElement;
var id = $qs('.js-record-id', $pel).value;
var existingRecord = cache.recordsMap[id];
var record = {};
var change;
$qsa('input[class*="js-record-"]', $pel).forEach(function ($el) {
var key;
Array.prototype.some.call($el.classList, function (str) {
if (/^js-record-/.test(str)) {
key = str.replace(/^js-record-(.*)/, '$1');
return true;
}
});
if (!key) { return; }
if ('undefined' === typeof $el.value || 'INPUT' !== $el.tagName) { return; }
record[key] = $el.value;
});
if (!record.id) {
record.id = '';
}
if (!record.zone) {
record.zone = myZone;
}
if (!record.name) {
if (record.host) {
record.name = record.host + '.' + myZone;
} else {
record.name = myZone;
}
}
record.type = record.type || $qs('.js-record-type', $pel).innerHTML || $qs('.js-record-type', $pel).value;
console.log('record.type:', record.type);
/*
if (!tpls.recordsMap[record.type.toLowerCase()]) {
record.typex = 'typex';
}
*/
if (!existingRecord) {
change = true;
cache.recordsMap[record.id] = record;
cache.records.push(record);
} else {
console.log('keys:', Object.keys(record));
Object.keys(record).forEach(function (key) {
console.log('key:', key);
//if ('SOA' === record.type && 'zone' === key) { return; }
if (existingRecord[key].toString() !== record[key].toString()) {
change = true;
existingRecord[key] = record[key];
}
});
}
if (!change) {
return;
}
renderRecords();
return window.fetch(
'/api/records/' + record.id
, { method: 'POST'
, headers: new window.Headers({
'Authorization': 'Bearer ' + auth
, 'Content-Type': 'application/json;charset=UTF-8'
})
, body: JSON.stringify(record)
}
).then(function (resp) {
return resp.json().then(function (data) {
if (data.error) {
console.error(data);
window.alert(data.error.message);
return;
}
console.log('result:', data);
if (!record.id) {
record.id = data.id;
renderRecords();
}
});
});
});
$on('button.js-record-destroy', 'click', function (ev) {
console.log('destroy');
var $pel = ev.target.parentElement;
var id = $qs('.js-record-id', $pel).value;
var existingRecord = cache.recordsMap[id];
return window.fetch(
'/api/records/' + id
, { method: 'DELETE'
, headers: new window.Headers({
'Authorization': 'Bearer ' + auth
, 'Content-Type': 'application/json;charset=UTF-8'
})
}
).then(function (resp) {
return resp.json().then(function (data) {
if (data.error) {
console.error(data);
window.alert(data.error.message);
return;
}
delete cache.recordsMap[id];
cache.records.some(function (r, i) {
if (r === existingRecord) {
cache.records.splice(i, 1);
return true;
}
});
renderRecords();
console.log('result:', data);
});
});
});
tpls.newNs = $qs('.js-zone-form-tpl .js-zone-form-vns-tpl').innerHTML;
$qs('.js-zone-form-tpl .js-zone-form-vns-tpl').innerHTML = '';
tpls.newZone = $qs('.js-zone-form-tpl').innerHTML;
tpls.peer = $qs('.js-peer-tpl').innerHTML;
$qs('.js-peer-tpl').innerHTML = '';
tpls.formsMap = {};
tpls.formsMap.SOA = $qs('.js-record-form-soa').outerHTML;
tpls.formsMap.NS = $qs('.js-record-form-ns').outerHTML;
tpls.formsMap.A = $qs('.js-record-form-a').outerHTML;
tpls.formsMap.AAAA = $qs('.js-record-form-aaaa').outerHTML;
tpls.formsMap.ANAME = $qs('.js-record-form-aname').outerHTML;
tpls.formsMap.CAA = $qs('.js-record-form-caa').outerHTML;
tpls.formsMap.CNAME = $qs('.js-record-form-cname').outerHTML;
tpls.formsMap.MX = $qs('.js-record-form-mx').outerHTML;
tpls.formsMap.PTR = $qs('.js-record-form-ptr').outerHTML;
tpls.formsMap.SRV = $qs('.js-record-form-srv').outerHTML;
tpls.formsMap.TXT = $qs('.js-record-form-txt').outerHTML;
tpls.formsMap.typeX = $qs('.js-record-form-typex').outerHTML;
tpls.recordsMap = {};
tpls.recordsMap.soa = $qs('.js-record-soa').outerHTML;
tpls.recordsMap.ns = $qs('.js-record-ns').outerHTML;
tpls.recordsMap.a = $qs('.js-record-a').outerHTML;
tpls.recordsMap.aaaa = $qs('.js-record-aaaa').outerHTML;
tpls.recordsMap.aname = $qs('.js-record-aname').outerHTML;
tpls.recordsMap.cname = $qs('.js-record-cname').outerHTML;
tpls.recordsMap.caa = $qs('.js-record-caa').outerHTML;
tpls.recordsMap.ptr = $qs('.js-record-ptr').outerHTML;
tpls.recordsMap.mx = $qs('.js-record-mx').outerHTML;
tpls.recordsMap.txt = $qs('.js-record-txt').outerHTML;
tpls.recordsMap.srv = $qs('.js-record-srv').outerHTML;
$qs('.js-record-tpl').innerHTML = '';
$qs('.js-zone-form-tpl').innerHTML = '';
$qs('select.js-record-form-type').value = '';
// Create a new 'change' event and dispatch it.
$qs('select.js-record-form-type').dispatchEvent(new Event('change', { bubbles: true }));
}());

View File

@ -4,33 +4,484 @@ module.exports.create = function (opts) {
// opts = { filepath };
var engine = { db: null };
var db = require(opts.filepath);
function notDeleted(r) {
return !r.revokedAt && !r.deletedAt;
}
engine.primaryNameservers = db.primaryNameservers;
engine.getSoas = function (query, cb) {
var myDomains = db.domains.filter(function (d) {
return -1 !== query.names.indexOf(d.id.toLowerCase());
});
process.nextTick(function () {
cb(null, myDomains);
var db = require(opts.filepath);
var stat = require('fs').statSync(opts.filepath);
var crypto = require('crypto');
//
// Manual Migration
//
db.primaryNameservers.forEach(function (ns, i, arr) {
if ('string' === typeof ns) {
ns = { name: ns };
arr[i] = ns;
}
if (!ns.id) {
ns.id = crypto.randomBytes(16).toString('hex');
}
});
db.zones = db.zones || [];
if (db.domains) {
db.zones = db.zones.concat(db.domains);
}
db.zones.forEach(function (zone) {
if (!zone.name) {
zone.name = zone.id;
zone.id = null;
}
if (!zone.id) {
zone.id = crypto.randomBytes(16).toString('hex');
}
if (!zone.createdAt) { zone.createdAt = stat.mtime.valueOf(); }
if (!zone.updatedAt) { zone.updatedAt = stat.mtime.valueOf(); }
});
db.records.forEach(function (record) {
if (!record.id) {
record.id = crypto.randomBytes(16).toString('hex');
}
});
require('fs').writeFileSync(opts.filepath, JSON.stringify(db, null, 2));
//
// End Migration
//
db.save = function (cb) {
if (db.save._saving) {
console.log('make pending');
db.save._pending.push(cb);
return;
}
db.save._saving = true;
require('fs').writeFile(opts.filepath, JSON.stringify(db, null, 2), function (err) {
console.log('done writing');
var pending = db.save._pending.splice(0);
db.save._saving = false;
cb(err);
if (!pending.length) {
return;
}
db.save(function (err) {
console.log('double save');
pending.forEach(function (cb) { cb(err); });
});
});
};
engine.getRecords = function (query, cb) {
var myRecords = db.records.slice(0).filter(function (r) {
db.save._pending = [];
if ('string' !== typeof r.name) {
return false;
engine.primaryNameservers = db.primaryNameservers;
engine.peers = {
all: function (cb) {
var dns = require('dns');
var count = db.primaryNameservers.length;
function gotRecord() {
count -= 1;
if (!count) {
cb(null, db.primaryNameservers);
}
}
function getRecord(ns) {
dns.resolve4(ns.name, function (err, addresses) {
console.log('ns addresses:');
console.log(addresses);
if (err) { console.error(err); gotRecord(); return; }
ns.type = 'A';
ns.address = addresses[0];
gotRecord();
});
}
db.primaryNameservers.forEach(getRecord);
}
};
engine.zones = {
_immutableKeys: [ 'id', 'name', 'primary', 'serial', 'revokedAt', 'changedAt', 'insertedAt', 'updatedAt', 'deletedAt' ]
, _mutableKeys: [ 'admin', 'expiration', 'minimum', 'refresh', 'retry', 'ttl', 'vanity' ]
, _dateToSerial: function (date) {
// conventionally the format is YYYYMMDDxx,
// but since it's an integer and I don't want to keep track of incrementing xx,
// epoch in seconds will do
return parseInt(Math.round(date/1000).toString().slice(-10), 10);
}
, _toSoa: function (domain) {
var nameservers = domain.vanityNs || engine.primaryNameservers.map(function (n) { return n.name; });
var index = Math.floor(Math.random() * nameservers.length) % nameservers.length;
var nameserver = nameservers[index];
return {
id: domain.id
, name: domain.name
, typeName: 'SOA'
, className: 'IN'
, ttl: domain.ttl || 60
// nameserver -- select an NS at random if they're all in sync
, primary: nameserver
, name_server: nameserver
// admin -- email address or domain for admin
, admin: domain.admin || ('admin.' + domain.name)
, email_addr: domain.admin || ('admin.' + domain.name)
// serial -- the version, for cache-busting of secondary nameservers. suggested format: YYYYMMDDnn
, serial: domain.serial || engine.zones._dateToSerial(domain.updatedAt || domain.createdAt || Date.now())
, sn: domain.serial || engine.zones._dateToSerial(domain.updatedAt || domain.createdAt || Date.now())
// refresh -- only used when nameservers following the DNS NOTIFY spec talk
, refresh: domain.refresh || 1800
, ref: domain.refresh || 1800
// retry -- only used when nameservers following the DNS NOTIFY spec talk
, retry: domain.retry || 600
, ret: domain.retry || 600
// expiration -- how long other nameservers should continue when the primary goes down
, expiration: domain.expiration || 2419200 // 4 weeks
, ex: domain.expiration || 2419200 // 4 weeks
// minimum -- how long to cache a non-existent domain (also the default ttl for BIND)
, minimum: domain.minimum || 5
, nx: domain.minimum || 5
};
}
, all: function (cb) {
process.nextTick(function () {
cb(null, db.zones.slice(0).filter(notDeleted));
});
}
, get: function (queries, cb) {
if (!Array.isArray(queries)) {
queries = queries.names.map(function (n) {
return { name: n };
});
}
var myDomains = db.zones.filter(function (d) {
return queries.some(function (q) {
return (d.name.toLowerCase() === q.name) && notDeleted(d);
});
});
process.nextTick(function () {
cb(null, myDomains);
});
}
, touch: function (zone, cb) {
var existing;
db.zones.some(function (z) {
if (z.id && zone.id === z.id) { existing = z; return true; }
if (z.name && zone.name === z.name) { existing = z; return true; }
});
if (!existing) {
cb(null, null);
return;
}
existing.updatedAt = new Date().valueOf(); // toISOString();
console.log('touch saving...');
db.save(function (err) {
cb(err, !err && existing || null);
});
}
, save: function (zone, cb) {
if (zone.id) {
console.log('update zone!');
engine.zones.update(zone, cb);
} else {
engine.zones.create(zone, cb);
}
}
, update: function (zone, cb) {
var existing;
var dirty;
db.zones.some(function (z) {
if (z.id === zone.id) {
existing = z;
return true;
}
});
if (!existing) {
console.log('no existing zone');
cb(new Error("zone for '" + zone.id + "' does not exist"), null);
return;
}
// TODO use IN in masterquest (or implement OR)
// Only return single-level wildcard?
if (query.name === r.name || ('*.' + query.name.split('.').slice(1).join('.')) === r.name) {
return true;
console.log('found existing zone');
console.log(existing);
console.log(zone);
Object.keys(zone).forEach(function (key) {
if (-1 !== engine.zones._immutableKeys.indexOf(key)) { return; }
if (existing[key] !== zone[key]) {
dirty = true;
console.log('existing key', key, existing[key], zone[key]);
existing[key] = zone[key];
}
});
zone.updatedAt = new Date().valueOf(); // toISOString(); // Math.round(Date.now() / 1000);
if (dirty) {
zone.changedAt = zone.updatedAt;
}
});
process.nextTick(function () {
cb(null, myRecords);
});
console.log('saving...');
db.save(function (err) {
cb(err, !err && existing || null);
});
}
, create: function (zone, cb) {
var newZone = { id: crypto.randomBytes(16).toString('hex') };
var existing;
var nss = [];
zone.name = (zone.name||'').toLowerCase();
db.zones.some(function (z) {
if (z.name === zone.name) {
existing = z;
return true;
}
});
if (existing) {
cb(new Error("tried to create new zone, but '" + existing.name + "' already exists"));
return;
}
newZone.name = zone.name;
newZone.createdAt = Date.now();
newZone.updatedAt = newZone.createdAt;
Object.keys(zone).forEach(function (key) {
//if (-1 !== engine.zones._immutableKeys.indexOf(key)) { return; }
if (-1 === engine.zones._mutableKeys.indexOf(key)) { return; }
newZone[key] = zone[key];
});
// TODO create NS and A records for normal and vanity nameservers
if (zone.vanity) {
newZone.vanity = true;
} else {
newZone.vanity = false;
}
db.primaryNameservers.forEach(function (ns, i) {
var nsx = 'ns' + (i + 1);
var nsZone;
var ttl = 43200; // 12h // TODO pick a well-reasoned number
var now = Date.now();
if (zone.vanity) {
nsZone = nsx + '.' + newZone.name;
} else {
nsZone = ns.name;
}
// NS example.com ns1.example.com 43200
nss.push({
id: crypto.randomBytes(16).toString('hex')
, createdAt: Date.now()
, updatedAt: Date.now()
, changedAt: Date.now()
, zone: newZone.name
, soa: true
, type: 'NS'
, data: nsZone
, name: newZone.name
, ttl: ttl
});
// A ns1.example.com 127.0.0.1 43200
nss.push({
id: crypto.randomBytes(16).toString('hex')
, createdAt: now
, updatedAt: now
, changedAt: now
, zone: newZone.name
, soa: true
, type: ns.type
, name: nsZone
, address: ns.address
, ttl: 43200 // 12h // TODO pick a good number
});
});
db.zones.push(newZone);
nss.forEach(function (ns) {
db.records.push(ns);
});
console.log('[zone] [create] saving...');
db.save(function (err) {
cb(err, !err && newZone || null);
});
}
, destroy: function (zoneId, cb) {
var zone;
var records;
var now = Date.now();
db.zones.filter(notDeleted).some(function (z) {
if (zoneId === z.id) {
zone = z;
z.deletedAt = now;
return true;
}
});
if (!zone) {
process.nextTick(function () {
cb(null, null);
});
return;
}
records = [];
db.records.filter(notDeleted).forEach(function (r) {
if (zone.name === r.zone) {
r.deletedAt = now;
records.push(r);
}
});
console.log('[zone] [destroy] saving...');
db.save(function (err) {
zone.records = records;
cb(err, !err && zone || null);
});
}
};
engine.records = {
all: function (cb) {
process.nextTick(function () {
cb(null, db.records.slice(0).filter(notDeleted));
});
}
, one: function (id, cb) {
var myRecord;
db.records.slice(0).some(function (r) {
if (id && id === r.id) {
if (notDeleted(r)) {
myRecord = r;
return true;
}
return false;
}
});
process.nextTick(function () {
cb(null, myRecord);
});
}
, get: function (query, cb) {
var myRecords = db.records.slice(0).filter(function (r) {
if ('string' !== typeof r.name) {
return false;
}
// TODO use IN in masterquest (or implement OR)
// Only return single-level wildcard?
if (query.name === r.name || ('*.' + query.name.split('.').slice(1).join('.')) === r.name) {
if (notDeleted(r)) {
return true;
}
}
});
process.nextTick(function () {
cb(null, myRecords);
});
}
, save: function (record, cb) {
function touchZone(err, r) {
if (err) { cb(err); }
if (!r) { cb(null, null); }
engine.zones.touch({ name: r.zone }, cb);
}
if (record.id) {
console.log('update record!');
engine.records.update(record, touchZone);
} else {
engine.records.create(record, touchZone);
}
}
, update: function (record, cb) {
var existing;
var dirty;
db.records.some(function (r) {
if (r.id === record.id) {
existing = r;
return true;
}
});
if (!existing) {
console.log('no existing record');
cb(new Error("record for '" + record.id + "' does not exist"), null);
return;
}
console.log('found existing record');
console.log(existing);
console.log(record);
Object.keys(record).forEach(function (key) {
var keys = [ 'name', 'id', 'zone', 'revokedAt', 'changedAt', 'insertedAt', 'updatedAt', 'deletedAt' ];
if (-1 !== keys.indexOf(key)) { return; }
if (existing[key] !== record[key]) {
dirty = true;
console.log(existing[key], record[key]);
existing[key] = record[key];
}
});
record.updatedAt = new Date().valueOf(); // toISOString(); // Math.round(Date.now() / 1000);
if (dirty) {
record.changedAt = record.updatedAt;
}
console.log('saving...');
db.save(function (err) {
cb(err, !err && existing || null);
});
}
, create: function (record, cb) {
var obj = { id: crypto.randomBytes(16).toString('hex') };
console.log('found existing record');
console.log(record);
//var keys = [ 'name', 'id', 'zone', 'revokedAt', 'changedAt', 'insertedAt', 'updatedAt', 'deletedAt' ];
//var okeys = [ 'name', 'zone', 'admin', 'data', 'expiration', 'minimum', 'serial', 'retry', 'refresh', 'ttl', 'type' ]; // primary
var okeys = [ 'name', 'zone', 'type', 'data', 'class', 'ttl', 'address'
, 'exchange', 'priority', 'port', 'value', 'tag', 'flag', 'aname' ];
okeys.forEach(function (key) {
if ('undefined' !== typeof record[key]) {
obj[key] = record[key];
}
});
record.updatedAt = new Date().valueOf(); // toISOString(); // Math.round(Date.now() / 1000);
//record.changedAt = record.updatedAt;
record.insertedAt = record.updatedAt;
record.createdAt = record.updatedAt;
console.log('saving new...');
db.records.push(record);
db.save(function (err) {
cb(err, record);
});
}
, destroy: function (id, cb) {
var record;
db.records.some(function (r/*, i*/) {
if (id === r.id) {
record = r;
r.deletedAt = Date.now();
//record = db.records.splice(i, 1);
return true;
}
});
process.nextTick(function () {
db.save(function (err) {
cb(err, record);
});
});
}
};
return engine;

View File

@ -27,9 +27,10 @@ module.exports.create = function (cli, dnsd) {
// TODO pad two bytes for lengths
dnsd.onMessage(nb, function (err, newAb, dbgmsg) {
var lenbuf = Buffer.from([ newAb.length >> 8, newAb.length & 255 ]);
// TODO XXX generate legit error packet
if (err) { console.error("Error", err); c.end(); return; }
var lenbuf = Buffer.from([ newAb.length >> 8, newAb.length & 255 ]);
console.log('TCP ' + dbgmsg);
c.write(lenbuf);
@ -53,14 +54,15 @@ module.exports.create = function (cli, dnsd) {
process.exit(0);
}
console.error("TCP Server Error:");
console.error(err);
console.error(err.stack);
tcpServer.close(function () {
setTimeout(runTcp, 1000);
});
//throw new Error(err);
});
tcpServer.listen(cli.port, function () {
console.log('TCP Server bound');
console.log(tcpServer.address().address + '#' + tcpServer.address().port + ' (tcp)');
});
return tcpServer;

View File

@ -1,58 +1,66 @@
'use strict';
module.exports.create = function (cli, dnsd) {
var server = require('dgram').createSocket({
type: cli.udp6 ? 'udp6' : 'udp4'
, reuseAddr: true
});
server.bind({
port: cli.port
, address: cli.address
});
var handlers = {};
handlers.onError = function (err) {
if ('EACCES' === err.code) {
console.error("");
console.error("EACCES: Couldn't bind to port. You probably need to use sudo, authbind, or setcap.");
console.error("");
process.exit(123);
return;
}
console.error("error:", err.stack);
server.close();
};
handlers.onMessage = function (nb, rinfo) {
//console.log('[DEBUG] got a UDP message', nb.length);
//console.log(nb.toString('hex'));
dnsd.onMessage(nb, function (err, newAb, dbgmsg) {
// TODO send legit error message
if (err) { server.send(Buffer.from([0x00])); return; }
server.send(newAb, rinfo.port, rinfo.address, function () {
console.log(dbgmsg, rinfo.port, rinfo.address);
});
function runUdp() {
var server = require('dgram').createSocket({
type: cli.udp6 ? 'udp6' : 'udp4'
, reuseAddr: true
});
server.bind({
port: cli.port
, address: cli.address
});
};
handlers.onListening = function () {
/*jshint validthis:true*/
var server = this;
var handlers = {};
handlers.onError = function (err) {
if ('EACCES' === err.code) {
console.error("");
console.error("EACCES: Couldn't bind to port. You probably need to use sudo, authbind, or setcap.");
console.error("");
process.exit(123);
return;
}
console.error("UDP Server Error:");
console.error(err.stack);
server.close(function () {
setTimeout(runUdp, 1000);
});
//throw new Error(err);
};
if (cli.mdns || '224.0.0.251' === cli.nameserver) {
server.setBroadcast(true);
server.addMembership(cli.nameserver);
}
handlers.onMessage = function (nb, rinfo) {
//console.log('[DEBUG] got a UDP message', nb.length);
//console.log(nb.toString('hex'));
console.log('');
console.log('Bound and Listening:');
console.log(server.address().address + '#' + server.address().port + ' (' + server.type + ')');
};
dnsd.onMessage(nb, function (err, newAb, dbgmsg) {
// TODO send legit error message
if (err) { server.send(Buffer.from([0x00]), rinfo.port, rinfo.address); return; }
server.send(newAb, rinfo.port, rinfo.address, function () {
console.log('[dnsd.onMessage] ' + dbgmsg, rinfo.port, rinfo.address);
});
});
};
server.on('error', handlers.onError);
server.on('message', handlers.onMessage);
server.on('listening', handlers.onListening);
handlers.onListening = function () {
/*jshint validthis:true*/
var server = this;
return server;
if (cli.mdns || '224.0.0.251' === cli.nameserver) {
server.setBroadcast(true);
server.addMembership(cli.nameserver);
}
console.log('');
console.log('Bound and Listening:');
console.log(server.address().address + '#' + server.address().port + ' (' + server.type + ')');
};
server.on('error', handlers.onError);
server.on('message', handlers.onMessage);
server.on('listening', handlers.onListening);
return server;
}
return runUdp();
};

View File

@ -1,6 +1,6 @@
{
"name": "digd.js",
"version": "1.2.1",
"version": "1.3.0",
"description": "A lightweight DNS / mDNS daemon (server) for creating and capturing DNS and mDNS query and response packets to disk as binary and/or JSON. Options are similar to the Unix dig command.",
"main": "bin/digd.js",
"homepage": "https://git.coolaj86.com/coolaj86/digd.js",
@ -47,7 +47,11 @@
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com)",
"license": "MIT OR Apache-2.0",
"dependencies": {
"body-parser": "^1.18.2",
"dig.js": "git+https://git.coolaj86.com/coolaj86/dig.js#v1.3",
"hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4"
"express": "^4.16.2",
"hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4",
"jsonwebtoken": "^8.1.0",
"jwk-to-pem": "^1.2.6"
}
}

View File

@ -1,86 +1,764 @@
{ "primaryNameservers": [ "ns1.daplie.com", "ns2.daplie.com", "ns3.daplie.com" ]
, "domains": [
{ "id": "daplie.com", "revokedAt": 0 }
, { "id": "daplie.domains", "revokedAt": 0, "vanityNs": [ "ns1.daplie.domains", "ns2.daplie.domains", "ns3.daplie.domains" ] }
, { "id": "daplie.me", "revokedAt": 0, "vanityNs": [ "ns1.daplie.me", "ns2.daplie.me", "ns3.daplie.me" ] }
, { "id": "oauth3.org", "revokedAt": 0 }
{
"primaryNameservers": [
{
"name": "ns1.daplie.com",
"id": "65a90f49f906ee5a3823e443ce5cc9a9"
},
{
"name": "ns2.daplie.com",
"id": "5c05ac90852ee645e465da523b306800"
},
{
"name": "ns3.daplie.com",
"id": "24dd0e0881f900b0bb3ed7bf9267e8f1"
}
],
"zones": [
{
"id": "1dd4ca0da57dbab8b536e4a4148ebf7b",
"revokedAt": 0,
"name": "daplie.com"
},
{
"id": "b3a542f49a1cd4bc417622b9f0712400",
"revokedAt": 0,
"vanityNs": [
"ns1.daplie.domains",
"ns2.daplie.domains",
"ns3.daplie.domains"
],
"name": "daplie.domains"
},
{
"id": "e2cf3bc08cf090d139ea45a6542b0caa",
"revokedAt": 0,
"vanityNs": [
"ns1.daplie.me",
"ns2.daplie.me",
"ns3.daplie.me"
],
"name": "daplie.me"
},
{
"id": "e8ba9456b5966d22b5737fccc09a2685",
"revokedAt": 0,
"name": "oauth3.org"
}
],
"records": [
{
"zone": "daplie.com",
"name": "daplie.com",
"type": "NS",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "ns1",
"data": "ns1.daplie.com",
"id": "1d650317017daa83f493786268a267d9"
},
{
"zone": "daplie.com",
"name": "daplie.com",
"type": "NS",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "ns2",
"data": "ns2.daplie.com",
"id": "aabaa77aa289d20d6e55bcc49da2f543"
},
{
"zone": "daplie.com",
"name": "daplie.com",
"type": "NS",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "ns3",
"data": "ns3.daplie.com",
"id": "d523d2fe91ea2fc7d5f013536f583b38"
},
{
"zone": "daplie.com",
"name": "ns1.daplie.com",
"type": "A",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "ns1",
"address": "45.55.1.122",
"id": "19503a7b0dc6ec9bf512998fe69fa3cb"
},
{
"zone": "daplie.com",
"name": "ns2.daplie.com",
"type": "A",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "ns2",
"address": "45.55.254.197",
"id": "12b35252ff4a9d9b08c7824ebd20d452"
},
{
"zone": "daplie.com",
"name": "ns3.daplie.com",
"type": "A",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "ns3",
"address": "159.203.25.112",
"id": "1db74720ae593c3bbe171882cf34335c"
},
{
"zone": "daplie.me",
"name": "daplie.me",
"type": "NS",
"class": "IN",
"ttl": 43200,
"tld": "me",
"sld": "daplie",
"sub": "ns1",
"data": "ns1.daplie.me",
"id": "98f8f748dffc0c1025b3154d67a71bad"
},
{
"zone": "daplie.me",
"name": "daplie.me",
"type": "NS",
"class": "IN",
"ttl": 43200,
"tld": "me",
"sld": "daplie",
"sub": "ns2",
"data": "ns2.daplie.me",
"id": "0b28c6ed7f2cbe57590e673f12df2ee6"
},
{
"zone": "daplie.me",
"name": "daplie.me",
"type": "NS",
"class": "IN",
"ttl": 43200,
"tld": "me",
"sld": "daplie",
"sub": "ns3",
"data": "ns3.daplie.me",
"id": "870820f0937a0066dadc2ba283d9afcc"
},
{
"zone": "daplie.me",
"name": "ns1.daplie.me",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "me",
"sld": "daplie",
"sub": "ns1",
"address": "45.55.1.122",
"id": "99f98a25d228108c573a91be55f90e38"
},
{
"zone": "daplie.me",
"name": "ns2.daplie.me",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "me",
"sld": "daplie",
"sub": "ns2",
"address": "45.55.254.197",
"id": "0bbcf7056c4191f48d0c4b4394295d59"
},
{
"zone": "daplie.me",
"name": "ns3.daplie.me",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "me",
"sld": "daplie",
"sub": "ns3",
"address": "159.203.25.112",
"id": "d017cadd989d00beab9abf855ef86df2"
},
{
"zone": "oauth3.org",
"name": "ns1.oauth3.org",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "org",
"sld": "oauth3",
"sub": "ns1",
"address": "45.55.1.122",
"id": "dbd12081d23ba12c17b0cc8c58f4a578"
},
{
"zone": "oauth3.org",
"name": "ns2.oauth3.org",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "org",
"sld": "oauth3",
"sub": "ns2",
"address": "45.55.254.197",
"id": "a6b2a9c476e20ea34c61d834303f8fff"
},
{
"zone": "oauth3.org",
"name": "ns3.oauth3.org",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "org",
"sld": "oauth3",
"sub": "ns3",
"address": "159.203.25.112",
"id": "59a041995080f04c7516a5cdc5925494"
},
{
"zone": "oauth3.org",
"name": "oauth3.org",
"type": "NS",
"class": "IN",
"ttl": 43200,
"tld": "me",
"sld": "oauth3",
"sub": "ns1",
"data": "ns1.oauth3.org",
"id": "7ae22868acb69760f56a2a1b98285a19"
},
{
"zone": "oauth3.org",
"name": "oauth3.org",
"type": "NS",
"class": "IN",
"ttl": 43200,
"tld": "me",
"sld": "oauth3",
"sub": "ns2",
"data": "ns2.oauth3.org",
"id": "237901ec60a8c1812250f9c389ada7b1"
},
{
"zone": "oauth3.org",
"name": "oauth3.org",
"type": "NS",
"class": "IN",
"ttl": 43200,
"tld": "me",
"sld": "oauth3",
"sub": "ns3",
"data": "ns3.oauth3.org",
"id": "04bc0d1dfae48ad560bc05e60f834087"
},
{
"zone": "daplie.com",
"name": "daplie.com",
"type": "A",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"address": "23.228.168.108",
"id": "52fabdc61de1e0c3e941f82354509c4d"
},
{
"zone": "daplie.com",
"name": "daplie.com",
"type": "TXT",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"data": [
"v=spf1 include:mailgun.org include:spf.mandrillapp.com include:_spf.google.com include:servers.mcsv.net include:mail.zendesk.com ~all"
],
"id": "dee3d19cfb94616871f2dd2f20a9b025"
},
{
"zone": "daplie.com",
"name": "www.daplie.com",
"type": "A",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "www",
"address": "23.228.168.108",
"id": "e0cf7db3a53773e2b1b18c8c813feb7e"
},
{
"zone": "daplie.com",
"name": "daplie.com",
"type": "MX",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"exchange": "mxa.mailgun.org",
"priority": 10,
"id": "9226b3e23aeecc6e40158c1aa8f94900"
},
{
"zone": "daplie.com",
"name": "daplie.com",
"type": "MX",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"exchange": "mxb.mailgun.org",
"priority": 10,
"id": "cd4c0912c7cd6c66bc35a25c1a7fcb44"
},
{
"zone": "daplie.com",
"name": "email.daplie.com",
"type": "CNAME",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "email",
"data": "mailgun.org",
"id": "ae35a02033e64ec171ea47f6bee614a4"
},
{
"zone": "daplie.com",
"name": "k1._domainkey.daplie.com",
"type": "CNAME",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "k1._domainkey",
"data": "dkim.mcsv.net",
"id": "34a9bab7b4cf424aac48e16c9151ed28"
},
{
"zone": "daplie.com",
"name": "smtp._domainkey.daplie.com",
"type": "TXT",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "smtp._domainkey",
"data": [
"k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdEzzYX8U31O5p5Uvyb1B50/JPMcKnsnIQcPDWWYkBUQxMt+FyD1SRZLCaVxWybZ8eFQUwxlh0qFeLd/mIIGhCazQ74a3AH+TJhz4gOAvNQHmWvS0Sv9ZZjGuDM/RdOAFSwZET8+WUpJfDADfijihj5KqMab13NDDLOQ96wObuwQIDAQAB"
],
"id": "c421b6ec538d0155b48a3981ea1b96c4"
},
{
"zone": "daplie.com",
"name": "mandrill._domainkey.daplie.com",
"type": "TXT",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "mandrill._domainkey",
"data": [
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrLHiExVd55zd/IQ/J/mRwSRMAocV/hMB3jXwaHH36d9NaVynQFYV8NaWi69c1veUtRzGt7yAioXqLj7Z4TeEUoOLgrKsn8YnckGs9i3B3tVFB+Ch/4mPhXWiNfNdynHWBcPcbJ8kjEQ2U8y78dHZj1YeRXXVvWob2OaKynO8/lQIDAQAB;"
],
"id": "b99eaca18715483ae51602043128c3ce"
},
{
"zone": "daplie.com",
"name": "iqqsuxwfyvyw.daplie.com",
"type": "CNAME",
"class": "IN",
"ttl": 5,
"tld": "com",
"sld": "daplie",
"sub": "iqqsuxwfyvyw",
"data": "gv-roynzijsoqayyg.dv.googlehosted.com",
"id": "e6fce430c0c4c0841c0c081b9c5a34f3"
},
{
"zone": "daplie.com",
"name": "support.daplie.com",
"type": "CNAME",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "support",
"data": "daplie.zendesk.com",
"id": "8345c4944c5b8d1c997f51f9085db1c2"
},
{
"zone": "daplie.com",
"name": "proxy.tardigrade.devices.daplie.com",
"type": "A",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "proxy.tardigrade.devices",
"address": "23.228.168.108",
"id": "ca8456b1003b929b734ffac3d442ffa9"
},
{
"zone": "daplie.com",
"name": "redleader.devices.daplie.com",
"type": "A",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "redleader.devices",
"address": "104.36.98.166",
"id": "6a78a8131a3bf053c6e3d1c9ad6b7120"
},
{
"zone": "daplie.com",
"name": "beast.devices.daplie.com",
"type": "A",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "beast.devices",
"address": "96.19.92.42",
"id": "7a83df9c98ea6e86574a2966846a3a80"
},
{
"zone": "daplie.com",
"name": "ossus.devices.daplie.com",
"type": "A",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "ossus.devices",
"address": "73.65.206.97",
"id": "b7731ad942a5051acc52054a5b228a5b"
},
{
"zone": "daplie.com",
"name": "leo.devices.daplie.com",
"type": "A",
"class": "IN",
"ttl": 3600,
"tld": "com",
"sld": "daplie",
"sub": "leo.devices",
"address": "45.56.59.142",
"id": "05d05b135fa7ee4aaca663f82263035f"
},
{
"zone": "daplie.com",
"name": "proxy.leo.devices.daplie.com",
"type": "A",
"class": "IN",
"ttl": 3600,
"tld": "com",
"sld": "daplie",
"sub": "proxy.leo.devices",
"address": "45.56.59.142",
"id": "7f235d22c012df06f851f4fb074dcf2d"
},
{
"zone": "daplie.com",
"name": "git.daplie.com",
"type": "A",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "git",
"address": "23.228.168.108",
"id": "7cc0dfd69956ac97193d4d64c7911ee1"
},
{
"zone": "daplie.com",
"name": "tunnel.daplie.com",
"type": "A",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "tunnel",
"address": "162.243.160.23",
"id": "19d036f38c44598e36368f50f38ca3ac"
},
{
"zone": "daplie.com",
"name": "api.daplie.com",
"type": "A",
"class": "IN",
"ttl": 43200,
"tld": "com",
"sld": "daplie",
"sub": "api",
"address": "23.228.168.108",
"id": "143c7ef20ebed52fd422efa9bd8edf44"
},
{
"zone": "daplie.com",
"name": "preorder.daplie.com",
"type": "CNAME",
"class": "IN",
"ttl": 5,
"tld": "com",
"sld": "daplie",
"sub": "preorder",
"data": "daplie.myshopify.com",
"id": "07ed749a2bb04d15c20b0df41e20e71b"
},
{
"zone": "daplie.com",
"name": "rvpn.daplie.com",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "com",
"sld": "daplie",
"sub": "rvpn",
"address": "104.236.182.24",
"id": "3e57f0a99a0232e33ffc78018dfa5589"
},
{
"zone": "daplie.com",
"name": "mailapp.daplie.com",
"type": "CNAME",
"class": "IN",
"ttl": 5,
"tld": "com",
"sld": "daplie",
"sub": "mailapp",
"data": "mandrillapp.com",
"id": "069069ef7f8b0ffd7182a55135810c3f"
},
{
"zone": "daplie.com",
"name": "hero.daplie.com",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "com",
"sld": "daplie",
"sub": "hero",
"address": "138.197.54.15",
"id": "3fbc0c3d51b5ae9afe34a7d2a7f3e480"
},
{
"zone": "daplie.com",
"name": "mattermost.daplie.com",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "com",
"sld": "daplie",
"sub": "mattermost",
"address": "23.228.168.108",
"id": "d4a05a3bb296a8b7e5ae579208d6a7dd"
},
{
"zone": "daplie.com",
"name": "china-ftp.daplie.com",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "com",
"sld": "daplie",
"sub": "china-ftp",
"address": "210.5.144.209",
"id": "8171ab72b4244492f15105e429fb9ee4"
},
{
"zone": "daplie.com",
"name": "shop.daplie.com",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "com",
"sld": "daplie",
"sub": "shop",
"address": "23.227.38.32",
"id": "4bf66b3196e658edb32b79c24555c4a3"
},
{
"zone": "daplie.com",
"name": "shop.daplie.com",
"type": "CNAME",
"class": "IN",
"ttl": 5,
"tld": "com",
"sld": "daplie",
"sub": "shop",
"data": "shops.myshopify.com",
"id": "4d235ba90b0eac2a6cdd2ee887ece0e8"
},
{
"zone": "daplie.com",
"name": "new.daplie.com",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "com",
"sld": "daplie",
"sub": "new",
"address": "23.228.168.108",
"id": "c3364d94014f3250f30810bf8ce42925"
},
{
"zone": "daplie.com",
"name": "media.daplie.com",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "com",
"sld": "daplie",
"sub": "media",
"address": "45.56.59.142",
"id": "ceeb2618ebd0c8d6232f5b31f3b16eec"
},
{
"zone": "daplie.com",
"name": "domains.daplie.com",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "com",
"sld": "daplie",
"sub": "domains",
"address": "23.228.168.108",
"id": "0a550bc92aa23a91b6f09dcfbb051bc7"
},
{
"zone": "daplie.com",
"name": "labs.daplie.com",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "com",
"sld": "daplie",
"sub": "labs",
"address": "23.228.168.108",
"id": "e8ced4eb0a1ac0089e9c3f8981aab7de"
},
{
"zone": "daplie.domains",
"name": "daplie.domains",
"type": "NS",
"class": "IN",
"ttl": 5,
"tld": "domains",
"sld": "daplie",
"sub": "ns1",
"data": "ns1.daplie.domains",
"id": "9994c44030200acc6bf32d4d1f6c8854"
},
{
"zone": "daplie.domains",
"name": "daplie.domains",
"type": "NS",
"class": "IN",
"ttl": 5,
"tld": "domains",
"sld": "daplie",
"sub": "ns2",
"data": "ns2.daplie.domains",
"id": "3b5322d19ed697decc8ea399901925c7"
},
{
"zone": "daplie.domains",
"name": "daplie.domains",
"type": "NS",
"class": "IN",
"ttl": 5,
"tld": "domains",
"sld": "daplie",
"sub": "ns3",
"data": "ns3.daplie.domains",
"id": "1444690ec1c527ac8894f198e9bc55a3"
},
{
"zone": "daplie.domains",
"name": "ns1.daplie.domains",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "domains",
"sld": "daplie",
"sub": "ns1",
"address": "45.55.1.122",
"id": "ac237469a1afdef3773b14fa1280ee9e"
},
{
"zone": "daplie.domains",
"name": "ns2.daplie.domains",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "domains",
"sld": "daplie",
"sub": "ns2",
"address": "45.55.254.197",
"id": "ac4c92727b045e40545e635f08f3e002"
},
{
"zone": "daplie.domains",
"name": "ns3.daplie.domains",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "domains",
"sld": "daplie",
"sub": "ns3",
"address": "159.203.25.112",
"id": "96012efb05f1dea1177e8311dd8ee71a"
},
{
"zone": "daplie.domains",
"name": "leo.devices.daplie.domains",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "domains",
"sld": "daplie",
"sub": "leo.devices",
"address": "45.56.59.142",
"id": "2c0c54b31598b769456c79f17f4c8469"
},
{
"zone": "daplie.domains",
"name": "daplie.domains",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "domains",
"sld": "daplie",
"sub": "",
"address": "45.56.59.142",
"aname": "leo.devices.daplie.domains",
"id": "e792d963b60c4b5eac7137513414f229"
},
{
"zone": "daplie.domains",
"name": "daplie.domains",
"type": "A",
"class": "IN",
"ttl": 5,
"tld": "domains",
"sld": "daplie",
"sub": "www",
"address": "45.56.59.142",
"aname": "leo.devices.daplie.domains",
"id": "299767640358e61d2f95ee80fc4505bb"
}
]
, "records": [
{"zone":"daplie.com","name":"daplie.com","type":"NS","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ns1","data":"ns1.daplie.com"}
, {"zone":"daplie.com","name":"daplie.com","type":"NS","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ns2","data":"ns2.daplie.com"}
, {"zone":"daplie.com","name":"daplie.com","type":"NS","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ns3","data":"ns3.daplie.com"}
, {"zone":"daplie.com","name":"ns1.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ns1","address":"45.55.1.122"}
, {"zone":"daplie.com","name":"ns2.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ns2","address":"45.55.254.197"}
, {"zone":"daplie.com","name":"ns3.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ns3","address":"159.203.25.112"}
, {"zone":"daplie.me","name":"daplie.me","type":"NS","class":"IN","ttl":43200,"tld":"me","sld":"daplie","sub":"ns1","data":"ns1.daplie.me"}
, {"zone":"daplie.me","name":"daplie.me","type":"NS","class":"IN","ttl":43200,"tld":"me","sld":"daplie","sub":"ns2","data":"ns2.daplie.me"}
, {"zone":"daplie.me","name":"daplie.me","type":"NS","class":"IN","ttl":43200,"tld":"me","sld":"daplie","sub":"ns3","data":"ns3.daplie.me"}
, {"zone":"daplie.me","name":"ns1.daplie.me","type":"A","class":"IN","ttl":5,"tld":"me","sld":"daplie","sub":"ns1","address":"45.55.1.122"}
, {"zone":"daplie.me","name":"ns2.daplie.me","type":"A","class":"IN","ttl":5,"tld":"me","sld":"daplie","sub":"ns2","address":"45.55.254.197"}
, {"zone":"daplie.me","name":"ns3.daplie.me","type":"A","class":"IN","ttl":5,"tld":"me","sld":"daplie","sub":"ns3","address":"159.203.25.112"}
, {"zone":"oauth3.org","name":"ns1.oauth3.org","type":"A","class":"IN","ttl":5,"tld":"org","sld":"oauth3","sub":"ns1","address":"45.55.1.122"}
, {"zone":"oauth3.org","name":"ns2.oauth3.org","type":"A","class":"IN","ttl":5,"tld":"org","sld":"oauth3","sub":"ns2","address":"45.55.254.197"}
, {"zone":"oauth3.org","name":"ns3.oauth3.org","type":"A","class":"IN","ttl":5,"tld":"org","sld":"oauth3","sub":"ns3","address":"159.203.25.112"}
, {"zone":"oauth3.org","name":"oauth3.org","type":"NS","class":"IN","ttl":43200,"tld":"me","sld":"oauth3","sub":"ns1","data":"ns1.oauth3.org"}
, {"zone":"oauth3.org","name":"oauth3.org","type":"NS","class":"IN","ttl":43200,"tld":"me","sld":"oauth3","sub":"ns2","data":"ns2.oauth3.org"}
, {"zone":"oauth3.org","name":"oauth3.org","type":"NS","class":"IN","ttl":43200,"tld":"me","sld":"oauth3","sub":"ns3","data":"ns3.oauth3.org"}
, {"zone":"daplie.com","name":"daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"daplie.com","type":"TXT","class":"IN","ttl":43200,"tld":"com","sld":"daplie","data":["v=spf1 include:mailgun.org include:spf.mandrillapp.com include:_spf.google.com include:servers.mcsv.net include:mail.zendesk.com ~all"]}
, {"zone":"daplie.com","name":"www.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"www","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"daplie.com","type":"MX","class":"IN","ttl":43200,"tld":"com","sld":"daplie","exchange":"mxa.mailgun.org","priority":10}
, {"zone":"daplie.com","name":"daplie.com","type":"MX","class":"IN","ttl":43200,"tld":"com","sld":"daplie","exchange":"mxb.mailgun.org","priority":10}
, {"zone":"daplie.com","name":"email.daplie.com","type":"CNAME","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"email","data":"mailgun.org"}
, {"zone":"daplie.com","name":"k1._domainkey.daplie.com","type":"CNAME","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"k1._domainkey","data":"dkim.mcsv.net"}
, {"zone":"daplie.com","name":"smtp._domainkey.daplie.com","type":"TXT","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"smtp._domainkey","data":["k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdEzzYX8U31O5p5Uvyb1B50/JPMcKnsnIQcPDWWYkBUQxMt+FyD1SRZLCaVxWybZ8eFQUwxlh0qFeLd/mIIGhCazQ74a3AH+TJhz4gOAvNQHmWvS0Sv9ZZjGuDM/RdOAFSwZET8+WUpJfDADfijihj5KqMab13NDDLOQ96wObuwQIDAQAB"]}
, {"zone":"daplie.com","name":"mandrill._domainkey.daplie.com","type":"TXT","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"mandrill._domainkey","data":["v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrLHiExVd55zd/IQ/J/mRwSRMAocV/hMB3jXwaHH36d9NaVynQFYV8NaWi69c1veUtRzGt7yAioXqLj7Z4TeEUoOLgrKsn8YnckGs9i3B3tVFB+Ch/4mPhXWiNfNdynHWBcPcbJ8kjEQ2U8y78dHZj1YeRXXVvWob2OaKynO8/lQIDAQAB;"]}
, {"zone":"daplie.com","name":"iqqsuxwfyvyw.daplie.com","type":"CNAME","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"iqqsuxwfyvyw","data":"gv-roynzijsoqayyg.dv.googlehosted.com"}
, {"zone":"daplie.com","name":"support.daplie.com","type":"CNAME","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"support","data":"daplie.zendesk.com"}
, {"zone":"daplie.com","name":"proxy.tardigrade.devices.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"proxy.tardigrade.devices","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"redleader.devices.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"redleader.devices","address":"104.36.98.166"}
, {"zone":"daplie.com","name":"beast.devices.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"beast.devices","address":"96.19.92.42"}
, {"zone":"daplie.com","name":"ossus.devices.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ossus.devices","address":"73.65.206.97"}
, {"zone":"daplie.com","name":"leo.devices.daplie.com","type":"A","class":"IN","ttl":3600,"tld":"com","sld":"daplie","sub":"leo.devices","address":"45.56.59.142"}
, {"zone":"daplie.com","name":"proxy.leo.devices.daplie.com","type":"A","class":"IN","ttl":3600,"tld":"com","sld":"daplie","sub":"proxy.leo.devices","address":"45.56.59.142"}
, {"zone":"daplie.com","name":"git.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"git","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"tunnel.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"tunnel","address":"162.243.160.23"}
, {"zone":"daplie.com","name":"api.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"api","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"preorder.daplie.com","type":"CNAME","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"preorder","data":"daplie.myshopify.com"}
, {"zone":"daplie.com","name":"rvpn.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"rvpn","address":"104.236.182.24"}
, {"zone":"daplie.com","name":"mailapp.daplie.com","type":"CNAME","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"mailapp","data":"mandrillapp.com"}
, {"zone":"daplie.com","name":"hero.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"hero","address":"138.197.54.15"}
, {"zone":"daplie.com","name":"mattermost.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"mattermost","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"china-ftp.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"china-ftp","address":"210.5.144.209"}
, {"zone":"daplie.com","name":"shop.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"shop","address":"23.227.38.32"}
, {"zone":"daplie.com","name":"shop.daplie.com","type":"CNAME","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"shop","data":"shops.myshopify.com"}
, {"zone":"daplie.com","name":"new.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"new","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"media.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"media","address":"45.56.59.142"}
, {"zone":"daplie.com","name":"domains.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"domains","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"labs.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"labs","address":"23.228.168.108"}
, {"zone":"daplie.domains","name":"daplie.domains","type":"NS","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"ns1","data":"ns1.daplie.domains"}
, {"zone":"daplie.domains","name":"daplie.domains","type":"NS","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"ns2","data":"ns2.daplie.domains"}
, {"zone":"daplie.domains","name":"daplie.domains","type":"NS","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"ns3","data":"ns3.daplie.domains"}
, {"zone":"daplie.domains","name":"ns1.daplie.domains","type":"A","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"ns1","address":"45.55.1.122"}
, {"zone":"daplie.domains","name":"ns2.daplie.domains","type":"A","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"ns2","address":"45.55.254.197"}
, {"zone":"daplie.domains","name":"ns3.daplie.domains","type":"A","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"ns3","address":"159.203.25.112"}
, {"zone":"daplie.domains","name":"leo.devices.daplie.domains","type":"A","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"leo.devices","address":"45.56.59.142"}
, {"zone":"daplie.domains","name":"daplie.domains","type":"A","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"","address":"45.56.59.142","aname":"leo.devices.daplie.domains"}
, {"zone":"daplie.domains","name":"daplie.domains","type":"A","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"www","address":"45.56.59.142","aname":"leo.devices.daplie.domains"}
]
}
}

View File

@ -2,8 +2,8 @@
module.exports =
{
"primaryNameservers": [ "localhost" ] // 'ns1.vanity-dns.org'
, "domains": [
"primaryNameservers": [ { "name": "localhost" } ] // 'ns1.vanity-dns.org'
, "zones": [
{ "id": "example.com", "revokedAt": 0 }
, { "id": "smith.example.com", "revokedAt": 0 }
, { "id": "in-delegated.example.com", "revokedAt": 0 }

View File

@ -1,6 +1,8 @@
{
"primaryNameservers": [ "localhost" ]
, "domains": [
"primaryNameservers": [
{ "name": "localhost" }
]
, "zones": [
{ "id": "example.com", "revokedAt": 0 }
, { "id": "smith.example.com", "revokedAt": 0 }
, { "id": "in-delegated.example.com", "revokedAt": 0 }