From 39bd87ebaadc636e3df73be46d5f1c7d45b146ff Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Tue, 12 Feb 2019 13:20:32 +0200 Subject: [PATCH] allow to set user specific IMAP max connections --- lib/api/users.js | 18 ++++++++++++++++++ lib/user-handler.js | 14 +++++++++++++- package.json | 6 +++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/lib/api/users.js b/lib/api/users.js index 4f9ca02..14466b4 100644 --- a/lib/api/users.js +++ b/lib/api/users.js @@ -352,6 +352,7 @@ module.exports = (db, server, userHandler) => { * @apiParam {Number} [imapMaxUpload] How many bytes can be uploaded via IMAP during 24 hour * @apiParam {Number} [imapMaxDownload] How many bytes can be downloaded via IMAP during 24 hour * @apiParam {Number} [pop3MaxDownload] How many bytes can be downloaded via POP3 during 24 hour + * @apiParam {Number} [imapMaxConnections] How many parallel IMAP connections are alowed * @apiParam {Number} [receivedMax] How many messages can be received from MX during 60 seconds * @apiParam {Object} [mailboxes] Optional names for special mailboxes * @apiParam {String} [mailboxes.sent="Sent Mail"] Path of Sent Mail folder @@ -486,6 +487,9 @@ module.exports = (db, server, userHandler) => { pop3MaxDownload: Joi.number() .min(0) .default(0), + imapMaxConnections: Joi.number() + .min(0) + .default(0), receivedMax: Joi.number() .min(0) .default(0), @@ -909,6 +913,8 @@ module.exports = (db, server, userHandler) => { * @apiSuccess {Number} limits.pop3Download.allowed How many bytes per 24 hours can be downloaded via POP3. Only message contents are counted, not protocol overhead. * @apiSuccess {Number} limits.pop3Download.used How many bytes are downloaded during current 24 hour period * @apiSuccess {Number} limits.pop3Download.ttl Time until the end of current 24 hour period + * @apiSuccess {Number} limits.imapMaxConnections.allowed How many parallel IMAP connections are permitted + * @apiSuccess {Number} limits.imapMaxConnections.used How many parallel IMAP connections are currenlty in use * * @apiSuccess {String[]} tags List of tags associated with the User * @apiSuccess {String[]} disabledScopes Disabled scopes for this user @@ -1062,6 +1068,8 @@ module.exports = (db, server, userHandler) => { .get('pdw:' + userData._id.toString()) .ttl('pdw:' + userData._id.toString()) + .hget('lim:imap', userData._id.toString()) + .exec(); } catch (err) { // ignore @@ -1089,6 +1097,8 @@ module.exports = (db, server, userHandler) => { let pop3Download = Number(response && response[10] && response[10][1]) || 0; let pop3DownloadTtl = Number(response && response[11] && response[11][1]) || 0; + let imapMaxConnections = Number(response && response[12] && response[12][1]) || 0; + let keyInfo; try { keyInfo = await getKeyInfo(userData.pubKey); @@ -1161,6 +1171,11 @@ module.exports = (db, server, userHandler) => { allowed: Number(userData.pop3MaxDownload) || (config.pop3.maxDownloadMB || 10000) * 1024 * 1024, used: pop3Download, ttl: pop3DownloadTtl >= 0 ? pop3DownloadTtl : false + }, + + imapMaxConnections: { + allowed: Number(userData.imapMaxConnections) || config.imap.maxConnections, + used: imapMaxConnections } }, @@ -1210,6 +1225,7 @@ module.exports = (db, server, userHandler) => { * @apiParam {Number} [imapMaxUpload] How many bytes can be uploaded via IMAP during 24 hour * @apiParam {Number} [imapMaxDownload] How many bytes can be downloaded via IMAP during 24 hour * @apiParam {Number} [pop3MaxDownload] How many bytes can be downloaded via POP3 during 24 hour + * @apiParam {Number} [imapMaxConnections] How many parallel IMAP connections are alowed * @apiParam {Number} [receivedMax] How many messages can be received from MX during 60 seconds * @apiParam {Boolean} [disable2fa] If true, then disables 2FA for this user * @apiParam {String[]} disabledScopes List of scopes that are disabled for this user ("imap", "pop3", "smtp") @@ -1319,6 +1335,8 @@ module.exports = (db, server, userHandler) => { imapMaxUpload: Joi.number().min(0), imapMaxDownload: Joi.number().min(0), pop3MaxDownload: Joi.number().min(0), + imapMaxConnections: Joi.number().min(0), + receivedMax: Joi.number().min(0), disable2fa: Joi.boolean() diff --git a/lib/user-handler.js b/lib/user-handler.js index 1a66634..2614341 100644 --- a/lib/user-handler.js +++ b/lib/user-handler.js @@ -1382,6 +1382,8 @@ class UserHandler { imapMaxUpload: data.imapMaxUpload || 0, imapMaxDownload: data.imapMaxDownload || 0, pop3MaxDownload: data.pop3MaxDownload || 0, + imapMaxConnections: data.imapMaxConnections || 0, + receivedMax: data.receivedMax || 0, targets: [].concat(data.targets || []), @@ -2831,6 +2833,7 @@ class UserHandler { ['receivedMax', 'rl:rcpt'] ]); let flushKeys = []; + let flushHKeys = []; Object.keys(data).forEach(key => { if (['user', 'existingPassword', 'hashedPassword', 'allowUnsafe', 'ip', 'sess'].includes(key)) { @@ -2840,6 +2843,9 @@ class UserHandler { if (resetKeys.has(key)) { flushKeys.push(resetKeys.get(key) + ':' + user); } + if (key === 'imapMaxConnections') { + flushHKeys.push({ key: 'lim:imap', value: user.toString() }); + } if (key === 'password') { if (!data[key]) { @@ -3042,11 +3048,17 @@ class UserHandler { } // check if we need to reset any ttl counters - if (flushKeys.length) { + if (flushKeys.length || flushHKeys.length) { let flushreq = this.redis.multi(); + flushKeys.forEach(key => { flushreq = flushreq.del(key); }); + + flushHKeys.forEach(entry => { + flushreq = flushreq.hdel(entry.key, entry.value); + }); + // just call the operations and hope for the best, no problems if fails flushreq.exec(() => false); } diff --git a/package.json b/package.json index 654eeaa..3a9405b 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "author": "Andris Reinman", "license": "EUPL-1.1+", "devDependencies": { - "ajv": "6.8.1", + "ajv": "6.9.1", "apidoc": "0.17.7", "browserbox": "0.9.1", "chai": "4.2.0", @@ -61,7 +61,7 @@ "node-forge": "0.8.0", "nodemailer": "5.1.1", "npmlog": "4.1.2", - "openpgp": "4.4.6", + "openpgp": "4.4.7", "pem": "1.14.1", "pwnedpasswords": "1.0.4", "qrcode": "1.3.3", @@ -74,7 +74,7 @@ "utf7": "1.0.2", "uuid": "3.3.2", "wild-config": "1.4.0", - "yargs": "12.0.5" + "yargs": "13.1.0" }, "repository": { "type": "git",