This commit is contained in:
Andris Reinman 2020-07-16 13:06:38 +03:00
parent 4678768026
commit 8f31640424
6 changed files with 61 additions and 5 deletions

View file

@ -8,3 +8,8 @@ gfs="mail"
# see [dbs].sender option for choosing correct database to use for ZoneMTA queues
# by default the main wildduck database is used
collection="zone-queue"
# Hashing secret for loop detection
# Must be shared with haraka-plugin-wildduck
# If not set then looping is not tracked
loopSecret=""

View file

@ -25,7 +25,8 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler) => {
db,
zone: config.sender.zone,
collection: config.sender.collection,
gfs: config.sender.gfs
gfs: config.sender.gfs,
loopSecret: config.sender.loopSecret
});
const putMessage = util.promisify(messageHandler.put.bind(messageHandler));

View file

@ -34,7 +34,8 @@ module.exports = (db, server, messageHandler, userHandler) => {
db,
zone: config.sender.zone,
collection: config.sender.collection,
gfs: config.sender.gfs
gfs: config.sender.gfs,
loopSecret: config.sender.loopSecret
});
function submitMessage(options, callback) {

View file

@ -35,7 +35,8 @@ class FilterHandler {
db: this.db,
zone: options.sender.zone,
collection: options.sender.collection,
gfs: options.sender.gfs
gfs: options.sender.gfs,
loopSecret: options.sender.loopSecret
});
}

View file

@ -13,7 +13,8 @@ module.exports = (options, callback) => {
db,
zone: config.sender.zone,
collection: config.sender.collection,
gfs: config.sender.gfs
gfs: config.sender.gfs,
loopSecret: config.sender.loopSecret
});
return maildropper.push(options, callback);

View file

@ -10,6 +10,7 @@ const os = require('os');
const hostname = os.hostname().toLowerCase();
const addressparser = require('nodemailer/lib/addressparser');
const punycode = require('punycode');
const crypto = require('crypto');
const tools = require('./tools');
class Maildropper {
@ -27,6 +28,42 @@ class Maildropper {
});
}
checkLoop(envelope, deliveries) {
if (envelope.reason !== 'forward' || !this.options.loopSecret) {
return false;
}
const loopKey = 'X-WildDuck-Seen';
const algo = 'sha256';
const secret = this.options.loopSecret;
const targetStr = JSON.stringify(deliveries);
const loopFields = envelope.headers.getDecoded(loopKey);
// check existing loop headers (max 100 to avoid checking too many hashes)
for (let i = 0, len = Math.min(loopFields.length, 100); i < len; i++) {
let field = (loopFields[i].value || '').toLowerCase().trim();
let salt = field.substr(0, 12);
let hash = field.substr(12);
let hmac = crypto.createHmac(algo, secret);
hmac.update(salt);
hmac.update(targetStr);
let result = hmac.digest('hex');
if (result.toLowerCase() === hash) {
// Loop detected!
envelope.looped = true;
return true;
}
}
const salt = crypto.randomBytes(6).toString('hex').toLowerCase();
const loopHeader = salt + crypto.createHmac(algo, secret).update(salt).update(targetStr).digest('hex').toLocaleLowerCase();
envelope.headers.add(loopKey, loopHeader);
return false;
}
push(options, callback) {
let id = options.id || seqIndex.get();
let seq = 0;
@ -140,6 +177,13 @@ class Maildropper {
return callback(err);
}
if (this.checkLoop(envelope, deliveries)) {
// looped message
let err = new Error('Message loop detected');
err.code = 'ELOOP';
return this.removeMessage(id, () => callback(err));
}
envelope.headers = envelope.headers.getList();
this.setMeta(id, envelope, err => {
if (err) {
@ -234,7 +278,10 @@ class Maildropper {
}
parseAddressList(headers, key, withNames) {
return this.parseAddressses(headers.getDecoded(key).map(header => header.value), withNames);
return this.parseAddressses(
headers.getDecoded(key).map(header => header.value),
withNames
);
}
parseAddressses(headerList, withNames) {