allow encrypting forwarded emails

This commit is contained in:
Andris Reinman 2017-10-30 13:41:53 +02:00
parent 0fc0dad855
commit 98a38fab04
13 changed files with 130 additions and 61 deletions

View file

@ -20,6 +20,7 @@ module.exports = (db, server, userHandler) => {
fresh: Joi.boolean()
.truthy(['Y', 'true', 'yes', 1])
.default(false),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
@ -70,6 +71,7 @@ module.exports = (db, server, userHandler) => {
token: Joi.string()
.length(6)
.required(),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
@ -123,6 +125,7 @@ module.exports = (db, server, userHandler) => {
.lowercase()
.length(24)
.required(),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
@ -181,6 +184,7 @@ module.exports = (db, server, userHandler) => {
token: Joi.string()
.length(6)
.required(),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
@ -234,6 +238,7 @@ module.exports = (db, server, userHandler) => {
.lowercase()
.length(24)
.required(),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'

View file

@ -30,6 +30,7 @@ module.exports = (db, server, userHandler) => {
.lowercase()
.length(24)
.required(),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
@ -88,6 +89,7 @@ module.exports = (db, server, userHandler) => {
challenge: Joi.string()
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
.max(1024),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
@ -159,6 +161,7 @@ module.exports = (db, server, userHandler) => {
.lowercase()
.length(24)
.required(),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
@ -214,6 +217,7 @@ module.exports = (db, server, userHandler) => {
.lowercase()
.length(24)
.required(),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
@ -275,6 +279,7 @@ module.exports = (db, server, userHandler) => {
signatureData: Joi.string()
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
.max(10240),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'

View file

@ -109,6 +109,7 @@ module.exports = (db, server, userHandler) => {
generateMobileconfig: Joi.boolean()
.truthy(['Y', 'true', 'yes', 1])
.default(false),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
@ -255,6 +256,7 @@ module.exports = (db, server, userHandler) => {
.lowercase()
.length(24)
.required(),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'

View file

@ -26,6 +26,7 @@ module.exports = (db, server, userHandler) => {
protocol: Joi.string().default('API'),
scope: Joi.string().default('master'),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
@ -46,6 +47,7 @@ module.exports = (db, server, userHandler) => {
let meta = {
protocol: result.value.protocol,
sess: result.value.sess,
ip: result.value.ip
};

View file

@ -99,7 +99,8 @@ module.exports = (db, server, userHandler) => {
targetUrl: true,
quota: true,
disabled: true,
encryptMessages: true
encryptMessages: true,
encryptForwarded: true
},
sortAscending: true
};
@ -137,6 +138,7 @@ module.exports = (db, server, userHandler) => {
forward: userData.forward,
targetUrl: userData.targetUrl,
encryptMessages: !!userData.encryptMessages,
encryptForwarded: !!userData.encryptForwarded,
quota: {
allowed: Number(userData.quota) || config.maxStorage * 1024 * 1024,
used: Math.max(Number(userData.storageUsed) || 0, 0)
@ -200,7 +202,10 @@ module.exports = (db, server, userHandler) => {
encryptMessages: Joi.boolean()
.truthy(['Y', 'true', 'yes', 1])
.default(false),
encryptForwarded: Joi.boolean()
.truthy(['Y', 'true', 'yes', 1])
.default(false),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
@ -339,6 +344,7 @@ module.exports = (db, server, userHandler) => {
enabled2fa: Array.isArray(userData.enabled2fa) ? userData.enabled2fa : [].concat(userData.enabled2fa ? 'totp' : []),
encryptMessages: userData.encryptMessages,
encryptForwarded: userData.encryptForwarded,
pubKey: userData.pubKey,
keyInfo: getKeyInfo(userData.pubKey),
@ -411,7 +417,9 @@ module.exports = (db, server, userHandler) => {
encryptMessages: Joi.boolean()
.empty('')
.truthy(['Y', 'true', 'yes', 1]),
encryptForwarded: Joi.boolean()
.empty('')
.truthy(['Y', 'true', 'yes', 1]),
retention: Joi.number().min(0),
quota: Joi.number().min(0),
recipients: Joi.number().min(0),
@ -420,7 +428,7 @@ module.exports = (db, server, userHandler) => {
disabled: Joi.boolean()
.empty('')
.truthy(['Y', 'true', 'yes', 1]),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
@ -501,7 +509,7 @@ module.exports = (db, server, userHandler) => {
reason: Joi.string()
.empty('')
.max(128),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'

View file

@ -78,6 +78,7 @@ class FilterHandler {
targetUrl: true,
autoreply: true,
encryptMessages: true,
encryptForwarded: true,
pubKey: true
};
@ -230,6 +231,7 @@ class FilterHandler {
}))
);
let isEncrypted = false;
let forwardTargets = new Map();
plugins.runHooks('filter', filters, forwardTargets, () => {
@ -272,6 +274,35 @@ class FilterHandler {
}
});
let encryptMessage = (condition, next) => {
if (!condition || isEncrypted) {
return next();
}
this.messageHandler.encryptMessage(
userData.pubKey,
{
chunks,
chunklen
},
(err, encrypted) => {
if (err) {
return next();
}
if (encrypted) {
chunks = [encrypted];
chunklen = encrypted.length;
isEncrypted = true;
prepared = this.messageHandler.prepareMessage({
raw: Buffer.concat([extraHeader, encrypted]),
indexedHeaders: this.spamHeaderKeys
});
maildata = this.messageHandler.indexer.getMaildata(prepared.mimeTree);
}
next();
}
);
};
let forwardMessage = done => {
if (userData.forward && !filterActions.get('delete')) {
// forward to default recipient only if the message is not deleted
@ -303,25 +334,27 @@ class FilterHandler {
return done();
}
forward(
{
parentId: prepared.id,
userData,
sender,
recipient,
encryptMessage(userData.encryptForwarded && userData.pubKey, () => {
forward(
{
parentId: prepared.id,
userData,
sender,
recipient,
targets: forwardTargets.size
? Array.from(forwardTargets).map(row => ({
type: row[1].type,
value: row[1].value
}))
: false,
targets: forwardTargets.size
? Array.from(forwardTargets).map(row => ({
type: row[1].type,
value: row[1].value
}))
: false,
chunks,
chunklen
},
done
);
chunks,
chunklen
},
done
);
});
}
);
};
@ -451,39 +484,31 @@ class FilterHandler {
}));
}
this.messageHandler.encryptMessage(
userData.encryptMessages ? userData.pubKey : false,
{
chunks,
chunklen
},
(err, encrypted) => {
if (!err && encrypted) {
messageOpts.prepared = this.messageHandler.prepareMessage({
raw: Buffer.concat([extraHeader, encrypted]),
indexedHeaders: this.spamHeaderKeys
});
messageOpts.maildata = this.messageHandler.indexer.getMaildata(messageOpts.prepared.mimeTree);
}
this.messageHandler.add(messageOpts, (err, inserted, info) => {
// push to response list
callback(
null,
{
userData,
response: err ? err : 'Message stored as ' + info.id.toString()
},
!encrypted
? {
mimeTree: messageOpts.prepared.mimeTree,
maildata: messageOpts.maildata
}
: false
);
});
encryptMessage(userData.encryptMessages && userData.pubKey, () => {
if (isEncrypted) {
// make sure we have the updated message structure values
messageOpts.prepared = prepared;
messageOpts.maildata = maildata;
}
);
this.messageHandler.add(messageOpts, (err, inserted, info) => {
// push to response list
callback(
null,
{
userData,
response: err ? err : 'Message stored as ' + info.id.toString()
},
!isEncrypted
? {
// reuse parsed values
mimeTree: messageOpts.prepared.mimeTree,
maildata: messageOpts.maildata
}
: false
);
});
});
});
});
});

View file

@ -9,6 +9,7 @@ module.exports = (server, userHandler) => (login, session, callback) => {
'imap',
{
protocol: 'IMAP',
sess: session.id,
ip: session.remoteAddress
},
(err, result) => {

View file

@ -791,6 +791,7 @@ class IRCConnection extends EventEmitter {
'irc',
{
protocol: 'IRC',
sess: this.id,
ip: this.remoteAddress
},
(err, result) => {

View file

@ -162,6 +162,7 @@ class POP3Connection extends EventEmitter {
_resetSession() {
this.session = {
id: this._id,
state: 'AUTHORIZATION',
remoteAddress: this.remoteAddress
};

View file

@ -453,6 +453,7 @@ class UserHandler {
action: 'create asp',
asp: passwordData._id,
result: 'success',
sess: data.session,
ip: data.ip
},
() =>
@ -484,6 +485,7 @@ class UserHandler {
action: 'delete asp',
asp,
result: 'success',
sess: data.session,
ip: data.ip
},
() => callback(null, true)
@ -550,6 +552,7 @@ class UserHandler {
pubKey: data.pubKey || '',
encryptMessages: !!data.encryptMessages,
encryptForwarded: !!data.encryptForwarded,
// default retention for user mailboxes
retention: data.retention || 0,
@ -681,6 +684,7 @@ class UserHandler {
{
action: 'account created',
result: 'success',
sess: data.session,
ip: data.ip
},
() => callback(null, id)
@ -959,6 +963,7 @@ class UserHandler {
{
action: 'enable 2fa totp',
result: 'fail',
sess: data.session,
ip: data.ip
},
() => callback(null, false)
@ -998,6 +1003,7 @@ class UserHandler {
{
action: 'enable 2fa totp',
result: 'success',
sess: data.session,
ip: data.ip
},
() => callback(null, true)
@ -1066,6 +1072,7 @@ class UserHandler {
user,
{
action: 'disable 2fa totp',
sess: data.session,
ip: data.ip
},
() => callback(null, true)
@ -1144,8 +1151,9 @@ class UserHandler {
user,
{
action: 'check 2fa totp',
ip: data.ip,
result: verified ? 'success' : 'fail'
result: verified ? 'success' : 'fail',
sess: data.session,
ip: data.ip
},
() => {
if (verified) {
@ -1331,6 +1339,7 @@ class UserHandler {
{
action: 'enable 2fa u2f',
result: 'success',
sess: data.session,
ip: data.ip
},
() => callback(null, true)
@ -1405,6 +1414,7 @@ class UserHandler {
user,
{
action: 'disable 2fa u2f',
sess: data.session,
ip: data.ip
},
() => callback(null, true)
@ -1529,8 +1539,9 @@ class UserHandler {
user,
{
action: 'check 2fa u2f',
ip: data.ip,
result: verified ? 'success' : 'fail'
result: verified ? 'success' : 'fail',
sess: data.session,
ip: data.ip
},
() => {
callback(null, verified);
@ -1565,6 +1576,7 @@ class UserHandler {
user,
{
action: 'disable 2fa',
sess: data.session,
ip: data.ip
},
() => callback(null, true)
@ -1624,6 +1636,7 @@ class UserHandler {
{
action: 'password change',
result: 'fail',
sess: data.session,
ip: data.ip
},
() => callback(new Error('Password verification failed'))
@ -1657,6 +1670,7 @@ class UserHandler {
{
action: 'password change',
result: 'success',
sess: data.session,
ip: data.ip
},
() => callback(null, true)

View file

@ -89,6 +89,7 @@ const serverOptions = {
targetUrl: true,
autoreply: true,
encryptMessages: true,
encryptForwarded: true,
pubKey: true
}
}, (err, user) => {

View file

@ -46,6 +46,7 @@ const serverOptions = {
'pop3',
{
protocol: 'POP3',
sess: session.id,
ip: session.remoteAddress
},
(err, result) => {

View file

@ -61,7 +61,8 @@ describe('Send multiple messages', function() {
address: 'user2@example.com',
name: 'user2',
pubKey: user2PubKey,
encryptMessages: true
encryptMessages: true,
encryptForwarded: true
}
},
(err, meta, response) => {
@ -77,7 +78,8 @@ describe('Send multiple messages', function() {
address: 'user3@example.com',
name: 'user3',
pubKey: user3PubKey,
encryptMessages: true
encryptMessages: true,
encryptForwarded: true
}
},
(err, meta, response) => {
@ -93,7 +95,8 @@ describe('Send multiple messages', function() {
address: 'user4@example.com',
name: 'user4',
pubKey: user2PubKey,
encryptMessages: false
encryptMessages: false,
encryptForwarded: true
}
},
(err, meta, response) => {