mailboxdb: merge into mail.js
This commit is contained in:
649
src/mail.js
649
src/mail.js
@@ -37,11 +37,13 @@ exports = module.exports = {
|
||||
|
||||
getMailboxCount,
|
||||
listMailboxes,
|
||||
listAllMailboxes,
|
||||
getMailbox,
|
||||
addMailbox,
|
||||
updateMailbox,
|
||||
removeMailbox,
|
||||
delMailbox,
|
||||
|
||||
getAlias,
|
||||
getAliases,
|
||||
setAliases,
|
||||
|
||||
@@ -49,7 +51,7 @@ exports = module.exports = {
|
||||
getList,
|
||||
addList,
|
||||
updateList,
|
||||
removeList,
|
||||
delList,
|
||||
resolveList,
|
||||
|
||||
OWNERTYPE_USER: 'user',
|
||||
@@ -57,7 +59,11 @@ exports = module.exports = {
|
||||
|
||||
DEFAULT_MEMORY_LIMIT: 512 * 1024 * 1024,
|
||||
|
||||
_removeMailboxes: removeMailboxes,
|
||||
TYPE_MAILBOX: 'mailbox',
|
||||
TYPE_LIST: 'list',
|
||||
TYPE_ALIAS: 'alias',
|
||||
|
||||
_delByDomain: delByDomain,
|
||||
_readDkimPublicKeySync: readDkimPublicKeySync,
|
||||
_updateDomain: updateDomain
|
||||
};
|
||||
@@ -75,8 +81,8 @@ const assert = require('assert'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
hat = require('./hat.js'),
|
||||
infra = require('./infra_version.js'),
|
||||
mailboxdb = require('./mailboxdb.js'),
|
||||
mailer = require('./mailer.js'),
|
||||
mysql = require('mysql'),
|
||||
net = require('net'),
|
||||
nodemailer = require('nodemailer'),
|
||||
path = require('path'),
|
||||
@@ -97,15 +103,38 @@ const assert = require('assert'),
|
||||
_ = require('underscore');
|
||||
|
||||
const DNS_OPTIONS = { timeout: 5000 };
|
||||
var NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
||||
const REMOVE_MAILBOX = path.join(__dirname, 'scripts/rmmailbox.sh');
|
||||
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
||||
const REMOVE_MAILBOX_CMD = path.join(__dirname, 'scripts/rmmailbox.sh');
|
||||
|
||||
const MAILBOX_FIELDS = [ 'name', 'type', 'ownerId', 'ownerType', 'aliasName', 'aliasDomain', 'creationTime', 'membersJson', 'membersOnly', 'domain', 'active' ].join(',');
|
||||
const MAILDB_FIELDS = [ 'domain', 'enabled', 'mailFromValidation', 'catchAllJson', 'relayJson', 'dkimSelector', 'bannerJson' ].join(',');
|
||||
|
||||
const domainsGet = util.callbackify(domains.get),
|
||||
domainsList = util.callbackify(domains.list);
|
||||
|
||||
function postProcess(data) {
|
||||
function postProcessMailbox(data) {
|
||||
data.members = safe.JSON.parse(data.membersJson) || [ ];
|
||||
delete data.membersJson;
|
||||
|
||||
data.membersOnly = !!data.membersOnly;
|
||||
data.active = !!data.active;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function postProcessAliases(data) {
|
||||
const aliasNames = JSON.parse(data.aliasNames), aliasDomains = JSON.parse(data.aliasDomains);
|
||||
delete data.aliasNames;
|
||||
delete data.aliasDomains;
|
||||
data.aliases = [];
|
||||
for (let i = 0; i < aliasNames.length; i++) { // NOTE: aliasNames is [ null ] when no aliases
|
||||
if (aliasNames[i]) data.aliases[i] = { name: aliasNames[i], domain: aliasDomains[i] };
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function postProcessDomain(data) {
|
||||
data.enabled = !!data.enabled; // int to boolean
|
||||
data.mailFromValidation = !!data.mailFromValidation; // int to boolean
|
||||
|
||||
@@ -542,121 +571,101 @@ function getStatus(domain, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function checkConfiguration(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
async function checkConfiguration() {
|
||||
let messages = {};
|
||||
|
||||
domainsList(function (error, allDomains) {
|
||||
if (error) return callback(error);
|
||||
const allDomains = await listDomains();
|
||||
|
||||
async.eachSeries(allDomains, function (domainObject, iteratorCallback) {
|
||||
getStatus(domainObject.domain, function (error, result) {
|
||||
if (error) return iteratorCallback(error);
|
||||
for (const domainObject of allDomains) {
|
||||
const result = await util.promisify(getStatus)(domainObject.domain);
|
||||
|
||||
let message = [];
|
||||
let message = [];
|
||||
|
||||
Object.keys(result.dns).forEach((type) => {
|
||||
const record = result.dns[type];
|
||||
if (!record.status) message.push(`${type.toUpperCase()} DNS record (${record.type}) did not match.\n * Hostname: \`${record.name}\`\n * Expected: \`${record.expected}\`\n * Actual: \`${record.value}\``);
|
||||
});
|
||||
if (result.relay && result.relay.status === false) message.push(`Relay error: ${result.relay.value}`);
|
||||
if (result.rbl && result.rbl.status === false) { // rbl field contents is optional
|
||||
const servers = result.rbl.servers.map((bs) => `[${bs.name}](${bs.site})`); // in markdown
|
||||
message.push(`This server's IP \`${result.rbl.ip}\` is blacklisted in the following servers - ${servers.join(', ')}`);
|
||||
}
|
||||
|
||||
if (message.length) messages[domainObject.domain] = message;
|
||||
|
||||
iteratorCallback(null);
|
||||
});
|
||||
}, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
// create bulleted list for each domain
|
||||
let markdownMessage = '';
|
||||
Object.keys(messages).forEach((domain) => {
|
||||
markdownMessage += `**${domain}**\n`;
|
||||
markdownMessage += messages[domain].map((m) => `* ${m}\n`).join('');
|
||||
markdownMessage += '\n\n';
|
||||
});
|
||||
|
||||
if (markdownMessage) markdownMessage += 'Email Status is checked every 30 minutes.\n See the [troubleshooting docs](https://docs.cloudron.io/troubleshooting/#mail-dns) for more information.\n';
|
||||
|
||||
callback(null, markdownMessage); // empty message means all status checks succeeded
|
||||
Object.keys(result.dns).forEach((type) => {
|
||||
const record = result.dns[type];
|
||||
if (!record.status) message.push(`${type.toUpperCase()} DNS record (${record.type}) did not match.\n * Hostname: \`${record.name}\`\n * Expected: \`${record.expected}\`\n * Actual: \`${record.value}\``);
|
||||
});
|
||||
if (result.relay && result.relay.status === false) message.push(`Relay error: ${result.relay.value}`);
|
||||
if (result.rbl && result.rbl.status === false) { // rbl field contents is optional
|
||||
const servers = result.rbl.servers.map((bs) => `[${bs.name}](${bs.site})`); // in markdown
|
||||
message.push(`This server's IP \`${result.rbl.ip}\` is blacklisted in the following servers - ${servers.join(', ')}`);
|
||||
}
|
||||
|
||||
if (message.length) messages[domainObject.domain] = message;
|
||||
}
|
||||
|
||||
// create bulleted list for each domain
|
||||
let markdownMessage = '';
|
||||
Object.keys(messages).forEach((domain) => {
|
||||
markdownMessage += `**${domain}**\n`;
|
||||
markdownMessage += messages[domain].map((m) => `* ${m}\n`).join('');
|
||||
markdownMessage += '\n\n';
|
||||
});
|
||||
|
||||
if (markdownMessage) markdownMessage += 'Email Status is checked every 30 minutes.\n See the [troubleshooting docs](https://docs.cloudron.io/troubleshooting/#mail-dns) for more information.\n';
|
||||
|
||||
return markdownMessage; // empty message means all status checks succeeded
|
||||
}
|
||||
|
||||
function createMailConfig(mailFqdn, mailDomain, callback) {
|
||||
async function createMailConfig(mailFqdn, mailDomain) {
|
||||
assert.strictEqual(typeof mailFqdn, 'string');
|
||||
assert.strictEqual(typeof mailDomain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('createMailConfig: generating mail config');
|
||||
|
||||
const listDomainsFunc = util.callbackify(listDomains);
|
||||
const mailDomains = await listDomains();
|
||||
|
||||
listDomainsFunc(function (error, mailDomains) {
|
||||
if (error) return callback(error);
|
||||
const mailOutDomains = mailDomains.filter(d => d.relay.provider !== 'noop').map(d => d.domain).join(',');
|
||||
const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(',');
|
||||
|
||||
const mailOutDomains = mailDomains.filter(d => d.relay.provider !== 'noop').map(d => d.domain).join(',');
|
||||
const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(',');
|
||||
// mail_domain is used for SRS
|
||||
if (!safe.fs.writeFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/mail.ini'),
|
||||
`mail_in_domains=${mailInDomains}\nmail_out_domains=${mailOutDomains}\nmail_server_name=${mailFqdn}\nmail_domain=${mailDomain}\n\n`, 'utf8')) {
|
||||
throw new BoxError(BoxError.FS_ERROR, `Could not create mail var file: ${safe.error.message}`);
|
||||
}
|
||||
|
||||
// mail_domain is used for SRS
|
||||
if (!safe.fs.writeFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/mail.ini'),
|
||||
`mail_in_domains=${mailInDomains}\nmail_out_domains=${mailOutDomains}\nmail_server_name=${mailFqdn}\nmail_domain=${mailDomain}\n\n`, 'utf8')) {
|
||||
return callback(new BoxError(BoxError.FS_ERROR, 'Could not create mail var file:' + safe.error.message));
|
||||
// enable_outbound makes plugin forward email for relayed mail. non-relayed mail always hits LMTP plugin first
|
||||
if (!safe.fs.writeFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/smtp_forward.ini'), 'enable_outbound=false\ndomain_selector=mail_from\n', 'utf8')) {
|
||||
throw new BoxError(BoxError.FS_ERROR, `Could not create smtp forward file: ${safe.error.message}`);
|
||||
}
|
||||
|
||||
// create sections for per-domain configuration
|
||||
for (const domain of mailDomains) {
|
||||
const catchAll = domain.catchAll.map(function (c) { return `${c}@${domain.domain}`; }).join(',');
|
||||
const mailFromValidation = domain.mailFromValidation;
|
||||
|
||||
if (!safe.fs.appendFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/mail.ini'),
|
||||
`[${domain.domain}]\ncatch_all=${catchAll}\nmail_from_validation=${mailFromValidation}\n\n`, 'utf8')) {
|
||||
throw new BoxError(BoxError.FS_ERROR, `Could not create mail var file: ${safe.error.message}`);
|
||||
}
|
||||
|
||||
// enable_outbound makes plugin forward email for relayed mail. non-relayed mail always hits LMTP plugin first
|
||||
if (!safe.fs.writeFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/smtp_forward.ini'), 'enable_outbound=false\ndomain_selector=mail_from\n', 'utf8')) {
|
||||
return callback(new BoxError(BoxError.FS_ERROR, 'Could not create smtp forward file:' + safe.error.message));
|
||||
if (!safe.fs.writeFileSync(`${paths.ADDON_CONFIG_DIR}/mail/banner/${domain.domain}.text`, domain.banner.text || '')) throw new BoxError(BoxError.FS_ERROR, `Could not create text banner file: ${safe.error.message}`);
|
||||
if (!safe.fs.writeFileSync(`${paths.ADDON_CONFIG_DIR}/mail/banner/${domain.domain}.html`, domain.banner.html || '')) throw new BoxError(BoxError.FS_ERROR, `Could not create html banner file: ${safe.error.message}`);
|
||||
|
||||
const relay = domain.relay;
|
||||
|
||||
const enableRelay = relay.provider !== 'cloudron-smtp' && relay.provider !== 'noop',
|
||||
host = relay.host || '',
|
||||
port = relay.port || 25,
|
||||
authType = relay.username ? 'plain' : '',
|
||||
username = relay.username || '',
|
||||
password = relay.password || '';
|
||||
|
||||
if (!enableRelay) continue;
|
||||
|
||||
if (!safe.fs.appendFileSync(paths.ADDON_CONFIG_DIR + '/mail/smtp_forward.ini',
|
||||
`[${domain.domain}]\nenable_outbound=true\nhost=${host}\nport=${port}\nenable_tls=true\nauth_type=${authType}\nauth_user=${username}\nauth_pass=${password}\n\n`, 'utf8')) {
|
||||
throw new BoxError(BoxError.FS_ERROR, `Could not create mail var file: ${safe.error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// create sections for per-domain configuration
|
||||
async.eachSeries(mailDomains, function (domain, iteratorDone) {
|
||||
const catchAll = domain.catchAll.map(function (c) { return `${c}@${domain.domain}`; }).join(',');
|
||||
const mailFromValidation = domain.mailFromValidation;
|
||||
|
||||
if (!safe.fs.appendFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/mail.ini'),
|
||||
`[${domain.domain}]\ncatch_all=${catchAll}\nmail_from_validation=${mailFromValidation}\n\n`, 'utf8')) {
|
||||
return iteratorDone(new BoxError(BoxError.FS_ERROR, 'Could not create mail var file:' + safe.error.message));
|
||||
}
|
||||
|
||||
if (!safe.fs.writeFileSync(`${paths.ADDON_CONFIG_DIR}/mail/banner/${domain.domain}.text`, domain.banner.text || '')) return iteratorDone(new BoxError(BoxError.FS_ERROR, 'Could not create text banner file:' + safe.error.message));
|
||||
if (!safe.fs.writeFileSync(`${paths.ADDON_CONFIG_DIR}/mail/banner/${domain.domain}.html`, domain.banner.html || '')) return iteratorDone(new BoxError(BoxError.FS_ERROR, 'Could not create html banner file:' + safe.error.message));
|
||||
|
||||
const relay = domain.relay;
|
||||
|
||||
const enableRelay = relay.provider !== 'cloudron-smtp' && relay.provider !== 'noop',
|
||||
host = relay.host || '',
|
||||
port = relay.port || 25,
|
||||
authType = relay.username ? 'plain' : '',
|
||||
username = relay.username || '',
|
||||
password = relay.password || '';
|
||||
|
||||
if (!enableRelay) return iteratorDone();
|
||||
|
||||
if (!safe.fs.appendFileSync(paths.ADDON_CONFIG_DIR + '/mail/smtp_forward.ini',
|
||||
`[${domain.domain}]\nenable_outbound=true\nhost=${host}\nport=${port}\nenable_tls=true\nauth_type=${authType}\nauth_user=${username}\nauth_pass=${password}\n\n`, 'utf8')) {
|
||||
return iteratorDone(new BoxError(BoxError.FS_ERROR, 'Could not create mail var file:' + safe.error.message));
|
||||
}
|
||||
|
||||
iteratorDone();
|
||||
}, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, mailInDomains.length !== 0 /* allowInbound */);
|
||||
});
|
||||
});
|
||||
return mailInDomains.length !== 0 /* allowInbound */;
|
||||
}
|
||||
|
||||
function configureMail(mailFqdn, mailDomain, serviceConfig, callback) {
|
||||
async function configureMail(mailFqdn, mailDomain, serviceConfig) {
|
||||
assert.strictEqual(typeof mailFqdn, 'string');
|
||||
assert.strictEqual(typeof mailDomain, 'string');
|
||||
assert.strictEqual(typeof serviceConfig, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// mail (note: 2587 is hardcoded in mail container and app use this port)
|
||||
// MAIL_SERVER_NAME is the hostname of the mailserver i.e server uses these certs
|
||||
@@ -668,52 +677,43 @@ function configureMail(mailFqdn, mailDomain, serviceConfig, callback) {
|
||||
const memory = system.getMemoryAllocation(memoryLimit);
|
||||
const cloudronToken = hat(8 * 128), relayToken = hat(8 * 128);
|
||||
|
||||
const getCertificatePath = util.callbackify(reverseProxy.getCertificatePath);
|
||||
getCertificatePath(mailFqdn, mailDomain, function (error, bundle) {
|
||||
if (error) return callback(error);
|
||||
const bundle = await reverseProxy.getCertificatePath(mailFqdn, mailDomain);
|
||||
|
||||
const dhparamsFilePath = path.join(paths.ADDON_CONFIG_DIR, 'mail/dhparams.pem');
|
||||
const mailCertFilePath = path.join(paths.ADDON_CONFIG_DIR, 'mail/tls_cert.pem');
|
||||
const mailKeyFilePath = path.join(paths.ADDON_CONFIG_DIR, 'mail/tls_key.pem');
|
||||
const dhparamsFilePath = path.join(paths.ADDON_CONFIG_DIR, 'mail/dhparams.pem');
|
||||
const mailCertFilePath = path.join(paths.ADDON_CONFIG_DIR, 'mail/tls_cert.pem');
|
||||
const mailKeyFilePath = path.join(paths.ADDON_CONFIG_DIR, 'mail/tls_key.pem');
|
||||
|
||||
if (!safe.child_process.execSync(`cp ${paths.DHPARAMS_FILE} ${dhparamsFilePath}`)) return callback(new BoxError(BoxError.FS_ERROR, 'Could not copy dhparams:' + safe.error.message));
|
||||
if (!safe.child_process.execSync(`cp ${bundle.certFilePath} ${mailCertFilePath}`)) return callback(new BoxError(BoxError.FS_ERROR, 'Could not create cert file:' + safe.error.message));
|
||||
if (!safe.child_process.execSync(`cp ${bundle.keyFilePath} ${mailKeyFilePath}`)) return callback(new BoxError(BoxError.FS_ERROR, 'Could not create key file:' + safe.error.message));
|
||||
if (!safe.child_process.execSync(`cp ${paths.DHPARAMS_FILE} ${dhparamsFilePath}`)) throw new BoxError(BoxError.FS_ERROR, 'Could not copy dhparams:' + safe.error.message);
|
||||
if (!safe.child_process.execSync(`cp ${bundle.certFilePath} ${mailCertFilePath}`)) throw new BoxError(BoxError.FS_ERROR, 'Could not create cert file:' + safe.error.message);
|
||||
if (!safe.child_process.execSync(`cp ${bundle.keyFilePath} ${mailKeyFilePath}`)) throw new BoxError(BoxError.FS_ERROR, 'Could not create key file:' + safe.error.message);
|
||||
|
||||
async.series([
|
||||
shell.exec.bind(null, 'stopMail', 'docker stop mail || true'),
|
||||
shell.exec.bind(null, 'removeMail', 'docker rm -f mail || true'),
|
||||
], function (error) {
|
||||
if (error) return callback(error);
|
||||
await shell.promises.exec('stopMail', 'docker stop mail || true');
|
||||
await shell.promises.exec('removeMail', 'docker rm -f mail || true');
|
||||
|
||||
createMailConfig(mailFqdn, mailDomain, function (error, allowInbound) {
|
||||
if (error) return callback(error);
|
||||
const allowInbound = await createMailConfig(mailFqdn, mailDomain);
|
||||
|
||||
var ports = allowInbound ? '-p 587:2587 -p 993:9993 -p 4190:4190 -p 25:2587' : '';
|
||||
const ports = allowInbound ? '-p 587:2587 -p 993:9993 -p 4190:4190 -p 25:2587' : '';
|
||||
|
||||
const cmd = `docker run --restart=always -d --name="mail" \
|
||||
--net cloudron \
|
||||
--net-alias mail \
|
||||
--log-driver syslog \
|
||||
--log-opt syslog-address=udp://127.0.0.1:2514 \
|
||||
--log-opt syslog-format=rfc5424 \
|
||||
--log-opt tag=mail \
|
||||
-m ${memory} \
|
||||
--memory-swap ${memoryLimit} \
|
||||
--dns 172.18.0.1 \
|
||||
--dns-search=. \
|
||||
-e CLOUDRON_MAIL_TOKEN="${cloudronToken}" \
|
||||
-e CLOUDRON_RELAY_TOKEN="${relayToken}" \
|
||||
-v "${paths.MAIL_DATA_DIR}:/app/data" \
|
||||
-v "${paths.PLATFORM_DATA_DIR}/addons/mail:/etc/mail" \
|
||||
${ports} \
|
||||
--label isCloudronManaged=true \
|
||||
--read-only -v /run -v /tmp ${tag}`;
|
||||
const cmd = `docker run --restart=always -d --name="mail" \
|
||||
--net cloudron \
|
||||
--net-alias mail \
|
||||
--log-driver syslog \
|
||||
--log-opt syslog-address=udp://127.0.0.1:2514 \
|
||||
--log-opt syslog-format=rfc5424 \
|
||||
--log-opt tag=mail \
|
||||
-m ${memory} \
|
||||
--memory-swap ${memoryLimit} \
|
||||
--dns 172.18.0.1 \
|
||||
--dns-search=. \
|
||||
-e CLOUDRON_MAIL_TOKEN="${cloudronToken}" \
|
||||
-e CLOUDRON_RELAY_TOKEN="${relayToken}" \
|
||||
-v "${paths.MAIL_DATA_DIR}:/app/data" \
|
||||
-v "${paths.PLATFORM_DATA_DIR}/addons/mail:/etc/mail" \
|
||||
${ports} \
|
||||
--label isCloudronManaged=true \
|
||||
--read-only -v /run -v /tmp ${tag}`;
|
||||
|
||||
shell.exec('startMail', cmd, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
await shell.promises.exec('startMail', cmd);
|
||||
}
|
||||
|
||||
function getMailAuth(callback) {
|
||||
@@ -746,11 +746,12 @@ function restartMail(callback) {
|
||||
|
||||
if (process.env.BOX_ENV === 'test' && !process.env.TEST_CREATE_INFRA) return callback();
|
||||
|
||||
services.getServiceConfig('mail', function (error, serviceConfig) {
|
||||
services.getServiceConfig('mail', async function (error, serviceConfig) {
|
||||
if (error) return callback(error);
|
||||
|
||||
debug(`restartMail: restarting mail container with mailFqdn:${settings.mailFqdn()} dashboardDomain:${settings.dashboardDomain()}`);
|
||||
configureMail(settings.mailFqdn(), settings.dashboardDomain(), serviceConfig, callback);
|
||||
[error] = await safe(configureMail(settings.mailFqdn(), settings.dashboardDomain(), serviceConfig));
|
||||
callback(error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -775,7 +776,7 @@ async function getDomain(domain) {
|
||||
|
||||
const result = await database.query(`SELECT ${MAILDB_FIELDS} FROM mail WHERE domain = ?`, [ domain ]);
|
||||
if (result.length === 0) return null;
|
||||
return postProcess(result[0]);
|
||||
return postProcessDomain(result[0]);
|
||||
}
|
||||
|
||||
async function updateDomain(domain, data) {
|
||||
@@ -804,7 +805,7 @@ async function updateDomain(domain, data) {
|
||||
|
||||
async function listDomains() {
|
||||
const results = await database.query(`SELECT ${MAILDB_FIELDS} FROM mail ORDER BY domain`);
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
results.forEach(function (result) { postProcessDomain(result); });
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -1126,79 +1127,99 @@ async function setMailEnabled(domain, enabled, auditSource) {
|
||||
await eventlog.add(enabled ? eventlog.ACTION_MAIL_ENABLED : eventlog.ACTION_MAIL_DISABLED, auditSource, { domain });
|
||||
}
|
||||
|
||||
function sendTestMail(domain, to, callback) {
|
||||
async function sendTestMail(domain, to) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof to, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const getDomainFunc = util.callbackify(getDomain);
|
||||
const result = await getDomain(domain);
|
||||
if (!result) throw new BoxError(BoxError.NOT_FOUND, 'mail domain not found');
|
||||
|
||||
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);
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
await util.promisify(mailer.sendTestMail)(result.domain);
|
||||
}
|
||||
|
||||
function listMailboxes(domain, search, page, perPage, callback) {
|
||||
async function listMailboxes(domain, search, page, perPage) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(typeof search === 'string' || search === null);
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.listMailboxes(domain, search, page, perPage, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const escapedSearch = mysql.escape('%' + search + '%'); // this also quotes the string
|
||||
const searchQuery = search ? ` HAVING (name LIKE ${escapedSearch} OR aliasNames LIKE ${escapedSearch} OR aliasDomains LIKE ${escapedSearch})` : ''; // having instead of where because of aggregated columns use
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
const query = 'SELECT m1.name AS name, m1.domain AS domain, m1.ownerId AS ownerId, m1.ownerType as ownerType, m1.active as active, JSON_ARRAYAGG(m2.name) AS aliasNames, JSON_ARRAYAGG(m2.domain) AS aliasDomains '
|
||||
+ ` FROM (SELECT * FROM mailboxes WHERE type='${exports.TYPE_MAILBOX}') AS m1`
|
||||
+ ` LEFT JOIN (SELECT * FROM mailboxes WHERE type='${exports.TYPE_ALIAS}') AS m2`
|
||||
+ ' ON m1.name=m2.aliasName AND m1.domain=m2.aliasDomain AND m1.ownerId=m2.ownerId'
|
||||
+ ' WHERE m1.domain = ?'
|
||||
+ ' GROUP BY m1.name, m1.domain, m1.ownerId'
|
||||
+ searchQuery
|
||||
+ ' ORDER BY name LIMIT ?,?';
|
||||
|
||||
const results = await database.query(query, [ domain, (page-1)*perPage, perPage ]);
|
||||
|
||||
results.forEach(postProcessMailbox);
|
||||
results.forEach(postProcessAliases);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function getMailboxCount(domain, callback) {
|
||||
async function listAllMailboxes(page, perPage) {
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
|
||||
const query = 'SELECT m1.name AS name, m1.domain AS domain, m1.ownerId AS ownerId, m1.ownerType as ownerType, m1.active as active, JSON_ARRAYAGG(m2.name) AS aliasNames, JSON_ARRAYAGG(m2.domain) AS aliasDomains '
|
||||
+ ` FROM (SELECT * FROM mailboxes WHERE type='${exports.TYPE_MAILBOX}') AS m1`
|
||||
+ ` LEFT JOIN (SELECT * FROM mailboxes WHERE type='${exports.TYPE_ALIAS}') AS m2`
|
||||
+ ' ON m1.name=m2.aliasName AND m1.domain=m2.aliasDomain AND m1.ownerId=m2.ownerId'
|
||||
+ ' GROUP BY m1.name, m1.domain, m1.ownerId'
|
||||
+ ' ORDER BY name LIMIT ?,?';
|
||||
|
||||
const results = await database.query(query, [ (page-1)*perPage, perPage ]);
|
||||
|
||||
results.forEach(postProcessMailbox);
|
||||
results.forEach(postProcessAliases);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async function getMailboxCount(domain) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.getMailboxCount(domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const results = await database.query('SELECT COUNT(*) AS total FROM mailboxes WHERE type = ? AND domain = ?', [ exports.TYPE_MAILBOX, domain ]);
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
return results[0].total;
|
||||
}
|
||||
|
||||
function removeMailboxes(domain, callback) {
|
||||
async function delByDomain(domain) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.delByDomain(domain, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback();
|
||||
});
|
||||
await database.query('DELETE FROM mailboxes WHERE domain = ?', [ domain ]);
|
||||
}
|
||||
|
||||
function getMailbox(name, domain, callback) {
|
||||
async function get(name, domain) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.getMailbox(name, domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const results = await database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND domain = ?', [ name, domain ]);
|
||||
if (results.length === 0) return null;
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
return postProcessMailbox(results[0]);
|
||||
}
|
||||
|
||||
function addMailbox(name, domain, data, auditSource, callback) {
|
||||
async function getMailbox(name, domain) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
|
||||
const results = await database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND type = ? AND domain = ?', [ name, exports.TYPE_MAILBOX, domain ]);
|
||||
if (results.length === 0) return null;
|
||||
return postProcessMailbox(results[0]);
|
||||
}
|
||||
|
||||
async function addMailbox(name, domain, data, auditSource) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const { ownerId, ownerType, active } = data;
|
||||
assert.strictEqual(typeof ownerId, 'string');
|
||||
@@ -1207,26 +1228,23 @@ function addMailbox(name, domain, data, auditSource, callback) {
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
var error = validateName(name);
|
||||
if (error) return callback(error);
|
||||
let error = validateName(name);
|
||||
if (error) throw error;
|
||||
|
||||
if (ownerType !== exports.OWNERTYPE_USER && ownerType !== exports.OWNERTYPE_GROUP) return callback(new BoxError(BoxError.BAD_FIELD, 'bad owner type'));
|
||||
if (ownerType !== exports.OWNERTYPE_USER && ownerType !== exports.OWNERTYPE_GROUP) throw new BoxError(BoxError.BAD_FIELD, 'bad owner type');
|
||||
|
||||
mailboxdb.addMailbox(name, domain, data, function (error) {
|
||||
if (error) return callback(error);
|
||||
[error] = await safe(database.query('INSERT INTO mailboxes (name, type, domain, ownerId, ownerType, active) VALUES (?, ?, ?, ?, ?, ?)', [ name, exports.TYPE_MAILBOX, domain, ownerId, ownerType, active ]));
|
||||
if (error && error.code === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, 'mailbox already exists');
|
||||
if (error) throw error;
|
||||
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_ADD, auditSource, { name, domain, ownerId, ownerType, active });
|
||||
|
||||
callback(null);
|
||||
});
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_ADD, auditSource, { name, domain, ownerId, ownerType, active });
|
||||
}
|
||||
|
||||
function updateMailbox(name, domain, data, auditSource, callback) {
|
||||
async function updateMailbox(name, domain, data, auditSource) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const { ownerId, ownerType, active } = data;
|
||||
assert.strictEqual(typeof ownerId, 'string');
|
||||
@@ -1235,19 +1253,15 @@ function updateMailbox(name, domain, data, auditSource, callback) {
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
if (ownerType !== exports.OWNERTYPE_USER && ownerType !== exports.OWNERTYPE_GROUP) return callback(new BoxError(BoxError.BAD_FIELD, 'bad owner type'));
|
||||
if (ownerType !== exports.OWNERTYPE_USER && ownerType !== exports.OWNERTYPE_GROUP) throw new BoxError(BoxError.BAD_FIELD, 'bad owner type');
|
||||
|
||||
getMailbox(name, domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const mailbox = await getMailbox(name, domain);
|
||||
if (!mailbox) throw new BoxError(BoxError.NOT_FOUND, 'No such mailbox');
|
||||
|
||||
mailboxdb.updateMailbox(name, domain, data, function (error) {
|
||||
if (error) return callback(error);
|
||||
const result = await database.query('UPDATE mailboxes SET ownerId = ?, ownerType = ?, active = ? WHERE name = ? AND domain = ?', [ ownerId, ownerType, active, name, domain ]);
|
||||
if (result.affectedRows === 0) throw new BoxError(BoxError.NOT_FOUND, 'Mailbox not found');
|
||||
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_UPDATE, auditSource, { name, domain, oldUserId: result.userId, ownerId, ownerType, active });
|
||||
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_UPDATE, auditSource, { name, domain, oldUserId: mailbox.userId, ownerId, ownerType, active });
|
||||
}
|
||||
|
||||
function removeSolrIndex(mailbox, callback) {
|
||||
@@ -1267,101 +1281,117 @@ function removeSolrIndex(mailbox, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function removeMailbox(name, domain, options, auditSource, callback) {
|
||||
async function delMailbox(name, domain, options, auditSource) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const mailbox =`${name}@${domain}`;
|
||||
const deleteMailFunc = options.deleteMails ? shell.sudo.bind(null, 'removeMailbox', [ REMOVE_MAILBOX, mailbox ], {}) : (next) => next();
|
||||
if (options.deleteMails) {
|
||||
const [error] = await safe(shell.promises.sudo('removeMailbox', [ REMOVE_MAILBOX_CMD, mailbox ], {}));
|
||||
if (error) throw new BoxError(BoxError.FS_ERROR, `Error removing mailbox: ${error.message}`);
|
||||
}
|
||||
|
||||
deleteMailFunc(function (error) {
|
||||
if (error) return callback(new BoxError(BoxError.FS_ERROR, `Error removing mailbox: ${error.message}`));
|
||||
// deletes aliases as well
|
||||
const result = await database.query('DELETE FROM mailboxes WHERE ((name=? AND domain=?) OR (aliasName = ? AND aliasDomain=?))', [ name, domain, name, domain ]);
|
||||
if (result.affectedRows === 0) throw new BoxError(BoxError.NOT_FOUND, 'Mailbox not found');
|
||||
|
||||
mailboxdb.del(name, domain, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
removeSolrIndex(mailbox, NOOP_CALLBACK);
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_REMOVE, auditSource, { name, domain });
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
removeSolrIndex(mailbox, NOOP_CALLBACK);
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_REMOVE, auditSource, { name, domain });
|
||||
}
|
||||
|
||||
function getAliases(name, domain, callback) {
|
||||
async function getAlias(name, domain) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getMailbox(name, domain, function (error) {
|
||||
if (error) return callback(error);
|
||||
const results = await database.query(`SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE name = ? AND type = ? AND domain = ?`, [ name, exports.TYPE_ALIAS, domain ]);
|
||||
if (results.length === 0) return null;
|
||||
|
||||
mailboxdb.getAliasesForName(name, domain, function (error, aliases) {
|
||||
if (error) return callback(error);
|
||||
results.forEach(function (result) { postProcessMailbox(result); });
|
||||
|
||||
callback(null, aliases);
|
||||
});
|
||||
});
|
||||
return results[0];
|
||||
}
|
||||
|
||||
function setAliases(name, domain, aliases, callback) {
|
||||
async function getAliases(name, domain) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
|
||||
const result = await getMailbox(name, domain); // check if mailbox exists
|
||||
if (result === null) throw new BoxError(BoxError.NOT_FOUND, 'No such mailbox');
|
||||
return await database.query('SELECT name, domain FROM mailboxes WHERE type = ? AND aliasName = ? AND aliasDomain = ? ORDER BY name', [ exports.TYPE_ALIAS, name, domain ]);
|
||||
}
|
||||
|
||||
async function setAliases(name, domain, aliases) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(Array.isArray(aliases));
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
for (var i = 0; i < aliases.length; i++) {
|
||||
for (let i = 0; i < aliases.length; i++) {
|
||||
let name = aliases[i].name.toLowerCase();
|
||||
let domain = aliases[i].domain.toLowerCase();
|
||||
|
||||
let error = validateName(name);
|
||||
if (error) return callback(error);
|
||||
if (error) throw error;
|
||||
|
||||
if (!validator.isEmail(`${name}@${domain}`)) return callback(new BoxError(BoxError.BAD_FIELD, `Invalid email: ${name}@${domain}`));
|
||||
if (!validator.isEmail(`${name}@${domain}`)) throw new BoxError(BoxError.BAD_FIELD, `Invalid email: ${name}@${domain}`);
|
||||
aliases[i] = { name, domain };
|
||||
}
|
||||
mailboxdb.setAliasesForName(name, domain, aliases, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null);
|
||||
const results = await database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND domain = ?', [ name, domain ]);
|
||||
if (results.length === 0) throw new BoxError(BoxError.NOT_FOUND, 'Mailbox not found');
|
||||
|
||||
let queries = [];
|
||||
// clear existing aliases
|
||||
queries.push({ query: 'DELETE FROM mailboxes WHERE aliasName = ? AND aliasDomain = ? AND type = ?', args: [ name, domain, exports.TYPE_ALIAS ] });
|
||||
aliases.forEach(function (alias) {
|
||||
queries.push({ query: 'INSERT INTO mailboxes (name, domain, type, aliasName, aliasDomain, ownerId, ownerType) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
args: [ alias.name, alias.domain, exports.TYPE_ALIAS, name, domain, results[0].ownerId, results[0].ownerType ] });
|
||||
});
|
||||
|
||||
const [error] = await safe(database.transaction(queries));
|
||||
if (error && error.code === 'ER_DUP_ENTRY' && error.message.indexOf('mailboxes_name_domain_unique_index') !== -1) {
|
||||
const aliasMatch = error.message.match(new RegExp(`^ER_DUP_ENTRY: Duplicate entry '(.*)-${domain}' for key 'mailboxes_name_domain_unique_index'$`));
|
||||
if (!aliasMatch) throw new BoxError(BoxError.ALREADY_EXISTS, error.message);
|
||||
|
||||
throw new BoxError(BoxError.ALREADY_EXISTS, `Mailbox, mailinglist or alias for ${aliasMatch[1]} already exists`);
|
||||
}
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
function getLists(domain, search, page, perPage, callback) {
|
||||
async function getLists(domain, search, page, perPage) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(typeof search === 'string' || search === null);
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.getLists(domain, search, page, perPage, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
let query = `SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE type = ? AND domain = ?`;
|
||||
if (search) query += ' AND (name LIKE ' + mysql.escape('%' + search + '%') + ' OR membersJson LIKE ' + mysql.escape('%' + search + '%') + ')';
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
query += 'ORDER BY name LIMIT ?,?';
|
||||
|
||||
const results = await database.query(query, [ exports.TYPE_LIST, domain, (page-1)*perPage, perPage ]);
|
||||
|
||||
results.forEach(function (result) { postProcessMailbox(result); });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function getList(name, domain, callback) {
|
||||
async function getList(name, domain) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.getList(name, domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const results = await database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE type = ? AND name = ? AND domain = ?', [ exports.TYPE_LIST, name, domain ]);
|
||||
if (results.length === 0) return null;
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
return postProcessMailbox(results[0]);
|
||||
}
|
||||
|
||||
function addList(name, domain, data, auditSource, callback) {
|
||||
async function addList(name, domain, data, auditSource) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const { members, membersOnly, active } = data;
|
||||
assert(Array.isArray(members));
|
||||
@@ -1370,28 +1400,25 @@ function addList(name, domain, data, auditSource, callback) {
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
var error = validateName(name);
|
||||
if (error) return callback(error);
|
||||
let error = validateName(name);
|
||||
if (error) throw error;
|
||||
|
||||
for (var i = 0; i < members.length; i++) {
|
||||
if (!validator.isEmail(members[i])) return callback(new BoxError(BoxError.BAD_FIELD, 'Invalid mail member: ' + members[i]));
|
||||
for (let i = 0; i < members.length; i++) {
|
||||
if (!validator.isEmail(members[i])) throw new BoxError(BoxError.BAD_FIELD, 'Invalid mail member: ' + members[i]);
|
||||
}
|
||||
|
||||
mailboxdb.addList(name, domain, data, function (error) {
|
||||
if (error) return callback(error);
|
||||
[error] = await safe(database.query('INSERT INTO mailboxes (name, type, domain, ownerId, ownerType, membersJson, membersOnly, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [ name, exports.TYPE_LIST, domain, 'admin', 'user', JSON.stringify(members), membersOnly, active ]));
|
||||
if (error && error.code === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, 'mailbox already exists');
|
||||
if (error) throw error;
|
||||
|
||||
eventlog.add(eventlog.ACTION_MAIL_LIST_ADD, auditSource, { name, domain, members, membersOnly, active });
|
||||
|
||||
callback();
|
||||
});
|
||||
eventlog.add(eventlog.ACTION_MAIL_LIST_ADD, auditSource, { name, domain, members, membersOnly, active });
|
||||
}
|
||||
|
||||
function updateList(name, domain, data, auditSource, callback) {
|
||||
async function updateList(name, domain, data, auditSource) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const { members, membersOnly, active } = data;
|
||||
assert(Array.isArray(members));
|
||||
@@ -1400,90 +1427,76 @@ function updateList(name, domain, data, auditSource, callback) {
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
var error = validateName(name);
|
||||
if (error) return callback(error);
|
||||
let error = validateName(name);
|
||||
if (error) throw error;
|
||||
|
||||
for (var i = 0; i < members.length; i++) {
|
||||
if (!validator.isEmail(members[i])) return callback(new BoxError(BoxError.BAD_FIELD, 'Invalid email: ' + members[i]));
|
||||
for (let i = 0; i < members.length; i++) {
|
||||
if (!validator.isEmail(members[i])) throw new BoxError(BoxError.BAD_FIELD, 'Invalid email: ' + members[i]);
|
||||
}
|
||||
|
||||
getList(name, domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const result = await database.query('UPDATE mailboxes SET membersJson = ?, membersOnly = ?, active = ? WHERE name = ? AND domain = ?',
|
||||
[ JSON.stringify(members), membersOnly, active, name, domain ]);
|
||||
if (result.affectedRows === 0) throw new BoxError(BoxError.NOT_FOUND, 'Mailbox not found');
|
||||
|
||||
mailboxdb.updateList(name, domain, data, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_UPDATE, auditSource, { name, domain, oldMembers: result.members, members, membersOnly, active });
|
||||
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_UPDATE, auditSource, { name, domain, oldMembers: result.members, members, membersOnly, active });
|
||||
}
|
||||
|
||||
function removeList(name, domain, auditSource, callback) {
|
||||
async function delList(name, domain, auditSource) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.del(name, domain, function (error) {
|
||||
if (error) return callback(error);
|
||||
// deletes aliases as well
|
||||
const result = await database.query('DELETE FROM mailboxes WHERE ((name=? AND domain=?) OR (aliasName = ? AND aliasDomain=?))', [ name, domain, name, domain ]);
|
||||
if (result.affectedRows === 0) throw new BoxError(BoxError.NOT_FOUND, 'Mailbox not found');
|
||||
|
||||
eventlog.add(eventlog.ACTION_MAIL_LIST_REMOVE, auditSource, { name, domain });
|
||||
|
||||
callback();
|
||||
});
|
||||
eventlog.add(eventlog.ACTION_MAIL_LIST_REMOVE, auditSource, { name, domain });
|
||||
}
|
||||
|
||||
// resolves the members of a list. i.e the lists and aliases
|
||||
function resolveList(listName, listDomain, callback) {
|
||||
async function resolveList(listName, listDomain) {
|
||||
assert.strictEqual(typeof listName, 'string');
|
||||
assert.strictEqual(typeof listDomain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const listDomainsFunc = util.callbackify(listDomains);
|
||||
const mailDomains = await listDomains();
|
||||
const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(',');
|
||||
|
||||
listDomainsFunc(function (error, mailDomains) {
|
||||
if (error) return callback(error);
|
||||
const list = await getList(listName, listDomain);
|
||||
if (!list) throw new BoxError(BoxError.NOT_FOUND, 'List not found');
|
||||
|
||||
const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(',');
|
||||
let resolvedMembers = [], toResolve = list.members.slice(), visited = []; // slice creates a copy of array
|
||||
|
||||
mailboxdb.getList(listName, listDomain, function (error, list) {
|
||||
if (error) return callback(error);
|
||||
while (toResolve.length != 0) {
|
||||
const toProcess = toResolve.shift();
|
||||
const parts = toProcess.split('@');
|
||||
const memberName = parts[0].split('+')[0], memberDomain = parts[1];
|
||||
|
||||
let result = [], toResolve = list.members.slice(), visited = []; // slice creates a copy of array
|
||||
if (!mailInDomains.includes(memberDomain)) { // external domain
|
||||
resolvedMembers.push(toProcess);
|
||||
continue;
|
||||
}
|
||||
|
||||
async.whilst((testDone) => testDone(null, toResolve.length != 0), function (iteratorCallback) {
|
||||
const toProcess = toResolve.shift();
|
||||
const parts = toProcess.split('@');
|
||||
const memberName = parts[0].split('+')[0], memberDomain = parts[1];
|
||||
const member =`${memberName}@${memberDomain}`; // cleaned up without any '+' subaddress
|
||||
if (visited.includes(member)) {
|
||||
debug(`resolveList: list ${listName}@${listDomain} has a recursion at member ${member}`);
|
||||
continue;
|
||||
}
|
||||
visited.push(member);
|
||||
|
||||
if (!mailInDomains.includes(memberDomain)) { result.push(toProcess); return iteratorCallback(); } // external domain
|
||||
const entry = await get(memberName, memberDomain);
|
||||
if (!entry) { // let it bounce
|
||||
resolvedMembers.push(member);
|
||||
continue;
|
||||
}
|
||||
|
||||
const member =`${memberName}@${memberDomain}`; // cleaned up without any '+' subaddress
|
||||
if (visited.includes(member)) {
|
||||
debug(`resolveList: list ${listName}@${listDomain} has a recursion at member ${member}`);
|
||||
return iteratorCallback();
|
||||
}
|
||||
visited.push(member);
|
||||
if (entry.type === exports.TYPE_MAILBOX) { // concrete mailbox
|
||||
resolvedMembers.push(member);
|
||||
} else if (entry.type === exports.TYPE_ALIAS) { // resolve aliases
|
||||
toResolve = toResolve.concat(`${entry.aliasName}@${entry.aliasDomain}`);
|
||||
} else { // resolve list members
|
||||
toResolve = toResolve.concat(entry.members);
|
||||
}
|
||||
}
|
||||
|
||||
mailboxdb.get(memberName, memberDomain, function (error, entry) {
|
||||
if (error && error.reason == BoxError.NOT_FOUND) { result.push(member); return iteratorCallback(); } // let it bounce
|
||||
if (error) return iteratorCallback(error);
|
||||
|
||||
if (entry.type === mailboxdb.TYPE_MAILBOX) { // concrete mailbox
|
||||
result.push(member);
|
||||
} else if (entry.type === mailboxdb.TYPE_ALIAS) { // resolve aliases
|
||||
toResolve = toResolve.concat(`${entry.aliasName}@${entry.aliasDomain}`);
|
||||
} else { // resolve list members
|
||||
toResolve = toResolve.concat(entry.members);
|
||||
}
|
||||
|
||||
iteratorCallback();
|
||||
});
|
||||
}, function (error) {
|
||||
callback(error, result, list);
|
||||
});
|
||||
});
|
||||
});
|
||||
return { resolvedMembers, list };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user