diff --git a/src/dig.js b/src/dig.js deleted file mode 100644 index d81bb6e9d..000000000 --- a/src/dig.js +++ /dev/null @@ -1,46 +0,0 @@ -'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/cloudflare.js b/src/dns/cloudflare.js index 2527f0520..b57d17b51 100644 --- a/src/dns/cloudflare.js +++ b/src/dns/cloudflare.js @@ -11,7 +11,7 @@ exports = module.exports = { var assert = require('assert'), async = require('async'), debug = require('debug')('box:dns/cloudflare'), - dns = require('dns'), + dns = require('../native-dns.js'), DomainError = require('../domains.js').DomainError, superagent = require('superagent'), util = require('util'), diff --git a/src/dns/digitalocean.js b/src/dns/digitalocean.js index 76ddbe5e9..63540487d 100644 --- a/src/dns/digitalocean.js +++ b/src/dns/digitalocean.js @@ -11,7 +11,7 @@ exports = module.exports = { var assert = require('assert'), async = require('async'), debug = require('debug')('box:dns/digitalocean'), - dns = require('dns'), + dns = require('../native-dns.js'), DomainError = require('../domains.js').DomainError, safe = require('safetydance'), superagent = require('superagent'), diff --git a/src/dns/gcdns.js b/src/dns/gcdns.js index a8f430d49..1fefa2569 100644 --- a/src/dns/gcdns.js +++ b/src/dns/gcdns.js @@ -10,7 +10,7 @@ exports = module.exports = { var assert = require('assert'), debug = require('debug')('box:dns/gcdns'), - dns = require('dns'), + dns = require('../native-dns.js'), DomainError = require('../domains.js').DomainError, GCDNS = require('@google-cloud/dns'), util = require('util'), diff --git a/src/dns/manual.js b/src/dns/manual.js index d9e63cea5..9dfe43ecb 100644 --- a/src/dns/manual.js +++ b/src/dns/manual.js @@ -10,7 +10,7 @@ exports = module.exports = { var assert = require('assert'), debug = require('debug')('box:dns/manual'), - dns = require('dns'), + dns = require('../native-dns.js'), DomainError = require('../domains.js').DomainError, util = require('util'); diff --git a/src/dns/route53.js b/src/dns/route53.js index a00de9d8f..eb999265e 100644 --- a/src/dns/route53.js +++ b/src/dns/route53.js @@ -13,9 +13,8 @@ exports = module.exports = { var assert = require('assert'), AWS = require('aws-sdk'), - config = require('../config.js'), debug = require('debug')('box:dns/route53'), - dns = require('dns'), + dns = require('../native-dns.js'), DomainError = require('../domains.js').DomainError, util = require('util'), _ = require('underscore'); @@ -193,7 +192,7 @@ function del(dnsConfig, zoneName, subdomain, type, values, callback) { }; var route53 = new AWS.Route53(getDnsCredentials(dnsConfig)); - route53.changeResourceRecordSets(params, function(error, result) { + route53.changeResourceRecordSets(params, function(error) { if (error && error.code === 'AccessDenied') return callback(new DomainError(DomainError.ACCESS_DENIED, error.message)); if (error && error.code === 'InvalidClientTokenId') return callback(new DomainError(DomainError.ACCESS_DENIED, error.message)); if (error && error.message && error.message.indexOf('it was not found') !== -1) { diff --git a/src/dns/waitfordns.js b/src/dns/waitfordns.js index 3342c4148..667676d8a 100644 --- a/src/dns/waitfordns.js +++ b/src/dns/waitfordns.js @@ -5,8 +5,7 @@ exports = module.exports = waitForDns; var assert = require('assert'), async = require('async'), debug = require('debug')('box:dns/waitfordns'), - dig = require('../dig.js'), - dns = require('dns'), + dns = require('../native-dns.js'), DomainError = require('../domains.js').DomainError, util = require('util'); @@ -25,7 +24,7 @@ function isChangeSynced(domain, value, type, nameserver, callback) { } async.every(nsIps, function (nsIp, iteratorCallback) { - dig.resolve(domain, type, { server: nsIp, timeout: 5000 }, function (error, answer) { + dns.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 diff --git a/src/mail.js b/src/mail.js index 2fa1de3fa..2691ce312 100644 --- a/src/mail.js +++ b/src/mail.js @@ -42,7 +42,7 @@ var assert = require('assert'), constants = require('./constants.js'), DatabaseError = require('./databaseerror.js'), debug = require('debug')('box:mail'), - dig = require('./dig.js'), + dns = require('./native-dns.js'), domains = require('./domains.js'), groups = require('./groups.js'), GroupError = groups.GroupError, @@ -65,7 +65,7 @@ var assert = require('assert'), util = require('util'), _ = require('underscore'); -const digOptions = { server: '127.0.0.1', port: 53, timeout: 5000 }; +const DNS_OPTIONS = { server: '127.0.0.1', timeout: 5000 }; var NOOP_CALLBACK = function (error) { if (error) debug(error); }; function MailError(reason, errorOrMessage) { @@ -200,14 +200,13 @@ function checkDkim(domain, callback) { var dkimKey = readDkimPublicKeySync(domain); if (!dkimKey) return callback(new Error('Failed to read dkim public key'), dkim); - dkim.expected = '"v=DKIM1; t=s; p=' + dkimKey + '"'; + dkim.expected = 'v=DKIM1; t=s; p=' + dkimKey; - dig.resolve(dkim.domain, dkim.type, digOptions, function (error, txtRecords) { - if (error && error.code === 'ENOTFOUND') return callback(null, dkim); // not setup + dns.resolve(dkim.domain, dkim.type, DNS_OPTIONS, function (error, txtRecords) { if (error) return callback(error, dkim); - if (Array.isArray(txtRecords) && txtRecords.length !== 0) { - dkim.value = txtRecords[0]; + if (txtRecords.length !== 0) { + dkim.value = txtRecords[0].join(''); dkim.status = (dkim.value === dkim.expected); } @@ -220,21 +219,18 @@ function checkSpf(domain, callback) { domain: domain, type: 'TXT', value: null, - expected: '"v=spf1 a:' + config.mailFqdn() + ' ~all"', + expected: 'v=spf1 a:' + config.mailFqdn() + ' ~all', status: false }; - // https://agari.zendesk.com/hc/en-us/articles/202952749-How-long-can-my-SPF-record-be- - dig.resolve(spf.domain, spf.type, digOptions, function (error, txtRecords) { - if (error && error.code === 'ENOTFOUND') return callback(null, spf); // not setup + dns.resolve(spf.domain, spf.type, DNS_OPTIONS, function (error, txtRecords) { if (error) return callback(error, spf); - if (!Array.isArray(txtRecords)) return callback(null, spf); - var i; for (i = 0; i < txtRecords.length; i++) { - if (txtRecords[i].indexOf('"v=spf1 ') !== 0) continue; // not SPF - spf.value = txtRecords[i]; + let txtRecord = txtRecords[i].join(''); // https://agari.zendesk.com/hc/en-us/articles/202952749-How-long-can-my-SPF-record-be- + if (txtRecord.indexOf('v=spf1 ') !== 0) continue; // not SPF + spf.value = txtRecord; spf.status = spf.value.indexOf(' a:' + config.adminFqdn()) !== -1; break; } @@ -242,7 +238,7 @@ function checkSpf(domain, callback) { if (spf.status) { spf.expected = spf.value; } else if (i !== txtRecords.length) { - spf.expected = '"v=spf1 a:' + config.adminFqdn() + ' ' + spf.value.slice('"v=spf1 '.length); + spf.expected = 'v=spf1 a:' + config.adminFqdn() + ' ' + spf.value.slice('v=spf1 '.length); } callback(null, spf); @@ -258,11 +254,10 @@ function checkMx(domain, callback) { status: false }; - dig.resolve(mx.domain, mx.type, digOptions, function (error, mxRecords) { - if (error && error.code === 'ENOTFOUND') return callback(null, mx); // not setup + dns.resolve(mx.domain, mx.type, DNS_OPTIONS, function (error, mxRecords) { if (error) return callback(error, mx); - if (Array.isArray(mxRecords) && mxRecords.length !== 0) { + if (mxRecords.length !== 0) { mx.status = mxRecords.length == 1 && mxRecords[0].exchange === (config.mailFqdn() + '.'); mx.value = mxRecords.map(function (r) { return r.priority + ' ' + r.exchange; }).join(' '); } @@ -276,16 +271,15 @@ function checkDmarc(domain, callback) { domain: '_dmarc.' + domain, type: 'TXT', value: null, - expected: '"v=DMARC1; p=reject; pct=100"', + expected: 'v=DMARC1; p=reject; pct=100', status: false }; - dig.resolve(dmarc.domain, dmarc.type, digOptions, function (error, txtRecords) { - if (error && error.code === 'ENOTFOUND') return callback(null, dmarc); // not setup + dns.resolve(dmarc.domain, dmarc.type, DNS_OPTIONS, function (error, txtRecords) { if (error) return callback(error, dmarc); - if (Array.isArray(txtRecords) && txtRecords.length !== 0) { - dmarc.value = txtRecords[0]; + if (txtRecords.length !== 0) { + dmarc.value = txtRecords[0].join(''); dmarc.status = (dmarc.value === dmarc.expected); } @@ -307,11 +301,10 @@ function checkPtr(callback) { ptr.domain = ip.split('.').reverse().join('.') + '.in-addr.arpa'; - dig.resolve(ip, 'PTR', digOptions, function (error, ptrRecords) { - if (error && error.code === 'ENOTFOUND') return callback(null, ptr); // not setup + dns.resolve(ip, 'PTR', DNS_OPTIONS, function (error, ptrRecords) { if (error) return callback(error, ptr); - if (Array.isArray(ptrRecords) && ptrRecords.length !== 0) { + if (ptrRecords.length !== 0) { ptr.value = ptrRecords.join(' '); ptr.status = ptrRecords.some(function (v) { return v === ptr.expected; }); } @@ -391,15 +384,15 @@ function checkRblStatus(domain, callback) { // https://tools.ietf.org/html/rfc5782 async.map(RBL_LIST, function (rblServer, iteratorDone) { - dig.resolve(flippedIp + '.' + rblServer.dns, 'A', digOptions, function (error, records) { + dns.resolve(flippedIp + '.' + rblServer.dns, 'A', DNS_OPTIONS, function (error, records) { if (error || !records) return iteratorDone(null, null); // not listed debug('checkRblStatus: %s (ip: %s) is in the blacklist of %j', domain, flippedIp, rblServer); var result = _.extend({ }, rblServer); - dig.resolve(flippedIp + '.' + rblServer.dns, 'TXT', digOptions, function (error, txtRecords) { - result.txtRecords = error || !txtRecords ? 'No txt record' : txtRecords; + dns.resolve(flippedIp + '.' + rblServer.dns, 'TXT', DNS_OPTIONS, function (error, txtRecords) { + result.txtRecords = error || !txtRecords ? 'No txt record' : txtRecords.map(x => x.join('')); debug('checkRblStatus: %s (error: %s) (txtRecords: %j)', domain, error, txtRecords); diff --git a/src/native-dns.js b/src/native-dns.js new file mode 100644 index 000000000..13be9c626 --- /dev/null +++ b/src/native-dns.js @@ -0,0 +1,39 @@ +'use strict'; + +exports = module.exports = { + resolveNs: resolveNs, + resolve4: resolve4 +}; + +var assert = require('assert'), + dns = require('dns'); + +// 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 resolver = new dns.Resolver(); + if (options.server) 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); + + callback(error, result); + }); +} + +function resolveNs(hostname, callback) { + resolve(hostname, 'NS', { timeout: 5000 }, callback); +} + +function resolve4(hostname, callback) { + resolve(hostname, 'A', { timeout: 5000 }, callback); +} diff --git a/src/routes/test/mail-test.js b/src/routes/test/mail-test.js index 1f17b8ae0..d51cae02f 100644 --- a/src/routes/test/mail-test.js +++ b/src/routes/test/mail-test.js @@ -198,16 +198,18 @@ describe('Mail API', function () { this.timeout(10000); before(function (done) { - var dig = require('../../dig.js'); + var dns = require('../../native-dns.js'); // replace dns resolveTxt() - resolve = dig.resolve; - dig.resolve = function (hostname, type, options, callback) { + resolve = dns.resolve; + dns.resolve = function (hostname, type, options, callback) { expect(hostname).to.be.a('string'); expect(callback).to.be.a('function'); if (!dnsAnswerQueue[hostname] || !(type in dnsAnswerQueue[hostname])) return callback(new Error('no mock answer')); + if (dnsAnswerQueue[hostname][type] === null) return callback(new Error({ code: 'ENODATA'} )); + callback(null, dnsAnswerQueue[hostname][type]); }; @@ -222,13 +224,13 @@ describe('Mail API', function () { .end(function (err, res) { expect(res.statusCode).to.equal(201); done(); - }); + }); }); after(function (done) { - var dig = require('../../dig.js'); + var dns = require('../../native-dns.js'); - dig.resolve = resolve; + dns.resolve = resolve; superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain) .send({ password: PASSWORD }) @@ -268,20 +270,20 @@ describe('Mail 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=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"'); + expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)); 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'); @@ -314,17 +316,17 @@ describe('Mail 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=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"'); + expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)); 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); @@ -346,9 +348,9 @@ describe('Mail API', function () { 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=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"']; - dnsAnswerQueue[spfDomain].TXT = ['"v=spf1 a:random.com ~all"']; + dnsAnswerQueue[dmarcDomain].TXT = [['v=DMARC2; p=reject; pct=100']]; + dnsAnswerQueue[dkimDomain].TXT = [['v=DKIM2; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)]]; + dnsAnswerQueue[spfDomain].TXT = [['v=spf1 a:random.com ~all']]; superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/status') .query({ access_token: token }) @@ -356,19 +358,19 @@ describe('Mail 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=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"'); + expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)); expect(res.body.dns.dkim.status).to.eql(false); - expect(res.body.dns.dkim.value).to.eql('"v=DKIM2; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"'); + expect(res.body.dns.dkim.value).to.eql('v=DKIM2; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)); 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); @@ -389,7 +391,7 @@ describe('Mail 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/mail/' + DOMAIN_0.domain + '/status') .query({ access_token: token }) @@ -399,8 +401,8 @@ describe('Mail 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(); @@ -411,9 +413,9 @@ describe('Mail API', function () { 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=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"']; - dnsAnswerQueue[spfDomain].TXT = ['"v=spf1 a:' + config.adminFqdn() + ' ~all"']; + dnsAnswerQueue[dmarcDomain].TXT = [['v=DMARC1; p=reject; pct=100']]; + dnsAnswerQueue[dkimDomain].TXT = [['v=DKIM1; t=s; p=', mail._readDkimPublicKeySync(DOMAIN_0.domain) ]]; + dnsAnswerQueue[spfDomain].TXT = [['v=spf1 a:' + config.adminFqdn() + ' ~all']]; superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/status') .query({ access_token: token }) @@ -423,21 +425,21 @@ describe('Mail 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=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"'); - expect(res.body.dns.dkim.expected).to.eql('"v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"'); + expect(res.body.dns.dkim.value).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)); + expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)); 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); @@ -457,7 +459,7 @@ describe('Mail API', function () { .end(function (err, res) { expect(res.statusCode).to.equal(201); done(); - }); + }); }); after(function (done) { @@ -509,7 +511,7 @@ describe('Mail API', function () { .end(function (err, res) { expect(res.statusCode).to.equal(201); done(); - }); + }); }); after(function (done) { @@ -580,7 +582,7 @@ describe('Mail API', function () { .end(function (err, res) { expect(res.statusCode).to.equal(201); done(); - }); + }); }); after(function (done) { @@ -658,7 +660,7 @@ describe('Mail API', function () { .end(function (err, res) { expect(res.statusCode).to.equal(201); done(); - }); + }); }); after(function (done) { @@ -770,7 +772,7 @@ describe('Mail API', function () { .end(function (err, res) { expect(res.statusCode).to.equal(201); done(); - }); + }); }); after(function (done) {