2017-10-28 22:18:07 +02:00
'use strict' ;
module . exports = exports = {
2020-12-23 15:34:23 -08:00
add ,
get ,
getAll ,
update ,
del ,
clear ,
2017-10-28 22:18:07 +02:00
2020-12-23 15:34:23 -08:00
fqdn ,
getName ,
2018-01-01 19:19:07 -08:00
2020-12-23 15:34:23 -08:00
getDnsRecords ,
upsertDnsRecords ,
removeDnsRecords ,
2017-10-29 00:15:11 +02:00
2020-12-23 15:34:23 -08:00
waitForDnsRecord ,
2017-10-29 00:15:11 +02:00
2020-12-23 15:34:23 -08:00
removePrivateFields ,
removeRestrictedFields ,
2018-04-27 11:38:09 -07:00
2020-12-23 15:34:23 -08:00
validateHostname ,
2018-08-30 20:05:08 -07:00
2020-12-23 15:34:23 -08:00
makeWildcard ,
2018-09-11 22:46:17 -07:00
2020-12-23 15:34:23 -08:00
parentDomain ,
2018-11-05 19:09:58 -08:00
2021-02-24 17:54:19 -08:00
registerLocations ,
unregisterLocations ,
2021-02-24 18:42:39 -08:00
checkDnsRecords ,
syncDnsRecords
2017-10-28 22:18:07 +02:00
} ;
2021-02-24 18:42:39 -08:00
const apps = require ( './apps.js' ) ,
assert = require ( 'assert' ) ,
2021-02-24 17:54:19 -08:00
async = require ( 'async' ) ,
2019-10-23 10:02:04 -07:00
BoxError = require ( './boxerror.js' ) ,
2018-08-30 20:05:08 -07:00
constants = require ( './constants.js' ) ,
2021-02-24 11:40:23 -08:00
crypto = require ( 'crypto' ) ,
2017-12-06 11:33:09 +05:30
debug = require ( 'debug' ) ( 'box:domains' ) ,
2017-10-28 22:18:07 +02:00
domaindb = require ( './domaindb.js' ) ,
2018-11-10 00:43:46 -08:00
eventlog = require ( './eventlog.js' ) ,
2020-03-31 12:04:46 -07:00
mail = require ( './mail.js' ) ,
2018-01-30 12:23:27 -08:00
reverseProxy = require ( './reverseproxy.js' ) ,
2019-07-26 10:49:29 -07:00
settings = require ( './settings.js' ) ,
2017-10-28 23:23:58 +02:00
sysinfo = require ( './sysinfo.js' ) ,
tld = require ( 'tldjs' ) ,
2018-04-27 11:38:09 -07:00
_ = require ( 'underscore' ) ;
2017-10-28 22:18:07 +02:00
2020-03-31 12:04:46 -07:00
const NOOP _CALLBACK = function ( error ) { if ( error ) debug ( error ) ; } ;
2017-10-29 00:15:11 +02:00
// choose which subdomain backend we use for test purpose we use route53
function api ( provider ) {
assert . strictEqual ( typeof provider , 'string' ) ;
switch ( provider ) {
2017-11-21 19:18:03 -08:00
case 'cloudflare' : return require ( './dns/cloudflare.js' ) ;
case 'route53' : return require ( './dns/route53.js' ) ;
case 'gcdns' : return require ( './dns/gcdns.js' ) ;
case 'digitalocean' : return require ( './dns/digitalocean.js' ) ;
2018-05-06 18:57:27 -07:00
case 'gandi' : return require ( './dns/gandi.js' ) ;
2018-05-06 22:22:42 -07:00
case 'godaddy' : return require ( './dns/godaddy.js' ) ;
2020-03-12 17:13:13 -07:00
case 'linode' : return require ( './dns/linode.js' ) ;
2021-05-29 22:30:26 -07:00
case 'vultr' : return require ( './dns/vultr.js' ) ;
2018-05-09 12:24:33 +02:00
case 'namecom' : return require ( './dns/namecom.js' ) ;
2019-01-16 18:05:42 +02:00
case 'namecheap' : return require ( './dns/namecheap.js' ) ;
2021-01-18 19:43:47 +01:00
case 'netcup' : return require ( './dns/netcup.js' ) ;
2017-11-21 19:18:03 -08:00
case 'noop' : return require ( './dns/noop.js' ) ;
case 'manual' : return require ( './dns/manual.js' ) ;
2018-09-06 20:26:24 -07:00
case 'wildcard' : return require ( './dns/wildcard.js' ) ;
2017-11-21 19:18:03 -08:00
default : return null ;
2017-10-29 00:15:11 +02:00
}
}
2018-11-05 19:09:58 -08:00
function parentDomain ( domain ) {
assert . strictEqual ( typeof domain , 'string' ) ;
return domain . replace ( /^\S+?\./ , '' ) ; // +? means non-greedy
}
2019-01-04 18:44:54 -08:00
function verifyDnsConfig ( dnsConfig , domain , zoneName , provider , callback ) {
2018-09-11 21:24:04 -07:00
assert ( dnsConfig && typeof dnsConfig === 'object' ) ; // the dns config to test with
2017-10-29 00:15:11 +02:00
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof zoneName , 'string' ) ;
2018-01-09 14:46:38 -08:00
assert . strictEqual ( typeof provider , 'string' ) ;
2017-10-29 00:15:11 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-09 14:46:38 -08:00
var backend = api ( provider ) ;
2019-10-23 10:02:04 -07:00
if ( ! backend ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'Invalid provider' , { field : 'provider' } ) ) ;
2017-10-29 00:15:11 +02:00
2019-01-04 18:44:54 -08:00
const domainObject = { config : dnsConfig , domain : domain , zoneName : zoneName } ;
api ( provider ) . verifyDnsConfig ( domainObject , function ( error , result ) {
2020-01-01 16:17:16 -08:00
if ( error && error . reason === BoxError . ACCESS _DENIED ) return callback ( new BoxError ( BoxError . BAD _FIELD , ` Access denied: ${ error . message } ` ) ) ;
if ( error && error . reason === BoxError . NOT _FOUND ) return callback ( new BoxError ( BoxError . BAD _FIELD , ` Zone not found: ${ error . message } ` ) ) ;
if ( error && error . reason === BoxError . EXTERNAL _ERROR ) return callback ( new BoxError ( BoxError . BAD _FIELD , ` Configuration error: ${ error . message } ` ) ) ;
2019-10-23 10:02:04 -07:00
if ( error ) return callback ( error ) ;
2018-09-11 21:24:04 -07:00
callback ( null , result ) ;
} ) ;
2018-08-22 11:53:23 +02:00
}
2017-10-29 00:15:11 +02:00
2019-01-04 18:44:54 -08:00
function fqdn ( location , domainObject ) {
2020-08-15 18:40:59 -07:00
return location + ( location ? '.' : '' ) + domainObject . domain ;
2018-08-30 20:05:08 -07:00
}
// Hostname validation comes from RFC 1123 (section 2.1)
// Domain name validation comes from RFC 2181 (Name syntax)
// https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names
// We are validating the validity of the location-fqdn as host name (and not dns name)
function validateHostname ( location , domainObject ) {
assert . strictEqual ( typeof location , 'string' ) ;
assert . strictEqual ( typeof domainObject , 'object' ) ;
2019-01-04 18:44:54 -08:00
const hostname = fqdn ( location , domainObject ) ;
2018-08-30 20:05:08 -07:00
const RESERVED _LOCATIONS = [
constants . SMTP _LOCATION ,
constants . IMAP _LOCATION
] ;
2019-10-23 10:02:04 -07:00
if ( RESERVED _LOCATIONS . indexOf ( location ) !== - 1 ) return new BoxError ( BoxError . BAD _FIELD , location + ' is reserved' , { field : 'location' } ) ;
2018-08-30 20:05:08 -07:00
2021-05-05 12:29:04 -07:00
if ( hostname === settings . dashboardFqdn ( ) ) return new BoxError ( BoxError . BAD _FIELD , location + ' is reserved' , { field : 'location' } ) ;
2018-08-30 20:05:08 -07:00
// workaround https://github.com/oncletom/tld.js/issues/73
var tmp = hostname . replace ( '_' , '-' ) ;
2019-10-23 10:02:04 -07:00
if ( ! tld . isValid ( tmp ) ) return new BoxError ( BoxError . BAD _FIELD , 'Hostname is not a valid domain name' , { field : 'location' } ) ;
2018-08-30 20:05:08 -07:00
2019-10-23 10:02:04 -07:00
if ( hostname . length > 253 ) return new BoxError ( BoxError . BAD _FIELD , 'Hostname length exceeds 253 characters' , { field : 'location' } ) ;
2018-08-30 20:05:08 -07:00
if ( location ) {
// label validation
2019-10-23 10:02:04 -07:00
if ( location . split ( '.' ) . some ( function ( p ) { return p . length > 63 || p . length < 1 ; } ) ) return new BoxError ( BoxError . BAD _FIELD , 'Invalid subdomain length' , { field : 'location' } ) ;
if ( location . match ( /^[A-Za-z0-9-.]+$/ ) === null ) return new BoxError ( BoxError . BAD _FIELD , 'Subdomain can only contain alphanumeric, hyphen and dot' , { field : 'location' } ) ;
if ( /^[-.]/ . test ( location ) ) return new BoxError ( BoxError . BAD _FIELD , 'Subdomain cannot start or end with hyphen or dot' , { field : 'location' } ) ;
2018-08-30 20:05:08 -07:00
}
return null ;
}
2018-09-12 12:25:07 -07:00
function validateTlsConfig ( tlsConfig , dnsProvider ) {
2018-09-11 21:53:18 -07:00
assert . strictEqual ( typeof tlsConfig , 'object' ) ;
2018-09-12 12:25:07 -07:00
assert . strictEqual ( typeof dnsProvider , 'string' ) ;
switch ( tlsConfig . provider ) {
case 'letsencrypt-prod' :
case 'letsencrypt-staging' :
case 'fallback' :
break ;
default :
2020-08-07 11:47:08 -07:00
return new BoxError ( BoxError . BAD _FIELD , 'tlsConfig.provider must be fallback, letsencrypt-prod/staging' , { field : 'tlsProvider' } ) ;
2018-09-12 12:25:07 -07:00
}
2018-09-11 21:53:18 -07:00
2018-09-12 12:25:07 -07:00
if ( tlsConfig . wildcard ) {
2019-10-23 10:02:04 -07:00
if ( ! tlsConfig . provider . startsWith ( 'letsencrypt' ) ) return new BoxError ( BoxError . BAD _FIELD , 'wildcard can only be set with letsencrypt' , { field : 'wildcard' } ) ;
if ( dnsProvider === 'manual' || dnsProvider === 'noop' || dnsProvider === 'wildcard' ) return new BoxError ( BoxError . BAD _FIELD , 'wildcard cert requires a programmable DNS backend' , { field : 'tlsProvider' } ) ;
2018-09-11 21:53:18 -07:00
}
return null ;
}
2020-12-23 15:34:23 -08:00
function validateWellKnown ( wellKnown ) {
assert . strictEqual ( typeof wellKnown , 'object' ) ;
return null ;
}
2018-11-10 00:43:46 -08:00
function add ( domain , data , auditSource , callback ) {
2017-10-28 22:18:07 +02:00
assert . strictEqual ( typeof domain , 'string' ) ;
2018-11-10 00:43:46 -08:00
assert . strictEqual ( typeof data . zoneName , 'string' ) ;
assert . strictEqual ( typeof data . provider , 'string' ) ;
assert . strictEqual ( typeof data . config , 'object' ) ;
assert . strictEqual ( typeof data . fallbackCertificate , 'object' ) ;
assert . strictEqual ( typeof data . tlsConfig , 'object' ) ;
2017-10-28 22:18:07 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2020-03-31 12:04:46 -07:00
let { zoneName , provider , config , fallbackCertificate , tlsConfig , dkimSelector } = data ;
2018-11-10 00:43:46 -08:00
2019-10-23 10:02:04 -07:00
if ( ! tld . isValid ( domain ) ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'Invalid domain' , { field : 'domain' } ) ) ;
if ( domain . endsWith ( '.' ) ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'Invalid domain' , { field : 'domain' } ) ) ;
2018-01-23 18:54:05 -08:00
if ( zoneName ) {
2019-10-23 10:02:04 -07:00
if ( ! tld . isValid ( zoneName ) ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'Invalid zoneName' , { field : 'zoneName' } ) ) ;
if ( zoneName . endsWith ( '.' ) ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'Invalid zoneName' , { field : 'zoneName' } ) ) ;
2018-01-23 18:54:05 -08:00
} else {
2018-01-24 14:17:26 -08:00
zoneName = tld . getDomain ( domain ) || domain ;
2018-01-23 18:54:05 -08:00
}
2017-10-28 22:18:07 +02:00
2017-11-09 02:06:36 +01:00
if ( fallbackCertificate ) {
2018-11-10 00:43:46 -08:00
let error = reverseProxy . validateCertificate ( 'test' , { domain , config } , fallbackCertificate ) ;
2019-10-23 10:02:04 -07:00
if ( error ) return callback ( error ) ;
2018-11-05 19:09:58 -08:00
} else {
2021-05-04 21:40:11 -07:00
fallbackCertificate = reverseProxy . generateFallbackCertificateSync ( domain ) ;
2021-01-13 12:33:40 +01:00
if ( fallbackCertificate . error ) return callback ( fallbackCertificate . error ) ;
2017-11-09 02:06:36 +01:00
}
2018-09-12 12:25:07 -07:00
let error = validateTlsConfig ( tlsConfig , provider ) ;
2018-09-11 21:53:18 -07:00
if ( error ) return callback ( error ) ;
2018-01-31 18:20:11 +01:00
2021-02-24 11:40:23 -08:00
if ( ! dkimSelector ) {
// create a unique suffix. this lets one add this domain can be added in another cloudron instance and not have their dkim selector conflict
2021-05-05 12:29:04 -07:00
const suffix = crypto . createHash ( 'sha256' ) . update ( settings . dashboardDomain ( ) ) . digest ( 'hex' ) . substr ( 0 , 6 ) ;
2021-02-24 11:40:23 -08:00
dkimSelector = ` cloudron- ${ suffix } ` ;
}
2020-03-31 12:04:46 -07:00
2019-01-04 18:44:54 -08:00
verifyDnsConfig ( config , domain , zoneName , provider , function ( error , sanitizedConfig ) {
if ( error ) return callback ( error ) ;
2017-10-28 22:18:07 +02:00
2021-05-04 21:40:11 -07:00
domaindb . add ( domain , { zoneName , provider , config : sanitizedConfig , tlsConfig , dkimSelector , fallbackCertificate } , function ( error ) {
2019-10-24 11:13:48 -07:00
if ( error ) return callback ( error ) ;
2017-10-28 23:23:58 +02:00
2019-01-04 18:44:54 -08:00
reverseProxy . setFallbackCertificate ( domain , fallbackCertificate , function ( error ) {
2019-10-23 10:02:04 -07:00
if ( error ) return callback ( error ) ;
2017-10-28 23:23:58 +02:00
2019-01-04 18:44:54 -08:00
eventlog . add ( eventlog . ACTION _DOMAIN _ADD , auditSource , { domain , zoneName , provider } ) ;
2018-01-26 19:16:43 -08:00
2020-03-31 12:04:46 -07:00
mail . onDomainAdded ( domain , NOOP _CALLBACK ) ;
2019-01-04 18:44:54 -08:00
callback ( ) ;
2017-10-28 23:23:58 +02:00
} ) ;
} ) ;
2017-10-28 22:18:07 +02:00
} ) ;
}
function get ( domain , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
domaindb . get ( domain , function ( error , result ) {
2019-10-24 11:13:48 -07:00
if ( error ) return callback ( error ) ;
2017-10-28 22:18:07 +02:00
2021-05-04 21:40:11 -07:00
return callback ( null , result ) ;
2017-10-28 22:18:07 +02:00
} ) ;
}
function getAll ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
domaindb . getAll ( function ( error , result ) {
2019-10-24 11:13:48 -07:00
if ( error ) return callback ( error ) ;
2017-10-28 22:18:07 +02:00
return callback ( null , result ) ;
} ) ;
}
2018-11-10 00:43:46 -08:00
function update ( domain , data , auditSource , callback ) {
2017-10-28 22:18:07 +02:00
assert . strictEqual ( typeof domain , 'string' ) ;
2018-11-10 00:43:46 -08:00
assert . strictEqual ( typeof data . zoneName , 'string' ) ;
assert . strictEqual ( typeof data . provider , 'string' ) ;
assert . strictEqual ( typeof data . config , 'object' ) ;
assert . strictEqual ( typeof data . fallbackCertificate , 'object' ) ;
assert . strictEqual ( typeof data . tlsConfig , 'object' ) ;
assert . strictEqual ( typeof auditSource , 'object' ) ;
2017-10-28 22:18:07 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2020-12-23 15:34:23 -08:00
let { zoneName , provider , config , fallbackCertificate , tlsConfig , wellKnown } = data ;
2018-11-10 00:43:46 -08:00
2021-05-05 12:29:04 -07:00
if ( settings . isDemo ( ) && ( domain === settings . dashboardDomain ( ) ) ) return callback ( new BoxError ( BoxError . CONFLICT , 'Not allowed in demo mode' ) ) ;
2020-02-19 10:45:55 -08:00
2018-11-05 19:09:58 -08:00
domaindb . get ( domain , function ( error , domainObject ) {
2019-10-24 11:13:48 -07:00
if ( error ) return callback ( error ) ;
2017-10-28 22:18:07 +02:00
2018-05-15 13:51:00 -07:00
if ( zoneName ) {
2019-10-23 10:02:04 -07:00
if ( ! tld . isValid ( zoneName ) ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'Invalid zoneName' , { field : 'zoneName' } ) ) ;
2018-05-15 13:51:00 -07:00
} else {
2018-11-05 19:09:58 -08:00
zoneName = domainObject . zoneName ;
2018-05-15 13:51:00 -07:00
}
2017-11-09 02:06:36 +01:00
if ( fallbackCertificate ) {
2018-11-05 22:36:16 -08:00
let error = reverseProxy . validateCertificate ( 'test' , domainObject , fallbackCertificate ) ;
2019-10-23 10:02:04 -07:00
if ( error ) return callback ( error ) ;
2017-11-09 02:06:36 +01:00
}
2018-09-12 12:25:07 -07:00
error = validateTlsConfig ( tlsConfig , provider ) ;
2018-09-11 21:53:18 -07:00
if ( error ) return callback ( error ) ;
2018-01-31 18:20:11 +01:00
2020-12-23 15:34:23 -08:00
error = validateWellKnown ( wellKnown , provider ) ;
if ( error ) return callback ( error ) ;
2019-02-09 17:59:32 -08:00
if ( provider === domainObject . provider ) api ( provider ) . injectPrivateFields ( config , domainObject . config ) ;
2019-02-09 19:08:15 +01:00
verifyDnsConfig ( config , domain , zoneName , provider , function ( error , sanitizedConfig ) {
2019-01-04 18:44:54 -08:00
if ( error ) return callback ( error ) ;
2017-10-28 23:23:58 +02:00
2019-02-09 19:08:15 +01:00
let newData = {
config : sanitizedConfig ,
2020-12-23 15:34:23 -08:00
zoneName ,
provider ,
tlsConfig ,
2021-05-04 21:40:11 -07:00
wellKnown ,
2019-02-09 19:08:15 +01:00
} ;
2019-02-08 20:35:05 -08:00
2021-05-04 21:40:11 -07:00
if ( fallbackCertificate ) newData . fallbackCertificate = fallbackCertificate ;
2019-02-08 20:35:05 -08:00
domaindb . update ( domain , newData , function ( error ) {
2019-10-24 11:13:48 -07:00
if ( error ) return callback ( error ) ;
2017-10-28 23:23:58 +02:00
2019-01-04 18:44:54 -08:00
if ( ! fallbackCertificate ) return callback ( ) ;
2018-01-26 22:27:32 -08:00
2019-01-04 18:44:54 -08:00
reverseProxy . setFallbackCertificate ( domain , fallbackCertificate , function ( error ) {
2019-10-23 10:02:04 -07:00
if ( error ) return callback ( error ) ;
2018-01-30 12:23:27 -08:00
2019-01-04 18:44:54 -08:00
eventlog . add ( eventlog . ACTION _DOMAIN _UPDATE , auditSource , { domain , zoneName , provider } ) ;
2018-11-10 00:43:46 -08:00
2019-01-04 18:44:54 -08:00
callback ( ) ;
2017-10-28 23:23:58 +02:00
} ) ;
} ) ;
} ) ;
2017-10-28 22:18:07 +02:00
} ) ;
}
2018-11-10 00:43:46 -08:00
function del ( domain , auditSource , callback ) {
2017-10-28 22:18:07 +02:00
assert . strictEqual ( typeof domain , 'string' ) ;
2018-11-10 00:43:46 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2017-10-28 22:18:07 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2021-05-05 12:29:04 -07:00
if ( domain === settings . dashboardDomain ( ) ) return callback ( new BoxError ( BoxError . CONFLICT , 'Cannot remove admin domain' ) ) ;
2021-02-24 09:53:57 -08:00
if ( domain === settings . mailDomain ( ) ) return callback ( new BoxError ( BoxError . CONFLICT , 'Cannot remove mail domain. Change the mail server location first' ) ) ;
2018-09-05 17:12:02 -07:00
2017-10-28 22:18:07 +02:00
domaindb . del ( domain , function ( error ) {
2019-10-24 11:13:48 -07:00
if ( error ) return callback ( error ) ;
2017-10-28 22:18:07 +02:00
2018-11-10 00:43:46 -08:00
eventlog . add ( eventlog . ACTION _DOMAIN _REMOVE , auditSource , { domain } ) ;
2020-03-31 12:04:46 -07:00
mail . onDomainRemoved ( domain , NOOP _CALLBACK ) ;
2017-10-28 22:18:07 +02:00
return callback ( null ) ;
} ) ;
}
2017-10-29 00:15:11 +02:00
2018-12-07 14:35:04 -08:00
function clear ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
domaindb . clear ( function ( error ) {
2019-10-24 11:13:48 -07:00
if ( error ) return callback ( error ) ;
2018-12-07 14:35:04 -08:00
return callback ( null ) ;
} ) ;
}
2018-08-30 16:17:56 -07:00
// returns the 'name' that needs to be inserted into zone
2021-02-24 17:54:19 -08:00
// eslint-disable-next-line no-unused-vars
2019-01-04 18:44:54 -08:00
function getName ( domain , location , type ) {
2018-10-31 15:41:02 -07:00
const part = domain . domain . slice ( 0 , - domain . zoneName . length - 1 ) ;
2018-01-23 20:25:45 -08:00
2019-01-04 18:44:54 -08:00
if ( location === '' ) return part ;
2018-01-23 20:25:45 -08:00
2020-08-15 18:40:59 -07:00
return part ? ` ${ location } . ${ part } ` : location ;
2018-01-23 20:25:45 -08:00
}
2019-01-04 18:44:54 -08:00
function getDnsRecords ( location , domain , type , callback ) {
assert . strictEqual ( typeof location , 'string' ) ;
2017-11-16 22:13:30 +01:00
assert . strictEqual ( typeof domain , 'string' ) ;
2017-10-29 00:15:11 +02:00
assert . strictEqual ( typeof type , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-04 18:44:54 -08:00
get ( domain , function ( error , domainObject ) {
2019-09-19 15:10:27 -07:00
if ( error ) return callback ( error ) ;
2017-10-29 00:15:11 +02:00
2019-01-04 18:44:54 -08:00
api ( domainObject . provider ) . get ( domainObject , location , type , function ( error , values ) {
2017-10-29 00:15:11 +02:00
if ( error ) return callback ( error ) ;
callback ( null , values ) ;
} ) ;
} ) ;
}
2019-09-23 14:34:29 -07:00
function checkDnsRecords ( location , domain , callback ) {
assert . strictEqual ( typeof location , 'string' ) ;
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
getDnsRecords ( location , domain , 'A' , function ( error , values ) {
if ( error ) return callback ( error ) ;
2019-10-29 15:46:33 -07:00
sysinfo . getServerIp ( function ( error , ip ) {
2019-10-23 10:02:04 -07:00
if ( error ) return callback ( error ) ;
2019-09-23 14:34:29 -07:00
if ( values . length === 0 ) return callback ( null , { needsOverwrite : false } ) ; // does not exist
if ( values [ 0 ] === ip ) return callback ( null , { needsOverwrite : false } ) ; // exists but in sync
callback ( null , { needsOverwrite : true } ) ;
} ) ;
} ) ;
}
2018-09-27 20:17:39 -07:00
// note: for TXT records the values must be quoted
2019-01-04 18:44:54 -08:00
function upsertDnsRecords ( location , domain , type , values , callback ) {
assert . strictEqual ( typeof location , 'string' ) ;
2017-11-21 19:18:03 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
2017-10-29 00:15:11 +02:00
assert . strictEqual ( typeof type , 'string' ) ;
2021-05-02 11:26:08 -07:00
assert ( Array . isArray ( values ) ) ;
2017-10-29 00:15:11 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-04 18:44:54 -08:00
debug ( 'upsertDNSRecord: %s on %s type %s values' , location , domain , type , values ) ;
2017-10-29 00:15:11 +02:00
2019-01-04 18:44:54 -08:00
get ( domain , function ( error , domainObject ) {
2019-10-23 10:02:04 -07:00
if ( error ) return callback ( error ) ;
2017-10-29 00:15:11 +02:00
2019-01-04 18:44:54 -08:00
api ( domainObject . provider ) . upsert ( domainObject , location , type , values , function ( error ) {
2017-11-21 19:18:03 -08:00
if ( error ) return callback ( error ) ;
2017-10-29 00:15:11 +02:00
2018-06-29 22:25:34 +02:00
callback ( null ) ;
2017-11-21 19:18:03 -08:00
} ) ;
2017-10-29 00:15:11 +02:00
} ) ;
2017-11-21 19:18:03 -08:00
}
2017-10-29 00:15:11 +02:00
2019-01-04 18:44:54 -08:00
function removeDnsRecords ( location , domain , type , values , callback ) {
assert . strictEqual ( typeof location , 'string' ) ;
2017-11-16 22:13:30 +01:00
assert . strictEqual ( typeof domain , 'string' ) ;
2017-10-29 00:15:11 +02:00
assert . strictEqual ( typeof type , 'string' ) ;
2021-05-02 11:26:08 -07:00
assert ( Array . isArray ( values ) ) ;
2017-10-29 00:15:11 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-04 18:44:54 -08:00
debug ( 'removeDNSRecord: %s on %s type %s values' , location , domain , type , values ) ;
2017-10-29 00:15:11 +02:00
2019-01-04 18:44:54 -08:00
get ( domain , function ( error , domainObject ) {
2018-01-10 21:25:51 -08:00
if ( error ) return callback ( error ) ;
2017-10-29 00:15:11 +02:00
2019-01-04 18:44:54 -08:00
api ( domainObject . provider ) . del ( domainObject , location , type , values , function ( error ) {
2019-10-23 10:02:04 -07:00
if ( error && error . reason !== BoxError . NOT _FOUND ) return callback ( error ) ;
2017-10-29 00:15:11 +02:00
callback ( null ) ;
} ) ;
} ) ;
}
2019-01-04 18:44:54 -08:00
function waitForDnsRecord ( location , domain , type , value , options , callback ) {
assert . strictEqual ( typeof location , 'string' ) ;
2017-12-06 11:33:09 +05:30
assert . strictEqual ( typeof domain , 'string' ) ;
2018-09-11 19:09:30 -07:00
assert ( type === 'A' || type === 'TXT' ) ;
2018-02-08 14:19:14 -08:00
assert . strictEqual ( typeof value , 'string' ) ;
2017-10-29 00:15:11 +02:00
assert ( options && typeof options === 'object' ) ; // { interval: 5000, times: 50000 }
assert . strictEqual ( typeof callback , 'function' ) ;
2018-09-22 11:25:58 -07:00
get ( domain , function ( error , domainObject ) {
2018-02-07 20:54:43 +01:00
if ( error ) return callback ( error ) ;
2018-01-10 21:37:12 -08:00
2020-03-12 17:13:13 -07:00
// linode DNS takes ~15mins
if ( ! options . interval ) options . interval = domainObject . provider === 'linode' ? 20000 : 5000 ;
2019-01-04 18:44:54 -08:00
api ( domainObject . provider ) . wait ( domainObject , location , type , value , options , callback ) ;
2017-10-29 00:15:11 +02:00
} ) ;
2017-11-11 22:02:34 +01:00
}
2018-01-01 19:19:07 -08:00
2018-06-25 15:12:20 -07:00
// removes all fields that are strictly private and should never be returned by API calls
2018-04-27 11:38:09 -07:00
function removePrivateFields ( domain ) {
2020-12-23 15:34:23 -08:00
var result = _ . pick ( domain , 'domain' , 'zoneName' , 'provider' , 'config' , 'tlsConfig' , 'fallbackCertificate' , 'wellKnown' ) ;
2019-02-08 11:11:49 +01:00
return api ( result . provider ) . removePrivateFields ( result ) ;
2018-04-29 11:20:12 -07:00
}
2018-06-25 15:12:20 -07:00
// removes all fields that are not accessible by a normal user
function removeRestrictedFields ( domain ) {
2020-02-13 21:12:49 -08:00
var result = _ . pick ( domain , 'domain' , 'zoneName' , 'provider' ) ;
2018-08-22 17:19:18 +02:00
2020-08-15 18:40:59 -07:00
result . config = { } ; // always ensure config object
2018-08-22 17:19:18 +02:00
2018-06-25 15:12:20 -07:00
return result ;
2018-09-05 22:58:43 -07:00
}
2018-09-11 22:46:17 -07:00
2021-01-19 13:47:11 -08:00
function makeWildcard ( vhost ) {
assert . strictEqual ( typeof vhost , 'string' ) ;
2018-09-11 22:46:17 -07:00
2021-01-19 13:47:11 -08:00
// if the vhost is like *.example.com, this function will do nothing
let parts = vhost . split ( '.' ) ;
2018-09-11 22:46:17 -07:00
parts [ 0 ] = '*' ;
return parts . join ( '.' ) ;
}
2021-02-24 17:54:19 -08:00
2021-02-24 18:32:28 -08:00
function registerLocations ( locations , options , progressCallback , callback ) {
2021-02-24 17:54:19 -08:00
assert ( Array . isArray ( locations ) ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-02-24 18:32:28 -08:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2021-02-24 17:54:19 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2021-02-26 11:43:00 -08:00
debug ( ` registerLocations: Will register ${ JSON . stringify ( locations ) } with options ${ JSON . stringify ( options ) } ` ) ;
2021-02-24 17:54:19 -08:00
2021-02-26 11:43:00 -08:00
const overwriteDns = options . overwriteDns || false ;
2021-02-24 17:54:19 -08:00
sysinfo . getServerIp ( function ( error , ip ) {
if ( error ) return callback ( error ) ;
async . eachSeries ( locations , function ( location , iteratorDone ) {
async . retry ( { times : 200 , interval : 5000 } , function ( retryCallback ) {
2021-02-24 18:32:28 -08:00
progressCallback ( { message : ` Registering location: ${ location . subdomain ? ( location . subdomain + '.' ) : '' } ${ location . domain } ` } ) ;
2021-02-24 17:54:19 -08:00
// get the current record before updating it
getDnsRecords ( location . subdomain , location . domain , 'A' , function ( error , values ) {
if ( error && error . reason === BoxError . EXTERNAL _ERROR ) return retryCallback ( new BoxError ( BoxError . EXTERNAL _ERROR , error . message , { domain : location } ) ) ; // try again
if ( error && error . reason === BoxError . ACCESS _DENIED ) return retryCallback ( null , new BoxError ( BoxError . ACCESS _DENIED , error . message , { domain : location } ) ) ;
if ( error && error . reason === BoxError . NOT _FOUND ) return retryCallback ( null , new BoxError ( BoxError . NOT _FOUND , error . message , { domain : location } ) ) ;
if ( error ) return retryCallback ( null , new BoxError ( BoxError . EXTERNAL _ERROR , error . message , location ) ) ; // give up for other errors
if ( values . length !== 0 && values [ 0 ] === ip ) return retryCallback ( null ) ; // up-to-date
// refuse to update any existing DNS record for custom domains that we did not create
2021-02-26 11:43:00 -08:00
if ( values . length !== 0 && ! overwriteDns ) return retryCallback ( null , new BoxError ( BoxError . ALREADY _EXISTS , 'DNS Record already exists' , { domain : location } ) ) ;
2021-02-24 17:54:19 -08:00
upsertDnsRecords ( location . subdomain , location . domain , 'A' , [ ip ] , function ( error ) {
if ( error && ( error . reason === BoxError . BUSY || error . reason === BoxError . EXTERNAL _ERROR ) ) {
2021-02-24 18:32:28 -08:00
progressCallback ( { message : ` registerSubdomains: Upsert error. Will retry. ${ error . message } ` } ) ;
2021-02-24 17:54:19 -08:00
return retryCallback ( new BoxError ( BoxError . EXTERNAL _ERROR , error . message , { domain : location } ) ) ; // try again
}
retryCallback ( null , error ? new BoxError ( BoxError . EXTERNAL _ERROR , error . message , location ) : null ) ;
} ) ;
} ) ;
} , function ( error , result ) {
if ( error || result ) return iteratorDone ( error || result ) ;
iteratorDone ( null ) ;
} ) ;
} , callback ) ;
} ) ;
}
2021-02-24 18:32:28 -08:00
function unregisterLocations ( locations , progressCallback , callback ) {
2021-02-24 17:54:19 -08:00
assert ( Array . isArray ( locations ) ) ;
2021-02-24 18:32:28 -08:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2021-02-24 17:54:19 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
sysinfo . getServerIp ( function ( error , ip ) {
if ( error ) return callback ( error ) ;
async . eachSeries ( locations , function ( location , iteratorDone ) {
async . retry ( { times : 30 , interval : 5000 } , function ( retryCallback ) {
2021-02-24 18:32:28 -08:00
progressCallback ( { message : ` Unregistering location: ${ location . subdomain ? ( location . subdomain + '.' ) : '' } ${ location . domain } ` } ) ;
2021-02-24 17:54:19 -08:00
removeDnsRecords ( location . subdomain , location . domain , 'A' , [ ip ] , function ( error ) {
if ( error && error . reason === BoxError . NOT _FOUND ) return retryCallback ( null , null ) ;
if ( error && ( error . reason === BoxError . SBUSY || error . reason === BoxError . EXTERNAL _ERROR ) ) {
2021-02-24 18:32:28 -08:00
progressCallback ( { message : ` Error unregistering location. Will retry. ${ error . message } ` } ) ;
2021-02-24 17:54:19 -08:00
return retryCallback ( new BoxError ( BoxError . EXTERNAL _ERROR , error . message , { domain : location } ) ) ; // try again
}
retryCallback ( null , error ? new BoxError ( BoxError . EXTERNAL _ERROR , error . message , { domain : location } ) : null ) ;
} ) ;
} , function ( error , result ) {
if ( error || result ) return iteratorDone ( error || result ) ;
iteratorDone ( ) ;
} ) ;
} , callback ) ;
} ) ;
}
2021-02-24 18:42:39 -08:00
function syncDnsRecords ( options , progressCallback , callback ) {
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
if ( options . domain && options . type === 'mail' ) return mail . setDnsRecords ( options . domain , callback ) ;
getAll ( function ( error , domains ) {
if ( error ) return callback ( error ) ;
if ( options . domain ) domains = domains . filter ( d => d . domain === options . domain ) ;
const mailSubdomain = settings . mailFqdn ( ) . substr ( 0 , settings . mailFqdn ( ) . length - settings . mailDomain ( ) . length - 1 ) ;
apps . getAll ( function ( error , allApps ) {
if ( error ) return callback ( error ) ;
let progress = 1 , errors = [ ] ;
// we sync by domain only to get some nice progress
async . eachSeries ( domains , function ( domain , iteratorDone ) {
progressCallback ( { percent : progress , message : ` Updating DNS of ${ domain . domain } ` } ) ;
2021-03-02 21:25:23 -08:00
progress += Math . round ( 100 / ( 1 + domains . length ) ) ;
2021-02-24 18:42:39 -08:00
let locations = [ ] ;
2021-05-05 12:29:04 -07:00
if ( domain . domain === settings . dashboardDomain ( ) ) locations . push ( { subdomain : constants . DASHBOARD _LOCATION , domain : settings . dashboardDomain ( ) } ) ;
if ( domain . domain === settings . mailDomain ( ) && settings . mailFqdn ( ) !== settings . dashboardFqdn ( ) ) locations . push ( { subdomain : mailSubdomain , domain : settings . mailDomain ( ) } ) ;
2021-02-24 18:42:39 -08:00
allApps . forEach ( function ( app ) {
2021-03-02 21:23:20 -08:00
const appLocations = [ { subdomain : app . location , domain : app . domain } ] . concat ( app . alternateDomains ) . concat ( app . aliasDomains ) ;
locations = locations . concat ( appLocations . filter ( al => al . domain === domain . domain ) ) ;
2021-02-24 18:42:39 -08:00
} ) ;
async . series ( [
2021-02-26 11:43:00 -08:00
registerLocations . bind ( null , locations , { overwriteDns : true } , progressCallback ) ,
2021-02-24 18:42:39 -08:00
progressCallback . bind ( null , { message : ` Updating mail DNS of ${ domain . domain } ` } ) ,
mail . setDnsRecords . bind ( null , domain . domain )
] , function ( error ) {
if ( error ) errors . push ( { domain : domain . domain , message : error . message } ) ;
iteratorDone ( ) ;
} ) ;
} , ( ) => callback ( null , { errors } ) ) ;
} ) ;
} ) ;
}