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  }