diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 048d4d696..28c972fd5 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -260,11 +260,6 @@ "from": "bignumber.js@3.1.2", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-3.1.2.tgz" }, - "binaryheap": { - "version": "0.0.3", - "from": "binaryheap@>=0.0.3", - "resolved": "http://registry.npmjs.org/binaryheap/-/binaryheap-0.0.3.tgz" - }, "bl": { "version": "1.2.0", "from": "bl@>=1.0.0 <2.0.0", @@ -336,28 +331,6 @@ "from": "buffer-shims@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz" }, - "buffercursor": { - "version": "0.0.12", - "from": "buffercursor@>=0.0.12", - "resolved": "http://registry.npmjs.org/buffercursor/-/buffercursor-0.0.12.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - }, - "extsprintf": { - "version": "1.3.0", - "from": "extsprintf@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" - }, - "verror": { - "version": "1.9.0", - "from": "verror@>=1.4.0 <2.0.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.9.0.tgz" - } - } - }, "buildmail": { "version": "2.0.0", "from": "buildmail@>=2.0.0 <3.0.0", @@ -2876,28 +2849,6 @@ "from": "nan@>=2.3.2 <3.0.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz" }, - "native-dns": { - "version": "0.7.0", - "from": "native-dns@>=0.7.0 <0.8.0", - "resolved": "https://registry.npmjs.org/native-dns/-/native-dns-0.7.0.tgz", - "dependencies": { - "ipaddr.js": { - "version": "0.1.9", - "from": "ipaddr.js@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-0.1.9.tgz" - } - } - }, - "native-dns-cache": { - "version": "0.0.2", - "from": "native-dns-cache@>=0.0.2 <0.1.0", - "resolved": "http://registry.npmjs.org/native-dns-cache/-/native-dns-cache-0.0.2.tgz" - }, - "native-dns-packet": { - "version": "0.1.1", - "from": "native-dns-packet@>=0.1.1 <0.2.0", - "resolved": "http://registry.npmjs.org/native-dns-packet/-/native-dns-packet-0.1.1.tgz" - }, "natives": { "version": "1.1.0", "from": "natives@>=1.1.0 <2.0.0", diff --git a/package.json b/package.json index 66be3fd12..4977e457c 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "morgan": "^1.7.0", "multiparty": "^4.1.2", "mysql": "^2.7.0", - "native-dns": "^0.7.0", "node-uuid": "^1.4.3", "nodemailer": "^1.3.0", "nodemailer-smtp-transport": "^1.0.3", diff --git a/src/dig.js b/src/dig.js new file mode 100644 index 000000000..d81bb6e9d --- /dev/null +++ b/src/dig.js @@ -0,0 +1,46 @@ +'use strict'; + +exports = module.exports = { + resolve: resolve +}; + +var assert = require('assert'), + child_process = require('child_process'), + debug = require('debug')('box:dig'); + +function resolve(domain, type, options, callback) { + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof type, 'string'); + assert.strictEqual(typeof options, 'object'); + assert.strictEqual(typeof callback, 'function'); + + // dig @server cloudron.io TXT +short + var args = [ ]; + if (options.server) args.push('@' + options.server); + if (type === 'PTR') { + args.push('-x', domain); + } else { + args.push(domain, type); + } + args.push('+short'); + + child_process.execFile('/usr/bin/dig', args, { encoding: 'utf8', killSignal: 'SIGKILL', timeout: options.timeout || 0 }, function (error, stdout, stderr) { + if (error && error.killed) error.code = 'ETIMEDOUT'; + + if (error || stderr) debug('resolve error (%j): %j %s %s', args, error, stdout, stderr); + if (error) return callback(error); + + debug('resolve (%j): %s', args, stdout); + + if (!stdout) return callback(); // timeout or no result + + var lines = stdout.trim().split('\n'); + if (type === 'MX') { + lines = lines.map(function (line) { + var parts = line.split(' '); + return { priority: parts[0], exchange: parts[1] }; + }); + } + return callback(null, lines); + }); +} diff --git a/src/dns/digitalocean.js b/src/dns/digitalocean.js index 486ebb524..79fe9b958 100644 --- a/src/dns/digitalocean.js +++ b/src/dns/digitalocean.js @@ -12,7 +12,7 @@ var assert = require('assert'), async = require('async'), constants = require('../constants.js'), debug = require('debug')('box:dns/digitalocean'), - dns = require('native-dns'), + dns = require('dns'), SubdomainError = require('../subdomains.js').SubdomainError, superagent = require('superagent'), util = require('util'); diff --git a/src/dns/manual.js b/src/dns/manual.js index d575b533b..ba67d4842 100644 --- a/src/dns/manual.js +++ b/src/dns/manual.js @@ -12,7 +12,8 @@ var assert = require('assert'), async = require('async'), constants = require('../constants.js'), debug = require('debug')('box:dns/manual'), - dns = require('native-dns'), + dig = require('../dig.js'), + dns = require('dns'), SubdomainError = require('../subdomains.js').SubdomainError, util = require('util'); @@ -69,42 +70,30 @@ function verifyDnsConfig(dnsConfig, domain, ip, callback) { } async.every(nsIps, function (nsIp, everyIpCallback) { - var req = dns.Request({ - question: dns.Question({ name: adminDomain, type: 'A' }), - server: { address: nsIp }, - timeout: 5000 - }); + dig.resolve(adminDomain, 'A', { server: nsIp, timeout: 5000 }, function (error, answer) { + if (error && error.code === 'ETIMEDOUT') { + debug('nameserver %s (%s) timed out when trying to resolve %s', nameserver, nsIp, adminDomain); + return everyIpCallback(null, true); // should be ok if dns server is down + } - req.on('timeout', function () { - debug('nameserver %s (%s) timed out when trying to resolve %s', nameserver, nsIp, adminDomain); - return everyIpCallback(null, true); // should be ok if dns server is down - }); - - req.on('message', function (error, message) { if (error) { debug('nameserver %s (%s) returned error trying to resolve %s: %s', nameserver, nsIp, adminDomain, error); return everyIpCallback(null, false); } - var answer = message.answer; - if (!answer || answer.length === 0) { - debug('bad answer from nameserver %s (%s) resolving %s (%s): %j', nameserver, nsIp, adminDomain, 'A', message); + debug('bad answer from nameserver %s (%s) resolving %s (%s): %j', nameserver, nsIp, adminDomain, 'A', answer); return everyIpCallback(null, false); } debug('verifyDnsConfig: ns: %s (%s), name:%s Actual:%j Expecting:%s', nameserver, nsIp, adminDomain, answer, ip); - var match = answer.some(function (a) { - return a.address === ip; - }); + var match = answer.some(function (a) { return a === ip; }); if (match) return everyIpCallback(null, true); // done! everyIpCallback(null, false); }); - - req.send(); }, everyNsCallback); }); }, function (error, success) { diff --git a/src/dns/route53.js b/src/dns/route53.js index d7f5c2a19..b741635ab 100644 --- a/src/dns/route53.js +++ b/src/dns/route53.js @@ -15,7 +15,7 @@ var assert = require('assert'), AWS = require('aws-sdk'), constants = require('../constants.js'), debug = require('debug')('box:dns/route53'), - dns = require('native-dns'), + dns = require('dns'), SubdomainError = require('../subdomains.js').SubdomainError, util = require('util'), _ = require('underscore'); diff --git a/src/dns/waitfordns.js b/src/dns/waitfordns.js index a56a7fe38..c3f4eeb83 100644 --- a/src/dns/waitfordns.js +++ b/src/dns/waitfordns.js @@ -5,7 +5,8 @@ exports = module.exports = waitForDns; var assert = require('assert'), async = require('async'), debug = require('debug')('box:dns/waitfordns'), - dns = require('native-dns'), + dig = require('../dig.js'), + dns = require('dns'), SubdomainError = require('../subdomains.js').SubdomainError, tld = require('tldjs'), util = require('util'); @@ -25,45 +26,36 @@ function isChangeSynced(domain, value, type, nameserver, callback) { } async.every(nsIps, function (nsIp, iteratorCallback) { - var req = dns.Request({ - question: dns.Question({ name: domain, type: type }), - server: { address: nsIp }, - timeout: 5000 - }); + dig.resolve(domain, type, { server: nsIp, timeout: 5000 }, function (error, answer) { + if (error && error.code === 'ETIMEDOUT') { + debug('nameserver %s (%s) timed out when trying to resolve %s', nameserver, nsIp, domain); + return iteratorCallback(null, true); // should be ok if dns server is down + } - req.on('timeout', function () { - debug('nameserver %s (%s) timed out when trying to resolve %s', nameserver, nsIp, domain); - return iteratorCallback(null, true); // should be ok if dns server is down - }); - - req.on('message', function (error, message) { if (error) { debug('nameserver %s (%s) returned error trying to resolve %s: %s', nameserver, nsIp, domain, error); return iteratorCallback(null, false); } - var answer = message.answer; - if (!answer || answer.length === 0) { - debug('bad answer from nameserver %s (%s) resolving %s (%s): %j', nameserver, nsIp, domain, type, message); + debug('bad answer from nameserver %s (%s) resolving %s (%s): %j', nameserver, nsIp, domain, type, answer); return iteratorCallback(null, false); } debug('isChangeSynced: ns: %s (%s), name:%s Actual:%j Expecting:%s', nameserver, nsIp, domain, answer, value); var match = answer.some(function (a) { - return ((type === 'A' && value.test(a.address)) || - (type === 'CNAME' && value.test(a.data)) || - (type === 'TXT' && value.test(a.data.join('')))); + return ((type === 'A' && value.test(a)) || + (type === 'CNAME' && value.test(a)) || + (type === 'TXT' && value.test(a))); }); if (match) return iteratorCallback(null, true); // done! iteratorCallback(null, false); }); - - req.send(); }, callback); + }); } diff --git a/src/mailer.js b/src/mailer.js index 72fac57fd..2ae25eebc 100644 --- a/src/mailer.js +++ b/src/mailer.js @@ -97,7 +97,7 @@ function mailConfig() { function checkDns() { if (process.env.BOX_ENV === 'test') return; - subdomains.waitForDns(config.fqdn(), new RegExp('^v=spf1 .*a:' + config.adminFqdn().replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '.*'), 'TXT', { interval: 60000, times: Infinity }, function (error) { + subdomains.waitForDns(config.fqdn(), new RegExp('^"v=spf1 .*a:' + config.adminFqdn().replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '.*'), 'TXT', { interval: 60000, times: Infinity }, function (error) { if (error) return debug(error); // can never happen debug('checkDns: SPF check passed. commencing mail processing'); diff --git a/src/routes/test/settings-test.js b/src/routes/test/settings-test.js index 54acf4e03..06f629525 100644 --- a/src/routes/test/settings-test.js +++ b/src/routes/test/settings-test.js @@ -536,11 +536,11 @@ describe('Settings API', function () { this.timeout(10000); before(function (done) { - var dns = require('native-dns'); + var dig = require('../../dig.js'); // replace dns resolveTxt() - resolve = dns.resolve; - dns.resolve = function (hostname, type, callback) { + resolve = dig.resolve; + dig.resolve = function (hostname, type, options, callback) { expect(hostname).to.be.a('string'); expect(callback).to.be.a('function'); @@ -558,9 +558,9 @@ describe('Settings API', function () { }); after(function (done) { - var dns = require('native-dns'); + var dig = require('../../dig.js'); - dns.resolve = resolve; + dig.resolve = resolve; done(); }); @@ -594,32 +594,32 @@ describe('Settings API', function () { expect(res.body.dns.dkim.domain).to.eql(dkimDomain); expect(res.body.dns.dkim.type).to.eql('TXT'); expect(res.body.dns.dkim.value).to.eql(null); - expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + cloudron.readDkimPublicKeySync()); + expect(res.body.dns.dkim.expected).to.eql('"v=DKIM1; t=s; p=' + cloudron.readDkimPublicKeySync() + '"'); expect(res.body.dns.dkim.status).to.eql(false); expect(res.body.dns.spf).to.be.an('object'); expect(res.body.dns.spf.domain).to.eql(spfDomain); expect(res.body.dns.spf.type).to.eql('TXT'); expect(res.body.dns.spf.value).to.eql(null); - expect(res.body.dns.spf.expected).to.eql('v=spf1 a:' + config.adminFqdn() + ' ~all'); + expect(res.body.dns.spf.expected).to.eql('"v=spf1 a:' + config.adminFqdn() + ' ~all"'); expect(res.body.dns.spf.status).to.eql(false); expect(res.body.dns.dmarc).to.be.an('object'); expect(res.body.dns.dmarc.type).to.eql('TXT'); expect(res.body.dns.dmarc.value).to.eql(null); - expect(res.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); + expect(res.body.dns.dmarc.expected).to.eql('"v=DMARC1; p=reject; pct=100"'); expect(res.body.dns.dmarc.status).to.eql(false); expect(res.body.dns.mx).to.be.an('object'); expect(res.body.dns.mx.type).to.eql('MX'); expect(res.body.dns.mx.value).to.eql(null); - expect(res.body.dns.mx.expected).to.eql('10 ' + config.mailFqdn()); + expect(res.body.dns.mx.expected).to.eql('10 ' + config.mailFqdn() + '.'); expect(res.body.dns.mx.status).to.eql(false); expect(res.body.dns.ptr).to.be.an('object'); expect(res.body.dns.ptr.type).to.eql('PTR'); // expect(res.body.ptr.value).to.eql(null); this will be anything random - expect(res.body.dns.ptr.expected).to.eql(config.mailFqdn()); + expect(res.body.dns.ptr.expected).to.eql(config.mailFqdn() + '.'); expect(res.body.dns.ptr.status).to.eql(false); done(); @@ -640,27 +640,27 @@ describe('Settings API', function () { expect(res.statusCode).to.equal(200); expect(res.body.dns.spf).to.be.an('object'); - expect(res.body.dns.spf.expected).to.eql('v=spf1 a:' + config.adminFqdn() + ' ~all'); + expect(res.body.dns.spf.expected).to.eql('"v=spf1 a:' + config.adminFqdn() + ' ~all"'); expect(res.body.dns.spf.status).to.eql(false); expect(res.body.dns.spf.value).to.eql(null); expect(res.body.dns.dkim).to.be.an('object'); - expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + cloudron.readDkimPublicKeySync()); + expect(res.body.dns.dkim.expected).to.eql('"v=DKIM1; t=s; p=' + cloudron.readDkimPublicKeySync() + '"'); expect(res.body.dns.dkim.status).to.eql(false); expect(res.body.dns.dkim.value).to.eql(null); expect(res.body.dns.dmarc).to.be.an('object'); - expect(res.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); + expect(res.body.dns.dmarc.expected).to.eql('"v=DMARC1; p=reject; pct=100"'); expect(res.body.dns.dmarc.status).to.eql(false); expect(res.body.dns.dmarc.value).to.eql(null); expect(res.body.dns.mx).to.be.an('object'); expect(res.body.dns.mx.status).to.eql(false); - expect(res.body.dns.mx.expected).to.eql('10 ' + config.mailFqdn()); + expect(res.body.dns.mx.expected).to.eql('10 ' + config.mailFqdn() + '.'); expect(res.body.dns.mx.value).to.eql(null); expect(res.body.dns.ptr).to.be.an('object'); - expect(res.body.dns.ptr.expected).to.eql(config.mailFqdn()); + expect(res.body.dns.ptr.expected).to.eql(config.mailFqdn() + '.'); expect(res.body.dns.ptr.status).to.eql(false); // expect(res.body.ptr.value).to.eql(null); this will be anything random @@ -671,10 +671,10 @@ describe('Settings API', function () { it('succeeds with all different spf, dkim, dmarc, mx, ptr records', function (done) { clearDnsAnswerQueue(); - dnsAnswerQueue[mxDomain].MX = [ { priority: '20', exchange: config.mailFqdn() }, { priority: '30', exchange: config.mailFqdn() } ]; - dnsAnswerQueue[dmarcDomain].TXT = [['v=DMARC2; p=reject; pct=100']]; - dnsAnswerQueue[dkimDomain].TXT = [['v=DKIM2; t=s; p=' + cloudron.readDkimPublicKeySync()]]; - dnsAnswerQueue[spfDomain].TXT = [['v=spf1 a:random.com ~all']]; + dnsAnswerQueue[mxDomain].MX = [ { priority: '20', exchange: config.mailFqdn() + '.' }, { priority: '30', exchange: config.mailFqdn() + '.'} ]; + dnsAnswerQueue[dmarcDomain].TXT = ['"v=DMARC2; p=reject; pct=100"']; + dnsAnswerQueue[dkimDomain].TXT = ['"v=DKIM2; t=s; p=' + cloudron.readDkimPublicKeySync() + '"']; + dnsAnswerQueue[spfDomain].TXT = ['"v=spf1 a:random.com ~all"']; superagent.get(SERVER_URL + '/api/v1/settings/email_status') .query({ access_token: token }) @@ -682,27 +682,27 @@ describe('Settings API', function () { expect(res.statusCode).to.equal(200); expect(res.body.dns.spf).to.be.an('object'); - expect(res.body.dns.spf.expected).to.eql('v=spf1 a:' + config.adminFqdn() + ' a:random.com ~all'); + expect(res.body.dns.spf.expected).to.eql('"v=spf1 a:' + config.adminFqdn() + ' a:random.com ~all"'); expect(res.body.dns.spf.status).to.eql(false); - expect(res.body.dns.spf.value).to.eql('v=spf1 a:random.com ~all'); + expect(res.body.dns.spf.value).to.eql('"v=spf1 a:random.com ~all"'); expect(res.body.dns.dkim).to.be.an('object'); - expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + cloudron.readDkimPublicKeySync()); + expect(res.body.dns.dkim.expected).to.eql('"v=DKIM1; t=s; p=' + cloudron.readDkimPublicKeySync() + '"'); expect(res.body.dns.dkim.status).to.eql(false); - expect(res.body.dns.dkim.value).to.eql('v=DKIM2; t=s; p=' + cloudron.readDkimPublicKeySync()); + expect(res.body.dns.dkim.value).to.eql('"v=DKIM2; t=s; p=' + cloudron.readDkimPublicKeySync() + '"'); expect(res.body.dns.dmarc).to.be.an('object'); - expect(res.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); + expect(res.body.dns.dmarc.expected).to.eql('"v=DMARC1; p=reject; pct=100"'); expect(res.body.dns.dmarc.status).to.eql(false); - expect(res.body.dns.dmarc.value).to.eql('v=DMARC2; p=reject; pct=100'); + expect(res.body.dns.dmarc.value).to.eql('"v=DMARC2; p=reject; pct=100"'); expect(res.body.dns.mx).to.be.an('object'); expect(res.body.dns.mx.status).to.eql(false); - expect(res.body.dns.mx.expected).to.eql('10 ' + config.mailFqdn()); - expect(res.body.dns.mx.value).to.eql('20 ' + config.mailFqdn() + ' 30 ' + config.mailFqdn()); + expect(res.body.dns.mx.expected).to.eql('10 ' + config.mailFqdn() + '.'); + expect(res.body.dns.mx.value).to.eql('20 ' + config.mailFqdn() + '. 30 ' + config.mailFqdn() + '.'); expect(res.body.dns.ptr).to.be.an('object'); - expect(res.body.dns.ptr.expected).to.eql(config.mailFqdn()); + expect(res.body.dns.ptr.expected).to.eql(config.mailFqdn() + '.'); expect(res.body.dns.ptr.status).to.eql(false); // expect(res.body.ptr.value).to.eql(null); this will be anything random @@ -715,7 +715,7 @@ describe('Settings API', function () { it('succeeds with existing embedded spf', function (done) { clearDnsAnswerQueue(); - dnsAnswerQueue[spfDomain].TXT = [['v=spf1 a:example.com a:' + config.mailFqdn() + ' ~all']]; + dnsAnswerQueue[spfDomain].TXT = ['"v=spf1 a:example.com a:' + config.mailFqdn() + ' ~all"']; superagent.get(SERVER_URL + '/api/v1/settings/email_status') .query({ access_token: token }) @@ -725,8 +725,8 @@ describe('Settings API', function () { expect(res.body.dns.spf).to.be.an('object'); expect(res.body.dns.spf.domain).to.eql(spfDomain); expect(res.body.dns.spf.type).to.eql('TXT'); - expect(res.body.dns.spf.value).to.eql('v=spf1 a:example.com a:' + config.mailFqdn() + ' ~all'); - expect(res.body.dns.spf.expected).to.eql('v=spf1 a:example.com a:' + config.mailFqdn() + ' ~all'); + expect(res.body.dns.spf.value).to.eql('"v=spf1 a:example.com a:' + config.mailFqdn() + ' ~all"'); + expect(res.body.dns.spf.expected).to.eql('"v=spf1 a:example.com a:' + config.mailFqdn() + ' ~all"'); expect(res.body.dns.spf.status).to.eql(true); done(); @@ -736,10 +736,10 @@ describe('Settings API', function () { it('succeeds with all correct records', function (done) { clearDnsAnswerQueue(); - dnsAnswerQueue[mxDomain].MX = [ { priority: '10', exchange: config.mailFqdn() } ]; - dnsAnswerQueue[dmarcDomain].TXT = [['v=DMARC1; p=reject; pct=100']]; - dnsAnswerQueue[dkimDomain].TXT = [['v=DKIM1;', 't=s;', 'p=' + cloudron.readDkimPublicKeySync()]]; - dnsAnswerQueue[spfDomain].TXT = [['v=spf1', ' a:' + config.adminFqdn(), ' ~all']]; + dnsAnswerQueue[mxDomain].MX = [ { priority: '10', exchange: config.mailFqdn() + '.' } ]; + dnsAnswerQueue[dmarcDomain].TXT = ['"v=DMARC1; p=reject; pct=100"']; + dnsAnswerQueue[dkimDomain].TXT = ['"v=DKIM1; t=s; p=' + cloudron.readDkimPublicKeySync() + '"']; + dnsAnswerQueue[spfDomain].TXT = ['"v=spf1 a:' + config.adminFqdn() + ' ~all"']; superagent.get(SERVER_URL + '/api/v1/settings/email_status') .query({ access_token: token }) @@ -749,26 +749,26 @@ describe('Settings API', function () { expect(res.body.dns.dkim).to.be.an('object'); expect(res.body.dns.dkim.domain).to.eql(dkimDomain); expect(res.body.dns.dkim.type).to.eql('TXT'); - expect(res.body.dns.dkim.value).to.eql('v=DKIM1; t=s; p=' + cloudron.readDkimPublicKeySync()); - expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + cloudron.readDkimPublicKeySync()); + expect(res.body.dns.dkim.value).to.eql('"v=DKIM1; t=s; p=' + cloudron.readDkimPublicKeySync() + '"'); + expect(res.body.dns.dkim.expected).to.eql('"v=DKIM1; t=s; p=' + cloudron.readDkimPublicKeySync() + '"'); expect(res.body.dns.dkim.status).to.eql(true); expect(res.body.dns.spf).to.be.an('object'); expect(res.body.dns.spf.domain).to.eql(spfDomain); expect(res.body.dns.spf.type).to.eql('TXT'); - expect(res.body.dns.spf.value).to.eql('v=spf1 a:' + config.adminFqdn() + ' ~all'); - expect(res.body.dns.spf.expected).to.eql('v=spf1 a:' + config.adminFqdn() + ' ~all'); + expect(res.body.dns.spf.value).to.eql('"v=spf1 a:' + config.adminFqdn() + ' ~all"'); + expect(res.body.dns.spf.expected).to.eql('"v=spf1 a:' + config.adminFqdn() + ' ~all"'); expect(res.body.dns.spf.status).to.eql(true); expect(res.body.dns.dmarc).to.be.an('object'); - expect(res.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); + expect(res.body.dns.dmarc.expected).to.eql('"v=DMARC1; p=reject; pct=100"'); expect(res.body.dns.dmarc.status).to.eql(true); - expect(res.body.dns.dmarc.value).to.eql('v=DMARC1; p=reject; pct=100'); + expect(res.body.dns.dmarc.value).to.eql('"v=DMARC1; p=reject; pct=100"'); expect(res.body.dns.mx).to.be.an('object'); expect(res.body.dns.mx.status).to.eql(true); - expect(res.body.dns.mx.expected).to.eql('10 ' + config.mailFqdn()); - expect(res.body.dns.mx.value).to.eql('10 ' + config.mailFqdn()); + expect(res.body.dns.mx.expected).to.eql('10 ' + config.mailFqdn() + '.'); + expect(res.body.dns.mx.value).to.eql('10 ' + config.mailFqdn() + '.'); done(); }); diff --git a/src/settings.js b/src/settings.js index 3c5746117..5a7ccc6fe 100644 --- a/src/settings.js +++ b/src/settings.js @@ -71,7 +71,7 @@ var assert = require('assert'), CronJob = require('cron').CronJob, DatabaseError = require('./databaseerror.js'), debug = require('debug')('box:settings'), - dns = require('native-dns'), + dig = require('./dig.js'), cloudron = require('./cloudron.js'), CloudronError = cloudron.CloudronError, moment = require('moment-timezone'), @@ -151,6 +151,8 @@ function uninitialize(callback) { function getEmailStatus(callback) { assert.strictEqual(typeof callback, 'function'); + var digOptions = { server: '127.0.0.1', port: 53, timeout: 5000 }; + var records = {}, outboundPort25 = {}; var dkimKey = cloudron.readDkimPublicKeySync(); @@ -160,17 +162,17 @@ function getEmailStatus(callback) { records.dkim = { domain: constants.DKIM_SELECTOR + '._domainkey.' + config.fqdn(), type: 'TXT', - expected: 'v=DKIM1; t=s; p=' + dkimKey, + expected: '"v=DKIM1; t=s; p=' + dkimKey + '"', value: null, status: false }; - dns.resolve(records.dkim.domain, records.dkim.type, function (error, txtRecords) { + dig.resolve(records.dkim.domain, records.dkim.type, digOptions, function (error, txtRecords) { if (error && error.code === 'ENOTFOUND') return callback(null); // not setup if (error) return callback(error); if (Array.isArray(txtRecords) && txtRecords.length !== 0) { - records.dkim.value = txtRecords[0].join(' '); + records.dkim.value = txtRecords[0]; records.dkim.status = (records.dkim.value === records.dkim.expected); } @@ -183,12 +185,12 @@ function getEmailStatus(callback) { domain: config.fqdn(), type: 'TXT', value: null, - expected: 'v=spf1 a:' + config.adminFqdn() + ' ~all', + expected: '"v=spf1 a:' + config.adminFqdn() + ' ~all"', status: false }; // https://agari.zendesk.com/hc/en-us/articles/202952749-How-long-can-my-SPF-record-be- - dns.resolve(records.spf.domain, records.spf.type, function (error, txtRecords) { + dig.resolve(records.spf.domain, records.spf.type, digOptions, function (error, txtRecords) { if (error && error.code === 'ENOTFOUND') return callback(null); // not setup if (error) return callback(error); @@ -196,8 +198,8 @@ function getEmailStatus(callback) { var i; for (i = 0; i < txtRecords.length; i++) { - if (txtRecords[i].join('').indexOf('v=spf1 ') !== 0) continue; // not SPF - records.spf.value = txtRecords[i].join(''); + if (txtRecords[i].indexOf('"v=spf1 ') !== 0) continue; // not SPF + records.spf.value = txtRecords[i]; records.spf.status = records.spf.value.indexOf(' a:' + config.adminFqdn()) !== -1; break; } @@ -205,7 +207,7 @@ function getEmailStatus(callback) { if (records.spf.status) { records.spf.expected = records.spf.value; } else if (i !== txtRecords.length) { - records.spf.expected = 'v=spf1 a:' + config.adminFqdn() + ' ' + records.spf.value.slice('v=spf1 '.length); + records.spf.expected = '"v=spf1 a:' + config.adminFqdn() + ' ' + records.spf.value.slice('"v=spf1 '.length); } callback(); @@ -217,16 +219,16 @@ function getEmailStatus(callback) { domain: config.fqdn(), type: 'MX', value: null, - expected: '10 ' + config.mailFqdn(), + expected: '10 ' + config.mailFqdn() + '.', status: false }; - dns.resolve(records.mx.domain, records.mx.type, function (error, mxRecords) { + dig.resolve(records.mx.domain, records.mx.type, digOptions, function (error, mxRecords) { if (error && error.code === 'ENOTFOUND') return callback(null); // not setup if (error) return callback(error); if (Array.isArray(mxRecords) && mxRecords.length !== 0) { - records.mx.status = mxRecords.length == 1 && mxRecords[0].exchange === config.mailFqdn(); + records.mx.status = mxRecords.length == 1 && mxRecords[0].exchange === (config.mailFqdn() + '.'); records.mx.value = mxRecords.map(function (r) { return r.priority + ' ' + r.exchange; }).join(' '); } @@ -239,16 +241,16 @@ function getEmailStatus(callback) { domain: '_dmarc.' + config.fqdn(), type: 'TXT', value: null, - expected: 'v=DMARC1; p=reject; pct=100', + expected: '"v=DMARC1; p=reject; pct=100"', status: false }; - dns.resolve(records.dmarc.domain, records.dmarc.type, function (error, txtRecords) { + dig.resolve(records.dmarc.domain, records.dmarc.type, digOptions, function (error, txtRecords) { if (error && error.code === 'ENOTFOUND') return callback(null); // not setup if (error) return callback(error); if (Array.isArray(txtRecords) && txtRecords.length !== 0) { - records.dmarc.value = txtRecords[0].join(' '); + records.dmarc.value = txtRecords[0]; records.dmarc.status = (records.dmarc.value === records.dmarc.expected); } @@ -261,7 +263,7 @@ function getEmailStatus(callback) { domain: null, type: 'PTR', value: null, - expected: config.mailFqdn(), + expected: config.mailFqdn() + '.', status: false }; @@ -270,13 +272,13 @@ function getEmailStatus(callback) { records.ptr.domain = ip.split('.').reverse().join('.') + '.in-addr.arpa'; - dns.reverse(ip, function (error, ptrRecords) { + dig.resolve(ip, 'PTR', digOptions, function (error, ptrRecords) { if (error && error.code === 'ENOTFOUND') return callback(null); // not setup if (error) return callback(error); if (Array.isArray(ptrRecords) && ptrRecords.length !== 0) { records.ptr.value = ptrRecords.join(' '); - records.ptr.status = ptrRecords.some(function (v) { return v === config.mailFqdn(); }); + records.ptr.status = ptrRecords.some(function (v) { return v === records.ptr.expected; }); } return callback(); @@ -334,11 +336,6 @@ function getEmailStatus(callback) { }; } - dns.platform.timeout = 5000; // hack so that each query finish in 5 seconds. this applies to _each_ ns - if (config.CLOUDRON) dns.platform.name_servers = [ { address: '127.0.0.1', port: 53 } ]; - dns.platform.attempts = 1; - dns.platform.hosts.purge(); // otherwise, reverse() uses /etc/hosts - async.parallel([ ignoreError('mx', checkMx), ignoreError('spf', checkSpf),