mirror of
https://github.com/nodemailer/wildduck.git
synced 2024-12-27 02:10:52 +08:00
allow encrypting forwarded emails
This commit is contained in:
parent
0fc0dad855
commit
98a38fab04
13 changed files with 130 additions and 61 deletions
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ module.exports = (server, userHandler) => (login, session, callback) => {
|
|||
'imap',
|
||||
{
|
||||
protocol: 'IMAP',
|
||||
sess: session.id,
|
||||
ip: session.remoteAddress
|
||||
},
|
||||
(err, result) => {
|
||||
|
|
|
@ -791,6 +791,7 @@ class IRCConnection extends EventEmitter {
|
|||
'irc',
|
||||
{
|
||||
protocol: 'IRC',
|
||||
sess: this.id,
|
||||
ip: this.remoteAddress
|
||||
},
|
||||
(err, result) => {
|
||||
|
|
|
@ -162,6 +162,7 @@ class POP3Connection extends EventEmitter {
|
|||
|
||||
_resetSession() {
|
||||
this.session = {
|
||||
id: this._id,
|
||||
state: 'AUTHORIZATION',
|
||||
remoteAddress: this.remoteAddress
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
|
|
1
lmtp.js
1
lmtp.js
|
@ -89,6 +89,7 @@ const serverOptions = {
|
|||
targetUrl: true,
|
||||
autoreply: true,
|
||||
encryptMessages: true,
|
||||
encryptForwarded: true,
|
||||
pubKey: true
|
||||
}
|
||||
}, (err, user) => {
|
||||
|
|
1
pop3.js
1
pop3.js
|
@ -46,6 +46,7 @@ const serverOptions = {
|
|||
'pop3',
|
||||
{
|
||||
protocol: 'POP3',
|
||||
sess: session.id,
|
||||
ip: session.remoteAddress
|
||||
},
|
||||
(err, result) => {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
Loading…
Reference in a new issue