From f28ec43dca5b2915deb69d54fb942ddf1303f48c Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Fri, 2 Mar 2018 12:07:18 +0100 Subject: Support encrypted mails forwarding Summary: Some notes: - What we do is: if the mail is encrypted, decrypt it and copy its content into a new message (with plaintext, html and attachments, if any), and use this message as attachment for forwarding - The `isEncrypted` function from KMime doesn't seem to detect every kind of encrypted mails. AFAIK this structure is not detected: - `multipart/mixed` - `text/plain` - `application/pgp-encrypted` (attachement, named "ATT00001") - `application/octet-stream` (attachment named "encrypted.asc") Reviewers: cmollekopf Tags: PHID-PROJ-6npnfcmppynqynn7slmv Maniphest Tasks: T8112, T7024 Differential Revision: https://phabricator.kde.org/D10966 --- framework/src/domain/composercontroller.cpp | 21 ++++---- framework/src/domain/mime/mailtemplates.cpp | 83 ++++++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/framework/src/domain/composercontroller.cpp b/framework/src/domain/composercontroller.cpp index 09d4c154..2286a71b 100644 --- a/framework/src/domain/composercontroller.cpp +++ b/framework/src/domain/composercontroller.cpp @@ -99,19 +99,19 @@ class AddresseeController : public Kube::ListPropertyController public: bool mFoundAllKeys = true; + QSet mMissingKeys; - AddresseeController() - : Kube::ListPropertyController{{"name", "keyFound", "key"}} + AddresseeController() : Kube::ListPropertyController{{"name", "keyFound", "key"}} { - QObject::connect(this, &Kube::ListPropertyController::added, this, [this] (const QByteArray &id, const QVariantMap &map) { - findKey(id, map.value("name").toString()); - }); + QObject::connect( + this, &Kube::ListPropertyController::added, this, [this](const QByteArray &id, const QVariantMap &map) { + findKey(id, map.value("name").toString()); + }); + QObject::connect(this, &Kube::ListPropertyController::removed, this, [this] (const QByteArray &id) { mMissingKeys.remove(id); setFoundAllKeys(mMissingKeys.isEmpty()); }); - - } bool foundAllKeys() @@ -133,12 +133,13 @@ public: mb.fromUnicodeString(addressee); SinkLog() << "Searching key for: " << mb.address(); - asyncRun>(this, [mb] { + asyncRun>(this, + [mb] { return MailCrypto::findKeys(QStringList{} << mb.address(), false, false, MailCrypto::OPENPGP); }, [this, addressee, id](const std::vector &keys) { if (!keys.empty()) { - if (keys.size() > 1 ) { + if (keys.size() > 1) { SinkWarning() << "Found more than one key, encrypting to all of them."; } SinkLog() << "Found key: " << keys.front().primaryFingerprint(); @@ -154,7 +155,7 @@ public: void set(const QStringList &list) { - for (const auto &email: list) { + for (const auto &email : list) { add({{"name", email}}); } } diff --git a/framework/src/domain/mime/mailtemplates.cpp b/framework/src/domain/mime/mailtemplates.cpp index 8e644b34..513f0353 100644 --- a/framework/src/domain/mime/mailtemplates.cpp +++ b/framework/src/domain/mime/mailtemplates.cpp @@ -53,6 +53,18 @@ static bool operator==(const KMime::Types::Mailbox &left, const KMime::Types::Ma return (left.addrSpec().asString() == right.addrSpec().asString()); } } + + Message* contentToMessage(Content* content) { + content->assemble(); + const auto encoded = content->encodedContent(); + + auto message = new Message(); + message->setContent(encoded); + message->parse(); + + return message; + } + } static KMime::Types::Mailbox::List stripMyAddressesFromAddressList(const KMime::Types::Mailbox::List &list, const KMime::Types::AddrSpecList me) @@ -223,6 +235,19 @@ KMime::Content *createMultipartAlternativeContent(const QString &plainBody, cons return multipartAlternative; } +KMime::Message *createMultipartMixedContent(QVector contents) +{ + KMime::Message *multiPartMixed = new KMime::Message(); + multiPartMixed->contentType()->setMimeType("multipart/mixed"); + multiPartMixed->contentType()->setBoundary(KMime::multiPartBoundary()); + + for (const auto &content : contents) { + multiPartMixed->addContent(content); + } + + return multiPartMixed; +} + void addProcessedBodyToMessage(const KMime::Message::Ptr &msg, const QString &plainBody, const QString &htmlBody, bool forward) { //FIXME @@ -864,27 +889,73 @@ void MailTemplates::reply(const KMime::Message::Ptr &origMsg, const std::functio }); } -void MailTemplates::forward(const KMime::Message::Ptr &origMsg, const std::function &callback) +void MailTemplates::forward(const KMime::Message::Ptr &origMsg, + const std::function &callback) { KMime::Message::Ptr wrapperMsg(new KMime::Message); wrapperMsg->to()->clear(); wrapperMsg->cc()->clear(); - wrapperMsg->subject()->fromUnicodeString(forwardSubject(origMsg->subject()->asUnicodeString()), "utf-8"); + // Decrypt the original message, it will be encrypted again in the composer + // for the right recipient + KMime::Message::Ptr forwardedMessage; + if (isEncrypted(origMsg.data())) { + qDebug() << "Original message was encrypted, decrypting it"; + MimeTreeParser::ObjectTreeParser otp; + otp.parseObjectTree(origMsg.data()); + otp.decryptParts(); - const QByteArray refStr = getRefStr(origMsg); + auto htmlContent = otp.htmlContent(); + KMime::Content *recreatedMsg = + htmlContent.isEmpty() ? createPlainPartContent(otp.plainTextContent()) : + createMultipartAlternativeContent(otp.plainTextContent(), htmlContent); + + if (hasAttachment(origMsg.data())) { + QVector contents = {recreatedMsg}; + contents.append(origMsg->attachments()); + + auto msg = createMultipartMixedContent(contents); + + forwardedMessage.reset(KMime::contentToMessage(msg)); + } else { + forwardedMessage.reset(KMime::contentToMessage(recreatedMsg)); + } + + forwardedMessage->subject()->from7BitString(origMsg->subject()->as7BitString()); + + for (const auto &addr : origMsg->to()->mailboxes()) { + forwardedMessage->to()->addAddress(addr); + } + + for (const auto &addr : origMsg->cc()->mailboxes()) { + forwardedMessage->cc()->addAddress(addr); + } + + for (const auto &addr : origMsg->bcc()->mailboxes()) { + forwardedMessage->bcc()->addAddress(addr); + } + + } else { + qDebug() << "Original message was not encrypted, using it as-is"; + forwardedMessage = origMsg; + } + + wrapperMsg->subject()->fromUnicodeString( + forwardSubject(forwardedMessage->subject()->asUnicodeString()), "utf-8"); + + const QByteArray refStr = getRefStr(forwardedMessage); if (!refStr.isEmpty()) { wrapperMsg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8"); } - KMime::Content* fwdAttachment = new KMime::Content; + KMime::Content *fwdAttachment = new KMime::Content; fwdAttachment->contentDisposition()->setDisposition(KMime::Headers::CDinline); fwdAttachment->contentType()->setMimeType("message/rfc822"); - fwdAttachment->contentDisposition()->setFilename(origMsg->subject()->asUnicodeString() + ".eml"); + fwdAttachment->contentDisposition()->setFilename(forwardedMessage->subject()->asUnicodeString() + ".eml"); // The mail was parsed in loadMessage before, so no need to assemble it - fwdAttachment->setBody(origMsg->encodedContent()); + fwdAttachment->setBody(forwardedMessage->encodedContent()); wrapperMsg->addContent(fwdAttachment); wrapperMsg->assemble(); -- cgit v1.2.3