merge maildb.js into mail.js
This commit is contained in:
189
src/mail.js
189
src/mail.js
@@ -8,7 +8,7 @@ exports = module.exports = {
|
||||
setLocation, // triggers the change task
|
||||
changeLocation, // does the actual changing
|
||||
|
||||
getDomains,
|
||||
listDomains,
|
||||
|
||||
getDomain,
|
||||
clearDomains,
|
||||
@@ -66,6 +66,7 @@ const assert = require('assert'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
cloudron = require('./cloudron.js'),
|
||||
constants = require('./constants.js'),
|
||||
database = require('./database.js'),
|
||||
debug = require('debug')('box:mail'),
|
||||
dns = require('./native-dns.js'),
|
||||
docker = require('./docker.js'),
|
||||
@@ -74,7 +75,6 @@ const assert = require('assert'),
|
||||
hat = require('./hat.js'),
|
||||
infra = require('./infra_version.js'),
|
||||
mailboxdb = require('./mailboxdb.js'),
|
||||
maildb = require('./maildb.js'),
|
||||
mailer = require('./mailer.js'),
|
||||
net = require('net'),
|
||||
nodemailer = require('nodemailer'),
|
||||
@@ -91,6 +91,7 @@ const assert = require('assert'),
|
||||
system = require('./system.js'),
|
||||
tasks = require('./tasks.js'),
|
||||
users = require('./users.js'),
|
||||
util = require('util'),
|
||||
validator = require('validator'),
|
||||
_ = require('underscore');
|
||||
|
||||
@@ -98,6 +99,24 @@ const DNS_OPTIONS = { timeout: 5000 };
|
||||
var NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
||||
const REMOVE_MAILBOX = path.join(__dirname, 'scripts/rmmailbox.sh');
|
||||
|
||||
const MAILDB_FIELDS = [ 'domain', 'enabled', 'mailFromValidation', 'catchAllJson', 'relayJson', 'dkimSelector', 'bannerJson' ].join(',');
|
||||
|
||||
function postProcess(data) {
|
||||
data.enabled = !!data.enabled; // int to boolean
|
||||
data.mailFromValidation = !!data.mailFromValidation; // int to boolean
|
||||
|
||||
data.catchAll = safe.JSON.parse(data.catchAllJson) || [ ];
|
||||
delete data.catchAllJson;
|
||||
|
||||
data.relay = safe.JSON.parse(data.relayJson) || { provider: 'cloudron-smtp' };
|
||||
delete data.relayJson;
|
||||
|
||||
data.banner = safe.JSON.parse(data.bannerJson) || { text: null, html: null };
|
||||
delete data.bannerJson;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function validateName(name) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
|
||||
@@ -183,19 +202,17 @@ function checkSmtpRelay(relay, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function verifyRelay(relay, callback) {
|
||||
async function verifyRelay(relay) {
|
||||
assert.strictEqual(typeof relay, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// we used to verify cloudron-smtp with checkOutboundPort25 but that is unreliable given that we just
|
||||
// randomly select some smtp server
|
||||
if (relay.provider === 'cloudron-smtp' || relay.provider === 'noop') return callback();
|
||||
if (relay.provider === 'cloudron-smtp' || relay.provider === 'noop') return;
|
||||
|
||||
checkSmtpRelay(relay, function (error) {
|
||||
if (error) return callback(new BoxError(BoxError.BAD_FIELD, error.message));
|
||||
const checkSmtpRelayAsync = util.promisify(checkSmtpRelay);
|
||||
|
||||
callback();
|
||||
});
|
||||
const [error] = await safe(checkSmtpRelayAsync(relay));
|
||||
if (error) throw new BoxError(BoxError.BAD_FIELD, error.message);
|
||||
}
|
||||
|
||||
function checkDkim(mailDomain, callback) {
|
||||
@@ -478,7 +495,7 @@ function getStatus(domain, callback) {
|
||||
function recordResult(what, func) {
|
||||
return function (callback) {
|
||||
func(function (error, result) {
|
||||
if (error) debug('Ignored error - ' + what + ':', error);
|
||||
if (error) debug(`Ignored error - ${what} : ${error.message}`);
|
||||
|
||||
safe.set(results, what, result || {});
|
||||
|
||||
@@ -488,9 +505,11 @@ function getStatus(domain, callback) {
|
||||
}
|
||||
|
||||
const mailFqdn = settings.mailFqdn();
|
||||
const getDomainFunc = util.callbackify(getDomain);
|
||||
|
||||
getDomain(domain, function (error, mailDomain) {
|
||||
getDomainFunc(domain, function (error, mailDomain) {
|
||||
if (error) return callback(error);
|
||||
if (!mailDomain) return callback(new BoxError(BoxError.NOT_FOUND, 'mail domain not found'));
|
||||
|
||||
let checks = [];
|
||||
if (mailDomain.enabled) {
|
||||
@@ -572,7 +591,9 @@ function createMailConfig(mailFqdn, mailDomain, callback) {
|
||||
|
||||
debug('createMailConfig: generating mail config');
|
||||
|
||||
getDomains(function (error, mailDomains) {
|
||||
const listDomainsFunc = util.callbackify(listDomains);
|
||||
|
||||
listDomainsFunc(function (error, mailDomains) {
|
||||
if (error) return callback(error);
|
||||
|
||||
const mailOutDomains = mailDomains.filter(d => d.relay.provider !== 'noop').map(d => d.domain).join(',');
|
||||
@@ -750,25 +771,42 @@ function handleCertChanged(callback) {
|
||||
restartMailIfActivated(callback);
|
||||
}
|
||||
|
||||
function getDomain(domain, callback) {
|
||||
async function getDomain(domain) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
maildb.get(domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
return callback(null, result);
|
||||
});
|
||||
const result = await database.query(`SELECT ${MAILDB_FIELDS} FROM mail WHERE domain = ?`, [ domain ]);
|
||||
if (result.length === 0) return null;
|
||||
return postProcess(result[0]);
|
||||
}
|
||||
|
||||
function getDomains(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
async function updateDomain(domain, data) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
|
||||
maildb.list(function (error, results) {
|
||||
if (error) return callback(error);
|
||||
const args = [ ];
|
||||
const fields = [ ];
|
||||
for (var k in data) {
|
||||
if (k === 'catchAll' || k === 'banner') {
|
||||
fields.push(`${k}Json = ?`);
|
||||
args.push(JSON.stringify(data[k]));
|
||||
} else if (k === 'relay') {
|
||||
fields.push('relayJson = ?');
|
||||
args.push(JSON.stringify(data[k]));
|
||||
} else {
|
||||
fields.push(k + ' = ?');
|
||||
args.push(data[k]);
|
||||
}
|
||||
}
|
||||
args.push(domain);
|
||||
|
||||
return callback(null, results);
|
||||
});
|
||||
const result = await database.query('UPDATE mail SET ' + fields.join(', ') + ' WHERE domain=?', args);
|
||||
if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Mail domain not found');
|
||||
}
|
||||
|
||||
async function listDomains() {
|
||||
const results = await database.query(`SELECT ${MAILDB_FIELDS} FROM mail ORDER BY domain`);
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
return results;
|
||||
}
|
||||
|
||||
// https://agari.zendesk.com/hc/en-us/articles/202952749-How-long-can-my-SPF-record-be-
|
||||
@@ -868,8 +906,11 @@ function upsertDnsRecords(domain, mailFqdn, callback) {
|
||||
|
||||
debug(`upsertDnsRecords: updating mail dns records of domain ${domain} and mail fqdn ${mailFqdn}`);
|
||||
|
||||
maildb.get(domain, function (error, mailDomain) {
|
||||
const getDomainFunc = util.callbackify(getDomain);
|
||||
|
||||
getDomainFunc(domain, function (error, mailDomain) {
|
||||
if (error) return callback(error);
|
||||
if (!mailDomain) return callback(new BoxError(BoxError.NOT_FOUND, 'mail domain not found'));
|
||||
|
||||
error = ensureDkimKeySync(mailDomain);
|
||||
if (error) return callback(error);
|
||||
@@ -1012,14 +1053,8 @@ function onDomainRemoved(domain, callback) {
|
||||
restartMail(callback);
|
||||
}
|
||||
|
||||
function clearDomains(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
maildb.clear(function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback();
|
||||
});
|
||||
async function clearDomains() {
|
||||
await database.query('DELETE FROM mail', []);
|
||||
}
|
||||
|
||||
// remove all fields that should never be sent out via REST API
|
||||
@@ -1032,91 +1067,64 @@ function removePrivateFields(domain) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function setMailFromValidation(domain, enabled, callback) {
|
||||
async function setMailFromValidation(domain, enabled) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof enabled, 'boolean');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
maildb.update(domain, { mailFromValidation: enabled }, function (error) {
|
||||
if (error) return callback(error);
|
||||
await updateDomain(domain, { mailFromValidation: enabled });
|
||||
|
||||
restartMail(NOOP_CALLBACK); // have to restart mail container since haraka cannot watch symlinked config files (mail.ini)
|
||||
|
||||
callback(null);
|
||||
});
|
||||
restartMail(NOOP_CALLBACK); // have to restart mail container since haraka cannot watch symlinked config files (mail.ini)
|
||||
}
|
||||
|
||||
function setBanner(domain, banner, callback) {
|
||||
async function setBanner(domain, banner) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof banner, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
maildb.update(domain, { banner }, function (error) {
|
||||
if (error) return callback(error);
|
||||
await updateDomain(domain, { banner });
|
||||
|
||||
restartMail(NOOP_CALLBACK);
|
||||
|
||||
callback(null);
|
||||
});
|
||||
restartMail(NOOP_CALLBACK);
|
||||
}
|
||||
|
||||
function setCatchAllAddress(domain, addresses, callback) {
|
||||
async function setCatchAllAddress(domain, addresses) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(Array.isArray(addresses));
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
maildb.update(domain, { catchAll: addresses }, function (error) {
|
||||
if (error) return callback(error);
|
||||
await updateDomain(domain, { catchAll: addresses });
|
||||
|
||||
restartMail(NOOP_CALLBACK); // have to restart mail container since haraka cannot watch symlinked config files (mail.ini)
|
||||
|
||||
callback(null);
|
||||
});
|
||||
restartMail(NOOP_CALLBACK); // have to restart mail container since haraka cannot watch symlinked config files (mail.ini)
|
||||
}
|
||||
|
||||
function setMailRelay(domain, relay, callback) {
|
||||
async function setMailRelay(domain, relay, options) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof relay, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
getDomain(domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const result = await getDomain(domain);
|
||||
if (!domain) throw new BoxError(BoxError.NOT_FOUND, 'Mail domain not found');
|
||||
|
||||
// inject current username/password
|
||||
if (result.relay.provider === relay.provider) {
|
||||
if (relay.username === constants.SECRET_PLACEHOLDER) relay.username = result.relay.username;
|
||||
if (relay.password === constants.SECRET_PLACEHOLDER) relay.password = result.relay.password;
|
||||
}
|
||||
// inject current username/password
|
||||
if (result.relay.provider === relay.provider) {
|
||||
if (relay.username === constants.SECRET_PLACEHOLDER) relay.username = result.relay.username;
|
||||
if (relay.password === constants.SECRET_PLACEHOLDER) relay.password = result.relay.password;
|
||||
}
|
||||
|
||||
verifyRelay(relay, function (error) {
|
||||
if (error) return callback(error);
|
||||
if (!options.skipVerify) await verifyRelay(relay);
|
||||
|
||||
maildb.update(domain, { relay: relay }, function (error) {
|
||||
if (error) return callback(error);
|
||||
await updateDomain(domain, { relay: relay });
|
||||
|
||||
restartMail(NOOP_CALLBACK);
|
||||
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
restartMail(NOOP_CALLBACK);
|
||||
}
|
||||
|
||||
function setMailEnabled(domain, enabled, auditSource, callback) {
|
||||
async function setMailEnabled(domain, enabled, auditSource) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof enabled, 'boolean');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
maildb.update(domain, { enabled: enabled }, function (error) {
|
||||
if (error) return callback(error);
|
||||
await updateDomain(domain, { enabled: enabled });
|
||||
|
||||
restartMail(NOOP_CALLBACK);
|
||||
restartMail(NOOP_CALLBACK);
|
||||
|
||||
eventlog.add(enabled ? eventlog.ACTION_MAIL_ENABLED : eventlog.ACTION_MAIL_DISABLED, auditSource, { domain });
|
||||
|
||||
callback(null);
|
||||
});
|
||||
await eventlog.add(enabled ? eventlog.ACTION_MAIL_ENABLED : eventlog.ACTION_MAIL_DISABLED, auditSource, { domain });
|
||||
}
|
||||
|
||||
function sendTestMail(domain, to, callback) {
|
||||
@@ -1124,8 +1132,11 @@ function sendTestMail(domain, to, callback) {
|
||||
assert.strictEqual(typeof to, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getDomain(domain, function (error, result) {
|
||||
const getDomainFunc = util.callbackify(getDomain);
|
||||
|
||||
getDomainFunc(domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
if (!result) return callback(new BoxError(BoxError.NOT_FOUND, 'mail domain not found'));
|
||||
|
||||
mailer.sendTestMail(result.domain, to, function (error) {
|
||||
if (error) return callback(error);
|
||||
@@ -1431,7 +1442,9 @@ function resolveList(listName, listDomain, callback) {
|
||||
assert.strictEqual(typeof listDomain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getDomains(function (error, mailDomains) {
|
||||
const listDomainsFunc = util.callbackify(listDomains);
|
||||
|
||||
listDomainsFunc(function (error, mailDomains) {
|
||||
if (error) return callback(error);
|
||||
|
||||
const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(',');
|
||||
|
||||
Reference in New Issue
Block a user