github.com/hexonet/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  }