Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZMS-181 #758

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions lib/api/mailboxes.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ module.exports = (db, server, mailboxHandler) => {
specialUse: mailboxData.specialUse,
modifyIndex: mailboxData.modifyIndex,
subscribed: mailboxData.subscribed,
hidden: !!mailboxData.hidden
hidden: !!mailboxData.hidden,
encryptMessages: !!mailboxData.encryptMessages
};

if (mailboxData.retention) {
Expand Down Expand Up @@ -291,6 +292,7 @@ module.exports = (db, server, mailboxHandler) => {
.min(0)
.description('Retention policy for the created Mailbox. Milliseconds after a message added to mailbox expires. Set to 0 to disable.'),
sess: sessSchema,
encryptMessages: booleanSchema.default(false).description('If true then messages in this mailbox are encrypted'),
ip: sessIPSchema
},
queryParams: {},
Expand Down Expand Up @@ -344,7 +346,8 @@ module.exports = (db, server, mailboxHandler) => {

let opts = {
subscribed: true,
hidden: !!result.value.hidden
hidden: !!result.value.hidden,
encryptMessages: !!result.value.encryptMessages
};

if (retention) {
Expand Down Expand Up @@ -399,6 +402,7 @@ module.exports = (db, server, mailboxHandler) => {
modifyIndex: Joi.number().required().description('Modification sequence number. Incremented on every change in the mailbox.'),
subscribed: booleanSchema.required().description('Mailbox subscription status. IMAP clients may unsubscribe from a folder.'),
hidden: booleanSchema.required().description('Is the folder hidden or not'),
encryptMessages: booleanSchema.required().description('If true then messages in this mailbox are encrypted'),
total: Joi.number().required().description('How many messages are stored in this mailbox'),
unseen: Joi.number().required().description('How many unseen messages are stored in this mailbox')
})
Expand Down Expand Up @@ -527,6 +531,7 @@ module.exports = (db, server, mailboxHandler) => {
modifyIndex: mailboxData.modifyIndex,
subscribed: mailboxData.subscribed,
hidden: !!mailboxData.hidden,
encryptMessages: !!mailboxData.encryptMessages,
total,
unseen
});
Expand All @@ -552,6 +557,7 @@ module.exports = (db, server, mailboxHandler) => {
'Retention policy for the Mailbox (in ms). Changing retention value only affects messages added to this folder after the change'
),
subscribed: booleanSchema.description('Change Mailbox subscription state'),
encryptMessages: booleanSchema.description('If true then messages in this mailbox are encrypted'),
hidden: booleanSchema.description('Is the folder hidden or not. Hidden folders can not be opened in IMAP.'),
sess: sessSchema,
ip: sessIPSchema
Expand Down
3 changes: 2 additions & 1 deletion lib/api/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -2402,7 +2402,8 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti
});
}

if (userData.encryptMessages && !result.value.draft) {
if ((userData.encryptMessages || mailboxData.encryptMessages) && !result.value.draft) {
// encrypt message if global encryption ON or encrypted target mailbox
try {
let encrypted = await encryptMessage(userData.pubKey, raw);
if (encrypted) {
Expand Down
13 changes: 9 additions & 4 deletions lib/filter-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,9 @@ class FilterHandler {

let rawchunks = chunks;

let prepared;
let raw;

let prepared;
if (options.mimeTree) {
if (options.mimeTree && options.mimeTree.header) {
// remove old headers
Expand All @@ -157,7 +158,7 @@ class FilterHandler {
mimeTree: options.mimeTree
});
} else {
let raw = Buffer.concat(chunks, chunklen);
raw = Buffer.concat(chunks, chunklen);
prepared = await this.prepareMessage({
raw
});
Expand Down Expand Up @@ -660,10 +661,14 @@ class FilterHandler {

date: false,
flags,

rawchunks
rawchunks,
chunklen
};

if (raw) {
messageOpts.raw = raw;
}

if (options.verificationResults) {
messageOpts.verificationResults = options.verificationResults;
}
Expand Down
102 changes: 102 additions & 0 deletions lib/handlers/on-copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ async function copyHandler(server, messageHandler, connection, mailbox, update,

notifyLongRunning();

let targetMailboxEncrypted = false;

if (targetData.encryptMessages) {
targetMailboxEncrypted = true;
}

try {
while ((messageData = await cursor.next())) {
tools.checkSocket(socket); // do we even have to copy anything?
Expand All @@ -141,6 +147,12 @@ async function copyHandler(server, messageHandler, connection, mailbox, update,
uid: messageData.uid,
_id: messageData._id
};

const parsedHeader = (messageData.mimeTree && messageData.mimeTree.parsedHeader) || {};
const parsedContentType = parsedHeader['content-type'];

const isMessageEncrypted = parsedContentType ? parsedContentType.subtype === 'encrypted' : false;

// Copying is not done in bulk to minimize risk of going out of sync with incremental UIDs
sourceUid.unshift(messageData.uid);
let item = await db.database.collection('mailboxes').findOneAndUpdate(
Expand Down Expand Up @@ -218,6 +230,96 @@ async function copyHandler(server, messageHandler, connection, mailbox, update,
{ writeConcern: 'majority' }
);

const newPrepared = await new Promise((resolve, reject) => {
if (targetMailboxEncrypted && !isMessageEncrypted && userData.pubKey) {
// encrypt message
// get raw from existing mimetree
let outputStream = messageHandler.indexer.rebuild(messageData.mimeTree); // get raw rebuilder response obj (.value is the stream)

if (!outputStream || outputStream.type !== 'stream' || !outputStream.value) {
return reject(new Error('Cannot fetch message'));
}
outputStream = outputStream.value; // set stream to actual stream object (.value)

let chunks = [];
let chunklen = 0;
outputStream
.on('readable', () => {
let chunk;
while ((chunk = outputStream.read()) !== null) {
chunks.push(chunk);
chunklen += chunk.length;
}
})
.on('end', () => {
const raw = Buffer.concat(chunks, chunklen);
messageHandler.encryptMessages(userData.pubKey, raw, (err, res) => {
if (err) {
return reject(err);
}

// encrypted rebuilt raw

if (res) {
messageHandler.prepareMessage({ raw: res }, (err, prepared) => {
if (err) {
return reject(err);
}
// prepared new message structure from encrypted raw

const maildata = messageHandler.indexer.getMaildata(prepared.mimeTree);

// add attachments of encrypted messages
if (maildata.attachments && maildata.attachments.length) {
messageData.attachments = maildata.attachments;
messageData.ha = maildata.attachments.some(a => !a.related);
} else {
messageData.ha = false;
}

// remove fields that may leak data in FE or DB
delete messageData.text;
delete messageData.html;
messageData.intro = '';

messageHandler.indexer.storeNodeBodies(maildata, prepared.mimeTree, err => {
// store new attachments
let cleanup = () => {
let attachmentIds = Object.keys(prepared.mimeTree.attachmentMap || {}).map(
key => prepared.mimeTree.attachmentMap[key]
);

messageHandler.attachmentStorage.deleteMany(attachmentIds, maildata.magic);

if (err) {
return reject(err);
}
};

if (err) {
return cleanup(err);
}

return resolve(prepared);
});
});
}
});
});
} else {
resolve(false);
}
});

// replace fields
if (newPrepared) {
messageData.mimeTree = newPrepared.mimeTree;
messageData.size = newPrepared.size;
messageData.bodystructure = newPrepared.bodystructure;
messageData.envelope = newPrepared.envelope;
messageData.headers = newPrepared.headers;
}

let r = await db.database.collection('messages').insertOne(messageData, { writeConcern: 'majority' });

if (!r || !r.acknowledged) {
Expand Down
Loading