diff --git a/src/core/messagepart.cpp b/src/core/messagepart.cpp index d006dd4..22b2803 100644 --- a/src/core/messagepart.cpp +++ b/src/core/messagepart.cpp @@ -1,1097 +1,1096 @@ // SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com> // SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com> // SPDX-License-Identifier: LGPL-2.0-or-later #include "messagepart.h" #include "cryptohelper.h" #include "mimetreeparser_core_debug.h" #include "objecttreeparser.h" #include "utils.h" #include <KLocalizedString> #include <KMime/Content> #include <Libkleo/Compliance> #include <Libkleo/KeyCache> #include <QGpgME/DN> #include <QGpgME/DecryptVerifyJob> #include <QGpgME/Protocol> #include <QGpgME/VerifyDetachedJob> #include <QGpgME/VerifyOpaqueJob> #include <QStringDecoder> #include <gpgme++/key.h> #include <gpgme++/keylistresult.h> #include <gpgme.h> using namespace MimeTreeParser; //------MessagePart----------------------- MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text, KMime::Content *node) : mText(text) , mOtp(otp) , mParentPart(nullptr) , mNode(node) // only null for messagepartlist , mError(NoError) , mRoot(false) { } MessagePart::~MessagePart() { for (auto n : std::as_const(mNodesToDelete)) { delete n; } } MessagePart::Disposition MessagePart::disposition() const { if (!mNode) { return Invalid; } const auto cd = mNode->contentDisposition(false); if (!cd) { return Invalid; } switch (cd->disposition()) { case KMime::Headers::CDinline: return Inline; case KMime::Headers::CDattachment: return Attachment; default: return Invalid; } } QString MessagePart::filename() const { if (!mNode) { return {}; } if (const auto cd = mNode->contentDisposition(false)) { const auto name = cd->filename(); // Allow for a fallback for mails that have a ContentDisposition header, but don't set the filename anyways. // Not the recommended way, but exists. if (!name.isEmpty()) { return name; } } if (const auto ct = mNode->contentType(false)) { return ct->name(); } return {}; } static KMime::Headers::ContentType *contentType(KMime::Content *node) { if (node) { return node->contentType(false); } return nullptr; } QByteArray MessagePart::charset() const { if (!mNode) { return QByteArrayLiteral("us-ascii"); } if (auto ct = contentType(mNode)) { return ct->charset(); } // Per rfc2045 us-ascii is the default return QByteArrayLiteral("us-ascii"); } QByteArray MessagePart::mimeType() const { if (auto ct = contentType(mNode)) { return ct->mimeType(); } return {}; } bool MessagePart::isText() const { if (auto ct = contentType(mNode)) { return ct->isText(); } return false; } MessagePart::Error MessagePart::error() const { return mError; } QString MessagePart::errorString() const { return mMetaData.errorText; } PartMetaData *MessagePart::partMetaData() { return &mMetaData; } bool MessagePart::isAttachment() const { if (mNode) { return KMime::isAttachment(mNode); } return false; } KMime::Content *MessagePart::node() const { return mNode; } void MessagePart::setIsRoot(bool root) { mRoot = root; } bool MessagePart::isRoot() const { return mRoot; } QString MessagePart::text() const { return mText; } void MessagePart::setText(const QString &text) { mText = text; } bool MessagePart::isHtml() const { return false; } MessagePart *MessagePart::parentPart() const { return mParentPart; } void MessagePart::setParentPart(MessagePart *parentPart) { mParentPart = parentPart; } QString MessagePart::htmlContent() const { return text(); } QString MessagePart::plaintextContent() const { return text(); } void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart) { auto subMessagePart = mOtp->parseObjectTreeInternal(node, onlyOneMimePart); mRoot = subMessagePart->isRoot(); for (const auto &part : std::as_const(subMessagePart->subParts())) { appendSubPart(part); } } void MessagePart::parseInternal(const QByteArray &data) { auto tempNode = new KMime::Content(); const auto lfData = KMime::CRLFtoLF(data); // We have to deal with both bodies and full parts. In inline encrypted/signed parts we can have nested parts, // or just plain-text, and both ends up here. setContent defaults to setting only the header, so we have to avoid this. if (lfData.contains("\n\n")) { tempNode->setContent(lfData); } else { tempNode->setBody(lfData); } tempNode->parse(); tempNode->contentType()->setCharset(charset()); bindLifetime(tempNode); if (!tempNode->head().isEmpty()) { tempNode->contentDescription()->from7BitString(QByteArrayLiteral("temporary node")); } parseInternal(tempNode); } QString MessagePart::renderInternalText() const { QString text; for (const auto &mp : subParts()) { text += mp->text(); } return text; } void MessagePart::appendSubPart(const MessagePart::Ptr &messagePart) { messagePart->setParentPart(this); mBlocks.append(messagePart); } const QList<MessagePart::Ptr> &MessagePart::subParts() const { return mBlocks; } bool MessagePart::hasSubParts() const { return !mBlocks.isEmpty(); } QList<SignedMessagePart *> MessagePart::signatures() const { QList<SignedMessagePart *> list; if (auto sig = dynamic_cast<SignedMessagePart *>(const_cast<MessagePart *>(this))) { list << sig; } auto parent = parentPart(); while (parent) { if (auto sig = dynamic_cast<SignedMessagePart *>(parent)) { list << sig; } parent = parent->parentPart(); } return list; } QList<EncryptedMessagePart *> MessagePart::encryptions() const { QList<EncryptedMessagePart *> list; if (auto sig = dynamic_cast<EncryptedMessagePart *>(const_cast<MessagePart *>(this))) { list << sig; } auto parent = parentPart(); while (parent) { if (auto sig = dynamic_cast<EncryptedMessagePart *>(parent)) { list << sig; } parent = parent->parentPart(); } return list; } KMMsgEncryptionState MessagePart::encryptionState() const { if (!encryptions().isEmpty()) { return KMMsgFullyEncrypted; } return KMMsgNotEncrypted; } KMMsgSignatureState MessagePart::signatureState() const { if (!signatures().isEmpty()) { return KMMsgFullySigned; } return KMMsgNotSigned; } void MessagePart::bindLifetime(KMime::Content *node) { mNodesToDelete << node; } KMime::Headers::Base *MimeTreeParser::MessagePart::header(const char *header) const { if (node() && node()->hasHeader(header)) { return node()->headerByType(header); } if (auto parent = parentPart()) { return parent->header(header); } return nullptr; } //-----MessagePartList---------------------- MessagePartList::MessagePartList(ObjectTreeParser *otp, KMime::Content *node) : MessagePart(otp, QString(), node) { } QString MessagePartList::text() const { return renderInternalText(); } QString MessagePartList::plaintextContent() const { return QString(); } QString MessagePartList::htmlContent() const { return QString(); } //-----TextMessageBlock---------------------- TextMessagePart::TextMessagePart(ObjectTreeParser *otp, KMime::Content *node) : MessagePartList(otp, node) , mSignatureState(KMMsgSignatureStateUnknown) , mEncryptionState(KMMsgEncryptionStateUnknown) { if (!mNode) { qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node"; return; } parseContent(); } void TextMessagePart::parseContent() { mSignatureState = KMMsgNotSigned; mEncryptionState = KMMsgNotEncrypted; const auto blocks = prepareMessageForDecryption(mNode->decodedContent()); // We also get blocks for unencrypted messages if (!blocks.isEmpty()) { auto aCodec = QStringDecoder(mOtp->codecNameFor(mNode).constData()); const auto cryptProto = QGpgME::openpgp(); /* The (overall) signature/encrypted status is broken * if one unencrypted part is at the beginning or in the middle * because mailmain adds an unencrypted part at the end this should not break the overall status * * That's why we first set the tmp status and if one crypted/signed block comes afterwards, than * the status is set to unencryped */ bool fullySignedOrEncrypted = true; bool fullySignedOrEncryptedTmp = true; for (const auto &block : blocks) { if (!fullySignedOrEncryptedTmp) { fullySignedOrEncrypted = false; } if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) { fullySignedOrEncryptedTmp = false; appendSubPart(MessagePart::Ptr(new MessagePart(mOtp, aCodec.decode(KMime::CRLFtoLF(block.text()))))); } else if (block.type() == PgpMessageBlock) { auto content = new KMime::Content; content->setBody(block.text()); content->parse(); content->contentType()->setCharset(charset()); EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, content, content, false)); mp->bindLifetime(content); mp->setIsEncrypted(true); appendSubPart(mp); } else if (block.type() == ClearsignedBlock) { auto content = new KMime::Content; content->setBody(block.text()); content->parse(); content->contentType()->setCharset(charset()); SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, cryptProto, nullptr, content, false)); mp->bindLifetime(content); mp->setIsSigned(true); appendSubPart(mp); } else { continue; } const auto mp = subParts().last().staticCast<MessagePart>(); const PartMetaData *messagePart(mp->partMetaData()); if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) { mp->setText(aCodec.decode(KMime::CRLFtoLF(block.text()))); } if (messagePart->isEncrypted) { mEncryptionState = KMMsgPartiallyEncrypted; } if (messagePart->isSigned) { mSignatureState = KMMsgPartiallySigned; } } // Do we have an fully Signed/Encrypted Message? if (fullySignedOrEncrypted) { if (mSignatureState == KMMsgPartiallySigned) { mSignatureState = KMMsgFullySigned; } if (mEncryptionState == KMMsgPartiallyEncrypted) { mEncryptionState = KMMsgFullyEncrypted; } } } } KMMsgEncryptionState TextMessagePart::encryptionState() const { if (mEncryptionState == KMMsgNotEncrypted) { return MessagePart::encryptionState(); } return mEncryptionState; } KMMsgSignatureState TextMessagePart::signatureState() const { if (mSignatureState == KMMsgNotSigned) { return MessagePart::signatureState(); } return mSignatureState; } //-----AttachmentMessageBlock---------------------- AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node) : TextMessagePart(otp, node) { } //-----HtmlMessageBlock---------------------- HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node) : MessagePart(otp, QString(), node) { if (!mNode) { qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node"; return; } setText(QStringDecoder(mOtp->codecNameFor(mNode).constData()).decode(KMime::CRLFtoLF(mNode->decodedContent()))); } //-----MimeMessageBlock---------------------- MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart) : MessagePart(otp, QString(), node) { if (!mNode) { qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node"; return; } parseInternal(mNode, onlyOneMimePart); } MimeMessagePart::~MimeMessagePart() { } QString MimeMessagePart::text() const { return renderInternalText(); } QString MimeMessagePart::plaintextContent() const { return QString(); } QString MimeMessagePart::htmlContent() const { return QString(); } //-----AlternativeMessagePart---------------------- AlternativeMessagePart::AlternativeMessagePart(ObjectTreeParser *otp, KMime::Content *node) : MessagePart(otp, QString(), node) { if (auto dataIcal = findTypeInDirectChildren(mNode, "text/calendar")) { mChildParts[MultipartIcal] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataIcal, true)); } if (auto dataText = findTypeInDirectChildren(mNode, "text/plain")) { mChildParts[MultipartPlain] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataText, true)); } if (auto dataHtml = findTypeInDirectChildren(mNode, "text/html")) { mChildParts[MultipartHtml] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataHtml, true)); } else { // If we didn't find the HTML part as the first child of the multipart/alternative, it might // be that this is a HTML message with images, and text/plain and multipart/related are the // immediate children of this multipart/alternative node. // In this case, the HTML node is a child of multipart/related. // In the case of multipart/related we don't expect multiple html parts, it is usually used to group attachments // with html content. // // In any case, this is not a complete implementation of MIME, but an approximation for the kind of mails we actually see in the wild. auto data = [&] { if (auto d = findTypeInDirectChildren(mNode, "multipart/related")) { return d; } return findTypeInDirectChildren(mNode, "multipart/mixed"); }(); if (data) { QString htmlContent; const auto parts = data->contents(); for (auto p : parts) { if ((!p->contentType()->isEmpty()) && (p->contentType()->mimeType() == "text/html")) { htmlContent += MimeMessagePart(mOtp, p, true).text(); } else if (KMime::isAttachment(p)) { appendSubPart(MimeMessagePart::Ptr(new MimeMessagePart(otp, p, true))); } } mChildParts[MultipartHtml] = MessagePart::Ptr(new MessagePart(mOtp, htmlContent, nullptr)); } } } AlternativeMessagePart::~AlternativeMessagePart() { } QList<AlternativeMessagePart::HtmlMode> AlternativeMessagePart::availableModes() { return mChildParts.keys(); } QString AlternativeMessagePart::text() const { if (mChildParts.contains(MultipartPlain)) { return mChildParts[MultipartPlain]->text(); } return QString(); } bool AlternativeMessagePart::isHtml() const { return mChildParts.contains(MultipartHtml); } QString AlternativeMessagePart::plaintextContent() const { return text(); } QString AlternativeMessagePart::htmlContent() const { if (mChildParts.contains(MultipartHtml)) { return mChildParts[MultipartHtml]->text(); } else { return plaintextContent(); } } QString AlternativeMessagePart::icalContent() const { if (mChildParts.contains(MultipartIcal)) { return mChildParts[MultipartIcal]->text(); } return {}; } //-----CertMessageBlock---------------------- CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, QGpgME::Protocol *cryptoProto) : MessagePart(otp, QString(), node) , mCryptoProto(cryptoProto) { if (!mNode) { qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node"; return; } } CertMessagePart::~CertMessagePart() { } QString CertMessagePart::text() const { return QString(); } //-----SignedMessageBlock--------------------- SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp, const QGpgME::Protocol *cryptoProto, KMime::Content *node, KMime::Content *signedData, bool parseAfterDecryption) : MessagePart(otp, {}, node) , mParseAfterDecryption(parseAfterDecryption) , mCryptoProto(cryptoProto) , mSignedData(signedData) { mMetaData.isSigned = true; mMetaData.isGoodSignature = false; mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In."); } SignedMessagePart::~SignedMessagePart() { } void SignedMessagePart::setIsSigned(bool isSigned) { mMetaData.isSigned = isSigned; } bool SignedMessagePart::isSigned() const { return mMetaData.isSigned; } static QString prettifyDN(const char *uid) { // We used to use QGpgME::DN::prettyDN here. But I'm not sure what we actually need it for. return QString::fromUtf8(uid); } const QGpgME::Protocol *SignedMessagePart::cryptoProto() const { return mCryptoProto; } void SignedMessagePart::startVerification() { if (!mSignedData) { return; } mMetaData.isSigned = false; mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In."); mMetaData.isEncrypted = false; mMetaData.isDecryptable = false; auto codec = QStringDecoder(mOtp->codecNameFor(mSignedData).constData()); // If we have a mNode, this is a detached signature if (mNode) { const auto signature = mNode->decodedContent(); // This is necessary in case the original data contained CRLF's. Otherwise the signature will not match the data (since KMIME normalizes to LF) const QByteArray signedData = KMime::LFtoCRLF(mSignedData->encodedContent()); const auto job = mCryptoProto->verifyDetachedJob(); setVerificationResult(job->exec(signature, signedData), signedData); job->deleteLater(); setText(codec.decode(KMime::CRLFtoLF(signedData))); } else { QByteArray outdata; const auto job = mCryptoProto->verifyOpaqueJob(); setVerificationResult(job->exec(mSignedData->decodedContent(), outdata), outdata); job->deleteLater(); setText(codec.decode(KMime::CRLFtoLF(outdata))); } if (!mMetaData.isSigned) { mMetaData.creationTime = QDateTime(); } } static int signatureToStatus(const GpgME::Signature &sig) { switch (sig.status().code()) { case GPG_ERR_NO_ERROR: return GPGME_SIG_STAT_GOOD; case GPG_ERR_BAD_SIGNATURE: return GPGME_SIG_STAT_BAD; case GPG_ERR_NO_PUBKEY: return GPGME_SIG_STAT_NOKEY; case GPG_ERR_NO_DATA: return GPGME_SIG_STAT_NOSIG; case GPG_ERR_SIG_EXPIRED: return GPGME_SIG_STAT_GOOD_EXP; case GPG_ERR_KEY_EXPIRED: return GPGME_SIG_STAT_GOOD_EXPKEY; default: return GPGME_SIG_STAT_ERROR; } } void SignedMessagePart::sigStatusToMetaData() { - GpgME::Key key; - if (partMetaData()->isSigned) { - GpgME::Signature signature = mSignatures.front(); - mMetaData.status_code = signatureToStatus(signature); - mMetaData.isGoodSignature = partMetaData()->status_code == GPGME_SIG_STAT_GOOD; - // save extended signature status flags - mMetaData.sigSummary = signature.summary(); - - if (partMetaData()->isGoodSignature && !key.keyID()) { - // Search for the key by its fingerprint so that we can check for - // trust etc. - key = Kleo::KeyCache::instance()->findByFingerprint(signature.fingerprint()); - if (key.isNull() && signature.fingerprint()) { - // try to find a subkey that was used for signing; - // assumes that the key ID is the last 16 characters of the fingerprint - const auto fpr = std::string_view{signature.fingerprint()}; - const auto keyID = std::string{fpr, fpr.size() - 16, 16}; - const auto subkeys = Kleo::KeyCache::instance()->findSubkeysByKeyID({keyID}); - if (subkeys.size() > 0) { - key = subkeys[0].parent(); - } - } - if (key.isNull()) { - qCDebug(MIMETREEPARSER_CORE_LOG) << "Found no key or subkey for fingerprint" << signature.fingerprint(); - } - } + if (!partMetaData()->isSigned) { + return; + } - if (key.keyID()) { - partMetaData()->keyId = key.keyID(); + GpgME::Signature signature = mSignatures.front(); + mMetaData.status_code = signatureToStatus(signature); + mMetaData.isGoodSignature = partMetaData()->status_code == GPGME_SIG_STAT_GOOD; + // save extended signature status flags + mMetaData.sigSummary = signature.summary(); + + // Search for the key by its fingerprint so that we can check for + // trust etc. + GpgME::Key key = Kleo::KeyCache::instance()->findByFingerprint(signature.fingerprint()); + if (key.isNull() && signature.fingerprint()) { + // try to find a subkey that was used for signing; + // assumes that the key ID is the last 16 characters of the fingerprint + const auto fpr = std::string_view{signature.fingerprint()}; + const auto keyID = std::string{fpr, fpr.size() - 16, 16}; + const auto subkeys = Kleo::KeyCache::instance()->findSubkeysByKeyID({keyID}); + if (subkeys.size() > 0) { + key = subkeys[0].parent(); } - if (partMetaData()->keyId.isEmpty()) { - partMetaData()->keyId = signature.fingerprint(); - } - partMetaData()->keyTrust = signature.validity(); - if (key.numUserIDs() > 0 && key.userID(0).id()) { - partMetaData()->signer = prettifyDN(key.userID(0).id()); - } - for (const auto &uid : key.userIDs()) { - // The following if /should/ always result in TRUE but we - // won't trust implicitly the plugin that gave us these data. - if (uid.email()) { - QString email = QString::fromUtf8(uid.email()); - if (!email.isEmpty()) { - partMetaData()->signerMailAddresses.append(email); - } + } + if (key.isNull()) { + qCDebug(MIMETREEPARSER_CORE_LOG) << "Found no key or subkey for fingerprint" << signature.fingerprint(); + } + + if (key.keyID()) { + partMetaData()->keyId = key.keyID(); + } + if (partMetaData()->keyId.isEmpty()) { + partMetaData()->keyId = signature.fingerprint(); + } + partMetaData()->keyTrust = signature.validity(); + if (key.numUserIDs() > 0 && key.userID(0).id()) { + partMetaData()->signer = prettifyDN(key.userID(0).id()); + } + for (const auto &uid : key.userIDs()) { + // The following if /should/ always result in TRUE but we + // won't trust implicitly the plugin that gave us these data. + if (uid.email()) { + QString email = QString::fromUtf8(uid.email()); + if (!email.isEmpty()) { + partMetaData()->signerMailAddresses.append(email); } } + } - if (signature.creationTime()) { - partMetaData()->creationTime.setSecsSinceEpoch(signature.creationTime()); - } else { - partMetaData()->creationTime = QDateTime(); + if (signature.creationTime()) { + partMetaData()->creationTime.setSecsSinceEpoch(signature.creationTime()); + } else { + partMetaData()->creationTime = QDateTime(); + } + if (partMetaData()->signer.isEmpty()) { + if (key.numUserIDs() > 0 && key.userID(0).name()) { + partMetaData()->signer = prettifyDN(key.userID(0).name()); } - if (partMetaData()->signer.isEmpty()) { - if (key.numUserIDs() > 0 && key.userID(0).name()) { - partMetaData()->signer = prettifyDN(key.userID(0).name()); - } - if (!partMetaData()->signerMailAddresses.empty()) { - if (partMetaData()->signer.isEmpty()) { - partMetaData()->signer = partMetaData()->signerMailAddresses.front(); - } else { - partMetaData()->signer += QLatin1StringView(" <") + partMetaData()->signerMailAddresses.front() + QLatin1Char('>'); - } + if (!partMetaData()->signerMailAddresses.empty()) { + if (partMetaData()->signer.isEmpty()) { + partMetaData()->signer = partMetaData()->signerMailAddresses.front(); + } else { + partMetaData()->signer += QLatin1StringView(" <") + partMetaData()->signerMailAddresses.front() + QLatin1Char('>'); } } - if (Kleo::DeVSCompliance::isCompliant()) { - partMetaData()->isCompliant = signature.isDeVs(); - partMetaData()->compliance = Kleo::DeVSCompliance::name(signature.isDeVs()); - } else { - partMetaData()->isCompliant = true; - } + } + if (Kleo::DeVSCompliance::isCompliant()) { + partMetaData()->isCompliant = signature.isDeVs(); + partMetaData()->compliance = Kleo::DeVSCompliance::name(signature.isDeVs()); + } else { + partMetaData()->isCompliant = true; } } void SignedMessagePart::setVerificationResult(const GpgME::VerificationResult &result, const QByteArray &signedData) { mSignatures = result.signatures(); // FIXME // mMetaData.auditLogError = result.error; mMetaData.isSigned = !mSignatures.empty(); if (mMetaData.isSigned) { sigStatusToMetaData(); if (!signedData.isEmpty() && mParseAfterDecryption) { parseInternal(signedData); } } } QString SignedMessagePart::plaintextContent() const { if (!mNode) { return MessagePart::text(); } else { return QString(); } } QString SignedMessagePart::htmlContent() const { if (!mNode) { return MessagePart::text(); } else { return QString(); } } //-----CryptMessageBlock--------------------- EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, KMime::Content *node, KMime::Content *encryptedNode, bool parseAfterDecryption) : MessagePart(otp, text, node) , mParseAfterDecryption(parseAfterDecryption) , mPassphraseError(false) , mNoSecKey(false) , mDecryptMessage(false) , mCryptoProto(cryptoProto) , mEncryptedNode(encryptedNode) { mMetaData.isSigned = false; mMetaData.isGoodSignature = false; mMetaData.isEncrypted = false; mMetaData.isDecryptable = false; mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In."); } void EncryptedMessagePart::setIsEncrypted(bool encrypted) { mMetaData.isEncrypted = encrypted; } bool EncryptedMessagePart::isEncrypted() const { return mMetaData.isEncrypted; } const QGpgME::Protocol *EncryptedMessagePart::cryptoProto() const { return mCryptoProto; } void EncryptedMessagePart::setDecryptMessage(bool decrypt) { mDecryptMessage = decrypt; } bool EncryptedMessagePart::decryptMessage() const { return mDecryptMessage; } bool EncryptedMessagePart::isDecryptable() const { return mMetaData.isDecryptable; } bool EncryptedMessagePart::isNoSecKey() const { return mNoSecKey; } bool EncryptedMessagePart::passphraseError() const { return mPassphraseError; } bool EncryptedMessagePart::decrypt(KMime::Content &data) { mError = NoError; mMetaData.errorText.clear(); // FIXME // mMetaData.auditLogError = GpgME::Error(); mMetaData.auditLog.clear(); const QByteArray ciphertext = data.decodedContent(); QByteArray plainText; auto job = mCryptoProto->decryptVerifyJob(); const std::pair<GpgME::DecryptionResult, GpgME::VerificationResult> p = job->exec(ciphertext, plainText); job->deleteLater(); auto decryptResult = p.first; auto verifyResult = p.second; mMetaData.isSigned = verifyResult.signatures().size() > 0; // Normalize CRLF's plainText = KMime::CRLFtoLF(plainText); auto codec = QStringDecoder(mOtp->codecNameFor(&data).constData()); const auto decoded = codec.decode(plainText); partMetaData()->isSigned = verifyResult.signatures().size() > 0; if (partMetaData()->isSigned) { // We simply attach a signed message part to indicate that this content is also signed // We're forwarding mNode to not loose the encoding information auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, mCryptoProto, mNode, nullptr)); subPart->setText(decoded); subPart->setVerificationResult(verifyResult, plainText); appendSubPart(subPart); } mDecryptRecipients.clear(); bool cannotDecrypt = false; bool bDecryptionOk = !decryptResult.error(); for (const auto &recipient : decryptResult.recipients()) { if (!recipient.status()) { bDecryptionOk = true; } GpgME::Key key; key = Kleo::KeyCache::instance()->findByKeyIDOrFingerprint(recipient.keyID()); if (key.isNull()) { auto ret = Kleo::KeyCache::instance()->findSubkeysByKeyID({recipient.keyID()}); if (ret.size() == 1) { key = ret.front().parent(); } if (key.isNull()) { qCDebug(MIMETREEPARSER_CORE_LOG) << "Found no Key for KeyID " << recipient.keyID(); } } mDecryptRecipients.emplace_back(recipient, key); } if (!bDecryptionOk && partMetaData()->isSigned) { // Only a signed part partMetaData()->isEncrypted = false; bDecryptionOk = true; mDecryptedData = plainText; } else { mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_BAD_PASSPHRASE; mMetaData.isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA; if (decryptResult.error().isCanceled()) { setDecryptMessage(false); } partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString()); if (Kleo::DeVSCompliance::isCompliant()) { partMetaData()->isCompliant = decryptResult.isDeVs(); partMetaData()->compliance = Kleo::DeVSCompliance::name(decryptResult.isDeVs()); } else { partMetaData()->isCompliant = true; } if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) { partMetaData()->keyId = decryptResult.recipient(0).keyID(); } if (bDecryptionOk) { mDecryptedData = plainText; } else { mNoSecKey = true; const auto decryRecipients = decryptResult.recipients(); for (const GpgME::DecryptionResult::Recipient &recipient : decryRecipients) { mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY); } if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly mPassphraseError = true; } } } if (!bDecryptionOk) { QString cryptPlugLibName; mError = UnknownError; if (mCryptoProto) { cryptPlugLibName = mCryptoProto->name(); } if (mNoSecKey) { mError = NoKeyError; } if (mPassphraseError) { mError = PassphraseError; } if (!mCryptoProto) { partMetaData()->errorText = i18n("No appropriate crypto plug-in was found."); } else if (cannotDecrypt) { partMetaData()->errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName); } else if (!passphraseError()) { partMetaData()->errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1StringView("<br />") + i18n("Error: %1", partMetaData()->errorText); } } return bDecryptionOk; } void EncryptedMessagePart::startDecryption(KMime::Content *data) { mMetaData.isEncrypted = true; mMetaData.isDecryptable = decrypt(*data); if (mParseAfterDecryption && !mMetaData.isSigned) { parseInternal(mDecryptedData); } else { setText(QString::fromUtf8(mDecryptedData.constData())); } } void EncryptedMessagePart::startDecryption() { if (mEncryptedNode) { startDecryption(mEncryptedNode); } else { startDecryption(mNode); } } std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> EncryptedMessagePart::decryptRecipients() const { return mDecryptRecipients; } QString EncryptedMessagePart::plaintextContent() const { if (!mNode) { return MessagePart::text(); } else { return QString(); } } QString EncryptedMessagePart::htmlContent() const { if (!mNode) { return MessagePart::text(); } else { return QString(); } } QString EncryptedMessagePart::text() const { if (hasSubParts()) { auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>(); if (_mp) { return _mp->text(); } } return MessagePart::text(); } EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message) : MessagePart(otp, QString(), node) , mMessage(message) { mMetaData.isEncrypted = false; mMetaData.isSigned = false; mMetaData.isEncapsulatedRfc822Message = true; if (!mMessage) { qCWarning(MIMETREEPARSER_CORE_LOG) << "Node is of type message/rfc822 but doesn't have a message!"; return; } parseInternal(message.data()); } QString EncapsulatedRfc822MessagePart::text() const { return renderInternalText(); } QString EncapsulatedRfc822MessagePart::from() const { if (auto from = mMessage->from(false)) { return from->asUnicodeString(); } return {}; } QDateTime EncapsulatedRfc822MessagePart::date() const { if (auto date = mMessage->date(false)) { return date->dateTime(); } return {}; } HeadersPart::HeadersPart(ObjectTreeParser *otp, KMime::Content *node) : MessagePart(otp, QString(), node) { } #include "moc_messagepart.cpp"