diff --git a/lib/api/messages.js b/lib/api/messages.js index d2ed7ce6..67a0cbe1 100644 --- a/lib/api/messages.js +++ b/lib/api/messages.js @@ -2634,6 +2634,7 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti let message = result.value.message; let messageData; + let userData; try { messageData = await db.database.collection('messages').findOne( { @@ -2653,6 +2654,18 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti } } ); + + userData = await db.database.collection('users').findOne( + { + _id: user + }, + { + projection: { + _id: true, + mtaRelay: true + } + } + ); } catch (err) { res.status(500); return res.json({ @@ -2705,7 +2718,8 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti sender: messageData.meta.from, recipient: messageData.meta.to, targets: forwardTargets, - stream: response.value + stream: response.value, + userData }; let queueId; @@ -3981,7 +3995,8 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti to: envelope.to, sendTime, origin: options.origin || options.ip, - runPlugins: true + runPlugins: true, + mtaRelay: userData.mtaRelay || false }, (err, ...args) => { if (err || !args[0]) { diff --git a/lib/api/submit.js b/lib/api/submit.js index 92f92c57..c7c5f5f9 100644 --- a/lib/api/submit.js +++ b/lib/api/submit.js @@ -58,7 +58,8 @@ module.exports = (db, server, messageHandler, userHandler, settingsHandler) => { pubKey: true, disabled: true, suspended: true, - fromWhitelist: true + fromWhitelist: true, + mtaRelay: true } }, (err, userData) => { @@ -472,7 +473,8 @@ module.exports = (db, server, messageHandler, userHandler, settingsHandler) => { to: compiledEnvelope.to, sendTime, origin: options.ip, - runPlugins: true + runPlugins: true, + mtaRelay: userData.mtaRelay || false }, (err, ...args) => { if (err || !args[0]) { diff --git a/lib/api/users.js b/lib/api/users.js index 3a27de16..ea05c960 100644 --- a/lib/api/users.js +++ b/lib/api/users.js @@ -352,6 +352,14 @@ module.exports = (db, server, userHandler, settingsHandler) => { 'An array of forwarding targets. The value could either be an email address or a relay url to next MX server ("smtp://mx2.zone.eu:25") or an URL where mail contents are POSTed to' ), + mtaRelay: Joi.string() + .uri({ + scheme: [/smtps?/], + allowRelative: false, + relativeOnly: false + }) + .description('An address of an SMTP MTA relay. The value should be a relay url. If specified uses the this relay as the outbound MTA.'), + spamLevel: Joi.number() .min(0) .max(100) @@ -500,6 +508,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { let values = permission.filter(result.value); let targets = values.targets; + let mtaRelay = values.mtaRelay; if (targets) { for (let i = 0, len = targets.length; i < len; i++) { @@ -535,6 +544,15 @@ module.exports = (db, server, userHandler, settingsHandler) => { values.targets = targets; } + if (mtaRelay && /^smtps?:/i.test(mtaRelay)) { + mtaRelay = { + id: new ObjectId(), + type: 'relay', + value: mtaRelay // current mtaRelay string value + }; + values.mtaRelay = mtaRelay; + } + if ('pubKey' in req.params && !values.pubKey) { values.pubKey = ''; } @@ -796,6 +814,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { .required() .description('Custom internal metadata object set for this user. Not available for user-role tokens'), targets: Joi.array().items(Joi.string()).required().description('List of forwarding targets'), + mtaRelay: Joi.string().required().description('MTA Relay url'), spamLevel: Joi.number() .required() .description('Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam'), @@ -1073,6 +1092,8 @@ module.exports = (db, server, userHandler, settingsHandler) => { .map(target => target.value) .filter(target => target), + mtaRelay: userData.mtaRelay?.value || false, + limits: { quota: { allowed: Number(userData.quota) || settings['const:max:storage'], @@ -1194,6 +1215,14 @@ module.exports = (db, server, userHandler, settingsHandler) => { 'An array of forwarding targets. The value could either be an email address or a relay url to next MX server ("smtp://mx2.zone.eu:25") or an URL where mail contents are POSTed to' ), + mtaRelay: Joi.string() + .uri({ + scheme: [/smtps?/], + allowRelative: false, + relativeOnly: false + }) + .description('An address of an SMTP MTA relay. The value should be a relay url. If specified uses the this relay as the outbound MTA.'), + spamLevel: Joi.number() .min(0) .max(100) @@ -1326,6 +1355,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { let targets = values.targets; let existingTargets; + let mtaRelay = values.mtaRelay; if (targets) { for (let i = 0, len = targets.length; i < len; i++) { @@ -1382,6 +1412,15 @@ module.exports = (db, server, userHandler, settingsHandler) => { } } + if (mtaRelay && /^smtps?:/i.test(mtaRelay)) { + mtaRelay = { + id: new ObjectId(), + type: 'relay', + value: mtaRelay // current mtaRelay string value + }; + values.mtaRelay = mtaRelay; + } + if (!values.name && 'name' in req.params) { values.name = ''; } diff --git a/lib/autoreply.js b/lib/autoreply.js index 2c988732..9bab163c 100644 --- a/lib/autoreply.js +++ b/lib/autoreply.js @@ -101,7 +101,8 @@ async function autoreply(options, autoreplyData) { reason: 'autoreply', from: '', to: options.sender, - interface: 'autoreplies' + interface: 'autoreplies', + mtaRelay: options.userData?.mtaRelay || false }, (err, ...args) => { if (err || !args[0]) { diff --git a/lib/filter-handler.js b/lib/filter-handler.js index ab5ba65d..edb27792 100644 --- a/lib/filter-handler.js +++ b/lib/filter-handler.js @@ -74,7 +74,8 @@ class FilterHandler { encryptForwarded: true, pubKey: true, spamLevel: true, - tagsview: true + tagsview: true, + mtaRelay: true }; if (collection === 'users') { diff --git a/lib/forward.js b/lib/forward.js index d139ee88..a8b59bcb 100644 --- a/lib/forward.js +++ b/lib/forward.js @@ -13,7 +13,9 @@ module.exports = (options, callback) => { targets: options.targets, - interface: 'forwarder' + interface: 'forwarder', + + mtaRelay: options.userData?.mtaRelay || false }; let message = options.maildrop.push(mail, (err, ...args) => { diff --git a/lib/maildropper.js b/lib/maildropper.js index b8239a8f..127dd996 100644 --- a/lib/maildropper.js +++ b/lib/maildropper.js @@ -144,16 +144,42 @@ class Maildropper { let deliveries = []; + let mxData = false; + + if (options.mtaRelay) { + // user has MTA Relay set, use it to send outbound email + let relayData = options.mtaRelay.value; + if (typeof relayData === 'string') { + relayData = tools.getRelayData(relayData); + } + + mxData = { + mx: relayData.mx, + mxPort: relayData.mxPort, + mxAuth: relayData.mxAuth, + mxSecure: relayData.mxSecure, + skipSRS: true, + skipSTS: true + }; + } + if (options.targets) { options.targets.forEach(target => { switch (target.type) { case 'mail': - deliveries.push({ - to: target.value, - forwardedFor: target.recipient - }); - break; + { + let delivery = { + to: target.value, + forwardedFor: target.recipient + }; + + if (mxData) { + delivery = { ...delivery, ...mxData }; + } + deliveries.push(delivery); + } + break; case 'relay': { let recipients = new Set([].concat(options.to || []).concat(target.recipient || [])); @@ -196,9 +222,15 @@ class Maildropper { } if (!deliveries.length) { - deliveries = envelope.to.map(to => ({ - to - })); + deliveries = envelope.to.map(to => { + let delivery = { to }; + + if (mxData) { + delivery = { ...delivery, ...mxData }; + } + + return delivery; + }); } if (!deliveries.length) { diff --git a/setup/09_install_zone_mta.sh b/setup/09_install_zone_mta.sh index 525e9b80..4d14485b 100755 --- a/setup/09_install_zone_mta.sh +++ b/setup/09_install_zone_mta.sh @@ -61,7 +61,7 @@ secret=\"$ZONEMTA_SECRET\" algo=\"md5\"" > /etc/zone-mta/plugins/loop-breaker.toml echo "[wildduck] -enabled=[\"receiver\", \"sender\"] +enabled=[\"receiver\", \"sender\", \"main\"] # which interfaces this plugin applies to interfaces=[\"feeder\"]