dnscontrol/pkg/js/helpers.js

804 lines
22 KiB
JavaScript
Raw Normal View History

'use strict';
2016-08-23 08:31:50 +08:00
var conf = {
registrars: [],
dns_providers: [],
domains: [],
domain_names: [],
2016-08-23 08:31:50 +08:00
};
var defaultArgs = [];
2016-08-23 08:31:50 +08:00
function initialize() {
2016-08-23 08:31:50 +08:00
conf = {
registrars: [],
dns_providers: [],
domains: [],
2016-08-23 08:31:50 +08:00
};
defaultArgs = [];
2016-08-23 08:31:50 +08:00
}
function NewRegistrar(name, type, meta) {
2016-08-23 08:31:50 +08:00
if (type) {
type == 'MANUAL';
2016-08-23 08:31:50 +08:00
}
var reg = { name: name, type: type, meta: meta };
2016-08-23 08:31:50 +08:00
conf.registrars.push(reg);
return name;
}
function NewDnsProvider(name, type, meta) {
if (typeof meta === 'object' && 'ip_conversions' in meta) {
meta.ip_conversions = format_tt(meta.ip_conversions);
2016-08-23 08:31:50 +08:00
}
var dsp = { name: name, type: type, meta: meta };
conf.dns_providers.push(dsp);
2016-08-23 08:31:50 +08:00
return name;
}
function newDomain(name, registrar) {
return {
name: name,
registrar: registrar,
meta: {},
records: [],
dnsProviders: {},
defaultTTL: 0,
nameservers: [],
ignored_labels: [],
};
2016-08-23 08:31:50 +08:00
}
function processDargs(m, domain) {
// for each modifier, if it is a...
// function: call it with domain
// array: process recursively
// object: merge it into metadata
if (_.isFunction(m)) {
m(domain);
} else if (_.isArray(m)) {
for (var j in m) {
processDargs(m[j], domain);
2016-08-23 08:31:50 +08:00
}
} else if (_.isObject(m)) {
_.extend(domain.meta, m);
} else {
throw 'WARNING: domain modifier type unsupported: ' +
typeof m +
' Domain: ' +
domain.name;
}
2016-08-23 08:31:50 +08:00
}
// D(name,registrar): Create a DNS Domain. Use the parameters as records and mods.
function D(name, registrar) {
var domain = newDomain(name, registrar);
for (var i = 0; i < defaultArgs.length; i++) {
processDargs(defaultArgs[i], domain);
}
for (var i = 2; i < arguments.length; i++) {
2016-08-23 08:31:50 +08:00
var m = arguments[i];
processDargs(m, domain);
2016-08-23 08:31:50 +08:00
}
2017-12-07 04:56:57 +08:00
if (conf.domain_names.indexOf(name) !== -1) {
throw name + ' is declared more than once';
}
conf.domains.push(domain);
conf.domain_names.push(name);
2016-08-23 08:31:50 +08:00
}
// DEFAULTS provides a set of default arguments to apply to all future domains.
// Each call to DEFAULTS will clear any previous values set.
function DEFAULTS() {
defaultArgs = [];
for (var i = 0; i < arguments.length; i++) {
defaultArgs.push(arguments[i]);
}
}
2016-08-23 08:31:50 +08:00
// TTL(v): Set the TTL for a DNS record.
function TTL(v) {
if (_.isString(v)) {
2017-06-08 21:15:14 +08:00
v = stringToDuration(v);
}
2016-08-23 08:31:50 +08:00
return function(r) {
r.ttl = v;
};
2016-08-23 08:31:50 +08:00
}
function stringToDuration(v) {
2017-06-08 21:15:14 +08:00
var matches = v.match(/^(\d+)([smhdwny]?)$/);
if (matches == null) {
throw v + ' is not a valid duration string';
2017-06-08 21:15:14 +08:00
}
unit = 's';
if (matches[2]) {
unit = matches[2];
2017-06-08 21:15:14 +08:00
}
v = parseInt(matches[1]);
var u = { s: 1, m: 60, h: 3600 };
u['d'] = u.h * 24;
u['w'] = u.d * 7;
u['n'] = u.d * 30;
u['y'] = u.d * 365;
2017-06-08 21:15:14 +08:00
v *= u[unit];
return v;
2017-06-08 21:15:14 +08:00
}
2016-08-23 08:31:50 +08:00
// DefaultTTL(v): Set the default TTL for the domain.
function DefaultTTL(v) {
if (_.isString(v)) {
2017-06-08 21:15:14 +08:00
v = stringToDuration(v);
}
2016-08-23 08:31:50 +08:00
return function(d) {
d.defaultTTL = v;
};
2016-08-23 08:31:50 +08:00
}
function makeCAAFlag(value) {
return function(record) {
record.caaflag |= value;
};
}
// CAA_CRITICAL: Critical CAA flag
var CAA_CRITICAL = makeCAAFlag(1 << 7);
// DnsProvider("providerName", 0)
// nsCount of 0 means don't use or register any nameservers.
// nsCount not provider means use all.
function DnsProvider(name, nsCount) {
if (typeof nsCount === 'undefined') {
nsCount = -1;
}
return function(d) {
d.dnsProviders[name] = nsCount;
};
}
2016-08-23 08:31:50 +08:00
// A(name,ip, recordModifiers...)
var A = recordBuilder('A');
2016-08-23 08:31:50 +08:00
// AAAA(name,ip, recordModifiers...)
var AAAA = recordBuilder('AAAA');
2016-08-23 08:31:50 +08:00
// ALIAS(name,target, recordModifiers...)
var ALIAS = recordBuilder('ALIAS');
// AZURE_ALIAS(name, type, target, recordModifiers...)
var AZURE_ALIAS = recordBuilder('AZURE_ALIAS', {
args: [
['name', _.isString],
['type', validateAzureAliasType],
['target', _.isString],
],
transform: function(record, args, modifier) {
record.name = args.name;
record.target = args.target;
if (_.isObject(record.azure_alias)) {
record.azure_alias['type'] = args.type;
} else {
record.azure_alias = { type: args.type };
}
},
});
function validateAzureAliasType(value) {
if (!_.isString(value)) {
return false;
}
return ['A', 'AAAA', 'CNAME'].indexOf(value) !== -1;
}
// R53_ALIAS(name, target, type, recordModifiers...)
var R53_ALIAS = recordBuilder('R53_ALIAS', {
2020-02-23 01:07:10 +08:00
args: [
['name', _.isString],
['type', validateR53AliasType],
['target', _.isString],
],
transform: function(record, args, modifiers) {
record.name = args.name;
record.target = args.target;
if (_.isObject(record.r53_alias)) {
record.r53_alias['type'] = args.type;
} else {
2020-02-23 01:07:10 +08:00
record.r53_alias = { type: args.type };
}
},
});
// R53_ZONE(zone_id)
function R53_ZONE(zone_id) {
2020-02-23 01:07:10 +08:00
return function(r) {
if (_.isObject(r.r53_alias)) {
r.r53_alias['zone_id'] = zone_id;
} else {
2020-02-23 01:07:10 +08:00
r.r53_alias = { zone_id: zone_id };
}
};
}
function validateR53AliasType(value) {
if (!_.isString(value)) {
return false;
}
2020-02-23 01:07:10 +08:00
return (
[
'A',
'AAAA',
'CNAME',
'CAA',
'MX',
'TXT',
'PTR',
'SPF',
'SRV',
'NAPTR',
].indexOf(value) !== -1
2020-02-23 01:07:10 +08:00
);
}
// CAA(name,tag,value, recordModifiers...)
var CAA = recordBuilder('CAA', {
// TODO(tlim): It should be an error if value is not 0 or 128.
2020-02-23 01:07:10 +08:00
args: [
['name', _.isString],
['tag', _.isString],
['value', _.isString],
],
transform: function(record, args, modifiers) {
record.name = args.name;
record.caatag = args.tag;
record.target = args.value;
},
modifierNumber: function(record, value) {
record.caaflags |= value;
},
});
2016-08-23 08:31:50 +08:00
// CNAME(name,target, recordModifiers...)
var CNAME = recordBuilder('CNAME');
2016-08-23 08:31:50 +08:00
// PTR(name,target, recordModifiers...)
var PTR = recordBuilder('PTR');
2019-03-28 22:40:13 +08:00
// NAPTR(name,order,preference,flags,service,regexp,target, recordModifiers...)
var NAPTR = recordBuilder('NAPTR', {
args: [
['name', _.isString],
['order', _.isNumber],
['preference', _.isNumber],
['flags', _.isString],
['service', _.isString],
['regexp', _.isString],
['target', _.isString],
],
transform: function(record, args, modifiers) {
record.name = args.name;
record.naptrorder = args.order;
record.naptrpreference = args.preference;
record.naptrflags = args.flags;
record.naptrservice = args.service;
record.naptrregexp = args.regexp;
record.target = args.target;
},
});
// SRV(name,priority,weight,port,target, recordModifiers...)
var SRV = recordBuilder('SRV', {
args: [
['name', _.isString],
['priority', _.isNumber],
['weight', _.isNumber],
['port', _.isNumber],
['target', _.isString],
],
transform: function(record, args, modifiers) {
record.name = args.name;
record.srvpriority = args.priority;
record.srvweight = args.weight;
record.srvport = args.port;
record.target = args.target;
},
});
// SSHFP(name,algorithm,type,value, recordModifiers...)
var SSHFP = recordBuilder('SSHFP', {
args: [
['name', _.isString],
['algorithm', _.isNumber],
['fingerprint', _.isNumber],
['value', _.isString],
],
transform: function(record, args, modifiers) {
record.name = args.name;
record.sshfpalgorithm = args.algorithm;
record.sshfpfingerprint = args.fingerprint;
record.target = args.value;
},
});
2017-09-15 21:03:29 +08:00
// name, usage, selector, matchingtype, certificate
var TLSA = recordBuilder('TLSA', {
args: [
['name', _.isString],
['usage', _.isNumber],
['selector', _.isNumber],
['matchingtype', _.isNumber],
['target', _.isString], // recordBuilder needs a "target" argument
2017-09-15 21:03:29 +08:00
],
transform: function(record, args, modifiers) {
2017-09-15 21:03:29 +08:00
record.name = args.name;
record.tlsausage = args.usage;
record.tlsaselector = args.selector;
record.tlsamatchingtype = args.matchingtype;
record.target = args.target;
},
});
function isStringOrArray(x) {
2018-01-07 04:13:22 +08:00
return _.isString(x) || _.isArray(x);
}
2016-08-23 08:31:50 +08:00
// TXT(name,target, recordModifiers...)
2018-01-07 04:13:22 +08:00
var TXT = recordBuilder('TXT', {
2020-02-23 01:07:10 +08:00
args: [
['name', _.isString],
['target', isStringOrArray],
],
2018-01-07 04:13:22 +08:00
transform: function(record, args, modifiers) {
record.name = args.name;
// Store the strings twice:
// .target is the first string
// .txtstrings is the individual strings.
// NOTE: If there are more than 1 string, providers should only access
// .txtstrings, thus it doesn't matter what we store in .target.
// However, by storing the first string there, it improves backwards
// compatibility when the len(array) == 1 and (intentionally) breaks
// broken providers early in the integration tests.
if (_.isString(args.target)) {
record.target = args.target;
record.txtstrings = [args.target];
} else {
record.target = args.target[0];
record.txtstrings = args.target;
}
},
});
2016-08-23 08:31:50 +08:00
// MX(name,priority,target, recordModifiers...)
var MX = recordBuilder('MX', {
args: [
['name', _.isString],
['priority', _.isNumber],
['target', _.isString],
],
transform: function(record, args, modifiers) {
record.name = args.name;
record.mxpreference = args.priority;
record.target = args.target;
},
});
2016-08-23 08:31:50 +08:00
// NS(name,target, recordModifiers...)
var NS = recordBuilder('NS');
2016-08-23 08:31:50 +08:00
// NAMESERVER(name,target)
function NAMESERVER(name) {
2020-02-23 01:07:10 +08:00
if (arguments.length != 1) {
throw 'NAMESERVER only accepts one argument for name.';
}
2016-08-23 08:31:50 +08:00
return function(d) {
d.nameservers.push({ name: name });
};
2016-08-23 08:31:50 +08:00
}
// NAMESERVER_TTL(v): Set the TTL for NAMESERVER records.
function NAMESERVER_TTL(v) {
if (_.isString(v)) {
v = stringToDuration(v);
}
2020-02-23 01:07:10 +08:00
return { ns_ttl: v.toString() };
}
2016-08-23 08:31:50 +08:00
function format_tt(transform_table) {
// Turn [[low: 1, high: 2, newBase: 3], [low: 4, high: 5, newIP: 6]]
// into "1 ~ 2 ~ 3 ~; 4 ~ 5 ~ ~ 6"
var lines = [];
for (var i = 0; i < transform_table.length; i++) {
var ip = transform_table[i];
var newIP = ip.newIP;
if (newIP) {
if (_.isArray(newIP)) {
newIP = _.map(newIP, function(i) {
return num2dot(i);
}).join(',');
} else {
newIP = num2dot(newIP);
}
2016-08-23 08:31:50 +08:00
}
var newBase = ip.newBase;
if (newBase) {
if (_.isArray(newBase)) {
newBase = _.map(newBase, function(i) {
return num2dot(i);
}).join(',');
} else {
newBase = num2dot(newBase);
}
}
var row = [num2dot(ip.low), num2dot(ip.high), newBase, newIP];
lines.push(row.join(' ~ '));
}
return lines.join(' ; ');
2016-08-23 08:31:50 +08:00
}
// IGNORE(name)
function IGNORE(name) {
2020-02-23 01:07:10 +08:00
return function(d) {
d.ignored_labels.push(name);
};
}
2016-08-23 08:31:50 +08:00
// IMPORT_TRANSFORM(translation_table, domain)
var IMPORT_TRANSFORM = recordBuilder('IMPORT_TRANSFORM', {
args: [['translation_table'], ['domain'], ['ttl', _.isNumber]],
transform: function(record, args, modifiers) {
record.name = '@';
record.target = args.domain;
record.meta['transform_table'] = format_tt(args.translation_table);
record.ttl = args.ttl;
},
});
2016-08-23 08:31:50 +08:00
// PURGE()
function PURGE(d) {
d.KeepUnknown = false;
2016-08-23 08:31:50 +08:00
}
// NO_PURGE()
function NO_PURGE(d) {
d.KeepUnknown = true;
2016-08-23 08:31:50 +08:00
}
// AUTODNSSEC()
function AUTODNSSEC(d) {
d.auto_dnssec = true;
}
/**
* @deprecated
*/
function getModifiers(args, start) {
2016-08-23 08:31:50 +08:00
var mods = [];
for (var i = start; i < args.length; i++) {
mods.push(args[i]);
2016-08-23 08:31:50 +08:00
}
return mods;
}
/**
* Record type builder
* @param {string} type Record type
* @param {string} opts.args[][0] Argument name
* @param {function=} opts.args[][1] Optional validator
* @param {function=} opts.transform Function to apply arguments to record.
* Take (record, args, modifier) as arguments. Any modifiers will be
* applied before this function. It should mutate the given record.
* @param {function=} opts.applyModifier Function to apply modifiers to the record
*/
function recordBuilder(type, opts) {
opts = _.defaults({}, opts, {
args: [['name', _.isString], ['target']],
transform: function(record, args, modifiers) {
// record will have modifiers already applied
// args will be an object for parameters defined
record.name = args.name;
if (_.isNumber(args.target)) {
record.target = num2dot(args.target);
} else {
record.target = args.target;
}
},
applyModifier: function(record, modifiers) {
for (var i = 0; i < modifiers.length; i++) {
var mod = modifiers[i];
if (_.isFunction(mod)) {
mod(record);
} else if (_.isObject(mod)) {
// convert transforms to strings
if (mod.transform && _.isArray(mod.transform)) {
mod.transform = format_tt(mod.transform);
}
_.extend(record.meta, mod);
} else {
throw 'ERROR: Unknown modifier type';
}
}
},
});
return function() {
var parsedArgs = {};
var modifiers = [];
if (arguments.length < opts.args.length) {
var argumentsList = opts.args
.map(function(item) {
return item[0];
})
.join(', ');
throw type +
' record requires ' +
opts.args.length +
' arguments (' +
argumentsList +
'). Only ' +
arguments.length +
' were supplied';
return;
}
// collect arguments
for (var i = 0; i < opts.args.length; i++) {
var argDefinition = opts.args[i];
var value = arguments[i];
if (argDefinition.length > 1) {
// run validator if supplied
if (!argDefinition[1](value)) {
throw type +
' record ' +
argDefinition[0] +
' argument validation failed';
}
}
parsedArgs[argDefinition[0]] = value;
}
// collect modifiers
for (var i = opts.args.length; i < arguments.length; i++) {
modifiers.push(arguments[i]);
}
return function(d) {
var record = {
type: type,
meta: {},
ttl: d.defaultTTL,
};
opts.applyModifier(record, modifiers);
opts.transform(record, parsedArgs, modifiers);
d.records.push(record);
return record;
};
};
}
/**
* @deprecated
*/
function addRecord(d, type, name, target, mods) {
2016-08-23 08:31:50 +08:00
// if target is number, assume ip address. convert it.
if (_.isNumber(target)) {
target = num2dot(target);
}
var rec = {
type: type,
name: name,
target: target,
ttl: d.defaultTTL,
priority: 0,
meta: {},
};
2016-08-23 08:31:50 +08:00
// for each modifier, decide based on type:
// - Function: call is with the record as the argument
// - Object: merge it into the metadata
// - Number: IF MX record assume it is priority
if (mods) {
for (var i = 0; i < mods.length; i++) {
var m = mods[i];
if (_.isFunction(m)) {
m(rec);
} else if (_.isObject(m)) {
// convert transforms to strings
if (m.transform && _.isArray(m.transform)) {
m.transform = format_tt(m.transform);
}
_.extend(rec.meta, m);
_.extend(rec.meta, m);
} else {
console.log(
'WARNING: Modifier type unsupported:',
typeof m,
'(Skipping!)'
);
}
}
}
d.records.push(rec);
return rec;
}
// ip conversion functions from http://stackoverflow.com/a/8105740/121660
2016-08-23 08:31:50 +08:00
// via http://javascript.about.com/library/blipconvert.htm
function IP(dot) {
2016-08-23 08:31:50 +08:00
var d = dot.split('.');
// prettier-ignore
return ((((((+d[0]) * 256) + (+d[1])) * 256) + (+d[2])) * 256) + (+d[3]);
2016-08-23 08:31:50 +08:00
}
function num2dot(num) {
if (num === undefined) {
return '';
2016-08-23 08:31:50 +08:00
}
if (_.isString(num)) {
return num;
2016-08-23 08:31:50 +08:00
}
var d = num % 256;
for (var i = 3; i > 0; i--) {
num = Math.floor(num / 256);
2020-02-23 01:07:10 +08:00
d = (num % 256) + '.' + d;
2016-08-23 08:31:50 +08:00
}
return d;
}
2017-05-11 13:02:57 +08:00
// Cloudflare aliases:
2017-05-16 04:28:26 +08:00
// Meta settings for individual records.
var CF_PROXY_OFF = { cloudflare_proxy: 'off' }; // Proxy disabled.
var CF_PROXY_ON = { cloudflare_proxy: 'on' }; // Proxy enabled.
var CF_PROXY_FULL = { cloudflare_proxy: 'full' }; // Proxy+Railgun enabled.
2017-05-16 04:28:26 +08:00
// Per-domain meta settings:
// Proxy default off for entire domain (the default):
var CF_PROXY_DEFAULT_OFF = { cloudflare_proxy_default: 'off' };
2017-05-16 04:28:26 +08:00
// Proxy default on for entire domain:
var CF_PROXY_DEFAULT_ON = { cloudflare_proxy_default: 'on' };
// UniversalSSL off for entire domain:
var CF_UNIVERSALSSL_OFF = { cloudflare_universalssl: 'off' };
// UniversalSSL on for entire domain:
var CF_UNIVERSALSSL_ON = { cloudflare_universalssl: 'on' };
// CUSTOM, PROVIDER SPECIFIC RECORD TYPES
function _validateCloudflareRedirect(value) {
if (!_.isString(value)) {
return false;
}
return value.indexOf(',') === -1;
}
var CF_REDIRECT = recordBuilder('CF_REDIRECT', {
args: [
['source', _validateCloudflareRedirect],
['destination', _validateCloudflareRedirect],
],
transform: function(record, args, modifiers) {
record.name = '@';
record.target = args.source + ',' + args.destination;
},
});
var CF_TEMP_REDIRECT = recordBuilder('CF_TEMP_REDIRECT', {
args: [
['source', _validateCloudflareRedirect],
['destination', _validateCloudflareRedirect],
],
transform: function(record, args, modifiers) {
record.name = '@';
record.target = args.source + ',' + args.destination;
},
});
2017-12-07 04:56:57 +08:00
var URL = recordBuilder('URL');
var URL301 = recordBuilder('URL301');
var FRAME = recordBuilder('FRAME');
// SPF_BUILDER takes an object:
// parts: The parts of the SPF record (to be joined with ' ').
// label: The DNS label for the primary SPF record. (default: '@')
2017-12-07 04:56:57 +08:00
// raw: Where (which label) to store an unaltered version of the SPF settings.
// ttl: The time for TTL, integer or string. (default: not defined, using DefaultTTL)
// split: The template for additional records to be created (default: '_spf%d')
// flatten: A list of domains to be flattened.
function SPF_BUILDER(value) {
2017-12-07 04:56:57 +08:00
if (!value.parts || value.parts.length < 2) {
throw 'SPF_BUILDER requires at least 2 elements';
}
if (!value.label) {
value.label = '@';
}
if (!value.raw) {
value.raw = '_rawspf';
}
r = []; // The list of records to return.
p = {}; // The metaparameters to set on the main TXT record.
rawspf = value.parts.join(' '); // The unaltered SPF settings.
// If flattening is requested, generate a TXT record with the raw SPF settings.
if (value.flatten && value.flatten.length > 0) {
p.flatten = value.flatten.join(',');
if (value.ttl) {
r.push(TXT(value.raw, rawspf, TTL(value.ttl)));
} else {
r.push(TXT(value.raw, rawspf));
}
2017-12-07 04:56:57 +08:00
}
// If overflow is specified, enable splitting.
if (value.overflow) {
p.split = value.overflow;
}
// Generate a TXT record with the metaparameters.
if (value.ttl) {
r.push(TXT(value.label, rawspf, p, TTL(value.ttl)));
} else {
r.push(TXT(value.label, rawspf, p));
}
2017-12-07 04:56:57 +08:00
return r;
}
// CAA_BUILDER takes an object:
// label: The DNS label for the CAA record. (default: '@')
// iodef: The contact mail address. (optional)
// iodef_critical: Boolean if sending report is required/critical. If not supported, certificate should be refused. (optional)
// issue: List of CAs which are allowed to issue certificates for the domain (creates one record for each).
// issuewild: Allowed CAs which can issue wildcard certificates for this domain. (creates one record for each)
function CAA_BUILDER(value) {
if (!value.label) {
value.label = '@';
}
2020-02-23 01:07:10 +08:00
if (value.issue && value.issue == 'none') value.issue = [';'];
if (value.issuewild && value.issuewild == 'none') value.issuewild = [';'];
2020-02-23 01:07:10 +08:00
if (
(!value.issue && !value.issuewild) ||
(value.issue &&
value.issue.length == 0 &&
value.issuewild &&
value.issuewild.length == 0)
2020-02-23 01:07:10 +08:00
) {
throw 'CAA_BUILDER requires at least one entry at issue or issuewild';
}
r = []; // The list of records to return.
if (value.iodef) {
if (value.iodef_critical) {
2020-02-23 01:07:10 +08:00
r.push(CAA(value.label, 'iodef', value.iodef, CAA_CRITICAL));
} else {
2020-02-23 01:07:10 +08:00
r.push(CAA(value.label, 'iodef', value.iodef));
}
}
if (value.issue)
for (var i = 0, len = value.issue.length; i < len; i++)
2020-02-23 01:07:10 +08:00
r.push(CAA(value.label, 'issue', value.issue[i]));
if (value.issuewild)
for (var i = 0, len = value.issuewild.length; i < len; i++)
2020-02-23 01:07:10 +08:00
r.push(CAA(value.label, 'issuewild', value.issuewild[i]));
return r;
}
// Split a DKIM string if it is >254 bytes.
function DKIM(arr) {
2018-01-07 04:13:22 +08:00
chunkSize = 255;
var R = [];
for (var i = 0, len = arr.length; i < len; i += chunkSize)
R.push(arr.slice(i, i + chunkSize));
return R;
}