2017-09-16 00:12:09 +07:00
'use strict' ;
2016-08-22 18:31:50 -06:00
2022-12-07 17:14:51 -05:00
// How to keep this file clean:
// 1. Add new functions in alphabetical order when it makes sense.
// 2. Run [prettier](https://github.com/prettier/prettier) on the file to ensure
// your code conforms to our coding standard:
// npm install prettier
// node_modules/.bin/prettier --write pkg/js/helpers.js
// Development tip:
// This file is embeded in the binary via "go build". If you are
// debugging/developing this code, it may be faster to specify the
// -dev file to have helpers.js read from the file instead.
2020-07-09 12:52:49 -04:00
2016-08-22 18:31:50 -06:00
var conf = {
registrars : [ ] ,
2016-12-16 13:10:27 -07:00
dns _providers : [ ] ,
2017-09-16 00:12:09 +07:00
domains : [ ] ,
2017-11-14 21:14:45 -07:00
domain _names : [ ] ,
2016-08-22 18:31:50 -06:00
} ;
2016-12-16 13:10:27 -07:00
var defaultArgs = [ ] ;
2016-08-22 18:31:50 -06:00
2017-09-16 00:12:09 +07:00
function initialize ( ) {
2016-08-22 18:31:50 -06:00
conf = {
registrars : [ ] ,
2016-12-16 13:10:27 -07:00
dns _providers : [ ] ,
2017-09-16 00:12:09 +07:00
domains : [ ] ,
2016-08-22 18:31:50 -06:00
} ;
2016-12-16 13:10:27 -07:00
defaultArgs = [ ] ;
2016-08-22 18:31:50 -06:00
}
2021-09-02 22:54:54 +02:00
function _isDomain ( d ) {
2022-12-07 17:14:51 -05:00
return (
_ . isArray ( d . nameservers ) && _ . isArray ( d . records ) && _ . isString ( d . name )
) ;
2021-09-02 22:54:54 +02:00
}
2020-08-04 14:43:02 +01:00
// Returns an array of domains which were registered so far during runtime
// Own function for compatibility reasons or if some day special processing would be required.
function getConfiguredDomains ( ) {
return conf . domain _names ;
}
2022-05-08 14:23:45 -04:00
// NewRegistrar returns an registrar object.
// For backwards compatibility, it accepts (name), (name, meta),
// (name, type), (name, type, meta).
function NewRegistrar ( ) {
2022-12-07 17:14:51 -05:00
// For backwards compatibility, this is a wrapper around the legacy
// version of this function.
switch ( arguments . length ) {
case 1 :
return oldNewRegistrar ( arguments [ 0 ] , '-' ) ;
case 2 :
// x = NewRegistrar("myThing", "THING")
// x = NewRegistrar("myThing", { metakey: metavalue } )
if ( typeof arguments [ 1 ] === 'object' ) {
return oldNewRegistrar ( arguments [ 0 ] , '-' , arguments [ 1 ] ) ;
}
break ;
default : // do nothing
}
return oldNewRegistrar . apply ( null , arguments ) ;
2022-05-08 14:23:45 -04:00
}
function oldNewRegistrar ( name , type , meta ) {
2016-08-22 18:31:50 -06:00
if ( type ) {
2017-09-16 00:12:09 +07:00
type == 'MANUAL' ;
2016-08-22 18:31:50 -06:00
}
2017-09-16 00:12:09 +07:00
var reg = { name : name , type : type , meta : meta } ;
2016-08-22 18:31:50 -06:00
conf . registrars . push ( reg ) ;
return name ;
}
2016-12-16 13:10:27 -07:00
function NewDnsProvider ( name , type , meta ) {
2022-12-07 17:14:51 -05:00
// For backwards compatibility, this is a wrapper around the legacy
// version of this function.
switch ( arguments . length ) {
case 1 :
return oldNewDnsProvider ( arguments [ 0 ] , '-' ) ;
case 2 :
// x = NewDnsProvider("myThing", "THING")
// x = NewDnsProvider("myThing", { metakey: metavalue } )
if ( typeof arguments [ 1 ] === 'object' ) {
return oldNewDnsProvider ( arguments [ 0 ] , '-' , arguments [ 1 ] ) ;
}
break ;
default : // do nothing
}
return oldNewDnsProvider . apply ( null , arguments ) ;
2022-05-08 14:23:45 -04:00
}
function oldNewDnsProvider ( name , type , meta ) {
2017-09-16 00:12:09 +07:00
if ( typeof meta === 'object' && 'ip_conversions' in meta ) {
meta . ip _conversions = format _tt ( meta . ip _conversions ) ;
2016-08-22 18:31:50 -06:00
}
2017-09-16 00:12:09 +07:00
var dsp = { name : name , type : type , meta : meta } ;
2016-12-16 13:10:27 -07:00
conf . dns _providers . push ( dsp ) ;
2016-08-22 18:31:50 -06:00
return name ;
}
2017-09-16 00:12:09 +07:00
function newDomain ( name , registrar ) {
return {
name : name ,
2020-10-07 14:27:33 -04:00
subdomain : '' ,
2017-09-16 00:12:09 +07:00
registrar : registrar ,
meta : { } ,
records : [ ] ,
2023-02-19 12:33:08 -05:00
recordsabsent : [ ] ,
2017-09-16 00:12:09 +07:00
dnsProviders : { } ,
defaultTTL : 0 ,
nameservers : [ ] ,
2020-08-19 03:14:34 +12:00
ignored _names : [ ] ,
ignored _targets : [ ] ,
2022-12-11 17:28:58 -05:00
unmanaged : [ ] ,
2017-09-16 00:12:09 +07:00
} ;
2016-08-22 18:31:50 -06:00
}
function processDargs ( m , domain ) {
2017-09-16 00:12:09 +07:00
// 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-22 18:31:50 -06:00
}
2017-09-16 00:12:09 +07:00
} else if ( _ . isObject ( m ) ) {
_ . extend ( domain . meta , m ) ;
} else {
2022-12-07 17:14:51 -05:00
throw (
'WARNING: domain modifier type unsupported: ' +
2017-09-16 00:12:09 +07:00
typeof m +
' Domain: ' +
2022-12-07 17:14:51 -05:00
domain . name
) ;
2017-09-16 00:12:09 +07:00
}
2016-08-22 18:31:50 -06:00
}
// D(name,registrar): Create a DNS Domain. Use the parameters as records and mods.
2017-09-16 00:12:09 +07:00
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-22 18:31:50 -06:00
var m = arguments [ i ] ;
2017-09-16 00:12:09 +07:00
processDargs ( m , domain ) ;
2016-08-22 18:31:50 -06:00
}
2017-12-06 15:56:57 -05:00
if ( conf . domain _names . indexOf ( name ) !== - 1 ) {
throw name + ' is declared more than once' ;
2017-11-14 21:14:45 -07:00
}
2017-09-16 00:12:09 +07:00
conf . domains . push ( domain ) ;
2017-11-14 21:14:45 -07:00
conf . domain _names . push ( name ) ;
2016-08-22 18:31:50 -06:00
}
2021-07-25 18:03:58 +02:00
function INCLUDE ( name ) {
var domain = _getDomainObject ( name ) ;
if ( domain == null ) {
2022-12-07 17:14:51 -05:00
throw (
name +
' was not declared yet and therefore cannot be updated. Use D() before.'
) ;
2021-07-25 18:03:58 +02:00
}
2022-12-07 17:14:51 -05:00
return function ( d ) {
d . records . push . apply ( d . records , domain . obj . records ) ;
} ;
2021-07-25 18:03:58 +02:00
}
2020-10-07 14:27:33 -04:00
// D_EXTEND(name): Update a DNS Domain already added with D(), or subdomain thereof
2020-08-04 14:43:02 +01:00
function D _EXTEND ( name ) {
var domain = _getDomainObject ( name ) ;
if ( domain == null ) {
2022-12-07 17:14:51 -05:00
throw (
name +
' was not declared yet and therefore cannot be updated. Use D() before.'
) ;
2020-08-04 14:43:02 +01:00
}
2022-12-07 17:14:51 -05:00
domain . obj . subdomain = name . substr (
0 ,
name . length - domain . obj . name . length - 1
) ;
2020-08-04 14:43:02 +01:00
for ( var i = 1 ; i < arguments . length ; i ++ ) {
var m = arguments [ i ] ;
processDargs ( m , domain . obj ) ;
}
conf . domains [ domain . id ] = domain . obj ; // let's overwrite the object.
}
2020-10-07 16:03:36 -04:00
// _getDomainObject(name): This implements the domain matching
// algorithm used by D_EXTEND(). Candidate matches are an exact match
// of the domain's name, or if name is a proper subdomain of the
2020-11-18 07:05:26 -05:00
// domain's name. The longest match is returned.
2020-08-04 14:43:02 +01:00
function _getDomainObject ( name ) {
2020-10-25 17:29:25 +00:00
var domain = null ;
var domainLen = 0 ;
for ( var i = 0 ; i < conf . domains . length ; i ++ ) {
2022-12-07 17:14:51 -05:00
var thisName = conf . domains [ i ] [ 'name' ] ;
var desiredSuffix = '.' + thisName ;
2020-10-25 17:29:25 +00:00
var foundSuffix = name . substr ( - desiredSuffix . length ) ;
// If this is an exact match or the suffix matches...
if ( name === thisName || foundSuffix === desiredSuffix ) {
// If this match is a longer match than our current best match...
if ( thisName . length > domainLen ) {
domainLen = thisName . length ;
domain = { id : i , obj : conf . domains [ i ] } ;
}
}
2020-08-04 14:43:02 +01:00
}
2020-10-25 17:29:25 +00:00
return domain ;
2020-08-04 14:43:02 +01:00
}
2016-12-16 13:10:27 -07:00
// DEFAULTS provides a set of default arguments to apply to all future domains.
// Each call to DEFAULTS will clear any previous values set.
2017-09-16 00:12:09 +07:00
function DEFAULTS ( ) {
2016-12-16 13:10:27 -07:00
defaultArgs = [ ] ;
2017-09-16 00:12:09 +07:00
for ( var i = 0 ; i < arguments . length ; i ++ ) {
2016-12-16 13:10:27 -07:00
defaultArgs . push ( arguments [ i ] ) ;
}
}
2016-08-22 18:31:50 -06:00
// TTL(v): Set the TTL for a DNS record.
function TTL ( v ) {
2017-09-16 00:12:09 +07:00
if ( _ . isString ( v ) ) {
2017-06-08 09:15:14 -04:00
v = stringToDuration ( v ) ;
}
2022-12-07 17:14:51 -05:00
return function ( r ) {
2016-08-22 18:31:50 -06:00
r . ttl = v ;
2017-09-16 00:12:09 +07:00
} ;
2016-08-22 18:31:50 -06:00
}
2017-09-16 00:12:09 +07:00
function stringToDuration ( v ) {
2017-06-08 09:15:14 -04:00
var matches = v . match ( /^(\d+)([smhdwny]?)$/ ) ;
2017-09-16 00:12:09 +07:00
if ( matches == null ) {
throw v + ' is not a valid duration string' ;
2017-06-08 09:15:14 -04:00
}
2017-09-16 00:12:09 +07:00
unit = 's' ;
if ( matches [ 2 ] ) {
unit = matches [ 2 ] ;
2017-06-08 09:15:14 -04:00
}
2017-09-16 00:12:09 +07: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 09:15:14 -04:00
v *= u [ unit ] ;
2017-09-16 00:12:09 +07:00
return v ;
2017-06-08 09:15:14 -04:00
}
2016-08-22 18:31:50 -06:00
// DefaultTTL(v): Set the default TTL for the domain.
function DefaultTTL ( v ) {
2017-09-16 00:12:09 +07:00
if ( _ . isString ( v ) ) {
2017-06-08 09:15:14 -04:00
v = stringToDuration ( v ) ;
}
2022-12-07 17:14:51 -05:00
return function ( d ) {
2016-08-22 18:31:50 -06:00
d . defaultTTL = v ;
2017-09-16 00:12:09 +07:00
} ;
2016-08-22 18:31:50 -06:00
}
2017-09-16 00:12:09 +07:00
function makeCAAFlag ( value ) {
2022-12-07 17:14:51 -05:00
return function ( record ) {
2017-08-12 01:48:43 +07:00
record . caaflag |= value ;
} ;
}
2017-07-25 14:59:40 -04:00
// CAA_CRITICAL: Critical CAA flag
2018-02-09 20:03:40 +01:00
var CAA _CRITICAL = makeCAAFlag ( 1 << 7 ) ;
2016-12-16 13:10:27 -07:00
2017-09-16 00:12:09 +07:00
// DnsProvider("providerName", 0)
2016-12-16 13:10:27 -07:00
// nsCount of 0 means don't use or register any nameservers.
// nsCount not provider means use all.
2017-09-16 00:12:09 +07:00
function DnsProvider ( name , nsCount ) {
if ( typeof nsCount === 'undefined' ) {
2016-12-16 13:10:27 -07:00
nsCount = - 1 ;
}
2022-12-07 17:14:51 -05:00
return function ( d ) {
2016-12-16 13:10:27 -07:00
d . dnsProviders [ name ] = nsCount ;
2017-09-16 00:12:09 +07:00
} ;
2016-12-16 13:10:27 -07:00
}
2016-08-22 18:31:50 -06:00
// A(name,ip, recordModifiers...)
2017-08-12 01:48:43 +07:00
var A = recordBuilder ( 'A' ) ;
2016-08-22 18:31:50 -06:00
// AAAA(name,ip, recordModifiers...)
2017-08-12 01:48:43 +07:00
var AAAA = recordBuilder ( 'AAAA' ) ;
2016-08-22 18:31:50 -06:00
2021-06-22 10:24:49 -04:00
// AKAMAICDN(name, target, recordModifiers...)
var AKAMAICDN = recordBuilder ( 'AKAMAICDN' ) ;
2017-04-19 13:13:28 -06:00
// ALIAS(name,target, recordModifiers...)
2017-08-12 01:48:43 +07:00
var ALIAS = recordBuilder ( 'ALIAS' ) ;
2017-04-19 13:13:28 -06:00
2020-03-02 20:25:42 +04:00
// AZURE_ALIAS(name, type, target, recordModifiers...)
var AZURE _ALIAS = recordBuilder ( 'AZURE_ALIAS' , {
args : [
[ 'name' , _ . isString ] ,
[ 'type' , validateAzureAliasType ] ,
[ 'target' , _ . isString ] ,
] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifier ) {
2020-03-02 20:25:42 +04:00
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 ;
}
ROUTE53: Support Route53's ALIAS record type (#239) (#301)
* Stable comparison of metadata (#239)
Iterating over a map in Go never produces twice the same ordering.
Thus when comparing two metadata map with more than one key, the
`differ` is always finding differences.
To properly compare records metadata, we need to iterate the maps
in a deterministic way.
Signed-off-by: Brice Figureau <brice@daysofwonder.com>
* Support for Route53 ALIAS record type (#239)
Route53 ALIAS doesn't behave like a regular ALIAS, and is much more
limited as its target can only be some specific AWS resources or
another record in the same zone.
According to #239, this change adds a new directive R53_ALIAS which
implements this specific alias. This record type can only be used
with the Route53 provider.
This directive usage looks like this:
```js
D("example.com", REGISTRAR, DnsProvider("ROUTE53"),
R53_ALIAS("foo1", "A", "bar") // record in same zone
R53_ALIAS("foo2", "A",
"blahblah.elasticloadbalancing.us-west-1.amazonaws.com",
R53_ZONE('Z368ELLRRE2KJ0')) // ELB in us-west-1
```
Unfortunately, Route53 requires indicating the hosted zone id
where the target is defined (those are listed in AWS documentation,
see the R53_ALIAS documentation for links).
2018-01-16 11:53:12 +01:00
// R53_ALIAS(name, target, type, recordModifiers...)
var R53 _ALIAS = recordBuilder ( 'R53_ALIAS' , {
2020-02-22 12:07:10 -05:00
args : [
[ 'name' , _ . isString ] ,
[ 'type' , validateR53AliasType ] ,
[ 'target' , _ . isString ] ,
] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
ROUTE53: Support Route53's ALIAS record type (#239) (#301)
* Stable comparison of metadata (#239)
Iterating over a map in Go never produces twice the same ordering.
Thus when comparing two metadata map with more than one key, the
`differ` is always finding differences.
To properly compare records metadata, we need to iterate the maps
in a deterministic way.
Signed-off-by: Brice Figureau <brice@daysofwonder.com>
* Support for Route53 ALIAS record type (#239)
Route53 ALIAS doesn't behave like a regular ALIAS, and is much more
limited as its target can only be some specific AWS resources or
another record in the same zone.
According to #239, this change adds a new directive R53_ALIAS which
implements this specific alias. This record type can only be used
with the Route53 provider.
This directive usage looks like this:
```js
D("example.com", REGISTRAR, DnsProvider("ROUTE53"),
R53_ALIAS("foo1", "A", "bar") // record in same zone
R53_ALIAS("foo2", "A",
"blahblah.elasticloadbalancing.us-west-1.amazonaws.com",
R53_ZONE('Z368ELLRRE2KJ0')) // ELB in us-west-1
```
Unfortunately, Route53 requires indicating the hosted zone id
where the target is defined (those are listed in AWS documentation,
see the R53_ALIAS documentation for links).
2018-01-16 11:53:12 +01:00
record . name = args . name ;
record . target = args . target ;
if ( _ . isObject ( record . r53 _alias ) ) {
record . r53 _alias [ 'type' ] = args . type ;
2023-11-27 17:50:21 -05:00
if ( ! _ . isString ( record . r53 _alias [ 'evaluate_target_health' ] ) ) {
record . r53 _alias [ 'evaluate_target_health' ] = 'false' ;
}
ROUTE53: Support Route53's ALIAS record type (#239) (#301)
* Stable comparison of metadata (#239)
Iterating over a map in Go never produces twice the same ordering.
Thus when comparing two metadata map with more than one key, the
`differ` is always finding differences.
To properly compare records metadata, we need to iterate the maps
in a deterministic way.
Signed-off-by: Brice Figureau <brice@daysofwonder.com>
* Support for Route53 ALIAS record type (#239)
Route53 ALIAS doesn't behave like a regular ALIAS, and is much more
limited as its target can only be some specific AWS resources or
another record in the same zone.
According to #239, this change adds a new directive R53_ALIAS which
implements this specific alias. This record type can only be used
with the Route53 provider.
This directive usage looks like this:
```js
D("example.com", REGISTRAR, DnsProvider("ROUTE53"),
R53_ALIAS("foo1", "A", "bar") // record in same zone
R53_ALIAS("foo2", "A",
"blahblah.elasticloadbalancing.us-west-1.amazonaws.com",
R53_ZONE('Z368ELLRRE2KJ0')) // ELB in us-west-1
```
Unfortunately, Route53 requires indicating the hosted zone id
where the target is defined (those are listed in AWS documentation,
see the R53_ALIAS documentation for links).
2018-01-16 11:53:12 +01:00
} else {
2023-11-27 17:50:21 -05:00
record . r53 _alias = {
type : args . type ,
evaluate _target _health : 'false' ,
} ;
ROUTE53: Support Route53's ALIAS record type (#239) (#301)
* Stable comparison of metadata (#239)
Iterating over a map in Go never produces twice the same ordering.
Thus when comparing two metadata map with more than one key, the
`differ` is always finding differences.
To properly compare records metadata, we need to iterate the maps
in a deterministic way.
Signed-off-by: Brice Figureau <brice@daysofwonder.com>
* Support for Route53 ALIAS record type (#239)
Route53 ALIAS doesn't behave like a regular ALIAS, and is much more
limited as its target can only be some specific AWS resources or
another record in the same zone.
According to #239, this change adds a new directive R53_ALIAS which
implements this specific alias. This record type can only be used
with the Route53 provider.
This directive usage looks like this:
```js
D("example.com", REGISTRAR, DnsProvider("ROUTE53"),
R53_ALIAS("foo1", "A", "bar") // record in same zone
R53_ALIAS("foo2", "A",
"blahblah.elasticloadbalancing.us-west-1.amazonaws.com",
R53_ZONE('Z368ELLRRE2KJ0')) // ELB in us-west-1
```
Unfortunately, Route53 requires indicating the hosted zone id
where the target is defined (those are listed in AWS documentation,
see the R53_ALIAS documentation for links).
2018-01-16 11:53:12 +01:00
}
} ,
} ) ;
// R53_ZONE(zone_id)
function R53 _ZONE ( zone _id ) {
2021-09-02 22:54:54 +02:00
return function ( r ) {
if ( _isDomain ( r ) ) {
r . meta . zone _id = zone _id ;
} else if ( _ . isObject ( r . r53 _alias ) ) {
ROUTE53: Support Route53's ALIAS record type (#239) (#301)
* Stable comparison of metadata (#239)
Iterating over a map in Go never produces twice the same ordering.
Thus when comparing two metadata map with more than one key, the
`differ` is always finding differences.
To properly compare records metadata, we need to iterate the maps
in a deterministic way.
Signed-off-by: Brice Figureau <brice@daysofwonder.com>
* Support for Route53 ALIAS record type (#239)
Route53 ALIAS doesn't behave like a regular ALIAS, and is much more
limited as its target can only be some specific AWS resources or
another record in the same zone.
According to #239, this change adds a new directive R53_ALIAS which
implements this specific alias. This record type can only be used
with the Route53 provider.
This directive usage looks like this:
```js
D("example.com", REGISTRAR, DnsProvider("ROUTE53"),
R53_ALIAS("foo1", "A", "bar") // record in same zone
R53_ALIAS("foo2", "A",
"blahblah.elasticloadbalancing.us-west-1.amazonaws.com",
R53_ZONE('Z368ELLRRE2KJ0')) // ELB in us-west-1
```
Unfortunately, Route53 requires indicating the hosted zone id
where the target is defined (those are listed in AWS documentation,
see the R53_ALIAS documentation for links).
2018-01-16 11:53:12 +01:00
r . r53 _alias [ 'zone_id' ] = zone _id ;
} else {
2020-02-22 12:07:10 -05:00
r . r53 _alias = { zone _id : zone _id } ;
ROUTE53: Support Route53's ALIAS record type (#239) (#301)
* Stable comparison of metadata (#239)
Iterating over a map in Go never produces twice the same ordering.
Thus when comparing two metadata map with more than one key, the
`differ` is always finding differences.
To properly compare records metadata, we need to iterate the maps
in a deterministic way.
Signed-off-by: Brice Figureau <brice@daysofwonder.com>
* Support for Route53 ALIAS record type (#239)
Route53 ALIAS doesn't behave like a regular ALIAS, and is much more
limited as its target can only be some specific AWS resources or
another record in the same zone.
According to #239, this change adds a new directive R53_ALIAS which
implements this specific alias. This record type can only be used
with the Route53 provider.
This directive usage looks like this:
```js
D("example.com", REGISTRAR, DnsProvider("ROUTE53"),
R53_ALIAS("foo1", "A", "bar") // record in same zone
R53_ALIAS("foo2", "A",
"blahblah.elasticloadbalancing.us-west-1.amazonaws.com",
R53_ZONE('Z368ELLRRE2KJ0')) // ELB in us-west-1
```
Unfortunately, Route53 requires indicating the hosted zone id
where the target is defined (those are listed in AWS documentation,
see the R53_ALIAS documentation for links).
2018-01-16 11:53:12 +01:00
}
} ;
}
2023-11-27 17:50:21 -05:00
// R53_EVALUATE_TARGET_HEALTH(enabled)
function R53 _EVALUATE _TARGET _HEALTH ( enabled ) {
return function ( r ) {
if ( _ . isObject ( r . r53 _alias ) ) {
r . r53 _alias [ 'evaluate_target_health' ] = enabled . toString ( ) ;
} else {
r . r53 _alias = { evaluate _target _health : enabled . toString ( ) } ;
}
} ;
}
ROUTE53: Support Route53's ALIAS record type (#239) (#301)
* Stable comparison of metadata (#239)
Iterating over a map in Go never produces twice the same ordering.
Thus when comparing two metadata map with more than one key, the
`differ` is always finding differences.
To properly compare records metadata, we need to iterate the maps
in a deterministic way.
Signed-off-by: Brice Figureau <brice@daysofwonder.com>
* Support for Route53 ALIAS record type (#239)
Route53 ALIAS doesn't behave like a regular ALIAS, and is much more
limited as its target can only be some specific AWS resources or
another record in the same zone.
According to #239, this change adds a new directive R53_ALIAS which
implements this specific alias. This record type can only be used
with the Route53 provider.
This directive usage looks like this:
```js
D("example.com", REGISTRAR, DnsProvider("ROUTE53"),
R53_ALIAS("foo1", "A", "bar") // record in same zone
R53_ALIAS("foo2", "A",
"blahblah.elasticloadbalancing.us-west-1.amazonaws.com",
R53_ZONE('Z368ELLRRE2KJ0')) // ELB in us-west-1
```
Unfortunately, Route53 requires indicating the hosted zone id
where the target is defined (those are listed in AWS documentation,
see the R53_ALIAS documentation for links).
2018-01-16 11:53:12 +01:00
function validateR53AliasType ( value ) {
if ( ! _ . isString ( value ) ) {
return false ;
}
2020-02-22 12:07:10 -05:00
return (
[
'A' ,
'AAAA' ,
'CNAME' ,
'CAA' ,
'MX' ,
'TXT' ,
'PTR' ,
'SPF' ,
'SRV' ,
'NAPTR' ,
2020-03-02 20:25:42 +04:00
] . indexOf ( value ) !== - 1
2020-02-22 12:07:10 -05:00
) ;
ROUTE53: Support Route53's ALIAS record type (#239) (#301)
* Stable comparison of metadata (#239)
Iterating over a map in Go never produces twice the same ordering.
Thus when comparing two metadata map with more than one key, the
`differ` is always finding differences.
To properly compare records metadata, we need to iterate the maps
in a deterministic way.
Signed-off-by: Brice Figureau <brice@daysofwonder.com>
* Support for Route53 ALIAS record type (#239)
Route53 ALIAS doesn't behave like a regular ALIAS, and is much more
limited as its target can only be some specific AWS resources or
another record in the same zone.
According to #239, this change adds a new directive R53_ALIAS which
implements this specific alias. This record type can only be used
with the Route53 provider.
This directive usage looks like this:
```js
D("example.com", REGISTRAR, DnsProvider("ROUTE53"),
R53_ALIAS("foo1", "A", "bar") // record in same zone
R53_ALIAS("foo2", "A",
"blahblah.elasticloadbalancing.us-west-1.amazonaws.com",
R53_ZONE('Z368ELLRRE2KJ0')) // ELB in us-west-1
```
Unfortunately, Route53 requires indicating the hosted zone id
where the target is defined (those are listed in AWS documentation,
see the R53_ALIAS documentation for links).
2018-01-16 11:53:12 +01:00
}
2017-07-25 14:59:40 -04:00
// CAA(name,tag,value, recordModifiers...)
2017-08-12 01:48:43 +07:00
var CAA = recordBuilder ( 'CAA' , {
2017-09-16 00:12:09 +07:00
// TODO(tlim): It should be an error if value is not 0 or 128.
2020-02-22 12:07:10 -05:00
args : [
[ 'name' , _ . isString ] ,
[ 'tag' , _ . isString ] ,
[ 'value' , _ . isString ] ,
] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
2017-08-12 01:48:43 +07:00
record . name = args . name ;
record . caatag = args . tag ;
record . target = args . value ;
} ,
2022-12-07 17:14:51 -05:00
modifierNumber : function ( record , value ) {
2017-08-12 01:48:43 +07:00
record . caaflags |= value ;
} ,
} ) ;
2017-04-19 13:13:28 -06:00
2016-08-22 18:31:50 -06:00
// CNAME(name,target, recordModifiers...)
2017-08-12 01:48:43 +07:00
var CNAME = recordBuilder ( 'CNAME' ) ;
2016-08-22 18:31:50 -06:00
2020-05-30 10:40:21 -04:00
// DS(name, keytag, algorithm, digestype, digest)
2022-12-07 17:14:51 -05:00
var DS = recordBuilder ( 'DS' , {
2020-05-30 10:40:21 -04:00
args : [
[ 'name' , _ . isString ] ,
[ 'keytag' , _ . isNumber ] ,
[ 'algorithm' , _ . isNumber ] ,
[ 'digesttype' , _ . isNumber ] ,
2022-12-07 17:14:51 -05:00
[ 'digest' , _ . isString ] ,
2020-05-30 10:40:21 -04:00
] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
2020-05-30 10:40:21 -04:00
record . name = args . name ;
record . dskeytag = args . keytag ;
record . dsalgorithm = args . algorithm ;
record . dsdigesttype = args . digesttype ;
record . dsdigest = args . digest ;
record . target = args . target ;
} ,
} ) ;
2023-06-02 06:10:35 +02:00
// DHCID(name,target, recordModifiers...)
var DHCID = recordBuilder ( 'DHCID' ) ;
2024-04-03 02:28:57 +08:00
// DNAME(name,target, recordModifiers...)
var DNAME = recordBuilder ( 'DNAME' ) ;
2024-04-22 21:54:12 +08:00
// DNSKEY(name, flags, protocol, algorithm, publickey)
var DNSKEY = recordBuilder ( 'DNSKEY' , {
args : [
[ 'name' , _ . isString ] ,
[ 'flags' , _ . isNumber ] ,
[ 'protocol' , _ . isNumber ] ,
[ 'algorithm' , _ . isNumber ] ,
[ 'publickey' , _ . isString ] ,
] ,
transform : function ( record , args , modifiers ) {
record . name = args . name ;
record . dnskeyflags = args . flags ;
record . dnskeyprotocol = args . protocol ;
record . dnskeyalgorithm = args . algorithm ;
record . dnskeypublickey = args . publickey ;
record . target = args . target ;
} ,
} ) ;
2024-05-01 17:37:15 +02:00
// name, priority, target, params
var HTTPS = recordBuilder ( 'HTTPS' , {
args : [
[ 'name' , _ . isString ] ,
[ 'priority' , _ . isNumber ] ,
[ 'target' , _ . isString ] ,
[ 'params' , _ . isString ] ,
] ,
transform : function ( record , args , modifiers ) {
record . name = args . name ;
record . svcpriority = args . priority ;
record . target = args . target ;
record . svcparams = args . params ;
} ,
} ) ;
2017-07-06 10:18:15 -04:00
// PTR(name,target, recordModifiers...)
2017-08-12 01:48:43 +07:00
var PTR = recordBuilder ( 'PTR' ) ;
2017-07-06 10:18:15 -04:00
2019-03-28 15:40:13 +01: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 ] ,
] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
2019-03-28 15:40:13 +01:00
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 ;
} ,
} ) ;
2021-05-07 14:39:26 -04:00
// SOA(name,ns,mbox,refresh,retry,expire,minimum, recordModifiers...)
2021-05-04 21:47:26 +02:00
var SOA = recordBuilder ( 'SOA' , {
2022-12-07 17:14:51 -05:00
args : [
[ 'name' , _ . isString ] ,
[ 'target' , _ . isString ] ,
[ 'mbox' , _ . isString ] ,
[ 'refresh' , _ . isNumber ] ,
[ 'retry' , _ . isNumber ] ,
[ 'expire' , _ . isNumber ] ,
[ 'minttl' , _ . isNumber ] ,
] ,
transform : function ( record , args , modifiers ) {
record . name = args . name ;
record . target = args . target ;
record . soambox = args . mbox ;
record . soarefresh = args . refresh ;
record . soaretry = args . retry ;
record . soaexpire = args . expire ;
record . soaminttl = args . minttl ;
} ,
2021-05-04 21:47:26 +02:00
} ) ;
2017-07-19 15:53:40 -04:00
// SRV(name,priority,weight,port,target, recordModifiers...)
2017-08-12 01:48:43 +07:00
var SRV = recordBuilder ( 'SRV' , {
args : [
[ 'name' , _ . isString ] ,
[ 'priority' , _ . isNumber ] ,
[ 'weight' , _ . isNumber ] ,
[ 'port' , _ . isNumber ] ,
[ 'target' , _ . isString ] ,
] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
2017-08-12 01:48:43 +07:00
record . name = args . name ;
record . srvpriority = args . priority ;
record . srvweight = args . weight ;
record . srvport = args . port ;
record . target = args . target ;
} ,
} ) ;
2017-07-19 15:53:40 -04:00
2019-01-28 23:26:20 +01:00
// SSHFP(name,algorithm,type,value, recordModifiers...)
var SSHFP = recordBuilder ( 'SSHFP' , {
args : [
[ 'name' , _ . isString ] ,
[ 'algorithm' , _ . isNumber ] ,
[ 'fingerprint' , _ . isNumber ] ,
[ 'value' , _ . isString ] ,
] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
2019-01-28 23:26:20 +01:00
record . name = args . name ;
record . sshfpalgorithm = args . algorithm ;
record . sshfpfingerprint = args . fingerprint ;
record . target = args . value ;
} ,
} ) ;
2024-05-01 17:37:15 +02:00
// name, priority, target, params
var SVCB = recordBuilder ( 'SVCB' , {
args : [
[ 'name' , _ . isString ] ,
[ 'priority' , _ . isNumber ] ,
[ 'target' , _ . isString ] ,
[ 'params' , _ . isString ] ,
] ,
transform : function ( record , args , modifiers ) {
record . name = args . name ;
record . svcpriority = args . priority ;
record . target = args . target ;
record . svcparams = args . params ;
} ,
} ) ;
2017-09-15 09:03:29 -04:00
// name, usage, selector, matchingtype, certificate
var TLSA = recordBuilder ( 'TLSA' , {
args : [
[ 'name' , _ . isString ] ,
[ 'usage' , _ . isNumber ] ,
[ 'selector' , _ . isNumber ] ,
[ 'matchingtype' , _ . isNumber ] ,
2018-01-09 12:53:16 -05:00
[ 'target' , _ . isString ] , // recordBuilder needs a "target" argument
2017-09-15 09:03:29 -04:00
] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
2017-09-15 09:03:29 -04:00
record . name = args . name ;
record . tlsausage = args . usage ;
record . tlsaselector = args . selector ;
record . tlsamatchingtype = args . matchingtype ;
record . target = args . target ;
} ,
} ) ;
2018-01-04 19:19:35 -05:00
function isStringOrArray ( x ) {
2018-01-06 15:13:22 -05:00
return _ . isString ( x ) || _ . isArray ( x ) ;
2018-01-04 19:19:35 -05:00
}
2021-03-07 13:19:22 -05:00
// AUTOSPLIT is deprecated. It is now a no-op.
2022-12-07 17:14:51 -05:00
var AUTOSPLIT = { } ;
2020-11-18 07:05:26 -05:00
2016-08-22 18:31:50 -06:00
// TXT(name,target, recordModifiers...)
2018-01-06 15:13:22 -05:00
var TXT = recordBuilder ( 'TXT' , {
2020-02-22 12:07:10 -05:00
args : [
[ 'name' , _ . isString ] ,
[ 'target' , isStringOrArray ] ,
] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
2018-01-06 15:13:22 -05:00
record . name = args . name ;
2021-03-07 13:19:22 -05:00
// Store the strings from the user verbatim.
2018-01-06 15:13:22 -05:00
if ( _ . isString ( args . target ) ) {
2023-12-04 17:45:25 -05:00
record . target = args . target ;
2018-01-06 15:13:22 -05:00
} else {
2023-12-04 17:45:25 -05:00
record . target = args . target . join ( '' ) ;
2018-01-06 15:13:22 -05:00
}
} ,
2018-01-04 19:19:35 -05:00
} ) ;
2016-08-22 18:31:50 -06:00
2023-03-16 19:04:20 +01:00
// Parses coordinates of the form 41°24'12.2"N 2°10'26.5"E
function parseDMSCoordinatesString ( inputString ) {
var lat = inputString . match ( /(-?\d+).(\d+).([\d\.]+).?\ ?([NS])/ ) ;
var lon = inputString . match ( /(-?\d+).(\d+).([\d\.]+).?\ ?([EW])/ ) ;
if ( ! lat || ! lon ) {
return null ;
}
return {
lati : {
dg : parseInt ( lat [ 1 ] ) ,
mn : parseInt ( lat [ 2 ] ) ,
sc : parseFloat ( lat [ 3 ] ) ,
hemi : lat [ 4 ] ,
} ,
long : {
dg : parseInt ( lon [ 1 ] ) ,
mn : parseInt ( lon [ 2 ] ) ,
sc : parseFloat ( lon [ 3 ] ) ,
hemi : lon [ 4 ] ,
} ,
} ;
}
// // Parses coordinates of the form 25.24°S 153.15°E
function parseDMMCoordinatesString ( inputString ) {
var lat = inputString . match ( /(-?\d+(\.\d+)?)°?\s*([NS])/i ) ;
var lon = inputString . match ( /(-?\d+(\.\d+)?)°?\s*([EW])/i ) ;
if ( ! lat || ! lon ) {
return null ;
}
var latDeg = Math . floor ( parseFloat ( lat [ 1 ] ) ) ;
var latMin = Math . floor ( ( parseFloat ( lat [ 1 ] ) - latDeg ) * 60 ) ;
var latSec = Math . round ( ( ( parseFloat ( lat [ 1 ] ) - latDeg ) * 60 - latMin ) * 60 ) ;
var lonDeg = Math . floor ( parseFloat ( lon [ 1 ] ) ) ;
var lonMin = Math . floor ( ( parseFloat ( lon [ 1 ] ) - lonDeg ) * 60 ) ;
var lonSec = Math . round ( ( ( parseFloat ( lon [ 1 ] ) - lonDeg ) * 60 - lonMin ) * 60 ) ;
var lati = {
dg : latDeg ,
mn : latMin ,
sc : latSec ,
hemi : lat [ 3 ] ,
} ;
var long = {
dg : lonDeg ,
mn : lonMin ,
sc : lonSec ,
hemi : lon [ 3 ] ,
} ;
return {
lati : lati ,
long : long ,
} ;
}
// builds a uint8 with 4 bit mantissa, 4 bit exponent from a float.
function getENotationInt ( x ) {
/ *
9000000000 cm = 9e9 == 153 ( 9 ^ 4 + 9 ) or 9 << 4 + 9
800000000 cm = 8e8 == 136 ( 8 ^ 4 + 8 ) or 8 << 4 + 8
70000000 cm = 7e7 == 119 ( 7 ^ 4 + 7 ) or 7 << 4 + 7
6000000 cm = 6e6 == 102 ( 6 ^ 4 + 6 ) or 6 << 4 + 6
1000000 cm = 1e6 == 22 ( 1 ^ 4 + 6 ) or 1 << 4 + 6
500000 cm = 5e5 == 85 ( 5 ^ 4 + 5 ) or 5 << 4 + 5
40000 cm = 4e4 == 68 ( 4 ^ 4 + 4 ) or 4 << 4 + 4
3000 cm = 3e3 == 51 ( 3 ^ 4 + 3 ) or 3 << 4 + 3
1000 cm = 1e3 == 19 ( 1 ^ 4 + 3 ) or 1 << 4 + 1
200 cm = 2e2 == 34 ( 2 ^ 4 + 2 ) or 2 << 4 + 2
10 cm = 1e1 == 17 ( 1 ^ 4 + 1 ) or 1 << 4 + 1
1 cm = 1e0 == 16 ( 1 ^ 4 + 0 ) or 0 << 4 + 0
0 cm = 0e0 == 0
* /
size = x * 100 ; // get cm value
// get the m^e version of size
array = size . toExponential ( 0 ) . split ( 'e+' ) . map ( Number ) ;
// convert it to 4bit:4bit uint8
m _e = ( array [ 0 ] << 4 ) | array [ 1 ] ;
return m _e ;
}
// Checks LOC parameters and if all is well, renders them into a 'target' string.
// The LOC record has no target string parameter. It only renders one via String().
function locStringBuilder ( record , args ) {
record . name = args . name ;
// technically, we don't need this part to build the text target, but
// it is a good sanity check to compare with later on down the chain
// when you're in the weeds with maths.
// Tests depend on it being present. Changes here must reflect in tests.
nsstring = '' ;
ewstring = '' ;
precisionbuffer = '' ;
ns = args . ns . toUpperCase ( ) ;
ew = args . ew . toUpperCase ( ) ;
// Handle N/S coords - can use also s1.toFixed(3)
nsstring =
args . d1 . toString ( ) +
' ' +
args . m1 . toString ( ) +
' ' +
args . s1 . toString ( ) +
' ' ;
var nsmatches = args . ns . match ( /^([NnSs])$/ ) ;
if ( nsmatches == null ) {
throw v + ' is not a valid latitude modifier' ;
} else {
nsstring += ns + ' ' ;
}
// Handle E/W coords - can use also s2.toFixed(3)
ewstring =
args . d2 . toString ( ) +
' ' +
args . m2 . toString ( ) +
' ' +
args . s2 . toString ( ) +
' ' ;
var nsmatches = args . ew . match ( /^([EeWw])$/ ) ;
if ( nsmatches == null ) {
throw v + ' is not a valid longitude modifier' ;
} else {
ewstring += ew + ' ' ;
}
// handle altitude, size, horizontal precision, vertical precision
precisionbuffer = args . alt . toString ( ) + 'm' ;
precisionbuffer += ' ' + args . siz . toString ( ) + 'm' ;
precisionbuffer += ' ' + args . hp . toString ( ) + 'm' ;
precisionbuffer += ' ' + args . vp . toString ( ) + 'm' ;
record . target = nsstring + ewstring + precisionbuffer ;
return record ;
}
// Renders LOC type internal properties from D˚M'S" parameters.
// Change anything here at your peril.
function locDMSBuilder ( record , args ) {
LOCEquator = 1 << 31 ; // RFC 1876, Section 2.
LOCPrimeMeridian = 1 << 31 ; // RFC 1876, Section 2.
LOCHours = 60 * 1000 ;
LOCDegrees = 60 * LOCHours ;
LOCAltitudeBase = 100000 ;
lat = args . d1 * LOCDegrees + args . m1 * LOCHours + args . s1 * 1000 ;
lon = args . d2 * LOCDegrees + args . m2 * LOCHours + args . s2 * 1000 ;
if ( ns == 'N' ) record . loclatitude = LOCEquator + lat ;
// S
else record . loclatitude = LOCEquator - lat ;
if ( ew == 'E' ) record . loclongitude = LOCPrimeMeridian + lon ;
// W
else record . loclongitude = LOCPrimeMeridian - lon ;
// Altitude
record . localtitude = ( args . alt + LOCAltitudeBase ) * 100 ;
// Size
record . locsize = getENotationInt ( args . siz ) ;
// Horizontal Precision
m _e = args . hp ;
record . lochorizpre = getENotationInt ( args . hp ) ;
// if (m_e != 0) {
// } else {
// record.lochorizpre = 22; // 10,000m default
// }
// Vertical Precision
m _e = args . vp ;
record . locvertpre = getENotationInt ( args . vp ) ;
// if (m_e != 0) {
// } else {
// record.lochorizpre = 19; // 10m default
// }
}
// LOC(name,d1,m1,s1,ns,d2,m2,s2,ew,alt,siz,hp,vp, recordModifiers...)
var LOC = recordBuilder ( 'LOC' , {
args : [
[ 'name' , _ . isString ] , //i.e. subdomain
[ 'd1' , _ . isNumber ] , // N/S degrees
[ 'm1' , _ . isNumber ] , // N/S minutes
[ 's1' , _ . isNumber ] , // N/S seconds
[ 'ns' , _ . isString ] , // N/S
[ 'd2' , _ . isNumber ] , // E/W degrees
[ 'm2' , _ . isNumber ] , // E/W minutes
[ 's2' , _ . isNumber ] , // E/W seconds
[ 'ew' , _ . isString ] , // E/W
[ 'alt' , _ . isNumber ] , // altitude
[ 'siz' , _ . isNumber ] , // size/precision
[ 'hp' , _ . isNumber ] , // horizontal precision
[ 'vp' , _ . isNumber ] , // vertical precision
] ,
transform : function ( record , args , modifiers ) {
record = locStringBuilder ( record , args ) ;
record = locDMSBuilder ( record , args ) ;
} ,
} ) ;
function ConvertDDToDMS ( D , longitude ) {
//stackoverflow, baby. do not re-order the rows.
return {
hemi : D < 0 ? ( longitude ? 'W' : 'S' ) : longitude ? 'E' : 'N' ,
dg : 0 | ( D < 0 ? ( D = - D ) : D ) ,
mn : 0 | ( ( ( D += 1e-9 ) % 1 ) * 60 ) ,
sc : ( 0 | ( ( ( D * 60 ) % 1 ) * 60000 ) ) / 1000 ,
} ;
}
2016-08-22 18:31:50 -06:00
// MX(name,priority,target, recordModifiers...)
2017-08-12 01:48:43 +07:00
var MX = recordBuilder ( 'MX' , {
args : [
[ 'name' , _ . isString ] ,
[ 'priority' , _ . isNumber ] ,
[ 'target' , _ . isString ] ,
] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
2017-08-12 01:48:43 +07:00
record . name = args . name ;
record . mxpreference = args . priority ;
record . target = args . target ;
} ,
} ) ;
2016-08-22 18:31:50 -06:00
// NS(name,target, recordModifiers...)
2017-08-12 01:48:43 +07:00
var NS = recordBuilder ( 'NS' ) ;
2016-08-22 18:31:50 -06:00
// NAMESERVER(name,target)
2018-03-22 11:52:52 -04:00
function NAMESERVER ( name ) {
2020-02-22 12:07:10 -05:00
if ( arguments . length != 1 ) {
throw 'NAMESERVER only accepts one argument for name.' ;
2018-03-22 11:52:52 -04:00
}
2022-12-07 17:14:51 -05:00
return function ( d ) {
2018-03-22 11:52:52 -04:00
d . nameservers . push ( { name : name } ) ;
2017-09-16 00:12:09 +07:00
} ;
2016-08-22 18:31:50 -06:00
}
2018-09-04 07:57:11 -07:00
// NAMESERVER_TTL(v): Set the TTL for NAMESERVER records.
function NAMESERVER _TTL ( v ) {
if ( _ . isString ( v ) ) {
v = stringToDuration ( v ) ;
}
2020-02-22 12:07:10 -05:00
return { ns _ttl : v . toString ( ) } ;
2018-09-04 07:57:11 -07:00
}
2016-08-22 18:31:50 -06:00
function format _tt ( transform _table ) {
2017-09-16 00:12:09 +07:00
// 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 ) ) {
2022-12-07 17:14:51 -05:00
newIP = _ . map ( newIP , function ( i ) {
2017-09-16 00:12:09 +07:00
return num2dot ( i ) ;
} ) . join ( ',' ) ;
} else {
newIP = num2dot ( newIP ) ;
}
2016-08-22 18:31:50 -06:00
}
2017-09-16 00:12:09 +07:00
var newBase = ip . newBase ;
if ( newBase ) {
if ( _ . isArray ( newBase ) ) {
2022-12-07 17:14:51 -05:00
newBase = _ . map ( newBase , function ( i ) {
2017-09-16 00:12:09 +07:00
return num2dot ( i ) ;
} ) . join ( ',' ) ;
} else {
newBase = num2dot ( newBase ) ;
}
2016-09-27 11:35:28 -06:00
}
2017-09-16 00:12:09 +07:00
var row = [ num2dot ( ip . low ) , num2dot ( ip . high ) , newBase , newIP ] ;
lines . push ( row . join ( ' ~ ' ) ) ;
2016-09-27 11:35:28 -06:00
}
2017-09-16 00:12:09 +07:00
return lines . join ( ' ; ' ) ;
2016-08-22 18:31:50 -06:00
}
2023-05-24 15:14:36 -04:00
//function UNMANAGED(label_pattern, rType_pattern, target_pattern) {
// return function (d) {
// d.unmanaged.push({
// label_pattern: label_pattern,
// rType_pattern: rType_pattern,
// target_pattern: target_pattern,
// });
// };
//}
function DISABLE _IGNORE _SAFETY _CHECK ( d ) {
// This disables a safety check intended to prevent DNSControl and
// another system getting into a battle as they both try to update
// the same record over and over, back and forth. However, people
// kept asking for it so... caveat emptor!
// It only affects the current domain.
d . unmanaged _disable _safety _check = true ;
}
// IGNORE(labelPattern, rtypePattern, targetPattern)
function IGNORE ( labelPattern , rtypePattern , targetPattern ) {
if ( labelPattern === undefined ) {
labelPattern = '*' ;
}
if ( rtypePattern === undefined ) {
rtypePattern = '*' ;
}
if ( targetPattern === undefined ) {
targetPattern = '*' ;
}
return function ( d ) {
d . unmanaged . push ( {
label _pattern : labelPattern ,
rType _pattern : rtypePattern ,
target _pattern : targetPattern ,
} ) ;
} ;
2020-08-19 03:14:34 +12:00
}
2022-11-07 08:27:04 -08:00
// IGNORE_NAME(name, rTypes)
function IGNORE _NAME ( name , rTypes ) {
2024-04-22 21:54:12 +08:00
return IGNORE ( name , rTypes ) ;
2018-01-15 21:39:29 +01:00
}
2020-08-19 03:14:34 +12:00
function IGNORE _TARGET ( target , rType ) {
2024-04-22 21:54:12 +08:00
return IGNORE ( '*' , rType , target ) ;
2020-08-19 03:14:34 +12:00
}
2016-08-22 18:31:50 -06:00
// IMPORT_TRANSFORM(translation_table, domain)
2017-08-12 01:48:43 +07:00
var IMPORT _TRANSFORM = recordBuilder ( 'IMPORT_TRANSFORM' , {
2017-09-16 00:12:09 +07:00
args : [ [ 'translation_table' ] , [ 'domain' ] , [ 'ttl' , _ . isNumber ] ] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
2017-08-12 01:48:43 +07:00
record . name = '@' ;
record . target = args . domain ;
record . meta [ 'transform_table' ] = format _tt ( args . translation _table ) ;
record . ttl = args . ttl ;
} ,
} ) ;
2016-08-22 18:31:50 -06:00
// PURGE()
function PURGE ( d ) {
2017-09-16 00:12:09 +07:00
d . KeepUnknown = false ;
2016-08-22 18:31:50 -06:00
}
// NO_PURGE()
function NO _PURGE ( d ) {
2017-09-16 00:12:09 +07:00
d . KeepUnknown = true ;
2016-08-22 18:31:50 -06:00
}
2023-02-19 12:33:08 -05:00
// ENSURE_ABSENT_REC()
// Usage: A("foo", "1.2.3.4", ENSURE_ABSENT_REC())
function ENSURE _ABSENT _REC ( ) {
return function ( r ) {
r . ensure _absent = true ;
} ;
}
// ENSURE_ABSENT()
// Usage: ENSURE_ABSENT(A("foo", "1.2.3.4"))
// (BROKEN. COMMENTED OUT UNTIL IT IS FIXED.)
// function ENSURE_ABSENT(r) {
// //console.log(r);
// return r;
// }
2020-09-27 16:37:42 -04:00
// AUTODNSSEC
// Permitted values are:
// "" Do not modify the setting (the default)
// "on" Enable AUTODNSSEC for this domain
// "off" Disable AUTODNSSEC for this domain
function AUTODNSSEC _ON ( d ) {
2022-12-07 17:14:51 -05:00
d . auto _dnssec = 'on' ;
2020-09-27 16:37:42 -04:00
}
function AUTODNSSEC _OFF ( d ) {
2022-12-07 17:14:51 -05:00
d . auto _dnssec = 'off' ;
2020-09-27 16:37:42 -04:00
}
2020-02-22 07:09:31 -05:00
function AUTODNSSEC ( d ) {
2022-12-07 17:14:51 -05:00
console . log (
'WARNING: AUTODNSSEC is deprecated. It is now a no-op. Please use AUTODNSSEC_ON or AUTODNSSEC_OFF. The default is to make no modifications. This message will disappear in a future release.'
) ;
2020-02-22 07:09:31 -05:00
}
2017-08-12 01:48:43 +07:00
/ * *
* @ deprecated
* /
2017-09-16 00:12:09 +07:00
function getModifiers ( args , start ) {
2016-08-22 18:31:50 -06:00
var mods = [ ] ;
2017-09-16 00:12:09 +07:00
for ( var i = start ; i < args . length ; i ++ ) {
mods . push ( args [ i ] ) ;
2016-08-22 18:31:50 -06:00
}
return mods ;
}
2017-08-12 01:48:43 +07:00
/ * *
* 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
* /
2017-09-16 00:12:09 +07:00
function recordBuilder ( type , opts ) {
2017-08-12 01:48:43 +07:00
opts = _ . defaults ( { } , opts , {
2017-09-16 00:12:09 +07:00
args : [ [ 'name' , _ . isString ] , [ 'target' ] ] ,
2017-08-12 01:48:43 +07:00
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
2017-08-12 01:48:43 +07:00
// 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 ;
}
} ,
2022-12-07 17:14:51 -05:00
applyModifier : function ( record , modifiers ) {
2017-08-12 01:48:43 +07:00
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 {
2017-09-16 00:12:09 +07:00
throw 'ERROR: Unknown modifier type' ;
2017-08-12 01:48:43 +07:00
}
}
} ,
} ) ;
2022-12-07 17:14:51 -05:00
return function ( ) {
2017-08-12 01:48:43 +07:00
var parsedArgs = { } ;
var modifiers = [ ] ;
if ( arguments . length < opts . args . length ) {
2017-09-16 00:12:09 +07:00
var argumentsList = opts . args
2022-12-07 17:14:51 -05:00
. map ( function ( item ) {
2017-09-16 00:12:09 +07:00
return item [ 0 ] ;
} )
. join ( ', ' ) ;
2022-12-07 17:14:51 -05:00
throw (
type +
2017-09-16 00:12:09 +07:00
' record requires ' +
opts . args . length +
' arguments (' +
argumentsList +
'). Only ' +
arguments . length +
2022-12-07 17:14:51 -05:00
' were supplied'
) ;
2017-08-12 01:48:43 +07:00
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
2017-09-16 00:12:09 +07:00
if ( ! argDefinition [ 1 ] ( value ) ) {
2022-12-07 17:14:51 -05:00
throw (
type +
2017-09-16 00:12:09 +07:00
' record ' +
argDefinition [ 0 ] +
2022-12-07 17:14:51 -05:00
' argument validation failed'
) ;
2017-08-12 01:48:43 +07:00
}
}
parsedArgs [ argDefinition [ 0 ] ] = value ;
}
// collect modifiers
for ( var i = opts . args . length ; i < arguments . length ; i ++ ) {
modifiers . push ( arguments [ i ] ) ;
}
2022-12-07 17:14:51 -05:00
return function ( d ) {
2017-08-12 01:48:43 +07:00
var record = {
type : type ,
meta : { } ,
ttl : d . defaultTTL ,
} ;
opts . applyModifier ( record , modifiers ) ;
opts . transform ( record , parsedArgs , modifiers ) ;
2020-10-07 14:27:33 -04:00
// Handle D_EXTEND() with subdomains.
2022-12-07 17:14:51 -05:00
if (
d . subdomain &&
2020-12-03 08:33:37 -05:00
record . type != 'CF_REDIRECT' &&
2021-10-11 17:04:49 -03:00
record . type != 'CF_TEMP_REDIRECT' &&
2022-12-07 17:14:51 -05:00
record . type != 'CF_WORKER_ROUTE'
) {
fqdn = [ d . subdomain , d . name ] . join ( '.' ) ;
2020-12-03 08:33:37 -05:00
2020-10-07 14:27:33 -04:00
record . subdomain = d . subdomain ;
if ( record . name == '@' ) {
2020-12-03 08:33:37 -05:00
record . subdomain = d . subdomain ;
2020-10-07 14:27:33 -04:00
record . name = d . subdomain ;
2020-12-03 08:33:37 -05:00
} else if ( fqdn != record . name && record . type != 'PTR' ) {
record . subdomain = d . subdomain ;
2020-10-07 14:27:33 -04:00
record . name += '.' + d . subdomain ;
}
}
2023-02-19 12:33:08 -05:00
// Now we finally have the record. If it is a normal record, we add
// it to "records". If it is an ENSURE_ABSENT record, we add it to
// the ensure_absent list.
if ( record . ensure _absent ) {
d . recordsabsent . push ( record ) ;
} else {
d . records . push ( record ) ;
}
2017-08-12 01:48:43 +07:00
return record ;
} ;
} ;
}
/ * *
* @ deprecated
* /
2017-09-16 00:12:09 +07:00
function addRecord ( d , type , name , target , mods ) {
2016-08-22 18:31:50 -06:00
// if target is number, assume ip address. convert it.
if ( _ . isNumber ( target ) ) {
target = num2dot ( target ) ;
}
2017-09-16 00:12:09 +07:00
var rec = {
type : type ,
name : name ,
target : target ,
ttl : d . defaultTTL ,
priority : 0 ,
meta : { } ,
} ;
2016-08-22 18:31:50 -06: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
2017-07-19 15:53:40 -04:00
if ( mods ) {
2017-09-16 00:12:09 +07:00
for ( var i = 0 ; i < mods . length ; i ++ ) {
var m = mods [ i ] ;
2017-07-19 15:53:40 -04:00
if ( _ . isFunction ( m ) ) {
m ( rec ) ;
} else if ( _ . isObject ( m ) ) {
2018-01-09 12:53:16 -05:00
// convert transforms to strings
2017-09-16 00:12:09 +07:00
if ( m . transform && _ . isArray ( m . transform ) ) {
m . transform = format _tt ( m . transform ) ;
}
_ . extend ( rec . meta , m ) ;
_ . extend ( rec . meta , m ) ;
2017-07-19 15:53:40 -04:00
} else {
2017-09-16 00:12:09 +07:00
console . log (
'WARNING: Modifier type unsupported:' ,
typeof m ,
'(Skipping!)'
) ;
2017-07-19 15:53:40 -04:00
}
}
}
d . records . push ( rec ) ;
return rec ;
}
2018-01-09 12:53:16 -05:00
// ip conversion functions from http://stackoverflow.com/a/8105740/121660
2016-08-22 18:31:50 -06:00
// via http://javascript.about.com/library/blipconvert.htm
2017-09-16 00:12:09 +07:00
function IP ( dot ) {
2016-08-22 18:31:50 -06:00
var d = dot . split ( '.' ) ;
2017-09-16 00:12:09 +07:00
// prettier-ignore
return ( ( ( ( ( ( + d [ 0 ] ) * 256 ) + ( + d [ 1 ] ) ) * 256 ) + ( + d [ 2 ] ) ) * 256 ) + ( + d [ 3 ] ) ;
2016-08-22 18:31:50 -06:00
}
2017-09-16 00:12:09 +07:00
function num2dot ( num ) {
if ( num === undefined ) {
return '' ;
2016-08-22 18:31:50 -06:00
}
2017-09-16 00:12:09 +07:00
if ( _ . isString ( num ) ) {
return num ;
2016-08-22 18:31:50 -06:00
}
2017-09-16 00:12:09 +07:00
var d = num % 256 ;
for ( var i = 3 ; i > 0 ; i -- ) {
num = Math . floor ( num / 256 ) ;
2020-02-22 12:07:10 -05:00
d = ( num % 256 ) + '.' + d ;
2016-08-22 18:31:50 -06:00
}
return d ;
}
2017-05-11 01:02:57 -04:00
// Cloudflare aliases:
2017-05-15 16:28:26 -04:00
// Meta settings for individual records.
2017-09-16 00:12:09 +07:00
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-15 16:28:26 -04:00
// Per-domain meta settings:
// Proxy default off for entire domain (the default):
2017-09-16 00:12:09 +07:00
var CF _PROXY _DEFAULT _OFF = { cloudflare _proxy _default : 'off' } ;
2017-05-15 16:28:26 -04:00
// Proxy default on for entire domain:
2017-09-16 00:12:09 +07:00
var CF _PROXY _DEFAULT _ON = { cloudflare _proxy _default : 'on' } ;
2019-06-13 13:32:54 +02:00
// UniversalSSL off for entire domain:
var CF _UNIVERSALSSL _OFF = { cloudflare _universalssl : 'off' } ;
// UniversalSSL on for entire domain:
var CF _UNIVERSALSSL _ON = { cloudflare _universalssl : 'on' } ;
2017-05-25 20:38:48 -04:00
2017-05-19 14:15:57 -04:00
// CUSTOM, PROVIDER SPECIFIC RECORD TYPES
2017-08-12 01:48:43 +07:00
2019-05-23 06:29:21 -07:00
function _validateCloudflareRedirect ( value ) {
2017-09-16 00:12:09 +07:00
if ( ! _ . isString ( value ) ) {
2017-08-12 01:48:43 +07:00
return false ;
}
2017-09-16 00:12:09 +07:00
return value . indexOf ( ',' ) === - 1 ;
2017-08-12 01:48:43 +07:00
}
2017-09-16 00:12:09 +07:00
var CF _REDIRECT = recordBuilder ( 'CF_REDIRECT' , {
2017-08-12 01:48:43 +07:00
args : [
2019-05-23 06:29:21 -07:00
[ 'source' , _validateCloudflareRedirect ] ,
[ 'destination' , _validateCloudflareRedirect ] ,
2017-08-12 01:48:43 +07:00
] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
2017-09-16 00:12:09 +07:00
record . name = '@' ;
record . target = args . source + ',' + args . destination ;
2017-08-12 01:48:43 +07:00
} ,
} ) ;
2017-09-16 00:12:09 +07:00
var CF _TEMP _REDIRECT = recordBuilder ( 'CF_TEMP_REDIRECT' , {
2017-08-12 01:48:43 +07:00
args : [
2019-05-23 06:29:21 -07:00
[ 'source' , _validateCloudflareRedirect ] ,
[ 'destination' , _validateCloudflareRedirect ] ,
2017-08-12 01:48:43 +07:00
] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
2017-09-16 00:12:09 +07:00
record . name = '@' ;
record . target = args . source + ',' + args . destination ;
2017-08-12 01:48:43 +07:00
} ,
} ) ;
2017-10-23 12:54:31 -04:00
2021-10-11 17:04:49 -03:00
var CF _WORKER _ROUTE = recordBuilder ( 'CF_WORKER_ROUTE' , {
args : [
[ 'pattern' , _validateCloudflareRedirect ] ,
[ 'script' , _validateCloudflareRedirect ] ,
] ,
2022-12-07 17:14:51 -05:00
transform : function ( record , args , modifiers ) {
2021-10-11 17:04:49 -03:00
record . name = '@' ;
record . target = args . pattern + ',' + args . script ;
} ,
} ) ;
2017-12-06 15:56:57 -05:00
var URL = recordBuilder ( 'URL' ) ;
var URL301 = recordBuilder ( 'URL301' ) ;
var FRAME = recordBuilder ( 'FRAME' ) ;
2021-02-10 16:57:15 +00:00
var NS1 _URLFWD = recordBuilder ( 'NS1_URLFWD' ) ;
2022-03-02 23:21:51 +09:00
var CLOUDNS _WR = recordBuilder ( 'CLOUDNS_WR' ) ;
2017-12-06 15:50:21 -05:00
2023-03-16 19:04:20 +01:00
// LOC_BUILDER_DD takes an object:
// label: The DNS label for the LOC record. (default: '@')
// x: Decimal X coordinate.
// y: Decimal Y coordinate.
// alt: Altitude in m. You imperial measurement system people are suckers for punishment.
// ttl: The time for TTL, integer or string. (default: not defined, using DefaultTTL)
function LOC _BUILDER _DD ( value ) {
if ( ! value . x && ! value . y ) {
throw 'LOC_BUILDER_DD requires x and y elements' ;
}
if ( ! value . label ) {
value . label = '@' ;
}
var lati = ConvertDDToDMS ( value . x , false ) ;
var long = ConvertDDToDMS ( value . y , true ) ;
dms = { lati : lati , long : long } ;
return LOC _builder _push ( value , dms ) ;
}
// LOC_BUILDER_DMM_STR takes an object:
// label: The DNS label for the LOC record. (default: '@')
// str: Input string of Degrees and decimal minutes (DMM) coordinates in the form: 25.24°S 153.15°E
// alt: Altitude in m. You imperial measurement system people are suckers for punishment.
// ttl: The time for TTL, integer or string. (default: not defined, using DefaultTTL)
function LOC _BUILDER _DMM _STR ( value ) {
if ( ! value . str ) {
throw 'LOC_BUILDER_DMM_STR requires a string of the form 25.24°S 153.15°E' ;
}
if ( ! value . label ) {
value . label = '@' ;
}
var dms = parseDMMCoordinatesString ( value . str ) ;
return LOC _builder _push ( value , dms ) ;
}
// LOC_BUILDER_DMS_STR takes an object:
// label: The DNS label for the LOC record. (default: '@')
// str: Input string of degrees, minutes, and seconds (DMS) coordinates in the form: 41°24'12.2"N 2°10'26.5"E
// alt: Altitude in m. You imperial measurement system people are suckers for punishment.
// ttl: The time for TTL, integer or string. (default: not defined, using DefaultTTL)
function LOC _BUILDER _DMS _STR ( value ) {
if ( ! value . str ) {
throw 'LOC_BUILDER_DMS_STR requires a string of the form 33°51′ 31″S 151°12′ 51″Es (or 33°51\'31"S 151°12\'51"Es)' ;
}
if ( ! value . label ) {
value . label = '@' ;
}
var dms = parseDMSCoordinatesString ( value . str ) ;
return LOC _builder _push ( value , dms ) ;
}
// LOC_BUILDER_STR takes an object:
// label: The DNS label for the LOC record. (default: '@')
// str: Input string of degrees, minutes, and seconds (DMS) coordinates in the form: 41°24'12.2"N 2°10'26.5"E
// alt: Altitude in m. You imperial measurement system people are suckers for punishment.
// ttl: The time for TTL, integer or string. (default: not defined, using DefaultTTL)
function LOC _BUILDER _STR ( value ) {
if ( ! value . str ) {
throw 'LOC_BUILDER_STR requires a string' ;
}
if ( ! value . label ) {
value . label = '@' ;
}
var dms = parseDMMCoordinatesString ( value . str ) ;
if ( ! dms ) dms = parseDMSCoordinatesString ( value . str ) ;
return LOC _builder _push ( value , dms ) ;
}
function LOC _builder _push ( value , dms ) {
r = [ ] ; // The list of records to return.
p = { } ; // The metaparameters to set on the LOC record.
// rawloc = "";
// Generate a LOC record with the metaparameters.
if ( value . ttl ) {
if ( value . alt )
r . push (
LOC (
value . label ,
dms . lati . dg ,
dms . lati . mn ,
dms . lati . sc ,
dms . lati . hemi ,
dms . long . dg ,
dms . long . mn ,
dms . long . sc ,
dms . long . hemi ,
value . alt ,
0 ,
0 ,
0 ,
p ,
TTL ( value . ttl )
)
) ;
else
r . push (
LOC (
value . label ,
dms . lati . dg ,
dms . lati . mn ,
dms . lati . sc ,
dms . lati . hemi ,
dms . long . dg ,
dms . long . mn ,
dms . long . sc ,
dms . long . hemi ,
0 ,
0 ,
0 ,
0 ,
p ,
TTL ( value . ttl )
)
) ;
} else {
if ( value . alt )
r . push (
LOC (
value . label ,
dms . lati . dg ,
dms . lati . mn ,
dms . lati . sc ,
dms . lati . hemi ,
dms . long . dg ,
dms . long . mn ,
dms . long . sc ,
dms . long . hemi ,
value . alt ,
0 ,
0 ,
0 ,
p
)
) ;
else
r . push (
LOC (
value . label ,
dms . lati . dg ,
dms . lati . mn ,
dms . lati . sc ,
dms . lati . hemi ,
dms . long . dg ,
dms . long . mn ,
dms . long . sc ,
dms . long . hemi ,
0 ,
0 ,
0 ,
0 ,
p
)
) ;
}
return r ;
}
2017-12-06 15:50:21 -05:00
// 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-06 15:56:57 -05:00
// raw: Where (which label) to store an unaltered version of the SPF settings.
2019-05-23 15:25:06 +02:00
// ttl: The time for TTL, integer or string. (default: not defined, using DefaultTTL)
2017-12-06 15:50:21 -05:00
// split: The template for additional records to be created (default: '_spf%d')
// flatten: A list of domains to be flattened.
2020-07-09 12:52:49 -04:00
// overhead1: Amout of "buffer room" to reserve on the first item in the spf chain.
2020-07-31 19:28:13 +02:00
// txtMaxSize: The maximum size for each TXT string. Values over 255 will result in multiple strings (default: '255')
2017-12-06 15:50:21 -05:00
function SPF _BUILDER ( value ) {
2017-12-06 15:56:57 -05:00
if ( ! value . parts || value . parts . length < 2 ) {
throw 'SPF_BUILDER requires at least 2 elements' ;
}
if ( ! value . label ) {
value . label = '@' ;
}
2020-07-31 16:40:22 +02:00
if ( ! value . raw && value . raw !== '' ) {
2017-12-06 15:56:57 -05:00
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 ( ',' ) ;
2020-07-31 16:40:22 +02:00
// Only add the raw spf record if it isn't an empty string
if ( value . raw !== '' ) {
2020-11-18 07:05:26 -05:00
rp = { } ;
2020-07-31 16:40:22 +02:00
if ( value . ttl ) {
2020-11-18 07:05:26 -05:00
r . push ( TXT ( value . raw , rawspf , rp , TTL ( value . ttl ) ) ) ;
2020-07-31 16:40:22 +02:00
} else {
2020-11-18 07:05:26 -05:00
r . push ( TXT ( value . raw , rawspf , rp ) ) ;
2020-07-31 16:40:22 +02:00
}
2019-05-23 15:25:06 +02:00
}
2017-12-06 15:56:57 -05:00
}
// If overflow is specified, enable splitting.
if ( value . overflow ) {
p . split = value . overflow ;
}
2020-07-09 12:52:49 -04:00
if ( value . overhead1 ) {
p . overhead1 = value . overhead1 ;
}
2020-07-31 19:28:13 +02:00
if ( value . txtMaxSize ) {
p . txtMaxSize = value . txtMaxSize ;
}
2017-12-06 15:56:57 -05:00
// Generate a TXT record with the metaparameters.
2019-05-23 15:25:06 +02:00
if ( value . ttl ) {
r . push ( TXT ( value . label , rawspf , p , TTL ( value . ttl ) ) ) ;
} else {
r . push ( TXT ( value . label , rawspf , p ) ) ;
}
2017-12-06 15:56:57 -05:00
return r ;
2017-12-06 15:50:21 -05:00
}
2018-01-04 21:17:08 -05:00
2019-05-18 17:10:18 +02:00
// 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-22 12:07:10 -05:00
if ( value . issue && value . issue == 'none' ) value . issue = [ ';' ] ;
if ( value . issuewild && value . issuewild == 'none' ) value . issuewild = [ ';' ] ;
2019-05-18 17:10:18 +02:00
2020-02-22 12:07:10 -05:00
if (
( ! value . issue && ! value . issuewild ) ||
( value . issue &&
value . issue . length == 0 &&
2020-03-02 20:25:42 +04:00
value . issuewild &&
value . issuewild . length == 0 )
2020-02-22 12:07:10 -05:00
) {
2019-05-18 17:10:18 +02: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-22 12:07:10 -05:00
r . push ( CAA ( value . label , 'iodef' , value . iodef , CAA _CRITICAL ) ) ;
2019-05-18 17:10:18 +02:00
} else {
2020-02-22 12:07:10 -05:00
r . push ( CAA ( value . label , 'iodef' , value . iodef ) ) ;
2019-05-18 17:10:18 +02:00
}
}
2023-12-18 15:35:10 +01:00
if ( value . issue ) {
2024-04-22 21:54:12 +08:00
var flag = function ( ) { } ;
2023-12-18 15:35:10 +01:00
if ( value . issue _critical ) {
2024-04-22 21:54:12 +08:00
flag = CAA _CRITICAL ;
2023-12-18 15:35:10 +01:00
}
2019-05-18 17:10:18 +02:00
for ( var i = 0 , len = value . issue . length ; i < len ; i ++ )
2023-12-18 15:35:10 +01:00
r . push ( CAA ( value . label , 'issue' , value . issue [ i ] , flag ) ) ;
}
2019-05-18 17:10:18 +02:00
2023-12-18 15:35:10 +01:00
if ( value . issuewild ) {
2024-04-22 21:54:12 +08:00
var flag = function ( ) { } ;
2023-12-18 15:35:10 +01:00
if ( value . issuewild _critical ) {
2024-04-22 21:54:12 +08:00
flag = CAA _CRITICAL ;
2023-12-18 15:35:10 +01:00
}
2019-05-18 17:10:18 +02:00
for ( var i = 0 , len = value . issuewild . length ; i < len ; i ++ )
2023-12-18 15:35:10 +01:00
r . push ( CAA ( value . label , 'issuewild' , value . issuewild [ i ] , flag ) ) ;
}
2019-05-18 17:10:18 +02:00
return r ;
}
2021-03-08 14:50:14 +01:00
// DMARC_BUILDER takes an object:
// label: The DNS label for the DMARC record (_dmarc prefix is added; default: '@')
2021-08-14 19:43:39 +01:00
// version: The DMARC version, by default DMARC1 (optional)
2021-03-08 14:50:14 +01:00
// policy: The DMARC policy (p=), must be one of 'none', 'quarantine', 'reject'
// subdomainPolicy: The DMARC policy for subdomains (sp=), must be one of 'none', 'quarantine', 'reject' (optional)
// alignmentSPF: 'strict'/'s' or 'relaxed'/'r' alignment for SPF (aspf=, default: 'r')
// alignmentDKIM: 'strict'/'s' or 'relaxed'/'r' alignment for DKIM (adkim=, default: 'r')
// percent: Number between 0 and 100, percentage for which policies are applied (pct=, default: 100)
// rua: Array of aggregate report targets (optional)
// ruf: Array of failure report targets (optional)
// failureOptions: Object or string; Object containing booleans SPF and DKIM, string is passed raw (fo=, default: '0')
// failureFormat: Format in which failure reports are requested (rf=, default: 'afrf')
// reportInterval: Interval in which reports are requested (ri=)
// ttl: Input for TTL method
function DMARC _BUILDER ( value ) {
if ( ! value ) {
value = { } ;
}
if ( ! value . label ) {
value . label = '@' ;
}
2021-08-14 19:43:39 +01:00
if ( ! value . version ) {
value . version = 'DMARC1' ;
}
2021-03-08 14:50:14 +01:00
var label = '_dmarc' ;
if ( value . label !== '@' ) {
label += '.' + value . label ;
}
if ( ! value . policy ) {
value . policy = 'none' ;
}
2022-12-07 17:14:51 -05:00
if (
! value . policy === 'none' ||
! value . policy === 'quarantine' ||
! value . policy === 'reject'
) {
2021-03-08 14:50:14 +01:00
throw 'Invalid DMARC policy' ;
}
2021-08-14 19:43:39 +01:00
var record = [ ] ;
record . push ( 'v=' + value . version ) ;
2021-03-08 14:50:14 +01:00
record . push ( 'p=' + value . policy ) ;
// Subdomain policy
2022-12-07 17:14:51 -05:00
if (
! value . subdomainPolicy === 'none' ||
! value . subdomainPolicy === 'quarantine' ||
! value . subdomainPolicy === 'reject'
) {
2021-03-08 14:50:14 +01:00
throw 'Invalid DMARC subdomain policy' ;
}
if ( value . subdomainPolicy ) {
record . push ( 'sp=' + value . subdomainPolicy ) ;
}
// Alignment DKIM
if ( value . alignmentDKIM ) {
switch ( value . alignmentDKIM ) {
case 'relaxed' :
value . alignmentDKIM = 'r' ;
break ;
case 'strict' :
value . alignmentDKIM = 's' ;
break ;
case 'r' :
case 's' :
break ;
default :
throw 'Invalid DMARC DKIM alignment policy' ;
}
record . push ( 'adkim=' + value . alignmentDKIM ) ;
}
// Alignment SPF
if ( value . alignmentSPF ) {
switch ( value . alignmentSPF ) {
case 'relaxed' :
value . alignmentSPF = 'r' ;
break ;
case 'strict' :
value . alignmentSPF = 's' ;
break ;
case 'r' :
case 's' :
break ;
default :
throw 'Invalid DMARC DKIM alignment policy' ;
}
record . push ( 'aspf=' + value . alignmentSPF ) ;
}
// Percentage
2021-08-14 19:43:39 +01:00
if ( value . percent ) {
2021-03-08 14:50:14 +01:00
record . push ( 'pct=' + value . percent ) ;
}
// Aggregate reports
if ( value . rua && value . rua . length > 0 ) {
record . push ( 'rua=' + value . rua . join ( ',' ) ) ;
}
// Failure reports
if ( value . ruf && value . ruf . length > 0 ) {
record . push ( 'ruf=' + value . ruf . join ( ',' ) ) ;
}
// Failure reporting options
if ( value . ruf && value . failureOptions ) {
var fo = '0' ;
if ( _ . isObject ( value . failureOptions ) ) {
if ( value . failureOptions . DKIM ) {
fo = 'd' ;
}
if ( value . failureOptions . SPF ) {
fo = 's' ;
}
if ( value . failureOptions . DKIM && value . failureOptions . SPF ) {
fo = '1' ;
}
} else {
fo = value . failureOptions ;
}
if ( fo !== '0' ) {
record . push ( 'fo=' + fo ) ;
}
}
// Failure report format
2021-08-14 19:43:39 +01:00
if ( value . ruf && value . failureFormat ) {
2021-03-08 14:50:14 +01:00
record . push ( 'rf=' + value . failureFormat ) ;
}
// Report interval
if ( value . reportInterval ) {
if ( _ . isString ( value . reportInterval ) ) {
value . reportInterval = stringToDuration ( value . reportInterval ) ;
}
record . push ( 'ri=' + value . reportInterval ) ;
}
if ( value . ttl ) {
return TXT ( label , record . join ( '; ' ) , TTL ( value . ttl ) ) ;
}
return TXT ( label , record . join ( '; ' ) ) ;
}
2023-05-17 16:38:24 +02:00
// Documentation of the records: https://learn.microsoft.com/en-us/microsoft-365/enterprise/external-domain-name-system-records?view=o365-worldwide
function M365 _BUILDER ( name , value ) {
// value is optional
if ( ! value ) {
value = { } ;
}
if ( value . mx !== false ) {
value . mx = true ;
}
if ( value . autodiscover !== false ) {
value . autodiscover = true ;
}
if ( value . dkim !== false ) {
value . dkim = true ;
}
if ( ! value . label ) {
value . label = '@' ;
}
if ( ! value . domainGUID ) {
// Does not work with dashes in domain name.
// Microsoft uses its own, (probably) deterministic algorithm to transform these domains.
// Unfortunately, underlying algorithm is not known to us.
if ( name . indexOf ( '-' ) !== - 1 ) {
throw (
'M365_BUILDER requires domainGUID for domains with dashes: ' +
name
) ;
}
value . domainGUID = name . replace ( '.' , '-' ) ;
}
if ( value . dkim && ! value . initialDomain ) {
throw (
"M365_BUILDER requires your M365 account's initial domain to set up DKIM (default: enabled): " +
name
) ;
}
2023-12-14 15:24:58 -05:00
var r = [ ] ;
2023-05-17 16:38:24 +02:00
// MX (default: true)
if ( value . mx ) {
r . push (
MX (
value . label ,
0 ,
value . domainGUID + '.mail.protection.outlook.com.'
)
) ;
}
// Autodiscover (default: true)
if ( value . autodiscover ) {
if ( ( value . label = '@' ) ) {
r . push ( CNAME ( 'autodiscover' , 'autodiscover.outlook.com.' ) ) ;
} else {
r . push (
CNAME (
'autodiscover.' + value . label ,
'autodiscover.outlook.com.'
)
) ;
}
}
// DKIM (default: true)
if ( value . dkim ) {
r . push (
CNAME (
'selector1._domainkey' ,
'selector1-' +
value . domainGUID +
'._domainkey.' +
value . initialDomain +
'.'
)
) ;
r . push (
CNAME (
'selector2._domainkey' ,
'selector2-' +
value . domainGUID +
'._domainkey.' +
value . initialDomain +
'.'
)
) ;
}
// Skype for Business (default: false)
if ( value . skypeForBusiness ) {
r . push ( CNAME ( 'lyncdiscover' , 'webdir.online.lync.com.' ) ) ;
r . push ( CNAME ( 'sip' , 'sipdir.online.lync.com.' ) ) ;
r . push ( SRV ( '_sip._tls' , 100 , 1 , 443 , 'sipdir.online.lync.com.' ) ) ;
r . push (
SRV (
'_sipfederationtls._tcp' ,
100 ,
1 ,
5061 ,
'sipfed.online.lync.com.'
)
) ;
}
// Mobile Device Management (default: false)
if ( value . mdm ) {
r . push (
CNAME (
'enterpriseregistration' ,
'enterpriseregistration.windows.net.'
)
) ;
r . push (
CNAME (
'enterpriseenrollment' ,
'enterpriseenrollment.manage.microsoft.com.'
)
) ;
}
return r ;
}
2021-03-07 15:01:03 -05:00
// This is a no-op. Long TXT records are handled natively now.
2018-01-04 21:17:08 -05:00
function DKIM ( arr ) {
2021-03-07 15:01:03 -05:00
return arr ;
2018-01-04 21:17:08 -05:00
}
2020-08-19 19:00:40 +01:00
// Function wrapper for glob() for recursively loading files.
// As the main function (in Go) is in our control anyway, all the values here are already sanity-checked.
// Note: glob() is only an internal undocumented helper function. So use it on your own risk.
function require _glob ( ) {
2022-12-07 17:14:51 -05:00
arguments [ 2 ] = 'js' ; // force to only include .js files.
2020-08-19 19:00:40 +01:00
var files = glob . apply ( null , arguments ) ;
2023-06-13 19:28:22 +02:00
for ( var i = 0 ; i < files . length ; i ++ ) {
2020-08-19 19:00:40 +01:00
require ( files [ i ] ) ;
}
2022-12-07 17:14:51 -05:00
return files ;
2020-08-19 19:00:40 +01:00
}
2020-10-27 15:43:00 +01:00
// Set default values for CLI variables
function CLI _DEFAULTS ( defaults ) {
for ( var key in defaults ) {
2022-12-07 17:14:51 -05:00
if ( typeof this [ key ] === 'undefined' ) {
this [ key ] = defaults [ key ] ;
2020-10-27 15:43:00 +01:00
}
}
}
2021-01-06 23:45:32 +08:00
function FETCH ( ) {
return fetch . apply ( null , arguments ) . catch ( PANIC ) ;
}
2021-09-02 15:41:22 -04:00
// DOMAIN_ELSEWHERE is a helper macro that delegates a domain to a
// static list of nameservers. It updates the registrar (the parent)
// with a list of nameservers. This is used when we own the domain (we
// control the registrar) but something else controls the DNS records
// (often a third-party of Azure).
// Usage: DOMAIN_ELSEWHERE("example.com", REG_NAMEDOTCOM, ["ns1.foo.com", "ns2.foo.com"]);
function DOMAIN _ELSEWHERE ( domain , registrar , nslist ) {
D ( domain , registrar , NO _PURGE ) ;
// NB(tlim): NO_PURGE is added as a precaution since something else
// is maintaining the DNS records in that zone. In theory this is
// not needed since this domain won't have a DSP defined.
2023-06-13 19:28:22 +02:00
for ( var i = 0 ; i < nslist . length ; i ++ ) {
2021-09-02 15:41:22 -04:00
D _EXTEND ( domain , NAMESERVER ( nslist [ i ] ) ) ;
}
}
// DOMAIN_ELSEWHERE_AUTO is similar to DOMAIN_ELSEWHERE but the list of
// nameservers is queried from a DNS Service Provider.
// Usage: DOMAIN_ELSEWHERE_AUTO("example.com", REG_NAMEDOTCOM, DNS_FOO)
function DOMAIN _ELSEWHERE _AUTO ( domain , registrar , dsplist ) {
D ( domain , registrar , NO _PURGE ) ;
// NB(tlim): NO_PURGE is required since something else
// is maintaining the DNS records in that zone, and we have access
// to updating it (but we don't want to use it.)
2023-06-13 19:28:22 +02:00
for ( var i = 2 ; i < arguments . length ; i ++ ) {
2021-09-02 15:41:22 -04:00
D _EXTEND ( domain , DnsProvider ( arguments [ i ] ) ) ;
}
}
2022-04-04 14:05:49 -04:00
var END = { } ; // This is null. It permits the last item to include a comma.
// D("foo.com", ...
// A(...),
// A(...),
// A(...),
// END)
// Record modifiers:
// Permit labels like "foo.bar.com.bar.com" (normally an error):
2022-12-07 17:14:51 -05:00
var DISABLE _REPEATED _DOMAIN _CHECK = { skip _fqdn _check : 'true' } ;
2022-04-04 14:05:49 -04:00
// D("bar.com", ...
// A("foo.bar.com", "10.1.1.1", DISABLE_REPEATED_DOMAIN_CHECK),
// )