github.com/karsthammer/dnscontrol@v0.2.8/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 // NAMESERVER_TTL(v): Set the TTL for NAMESERVER records. 311 function NAMESERVER_TTL(v) { 312 if (_.isString(v)) { 313 v = stringToDuration(v); 314 } 315 return {ns_ttl: v.toString()}; 316 } 317 318 function format_tt(transform_table) { 319 // Turn [[low: 1, high: 2, newBase: 3], [low: 4, high: 5, newIP: 6]] 320 // into "1 ~ 2 ~ 3 ~; 4 ~ 5 ~ ~ 6" 321 var lines = []; 322 for (var i = 0; i < transform_table.length; i++) { 323 var ip = transform_table[i]; 324 var newIP = ip.newIP; 325 if (newIP) { 326 if (_.isArray(newIP)) { 327 newIP = _.map(newIP, function(i) { 328 return num2dot(i); 329 }).join(','); 330 } else { 331 newIP = num2dot(newIP); 332 } 333 } 334 var newBase = ip.newBase; 335 if (newBase) { 336 if (_.isArray(newBase)) { 337 newBase = _.map(newBase, function(i) { 338 return num2dot(i); 339 }).join(','); 340 } else { 341 newBase = num2dot(newBase); 342 } 343 } 344 var row = [num2dot(ip.low), num2dot(ip.high), newBase, newIP]; 345 lines.push(row.join(' ~ ')); 346 } 347 return lines.join(' ; '); 348 } 349 350 // IGNORE(name) 351 function IGNORE(name) { 352 return function (d) { 353 d.ignored_labels.push(name); 354 }; 355 } 356 357 // IMPORT_TRANSFORM(translation_table, domain) 358 var IMPORT_TRANSFORM = recordBuilder('IMPORT_TRANSFORM', { 359 args: [['translation_table'], ['domain'], ['ttl', _.isNumber]], 360 transform: function(record, args, modifiers) { 361 record.name = '@'; 362 record.target = args.domain; 363 record.meta['transform_table'] = format_tt(args.translation_table); 364 record.ttl = args.ttl; 365 }, 366 }); 367 368 // PURGE() 369 function PURGE(d) { 370 d.KeepUnknown = false; 371 } 372 373 // NO_PURGE() 374 function NO_PURGE(d) { 375 d.KeepUnknown = true; 376 } 377 378 /** 379 * @deprecated 380 */ 381 function getModifiers(args, start) { 382 var mods = []; 383 for (var i = start; i < args.length; i++) { 384 mods.push(args[i]); 385 } 386 return mods; 387 } 388 389 /** 390 * Record type builder 391 * @param {string} type Record type 392 * @param {string} opts.args[][0] Argument name 393 * @param {function=} opts.args[][1] Optional validator 394 * @param {function=} opts.transform Function to apply arguments to record. 395 * Take (record, args, modifier) as arguments. Any modifiers will be 396 * applied before this function. It should mutate the given record. 397 * @param {function=} opts.applyModifier Function to apply modifiers to the record 398 */ 399 function recordBuilder(type, opts) { 400 opts = _.defaults({}, opts, { 401 args: [['name', _.isString], ['target']], 402 403 transform: function(record, args, modifiers) { 404 // record will have modifiers already applied 405 // args will be an object for parameters defined 406 record.name = args.name; 407 if (_.isNumber(args.target)) { 408 record.target = num2dot(args.target); 409 } else { 410 record.target = args.target; 411 } 412 }, 413 414 applyModifier: function(record, modifiers) { 415 for (var i = 0; i < modifiers.length; i++) { 416 var mod = modifiers[i]; 417 418 if (_.isFunction(mod)) { 419 mod(record); 420 } else if (_.isObject(mod)) { 421 // convert transforms to strings 422 if (mod.transform && _.isArray(mod.transform)) { 423 mod.transform = format_tt(mod.transform); 424 } 425 _.extend(record.meta, mod); 426 } else { 427 throw 'ERROR: Unknown modifier type'; 428 } 429 } 430 }, 431 }); 432 433 return function() { 434 var parsedArgs = {}; 435 var modifiers = []; 436 437 if (arguments.length < opts.args.length) { 438 var argumentsList = opts.args 439 .map(function(item) { 440 return item[0]; 441 }) 442 .join(', '); 443 throw type + 444 ' record requires ' + 445 opts.args.length + 446 ' arguments (' + 447 argumentsList + 448 '). Only ' + 449 arguments.length + 450 ' were supplied'; 451 return; 452 } 453 454 // collect arguments 455 for (var i = 0; i < opts.args.length; i++) { 456 var argDefinition = opts.args[i]; 457 var value = arguments[i]; 458 if (argDefinition.length > 1) { 459 // run validator if supplied 460 if (!argDefinition[1](value)) { 461 throw type + 462 ' record ' + 463 argDefinition[0] + 464 ' argument validation failed'; 465 } 466 } 467 parsedArgs[argDefinition[0]] = value; 468 } 469 470 // collect modifiers 471 for (var i = opts.args.length; i < arguments.length; i++) { 472 modifiers.push(arguments[i]); 473 } 474 475 return function(d) { 476 var record = { 477 type: type, 478 meta: {}, 479 ttl: d.defaultTTL, 480 }; 481 482 opts.applyModifier(record, modifiers); 483 opts.transform(record, parsedArgs, modifiers); 484 485 d.records.push(record); 486 return record; 487 }; 488 }; 489 } 490 491 /** 492 * @deprecated 493 */ 494 function addRecord(d, type, name, target, mods) { 495 // if target is number, assume ip address. convert it. 496 if (_.isNumber(target)) { 497 target = num2dot(target); 498 } 499 var rec = { 500 type: type, 501 name: name, 502 target: target, 503 ttl: d.defaultTTL, 504 priority: 0, 505 meta: {}, 506 }; 507 // for each modifier, decide based on type: 508 // - Function: call is with the record as the argument 509 // - Object: merge it into the metadata 510 // - Number: IF MX record assume it is priority 511 if (mods) { 512 for (var i = 0; i < mods.length; i++) { 513 var m = mods[i]; 514 if (_.isFunction(m)) { 515 m(rec); 516 } else if (_.isObject(m)) { 517 // convert transforms to strings 518 if (m.transform && _.isArray(m.transform)) { 519 m.transform = format_tt(m.transform); 520 } 521 _.extend(rec.meta, m); 522 _.extend(rec.meta, m); 523 } else { 524 console.log( 525 'WARNING: Modifier type unsupported:', 526 typeof m, 527 '(Skipping!)' 528 ); 529 } 530 } 531 } 532 d.records.push(rec); 533 return rec; 534 } 535 536 // ip conversion functions from http://stackoverflow.com/a/8105740/121660 537 // via http://javascript.about.com/library/blipconvert.htm 538 function IP(dot) { 539 var d = dot.split('.'); 540 // prettier-ignore 541 return ((((((+d[0]) * 256) + (+d[1])) * 256) + (+d[2])) * 256) + (+d[3]); 542 } 543 544 function num2dot(num) { 545 if (num === undefined) { 546 return ''; 547 } 548 if (_.isString(num)) { 549 return num; 550 } 551 var d = num % 256; 552 for (var i = 3; i > 0; i--) { 553 num = Math.floor(num / 256); 554 d = num % 256 + '.' + d; 555 } 556 return d; 557 } 558 559 // Cloudflare aliases: 560 561 // Meta settings for individual records. 562 var CF_PROXY_OFF = { cloudflare_proxy: 'off' }; // Proxy disabled. 563 var CF_PROXY_ON = { cloudflare_proxy: 'on' }; // Proxy enabled. 564 var CF_PROXY_FULL = { cloudflare_proxy: 'full' }; // Proxy+Railgun enabled. 565 // Per-domain meta settings: 566 // Proxy default off for entire domain (the default): 567 var CF_PROXY_DEFAULT_OFF = { cloudflare_proxy_default: 'off' }; 568 // Proxy default on for entire domain: 569 var CF_PROXY_DEFAULT_ON = { cloudflare_proxy_default: 'on' }; 570 571 // CUSTOM, PROVIDER SPECIFIC RECORD TYPES 572 573 function _validateCloudFlareRedirect(value) { 574 if (!_.isString(value)) { 575 return false; 576 } 577 return value.indexOf(',') === -1; 578 } 579 580 var CF_REDIRECT = recordBuilder('CF_REDIRECT', { 581 args: [ 582 ['source', _validateCloudFlareRedirect], 583 ['destination', _validateCloudFlareRedirect], 584 ], 585 transform: function(record, args, modifiers) { 586 record.name = '@'; 587 record.target = args.source + ',' + args.destination; 588 }, 589 }); 590 591 var CF_TEMP_REDIRECT = recordBuilder('CF_TEMP_REDIRECT', { 592 args: [ 593 ['source', _validateCloudFlareRedirect], 594 ['destination', _validateCloudFlareRedirect], 595 ], 596 transform: function(record, args, modifiers) { 597 record.name = '@'; 598 record.target = args.source + ',' + args.destination; 599 }, 600 }); 601 602 var URL = recordBuilder('URL'); 603 var URL301 = recordBuilder('URL301'); 604 var FRAME = recordBuilder('FRAME'); 605 606 // SPF_BUILDER takes an object: 607 // parts: The parts of the SPF record (to be joined with ' '). 608 // label: The DNS label for the primary SPF record. (default: '@') 609 // raw: Where (which label) to store an unaltered version of the SPF settings. 610 // split: The template for additional records to be created (default: '_spf%d') 611 // flatten: A list of domains to be flattened. 612 613 function SPF_BUILDER(value) { 614 if (!value.parts || value.parts.length < 2) { 615 throw 'SPF_BUILDER requires at least 2 elements'; 616 } 617 if (!value.label) { 618 value.label = '@'; 619 } 620 if (!value.raw) { 621 value.raw = '_rawspf'; 622 } 623 624 r = []; // The list of records to return. 625 p = {}; // The metaparameters to set on the main TXT record. 626 rawspf = value.parts.join(' '); // The unaltered SPF settings. 627 628 // If flattening is requested, generate a TXT record with the raw SPF settings. 629 if (value.flatten && value.flatten.length > 0) { 630 p.flatten = value.flatten.join(','); 631 r.push(TXT(value.raw, rawspf)); 632 } 633 634 // If overflow is specified, enable splitting. 635 if (value.overflow) { 636 p.split = value.overflow; 637 } 638 639 // Generate a TXT record with the metaparameters. 640 r.push(TXT(value.label, rawspf, p)); 641 642 return r; 643 } 644 645 // Split a DKIM string if it is >254 bytes. 646 function DKIM(arr) { 647 chunkSize = 255; 648 var R = []; 649 for (var i = 0, len = arr.length; i < len; i += chunkSize) 650 R.push(arr.slice(i, i + chunkSize)); 651 return R; 652 }