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

View file

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

View file

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

View file

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

View file

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

View file

@ -78,6 +78,7 @@ class FilterHandler {
targetUrl: true, targetUrl: true,
autoreply: true, autoreply: true,
encryptMessages: true, encryptMessages: true,
encryptForwarded: true,
pubKey: true pubKey: true
}; };
@ -230,6 +231,7 @@ class FilterHandler {
})) }))
); );
let isEncrypted = false;
let forwardTargets = new Map(); let forwardTargets = new Map();
plugins.runHooks('filter', filters, forwardTargets, () => { 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 => { let forwardMessage = done => {
if (userData.forward && !filterActions.get('delete')) { if (userData.forward && !filterActions.get('delete')) {
// forward to default recipient only if the message is not deleted // forward to default recipient only if the message is not deleted
@ -303,25 +334,27 @@ class FilterHandler {
return done(); return done();
} }
forward( encryptMessage(userData.encryptForwarded && userData.pubKey, () => {
{ forward(
parentId: prepared.id, {
userData, parentId: prepared.id,
sender, userData,
recipient, sender,
recipient,
targets: forwardTargets.size targets: forwardTargets.size
? Array.from(forwardTargets).map(row => ({ ? Array.from(forwardTargets).map(row => ({
type: row[1].type, type: row[1].type,
value: row[1].value value: row[1].value
})) }))
: false, : false,
chunks, chunks,
chunklen chunklen
}, },
done done
); );
});
} }
); );
}; };
@ -451,39 +484,31 @@ class FilterHandler {
})); }));
} }
this.messageHandler.encryptMessage( encryptMessage(userData.encryptMessages && userData.pubKey, () => {
userData.encryptMessages ? userData.pubKey : false, if (isEncrypted) {
{ // make sure we have the updated message structure values
chunks, messageOpts.prepared = prepared;
chunklen messageOpts.maildata = maildata;
},
(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
);
});
} }
);
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', 'imap',
{ {
protocol: 'IMAP', protocol: 'IMAP',
sess: session.id,
ip: session.remoteAddress ip: session.remoteAddress
}, },
(err, result) => { (err, result) => {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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