github.com/philhug/dnscontrol@v0.2.4-0.20180625181521-921fa9849001/pkg/js/helpers.js (about) 1 'use strict'; 2 3 var conf = { 4 registrars: [], 5 dns_providers: [], 6 domains: [], 7 domain_names: [], 8 }; 9 10 var defaultArgs = []; 11 12 function initialize() { 13 conf = { 14 registrars: [], 15 dns_providers: [], 16 domains: [], 17 }; 18 defaultArgs = []; 19 } 20 21 function NewRegistrar(name, type, meta) { 22 if (type) { 23 type == 'MANUAL'; 24 } 25 var reg = { name: name, type: type, meta: meta }; 26 conf.registrars.push(reg); 27 return name; 28 } 29 30 function NewDnsProvider(name, type, meta) { 31 if (typeof meta === 'object' && 'ip_conversions' in meta) { 32 meta.ip_conversions = format_tt(meta.ip_conversions); 33 } 34 var dsp = { name: name, type: type, meta: meta }; 35 conf.dns_providers.push(dsp); 36 return name; 37 } 38 39 function newDomain(name, registrar) { 40 return { 41 name: name, 42 registrar: registrar, 43 meta: {}, 44 records: [], 45 dnsProviders: {}, 46 defaultTTL: 0, 47 nameservers: [], 48 ignored_labels: [], 49 }; 50 } 51 52 function processDargs(m, domain) { 53 // for each modifier, if it is a... 54 // function: call it with domain 55 // array: process recursively 56 // object: merge it into metadata 57 if (_.isFunction(m)) { 58 m(domain); 59 } else if (_.isArray(m)) { 60 for (var j in m) { 61 processDargs(m[j], domain); 62 } 63 } else if (_.isObject(m)) { 64 _.extend(domain.meta, m); 65 } else { 66 throw 'WARNING: domain modifier type unsupported: ' + 67 typeof m + 68 ' Domain: ' + 69 domain.name; 70 } 71 } 72 73 // D(name,registrar): Create a DNS Domain. Use the parameters as records and mods. 74 function D(name, registrar) { 75 var domain = newDomain(name, registrar); 76 for (var i = 0; i < defaultArgs.length; i++) { 77 processDargs(defaultArgs[i], domain); 78 } 79 for (var i = 2; i < arguments.length; i++) { 80 var m = arguments[i]; 81 processDargs(m, domain); 82 } 83 if (conf.domain_names.indexOf(name) !== -1) { 84 throw name + ' is declared more than once'; 85 } 86 conf.domains.push(domain); 87 conf.domain_names.push(name); 88 } 89 90 // DEFAULTS provides a set of default arguments to apply to all future domains. 91 // Each call to DEFAULTS will clear any previous values set. 92 function DEFAULTS() { 93 defaultArgs = []; 94 for (var i = 0; i < arguments.length; i++) { 95 defaultArgs.push(arguments[i]); 96 } 97 } 98 99 // TTL(v): Set the TTL for a DNS record. 100 function TTL(v) { 101 if (_.isString(v)) { 102 v = stringToDuration(v); 103 } 104 return function(r) { 105 r.ttl = v; 106 }; 107 } 108 109 function stringToDuration(v) { 110 var matches = v.match(/^(\d+)([smhdwny]?)$/); 111 if (matches == null) { 112 throw v + ' is not a valid duration string'; 113 } 114 unit = 's'; 115 if (matches[2]) { 116 unit = matches[2]; 117 } 118 v = parseInt(matches[1]); 119 var u = { s: 1, m: 60, h: 3600 }; 120 u['d'] = u.h * 24; 121 u['w'] = u.d * 7; 122 u['n'] = u.d * 30; 123 u['y'] = u.d * 365; 124 v *= u[unit]; 125 return v; 126 } 127 128 // DefaultTTL(v): Set the default TTL for the domain. 129 function DefaultTTL(v) { 130 if (_.isString(v)) { 131 v = stringToDuration(v); 132 } 133 return function(d) { 134 d.defaultTTL = v; 135 }; 136 } 137 138 function makeCAAFlag(value) { 139 return function(record) { 140 record.caaflag |= value; 141 }; 142 } 143 144 // CAA_CRITICAL: Critical CAA flag 145 var CAA_CRITICAL = makeCAAFlag(1 << 7); 146 147 // DnsProvider("providerName", 0) 148 // nsCount of 0 means don't use or register any nameservers. 149 // nsCount not provider means use all. 150 function DnsProvider(name, nsCount) { 151 if (typeof nsCount === 'undefined') { 152 nsCount = -1; 153 } 154 return function(d) { 155 d.dnsProviders[name] = nsCount; 156 }; 157 } 158 159 // A(name,ip, recordModifiers...) 160 var A = recordBuilder('A'); 161 162 // AAAA(name,ip, recordModifiers...) 163 var AAAA = recordBuilder('AAAA'); 164 165 // ALIAS(name,target, recordModifiers...) 166 var ALIAS = recordBuilder('ALIAS'); 167 168 // R53_ALIAS(name, target, type, recordModifiers...) 169 var R53_ALIAS = recordBuilder('R53_ALIAS', { 170 args: [['name', _.isString], ['type', validateR53AliasType], ['target', _.isString]], 171 transform: function (record, args, modifiers) { 172 record.name = args.name; 173 record.target = args.target; 174 if (_.isObject(record.r53_alias)) { 175 record.r53_alias['type'] = args.type; 176 } else { 177 record.r53_alias = { 'type': args.type }; 178 } 179 }, 180 }); 181 182 // R53_ZONE(zone_id) 183 function R53_ZONE(zone_id) { 184 return function (r) { 185 if (_.isObject(r.r53_alias)) { 186 r.r53_alias['zone_id'] = zone_id; 187 } else { 188 r.r53_alias = { 'zone_id': zone_id } 189 } 190 }; 191 } 192 193 function validateR53AliasType(value) { 194 if (!_.isString(value)) { 195 return false; 196 } 197 return ['A', 'AAAA', 'CNAME', 'CAA', 'MX', 'TXT', 'PTR', 'SPF', 'SRV', 'NAPTR'].indexOf(value) != -1; 198 } 199 200 // CAA(name,tag,value, recordModifiers...) 201 var CAA = recordBuilder('CAA', { 202 // TODO(tlim): It should be an error if value is not 0 or 128. 203 args: [['name', _.isString], ['tag', _.isString], ['value', _.isString]], 204 transform: function(record, args, modifiers) { 205 record.name = args.name; 206 record.caatag = args.tag; 207 record.target = args.value; 208 }, 209 modifierNumber: function(record, value) { 210 record.caaflags |= value; 211 }, 212 }); 213 214 // CNAME(name,target, recordModifiers...) 215 var CNAME = recordBuilder('CNAME'); 216 217 // PTR(name,target, recordModifiers...) 218 var PTR = recordBuilder('PTR'); 219 220 // SRV(name,priority,weight,port,target, recordModifiers...) 221 var SRV = recordBuilder('SRV', { 222 args: [ 223 ['name', _.isString], 224 ['priority', _.isNumber], 225 ['weight', _.isNumber], 226 ['port', _.isNumber], 227 ['target', _.isString], 228 ], 229 transform: function(record, args, modifiers) { 230 record.name = args.name; 231 record.srvpriority = args.priority; 232 record.srvweight = args.weight; 233 record.srvport = args.port; 234 record.target = args.target; 235 }, 236 }); 237 238 // name, usage, selector, matchingtype, certificate 239 var TLSA = recordBuilder('TLSA', { 240 args: [ 241 ['name', _.isString], 242 ['usage', _.isNumber], 243 ['selector', _.isNumber], 244 ['matchingtype', _.isNumber], 245 ['target', _.isString], // recordBuilder needs a "target" argument 246 ], 247 transform: function(record, args, modifiers) { 248 record.name = args.name; 249 record.tlsausage = args.usage; 250 record.tlsaselector = args.selector; 251 record.tlsamatchingtype = args.matchingtype; 252 record.target = args.target; 253 }, 254 }); 255 256 function isStringOrArray(x) { 257 return _.isString(x) || _.isArray(x); 258 } 259 260 // TXT(name,target, recordModifiers...) 261 var TXT = recordBuilder('TXT', { 262 args: [['name', _.isString], ['target', isStringOrArray]], 263 transform: function(record, args, modifiers) { 264 record.name = args.name; 265 // Store the strings twice: 266 // .target is the first string 267 // .txtstrings is the individual strings. 268 // NOTE: If there are more than 1 string, providers should only access 269 // .txtstrings, thus it doesn't matter what we store in .target. 270 // However, by storing the first string there, it improves backwards 271 // compatibility when the len(array) == 1 and (intentionally) breaks 272 // broken providers early in the integration tests. 273 if (_.isString(args.target)) { 274 record.target = args.target; 275 record.txtstrings = [args.target]; 276 } else { 277 record.target = args.target[0]; 278 record.txtstrings = args.target; 279 } 280 }, 281 }); 282 283 // MX(name,priority,target, recordModifiers...) 284 var MX = recordBuilder('MX', { 285 args: [ 286 ['name', _.isString], 287 ['priority', _.isNumber], 288 ['target', _.isString], 289 ], 290 transform: function(record, args, modifiers) { 291 record.name = args.name; 292 record.mxpreference = args.priority; 293 record.target = args.target; 294 }, 295 }); 296 297 // NS(name,target, recordModifiers...) 298 var NS = recordBuilder('NS'); 299 300 // NAMESERVER(name,target) 301 function NAMESERVER(name) { 302 if (arguments.length != 1){ 303 throw("NAMESERVER only accepts one argument for name.") 304 } 305 return function(d) { 306 d.nameservers.push({ name: name }); 307 }; 308 } 309 310 function format_tt(transform_table) { 311 // Turn [[low: 1, high: 2, newBase: 3], [low: 4, high: 5, newIP: 6]] 312 // into "1 ~ 2 ~ 3 ~; 4 ~ 5 ~ ~ 6" 313 var lines = []; 314 for (var i = 0; i < transform_table.length; i++) { 315 var ip = transform_table[i]; 316 var newIP = ip.newIP; 317 if (newIP) { 318 if (_.isArray(newIP)) { 319 newIP = _.map(newIP, function(i) { 320 return num2dot(i); 321 }).join(','); 322 } else { 323 newIP = num2dot(newIP); 324 } 325 } 326 var newBase = ip.newBase; 327 if (newBase) { 328 if (_.isArray(newBase)) { 329 newBase = _.map(newBase, function(i) { 330 return num2dot(i); 331 }).join(','); 332 } else { 333 newBase = num2dot(newBase); 334 } 335 } 336 var row = [num2dot(ip.low), num2dot(ip.high), newBase, newIP]; 337 lines.push(row.join(' ~ ')); 338 } 339 return lines.join(' ; '); 340 } 341 342 // IGNORE(name) 343 function IGNORE(name) { 344 return function (d) { 345 d.ignored_labels.push(name); 346 }; 347 } 348 349 // IMPORT_TRANSFORM(translation_table, domain) 350 var IMPORT_TRANSFORM = recordBuilder('IMPORT_TRANSFORM', { 351 args: [['translation_table'], ['domain'], ['ttl', _.isNumber]], 352 transform: function(record, args, modifiers) { 353 record.name = '@'; 354 record.target = args.domain; 355 record.meta['transform_table'] = format_tt(args.translation_table); 356 record.ttl = args.ttl; 357 }, 358 }); 359 360 // PURGE() 361 function PURGE(d) { 362 d.KeepUnknown = false; 363 } 364 365 // NO_PURGE() 366 function NO_PURGE(d) { 367 d.KeepUnknown = true; 368 } 369 370 /** 371 * @deprecated 372 */ 373 function getModifiers(args, start) { 374 var mods = []; 375 for (var i = start; i < args.length; i++) { 376 mods.push(args[i]); 377 } 378 return mods; 379 } 380 381 /** 382 * Record type builder 383 * @param {string} type Record type 384 * @param {string} opts.args[][0] Argument name 385 * @param {function=} opts.args[][1] Optional validator 386 * @param {function=} opts.transform Function to apply arguments to record. 387 * Take (record, args, modifier) as arguments. Any modifiers will be 388 * applied before this function. It should mutate the given record. 389 * @param {function=} opts.applyModifier Function to apply modifiers to the record 390 */ 391 function recordBuilder(type, opts) { 392 opts = _.defaults({}, opts, { 393 args: [['name', _.isString], ['target']], 394 395 transform: function(record, args, modifiers) { 396 // record will have modifiers already applied 397 // args will be an object for parameters defined 398 record.name = args.name; 399 if (_.isNumber(args.target)) { 400 record.target = num2dot(args.target); 401 } else { 402 record.target = args.target; 403 } 404 }, 405 406 applyModifier: function(record, modifiers) { 407 for (var i = 0; i < modifiers.length; i++) { 408 var mod = modifiers[i]; 409 410 if (_.isFunction(mod)) { 411 mod(record); 412 } else if (_.isObject(mod)) { 413 // convert transforms to strings 414 if (mod.transform && _.isArray(mod.transform)) { 415 mod.transform = format_tt(mod.transform); 416 } 417 _.extend(record.meta, mod); 418 } else { 419 throw 'ERROR: Unknown modifier type'; 420 } 421 } 422 }, 423 }); 424 425 return function() { 426 var parsedArgs = {}; 427 var modifiers = []; 428 429 if (arguments.length < opts.args.length) { 430 var argumentsList = opts.args 431 .map(function(item) { 432 return item[0]; 433 }) 434 .join(', '); 435 throw type + 436 ' record requires ' + 437 opts.args.length + 438 ' arguments (' + 439 argumentsList + 440 '). Only ' + 441 arguments.length + 442 ' were supplied'; 443 return; 444 } 445 446 // collect arguments 447 for (var i = 0; i < opts.args.length; i++) { 448 var argDefinition = opts.args[i]; 449 var value = arguments[i]; 450 if (argDefinition.length > 1) { 451 // run validator if supplied 452 if (!argDefinition[1](value)) { 453 throw type + 454 ' record ' + 455 argDefinition[0] + 456 ' argument validation failed'; 457 } 458 } 459 parsedArgs[argDefinition[0]] = value; 460 } 461 462 // collect modifiers 463 for (var i = opts.args.length; i < arguments.length; i++) { 464 modifiers.push(arguments[i]); 465 } 466 467 return function(d) { 468 var record = { 469 type: type, 470 meta: {}, 471 ttl: d.defaultTTL, 472 }; 473 474 opts.applyModifier(record, modifiers); 475 opts.transform(record, parsedArgs, modifiers); 476 477 d.records.push(record); 478 return record; 479 }; 480 }; 481 } 482 483 /** 484 * @deprecated 485 */ 486 function addRecord(d, type, name, target, mods) { 487 // if target is number, assume ip address. convert it. 488 if (_.isNumber(target)) { 489 target = num2dot(target); 490 } 491 var rec = { 492 type: type, 493 name: name, 494 target: target, 495 ttl: d.defaultTTL, 496 priority: 0, 497 meta: {}, 498 }; 499 // for each modifier, decide based on type: 500 // - Function: call is with the record as the argument 501 // - Object: merge it into the metadata 502 // - Number: IF MX record assume it is priority 503 if (mods) { 504 for (var i = 0; i < mods.length; i++) { 505 var m = mods[i]; 506 if (_.isFunction(m)) { 507 m(rec); 508 } else if (_.isObject(m)) { 509 // convert transforms to strings 510 if (m.transform && _.isArray(m.transform)) { 511 m.transform = format_tt(m.transform); 512 } 513 _.extend(rec.meta, m); 514 _.extend(rec.meta, m); 515 } else { 516 console.log( 517 'WARNING: Modifier type unsupported:', 518 typeof m, 519 '(Skipping!)' 520 ); 521 } 522 } 523 } 524 d.records.push(rec); 525 return rec; 526 } 527 528 // ip conversion functions from http://stackoverflow.com/a/8105740/121660 529 // via http://javascript.about.com/library/blipconvert.htm 530 function IP(dot) { 531 var d = dot.split('.'); 532 // prettier-ignore 533 return ((((((+d[0]) * 256) + (+d[1])) * 256) + (+d[2])) * 256) + (+d[3]); 534 } 535 536 function num2dot(num) { 537 if (num === undefined) { 538 return ''; 539 } 540 if (_.isString(num)) { 541 return num; 542 } 543 var d = num % 256; 544 for (var i = 3; i > 0; i--) { 545 num = Math.floor(num / 256); 546 d = num % 256 + '.' + d; 547 } 548 return d; 549 } 550 551 // Cloudflare aliases: 552 553 // Meta settings for individual records. 554 var CF_PROXY_OFF = { cloudflare_proxy: 'off' }; // Proxy disabled. 555 var CF_PROXY_ON = { cloudflare_proxy: 'on' }; // Proxy enabled. 556 var CF_PROXY_FULL = { cloudflare_proxy: 'full' }; // Proxy+Railgun enabled. 557 // Per-domain meta settings: 558 // Proxy default off for entire domain (the default): 559 var CF_PROXY_DEFAULT_OFF = { cloudflare_proxy_default: 'off' }; 560 // Proxy default on for entire domain: 561 var CF_PROXY_DEFAULT_ON = { cloudflare_proxy_default: 'on' }; 562 563 // CUSTOM, PROVIDER SPECIFIC RECORD TYPES 564 565 function _validateCloudFlareRedirect(value) { 566 if (!_.isString(value)) { 567 return false; 568 } 569 return value.indexOf(',') === -1; 570 } 571 572 var CF_REDIRECT = recordBuilder('CF_REDIRECT', { 573 args: [ 574 ['source', _validateCloudFlareRedirect], 575 ['destination', _validateCloudFlareRedirect], 576 ], 577 transform: function(record, args, modifiers) { 578 record.name = '@'; 579 record.target = args.source + ',' + args.destination; 580 }, 581 }); 582 583 var CF_TEMP_REDIRECT = recordBuilder('CF_TEMP_REDIRECT', { 584 args: [ 585 ['source', _validateCloudFlareRedirect], 586 ['destination', _validateCloudFlareRedirect], 587 ], 588 transform: function(record, args, modifiers) { 589 record.name = '@'; 590 record.target = args.source + ',' + args.destination; 591 }, 592 }); 593 594 var URL = recordBuilder('URL'); 595 var URL301 = recordBuilder('URL301'); 596 var FRAME = recordBuilder('FRAME'); 597 598 // SPF_BUILDER takes an object: 599 // parts: The parts of the SPF record (to be joined with ' '). 600 // label: The DNS label for the primary SPF record. (default: '@') 601 // raw: Where (which label) to store an unaltered version of the SPF settings. 602 // split: The template for additional records to be created (default: '_spf%d') 603 // flatten: A list of domains to be flattened. 604 605 function SPF_BUILDER(value) { 606 if (!value.parts || value.parts.length < 2) { 607 throw 'SPF_BUILDER requires at least 2 elements'; 608 } 609 if (!value.label) { 610 value.label = '@'; 611 } 612 if (!value.raw) { 613 value.raw = '_rawspf'; 614 } 615 616 r = []; // The list of records to return. 617 p = {}; // The metaparameters to set on the main TXT record. 618 rawspf = value.parts.join(' '); // The unaltered SPF settings. 619 620 // If flattening is requested, generate a TXT record with the raw SPF settings. 621 if (value.flatten && value.flatten.length > 0) { 622 p.flatten = value.flatten.join(','); 623 r.push(TXT(value.raw, rawspf)); 624 } 625 626 // If overflow is specified, enable splitting. 627 if (value.overflow) { 628 p.split = value.overflow; 629 } 630 631 // Generate a TXT record with the metaparameters. 632 r.push(TXT(value.label, rawspf, p)); 633 634 return r; 635 } 636 637 // Split a DKIM string if it is >254 bytes. 638 function DKIM(arr) { 639 chunkSize = 255; 640 var R = []; 641 for (var i = 0, len = arr.length; i < len; i += chunkSize) 642 R.push(arr.slice(i, i + chunkSize)); 643 return R; 644 }