make waitForDns async
cloudflare is partly broken
This commit is contained in:
108
src/dns.js
108
src/dns.js
@@ -19,12 +19,6 @@ module.exports = exports = {
|
||||
|
||||
checkDnsRecords,
|
||||
syncDnsRecords,
|
||||
|
||||
resolve,
|
||||
|
||||
promises: {
|
||||
resolve: require('util').promisify(resolve)
|
||||
}
|
||||
};
|
||||
|
||||
const apps = require('./apps.js'),
|
||||
@@ -32,7 +26,6 @@ const apps = require('./apps.js'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
constants = require('./constants.js'),
|
||||
debug = require('debug')('box:dns'),
|
||||
dns = require('dns'),
|
||||
domains = require('./domains.js'),
|
||||
ipaddr = require('ipaddr.js'),
|
||||
mail = require('./mail.js'),
|
||||
@@ -41,8 +34,7 @@ const apps = require('./apps.js'),
|
||||
settings = require('./settings.js'),
|
||||
sysinfo = require('./sysinfo.js'),
|
||||
tld = require('tldjs'),
|
||||
util = require('util'),
|
||||
_ = require('underscore');
|
||||
util = require('util');
|
||||
|
||||
// choose which subdomain backend we use for test purpose we use route53
|
||||
function api(provider) {
|
||||
@@ -67,30 +59,30 @@ function api(provider) {
|
||||
}
|
||||
}
|
||||
|
||||
function fqdn(location, domainObject) {
|
||||
assert.strictEqual(typeof location, 'string');
|
||||
function fqdn(subdomain, domainObject) {
|
||||
assert.strictEqual(typeof subdomain, 'string');
|
||||
assert.strictEqual(typeof domainObject, 'object');
|
||||
|
||||
return location + (location ? '.' : '') + domainObject.domain;
|
||||
return subdomain + (subdomain ? '.' : '') + domainObject.domain;
|
||||
}
|
||||
|
||||
// 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');
|
||||
function validateHostname(subdomain, domainObject) {
|
||||
assert.strictEqual(typeof subdomain, 'string');
|
||||
assert.strictEqual(typeof domainObject, 'object');
|
||||
|
||||
const hostname = fqdn(location, domainObject);
|
||||
const hostname = fqdn(subdomain, domainObject);
|
||||
|
||||
const RESERVED_LOCATIONS = [
|
||||
constants.SMTP_LOCATION,
|
||||
constants.IMAP_LOCATION
|
||||
];
|
||||
if (RESERVED_LOCATIONS.indexOf(location) !== -1) return new BoxError(BoxError.BAD_FIELD, location + ' is reserved', { field: 'location' });
|
||||
if (RESERVED_LOCATIONS.indexOf(subdomain) !== -1) return new BoxError(BoxError.BAD_FIELD, subdomain + ' is reserved', { field: 'location' });
|
||||
|
||||
if (hostname === settings.dashboardFqdn()) return new BoxError(BoxError.BAD_FIELD, location + ' is reserved', { field: 'location' });
|
||||
if (hostname === settings.dashboardFqdn()) return new BoxError(BoxError.BAD_FIELD, subdomain + ' is reserved', { field: 'location' });
|
||||
|
||||
// workaround https://github.com/oncletom/tld.js/issues/73
|
||||
var tmp = hostname.replace('_', '-');
|
||||
@@ -98,11 +90,11 @@ function validateHostname(location, domainObject) {
|
||||
|
||||
if (hostname.length > 253) return new BoxError(BoxError.BAD_FIELD, 'Hostname length exceeds 253 characters', { field: 'location' });
|
||||
|
||||
if (location) {
|
||||
if (subdomain) {
|
||||
// label validation
|
||||
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' });
|
||||
if (subdomain.split('.').some(function (p) { return p.length > 63 || p.length < 1; })) return new BoxError(BoxError.BAD_FIELD, 'Invalid subdomain length', { field: 'location' });
|
||||
if (subdomain.match(/^[A-Za-z0-9-.]+$/) === null) return new BoxError(BoxError.BAD_FIELD, 'Subdomain can only contain alphanumeric, hyphen and dot', { field: 'location' });
|
||||
if (/^[-.]/.test(subdomain)) return new BoxError(BoxError.BAD_FIELD, 'Subdomain cannot start or end with hyphen or dot', { field: 'location' });
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -110,12 +102,12 @@ function validateHostname(location, domainObject) {
|
||||
|
||||
// returns the 'name' that needs to be inserted into zone
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function getName(domain, location, type) {
|
||||
function getName(domain, subdomain, type) {
|
||||
const part = domain.domain.slice(0, -domain.zoneName.length - 1);
|
||||
|
||||
if (location === '') return part;
|
||||
if (subdomain === '') return part;
|
||||
|
||||
return part ? `${location}.${part}` : location;
|
||||
return part ? `${subdomain}.${part}` : subdomain;
|
||||
}
|
||||
|
||||
function maybePromisify(func) {
|
||||
@@ -123,20 +115,20 @@ function maybePromisify(func) {
|
||||
return util.promisify(func);
|
||||
}
|
||||
|
||||
async function getDnsRecords(location, domain, type) {
|
||||
assert.strictEqual(typeof location, 'string');
|
||||
async function getDnsRecords(subdomain, domain, type) {
|
||||
assert.strictEqual(typeof subdomain, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof type, 'string');
|
||||
|
||||
const domainObject = await domains.get(domain);
|
||||
return await maybePromisify(api(domainObject.provider).get)(domainObject, location, type);
|
||||
return await maybePromisify(api(domainObject.provider).get)(domainObject, subdomain, type);
|
||||
}
|
||||
|
||||
async function checkDnsRecords(location, domain) {
|
||||
assert.strictEqual(typeof location, 'string');
|
||||
async function checkDnsRecords(subdomain, domain) {
|
||||
assert.strictEqual(typeof subdomain, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
|
||||
const ipv4Records = await getDnsRecords(location, domain, 'A');
|
||||
const ipv4Records = await getDnsRecords(subdomain, domain, 'A');
|
||||
const ipv4 = await sysinfo.getServerIPv4();
|
||||
|
||||
// if empty OR exactly one record with the ip, we don't need to overwrite
|
||||
@@ -144,7 +136,7 @@ async function checkDnsRecords(location, domain) {
|
||||
|
||||
const ipv6Enabled = await settings.getIPv6Config();
|
||||
if (ipv6Enabled) {
|
||||
const ipv6Records = await getDnsRecords(location, domain, 'AAAA');
|
||||
const ipv6Records = await getDnsRecords(subdomain, domain, 'AAAA');
|
||||
const ipv6 = await sysinfo.getServerIPv6();
|
||||
|
||||
// if empty OR exactly one record with the ip, we don't need to overwrite
|
||||
@@ -155,33 +147,33 @@ async function checkDnsRecords(location, domain) {
|
||||
}
|
||||
|
||||
// note: for TXT records the values must be quoted
|
||||
async function upsertDnsRecords(location, domain, type, values) {
|
||||
assert.strictEqual(typeof location, 'string');
|
||||
async function upsertDnsRecords(subdomain, domain, type, values) {
|
||||
assert.strictEqual(typeof subdomain, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof type, 'string');
|
||||
assert(Array.isArray(values));
|
||||
|
||||
debug(`upsertDNSRecord: location ${location} on domain ${domain} of type ${type} with values ${JSON.stringify(values)}`);
|
||||
debug(`upsertDNSRecord: location ${subdomain} on domain ${domain} of type ${type} with values ${JSON.stringify(values)}`);
|
||||
|
||||
const domainObject = await domains.get(domain);
|
||||
await maybePromisify(api(domainObject.provider).upsert)(domainObject, location, type, values);
|
||||
await maybePromisify(api(domainObject.provider).upsert)(domainObject, subdomain, type, values);
|
||||
}
|
||||
|
||||
async function removeDnsRecords(location, domain, type, values) {
|
||||
assert.strictEqual(typeof location, 'string');
|
||||
async function removeDnsRecords(subdomain, domain, type, values) {
|
||||
assert.strictEqual(typeof subdomain, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof type, 'string');
|
||||
assert(Array.isArray(values));
|
||||
|
||||
debug('removeDNSRecord: %s on %s type %s values', location, domain, type, values);
|
||||
debug('removeDNSRecord: %s on %s type %s values', subdomain, domain, type, values);
|
||||
|
||||
const domainObject = await domains.get(domain);
|
||||
const [error] = await safe(maybePromisify(api(domainObject.provider).del)(domainObject, location, type, values));
|
||||
const [error] = await safe(maybePromisify(api(domainObject.provider).del)(domainObject, subdomain, type, values));
|
||||
if (error && error.reason !== BoxError.NOT_FOUND) throw error;
|
||||
}
|
||||
|
||||
async function waitForDnsRecord(location, domain, type, value, options) {
|
||||
assert.strictEqual(typeof location, 'string');
|
||||
async function waitForDnsRecord(subdomain, domain, type, value, options) {
|
||||
assert.strictEqual(typeof subdomain, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(type === 'A' || type === 'AAAA' || type === 'TXT');
|
||||
assert.strictEqual(typeof value, 'string');
|
||||
@@ -192,7 +184,7 @@ async function waitForDnsRecord(location, domain, type, value, options) {
|
||||
// linode DNS takes ~15mins
|
||||
if (!options.interval) options.interval = domainObject.provider === 'linode' ? 20000 : 5000;
|
||||
|
||||
await maybePromisify(api(domainObject.provider).wait)(domainObject, location, type, value, options);
|
||||
await api(domainObject.provider).wait(domainObject, subdomain, type, value, options);
|
||||
}
|
||||
|
||||
function makeWildcard(vhost) {
|
||||
@@ -318,35 +310,3 @@ async function syncDnsRecords(options, progressCallback) {
|
||||
|
||||
return { errors };
|
||||
}
|
||||
|
||||
// a note on TXT records. It doesn't have quotes ("") at the DNS level. Those quotes
|
||||
// are added for DNS server software to enclose spaces. Such quotes may also be returned
|
||||
// by the DNS REST API of some providers
|
||||
function resolve(hostname, rrtype, options, callback) {
|
||||
assert.strictEqual(typeof hostname, 'string');
|
||||
assert.strictEqual(typeof rrtype, 'string');
|
||||
assert(options && typeof options === 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const defaultOptions = { server: '127.0.0.1', timeout: 5000 }; // unbound runs on 127.0.0.1
|
||||
const resolver = new dns.Resolver();
|
||||
options = _.extend({ }, defaultOptions, options);
|
||||
|
||||
// Only use unbound on a Cloudron
|
||||
if (constants.CLOUDRON) resolver.setServers([ options.server ]);
|
||||
|
||||
// should callback with ECANCELLED but looks like we might hit https://github.com/nodejs/node/issues/14814
|
||||
const timerId = setTimeout(resolver.cancel.bind(resolver), options.timeout || 5000);
|
||||
|
||||
resolver.resolve(hostname, rrtype, function (error, result) {
|
||||
clearTimeout(timerId);
|
||||
|
||||
if (error && error.code === 'ECANCELLED') error.code = 'TIMEOUT';
|
||||
|
||||
// result is an empty array if there was no error but there is no record. when you query a random
|
||||
// domain, it errors with ENOTFOUND. But if you query an existing domain (A record) but with different
|
||||
// type (CNAME) it is not an error and empty array
|
||||
// for TXT records, result is 2d array of strings
|
||||
callback(error, result);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user