File indexing completed on 2024-12-22 05:05:20
0001 // SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com> 0002 // SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com> 0003 // SPDX-License-Identifier: LGPL-2.0-or-later 0004 0005 #include "messagepart.h" 0006 #include "cryptohelper.h" 0007 #include "mimetreeparser_core_debug.h" 0008 #include "objecttreeparser.h" 0009 0010 #include "utils.h" 0011 0012 #include <KLocalizedString> 0013 #include <KMime/Content> 0014 #include <Libkleo/Compliance> 0015 #include <Libkleo/KeyCache> 0016 0017 #include <QGpgME/DN> 0018 #include <QGpgME/DecryptVerifyJob> 0019 #include <QGpgME/Protocol> 0020 #include <QGpgME/VerifyDetachedJob> 0021 #include <QGpgME/VerifyOpaqueJob> 0022 0023 #include <QStringDecoder> 0024 0025 #include <gpgme++/key.h> 0026 #include <gpgme++/keylistresult.h> 0027 #include <gpgme.h> 0028 0029 using namespace MimeTreeParser; 0030 0031 //------MessagePart----------------------- 0032 MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text, KMime::Content *node) 0033 : mText(text) 0034 , mOtp(otp) 0035 , mParentPart(nullptr) 0036 , mNode(node) // only null for messagepartlist 0037 , mError(NoError) 0038 , mRoot(false) 0039 { 0040 } 0041 0042 MessagePart::~MessagePart() 0043 { 0044 for (auto n : std::as_const(mNodesToDelete)) { 0045 delete n; 0046 } 0047 } 0048 0049 MessagePart::Disposition MessagePart::disposition() const 0050 { 0051 if (!mNode) { 0052 return Invalid; 0053 } 0054 const auto cd = mNode->contentDisposition(false); 0055 if (!cd) { 0056 return Invalid; 0057 } 0058 switch (cd->disposition()) { 0059 case KMime::Headers::CDinline: 0060 return Inline; 0061 case KMime::Headers::CDattachment: 0062 return Attachment; 0063 default: 0064 return Invalid; 0065 } 0066 } 0067 0068 QString MessagePart::filename() const 0069 { 0070 if (!mNode) { 0071 return {}; 0072 } 0073 0074 if (const auto cd = mNode->contentDisposition(false)) { 0075 const auto name = cd->filename(); 0076 // Allow for a fallback for mails that have a ContentDisposition header, but don't set the filename anyways. 0077 // Not the recommended way, but exists. 0078 if (!name.isEmpty()) { 0079 return name; 0080 } 0081 } 0082 if (const auto ct = mNode->contentType(false)) { 0083 return ct->name(); 0084 } 0085 return {}; 0086 } 0087 0088 static KMime::Headers::ContentType *contentType(KMime::Content *node) 0089 { 0090 if (node) { 0091 return node->contentType(false); 0092 } 0093 return nullptr; 0094 } 0095 0096 QByteArray MessagePart::charset() const 0097 { 0098 if (!mNode) { 0099 return "us-ascii"; 0100 } 0101 if (auto ct = contentType(mNode)) { 0102 return ct->charset(); 0103 } 0104 // Per rfc2045 us-ascii is the default 0105 return "us-ascii"; 0106 } 0107 0108 QByteArray MessagePart::mimeType() const 0109 { 0110 if (auto ct = contentType(mNode)) { 0111 return ct->mimeType(); 0112 } 0113 return {}; 0114 } 0115 0116 bool MessagePart::isText() const 0117 { 0118 if (auto ct = contentType(mNode)) { 0119 return ct->isText(); 0120 } 0121 return false; 0122 } 0123 0124 MessagePart::Error MessagePart::error() const 0125 { 0126 return mError; 0127 } 0128 0129 QString MessagePart::errorString() const 0130 { 0131 return mMetaData.errorText; 0132 } 0133 0134 PartMetaData *MessagePart::partMetaData() 0135 { 0136 return &mMetaData; 0137 } 0138 0139 bool MessagePart::isAttachment() const 0140 { 0141 if (mNode) { 0142 return KMime::isAttachment(mNode); 0143 } 0144 return false; 0145 } 0146 0147 KMime::Content *MessagePart::node() const 0148 { 0149 return mNode; 0150 } 0151 0152 void MessagePart::setIsRoot(bool root) 0153 { 0154 mRoot = root; 0155 } 0156 0157 bool MessagePart::isRoot() const 0158 { 0159 return mRoot; 0160 } 0161 0162 QString MessagePart::text() const 0163 { 0164 return mText; 0165 } 0166 0167 void MessagePart::setText(const QString &text) 0168 { 0169 mText = text; 0170 } 0171 0172 bool MessagePart::isHtml() const 0173 { 0174 return false; 0175 } 0176 0177 MessagePart *MessagePart::parentPart() const 0178 { 0179 return mParentPart; 0180 } 0181 0182 void MessagePart::setParentPart(MessagePart *parentPart) 0183 { 0184 mParentPart = parentPart; 0185 } 0186 0187 QString MessagePart::htmlContent() const 0188 { 0189 return text(); 0190 } 0191 0192 QString MessagePart::plaintextContent() const 0193 { 0194 return text(); 0195 } 0196 0197 void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart) 0198 { 0199 auto subMessagePart = mOtp->parseObjectTreeInternal(node, onlyOneMimePart); 0200 mRoot = subMessagePart->isRoot(); 0201 for (const auto &part : std::as_const(subMessagePart->subParts())) { 0202 appendSubPart(part); 0203 } 0204 } 0205 0206 void MessagePart::parseInternal(const QByteArray &data) 0207 { 0208 auto tempNode = new KMime::Content(); 0209 0210 const auto lfData = KMime::CRLFtoLF(data); 0211 // We have to deal with both bodies and full parts. In inline encrypted/signed parts we can have nested parts, 0212 // or just plain-text, and both ends up here. setContent defaults to setting only the header, so we have to avoid this. 0213 if (lfData.contains("\n\n")) { 0214 tempNode->setContent(lfData); 0215 } else { 0216 tempNode->setBody(lfData); 0217 } 0218 tempNode->parse(); 0219 tempNode->contentType()->setCharset(charset()); 0220 bindLifetime(tempNode); 0221 0222 if (!tempNode->head().isEmpty()) { 0223 tempNode->contentDescription()->from7BitString("temporary node"); 0224 } 0225 0226 parseInternal(tempNode); 0227 } 0228 0229 QString MessagePart::renderInternalText() const 0230 { 0231 QString text; 0232 for (const auto &mp : subParts()) { 0233 text += mp->text(); 0234 } 0235 return text; 0236 } 0237 0238 void MessagePart::appendSubPart(const MessagePart::Ptr &messagePart) 0239 { 0240 messagePart->setParentPart(this); 0241 mBlocks.append(messagePart); 0242 } 0243 0244 const QList<MessagePart::Ptr> &MessagePart::subParts() const 0245 { 0246 return mBlocks; 0247 } 0248 0249 bool MessagePart::hasSubParts() const 0250 { 0251 return !mBlocks.isEmpty(); 0252 } 0253 0254 QList<SignedMessagePart *> MessagePart::signatures() const 0255 { 0256 QList<SignedMessagePart *> list; 0257 if (auto sig = dynamic_cast<SignedMessagePart *>(const_cast<MessagePart *>(this))) { 0258 list << sig; 0259 } 0260 auto parent = parentPart(); 0261 while (parent) { 0262 if (auto sig = dynamic_cast<SignedMessagePart *>(parent)) { 0263 list << sig; 0264 } 0265 parent = parent->parentPart(); 0266 } 0267 return list; 0268 } 0269 0270 QList<EncryptedMessagePart *> MessagePart::encryptions() const 0271 { 0272 QList<EncryptedMessagePart *> list; 0273 if (auto sig = dynamic_cast<EncryptedMessagePart *>(const_cast<MessagePart *>(this))) { 0274 list << sig; 0275 } 0276 auto parent = parentPart(); 0277 while (parent) { 0278 if (auto sig = dynamic_cast<EncryptedMessagePart *>(parent)) { 0279 list << sig; 0280 } 0281 parent = parent->parentPart(); 0282 } 0283 return list; 0284 } 0285 0286 KMMsgEncryptionState MessagePart::encryptionState() const 0287 { 0288 if (!encryptions().isEmpty()) { 0289 return KMMsgFullyEncrypted; 0290 } 0291 return KMMsgNotEncrypted; 0292 } 0293 0294 KMMsgSignatureState MessagePart::signatureState() const 0295 { 0296 if (!signatures().isEmpty()) { 0297 return KMMsgFullySigned; 0298 } 0299 return KMMsgNotSigned; 0300 } 0301 0302 void MessagePart::bindLifetime(KMime::Content *node) 0303 { 0304 mNodesToDelete << node; 0305 } 0306 0307 KMime::Headers::Base *MimeTreeParser::MessagePart::header(const char *header) const 0308 { 0309 if (node() && node()->hasHeader(header)) { 0310 return node()->headerByType(header); 0311 } 0312 if (auto parent = parentPart()) { 0313 return parent->header(header); 0314 } 0315 0316 return nullptr; 0317 } 0318 0319 //-----MessagePartList---------------------- 0320 MessagePartList::MessagePartList(ObjectTreeParser *otp, KMime::Content *node) 0321 : MessagePart(otp, QString(), node) 0322 { 0323 } 0324 0325 QString MessagePartList::text() const 0326 { 0327 return renderInternalText(); 0328 } 0329 0330 QString MessagePartList::plaintextContent() const 0331 { 0332 return QString(); 0333 } 0334 0335 QString MessagePartList::htmlContent() const 0336 { 0337 return QString(); 0338 } 0339 0340 //-----TextMessageBlock---------------------- 0341 0342 TextMessagePart::TextMessagePart(ObjectTreeParser *otp, KMime::Content *node) 0343 : MessagePartList(otp, node) 0344 , mSignatureState(KMMsgSignatureStateUnknown) 0345 , mEncryptionState(KMMsgEncryptionStateUnknown) 0346 { 0347 if (!mNode) { 0348 qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node"; 0349 return; 0350 } 0351 0352 parseContent(); 0353 } 0354 0355 void TextMessagePart::parseContent() 0356 { 0357 mSignatureState = KMMsgNotSigned; 0358 mEncryptionState = KMMsgNotEncrypted; 0359 const auto blocks = prepareMessageForDecryption(mNode->decodedContent()); 0360 // We also get blocks for unencrypted messages 0361 if (!blocks.isEmpty()) { 0362 auto aCodec = QStringDecoder(mOtp->codecNameFor(mNode).constData()); 0363 const auto cryptProto = QGpgME::openpgp(); 0364 0365 /* The (overall) signature/encrypted status is broken 0366 * if one unencrypted part is at the beginning or in the middle 0367 * because mailmain adds an unencrypted part at the end this should not break the overall status 0368 * 0369 * That's why we first set the tmp status and if one crypted/signed block comes afterwards, than 0370 * the status is set to unencryped 0371 */ 0372 bool fullySignedOrEncrypted = true; 0373 bool fullySignedOrEncryptedTmp = true; 0374 0375 for (const auto &block : blocks) { 0376 if (!fullySignedOrEncryptedTmp) { 0377 fullySignedOrEncrypted = false; 0378 } 0379 0380 if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) { 0381 fullySignedOrEncryptedTmp = false; 0382 appendSubPart(MessagePart::Ptr(new MessagePart(mOtp, aCodec.decode(KMime::CRLFtoLF(block.text()))))); 0383 } else if (block.type() == PgpMessageBlock) { 0384 auto content = new KMime::Content; 0385 content->setBody(block.text()); 0386 content->parse(); 0387 content->contentType()->setCharset(charset()); 0388 EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, content, content, false)); 0389 mp->bindLifetime(content); 0390 mp->setIsEncrypted(true); 0391 appendSubPart(mp); 0392 } else if (block.type() == ClearsignedBlock) { 0393 auto content = new KMime::Content; 0394 content->setBody(block.text()); 0395 content->parse(); 0396 content->contentType()->setCharset(charset()); 0397 SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, cryptProto, nullptr, content, false)); 0398 mp->bindLifetime(content); 0399 mp->setIsSigned(true); 0400 appendSubPart(mp); 0401 } else { 0402 continue; 0403 } 0404 0405 const auto mp = subParts().last().staticCast<MessagePart>(); 0406 const PartMetaData *messagePart(mp->partMetaData()); 0407 0408 if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) { 0409 mp->setText(aCodec.decode(KMime::CRLFtoLF(block.text()))); 0410 } 0411 0412 if (messagePart->isEncrypted) { 0413 mEncryptionState = KMMsgPartiallyEncrypted; 0414 } 0415 0416 if (messagePart->isSigned) { 0417 mSignatureState = KMMsgPartiallySigned; 0418 } 0419 } 0420 0421 // Do we have an fully Signed/Encrypted Message? 0422 if (fullySignedOrEncrypted) { 0423 if (mSignatureState == KMMsgPartiallySigned) { 0424 mSignatureState = KMMsgFullySigned; 0425 } 0426 if (mEncryptionState == KMMsgPartiallyEncrypted) { 0427 mEncryptionState = KMMsgFullyEncrypted; 0428 } 0429 } 0430 } 0431 } 0432 0433 KMMsgEncryptionState TextMessagePart::encryptionState() const 0434 { 0435 if (mEncryptionState == KMMsgNotEncrypted) { 0436 return MessagePart::encryptionState(); 0437 } 0438 return mEncryptionState; 0439 } 0440 0441 KMMsgSignatureState TextMessagePart::signatureState() const 0442 { 0443 if (mSignatureState == KMMsgNotSigned) { 0444 return MessagePart::signatureState(); 0445 } 0446 return mSignatureState; 0447 } 0448 0449 //-----AttachmentMessageBlock---------------------- 0450 0451 AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node) 0452 : TextMessagePart(otp, node) 0453 { 0454 } 0455 0456 //-----HtmlMessageBlock---------------------- 0457 0458 HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node) 0459 : MessagePart(otp, QString(), node) 0460 { 0461 if (!mNode) { 0462 qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node"; 0463 return; 0464 } 0465 0466 setText(QStringDecoder(mOtp->codecNameFor(mNode).constData()).decode(KMime::CRLFtoLF(mNode->decodedContent()))); 0467 } 0468 0469 //-----MimeMessageBlock---------------------- 0470 0471 MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart) 0472 : MessagePart(otp, QString(), node) 0473 { 0474 if (!mNode) { 0475 qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node"; 0476 return; 0477 } 0478 0479 parseInternal(mNode, onlyOneMimePart); 0480 } 0481 0482 MimeMessagePart::~MimeMessagePart() 0483 { 0484 } 0485 0486 QString MimeMessagePart::text() const 0487 { 0488 return renderInternalText(); 0489 } 0490 0491 QString MimeMessagePart::plaintextContent() const 0492 { 0493 return QString(); 0494 } 0495 0496 QString MimeMessagePart::htmlContent() const 0497 { 0498 return QString(); 0499 } 0500 0501 //-----AlternativeMessagePart---------------------- 0502 0503 AlternativeMessagePart::AlternativeMessagePart(ObjectTreeParser *otp, KMime::Content *node) 0504 : MessagePart(otp, QString(), node) 0505 { 0506 if (auto dataIcal = findTypeInDirectChildren(mNode, "text/calendar")) { 0507 mChildParts[MultipartIcal] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataIcal, true)); 0508 } 0509 0510 if (auto dataText = findTypeInDirectChildren(mNode, "text/plain")) { 0511 mChildParts[MultipartPlain] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataText, true)); 0512 } 0513 0514 if (auto dataHtml = findTypeInDirectChildren(mNode, "text/html")) { 0515 mChildParts[MultipartHtml] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataHtml, true)); 0516 } else { 0517 // If we didn't find the HTML part as the first child of the multipart/alternative, it might 0518 // be that this is a HTML message with images, and text/plain and multipart/related are the 0519 // immediate children of this multipart/alternative node. 0520 // In this case, the HTML node is a child of multipart/related. 0521 // In the case of multipart/related we don't expect multiple html parts, it is usually used to group attachments 0522 // with html content. 0523 // 0524 // 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. 0525 auto data = [&] { 0526 if (auto d = findTypeInDirectChildren(mNode, "multipart/related")) { 0527 return d; 0528 } 0529 return findTypeInDirectChildren(mNode, "multipart/mixed"); 0530 }(); 0531 if (data) { 0532 QString htmlContent; 0533 const auto parts = data->contents(); 0534 for (auto p : parts) { 0535 if ((!p->contentType()->isEmpty()) && (p->contentType()->mimeType() == "text/html")) { 0536 htmlContent += MimeMessagePart(mOtp, p, true).text(); 0537 } else if (KMime::isAttachment(p)) { 0538 appendSubPart(MimeMessagePart::Ptr(new MimeMessagePart(otp, p, true))); 0539 } 0540 } 0541 mChildParts[MultipartHtml] = MessagePart::Ptr(new MessagePart(mOtp, htmlContent, nullptr)); 0542 } 0543 } 0544 } 0545 0546 AlternativeMessagePart::~AlternativeMessagePart() 0547 { 0548 } 0549 0550 QList<AlternativeMessagePart::HtmlMode> AlternativeMessagePart::availableModes() 0551 { 0552 return mChildParts.keys(); 0553 } 0554 0555 QString AlternativeMessagePart::text() const 0556 { 0557 if (mChildParts.contains(MultipartPlain)) { 0558 return mChildParts[MultipartPlain]->text(); 0559 } 0560 return QString(); 0561 } 0562 0563 bool AlternativeMessagePart::isHtml() const 0564 { 0565 return mChildParts.contains(MultipartHtml); 0566 } 0567 0568 QString AlternativeMessagePart::plaintextContent() const 0569 { 0570 return text(); 0571 } 0572 0573 QString AlternativeMessagePart::htmlContent() const 0574 { 0575 if (mChildParts.contains(MultipartHtml)) { 0576 return mChildParts[MultipartHtml]->text(); 0577 } else { 0578 return plaintextContent(); 0579 } 0580 } 0581 0582 QString AlternativeMessagePart::icalContent() const 0583 { 0584 if (mChildParts.contains(MultipartIcal)) { 0585 return mChildParts[MultipartIcal]->text(); 0586 } 0587 return {}; 0588 } 0589 0590 //-----CertMessageBlock---------------------- 0591 0592 CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, QGpgME::Protocol *cryptoProto) 0593 : MessagePart(otp, QString(), node) 0594 , mCryptoProto(cryptoProto) 0595 { 0596 if (!mNode) { 0597 qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node"; 0598 return; 0599 } 0600 } 0601 0602 CertMessagePart::~CertMessagePart() 0603 { 0604 } 0605 0606 QString CertMessagePart::text() const 0607 { 0608 return QString(); 0609 } 0610 0611 //-----SignedMessageBlock--------------------- 0612 SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp, 0613 const QGpgME::Protocol *cryptoProto, 0614 KMime::Content *node, 0615 KMime::Content *signedData, 0616 bool parseAfterDecryption) 0617 : MessagePart(otp, {}, node) 0618 , mParseAfterDecryption(parseAfterDecryption) 0619 , mCryptoProto(cryptoProto) 0620 , mSignedData(signedData) 0621 { 0622 mMetaData.isSigned = true; 0623 mMetaData.isGoodSignature = false; 0624 mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In."); 0625 } 0626 0627 SignedMessagePart::~SignedMessagePart() 0628 { 0629 } 0630 0631 void SignedMessagePart::setIsSigned(bool isSigned) 0632 { 0633 mMetaData.isSigned = isSigned; 0634 } 0635 0636 bool SignedMessagePart::isSigned() const 0637 { 0638 return mMetaData.isSigned; 0639 } 0640 0641 static QString prettifyDN(const char *uid) 0642 { 0643 // We used to use QGpgME::DN::prettyDN here. But I'm not sure what we actually need it for. 0644 return QString::fromUtf8(uid); 0645 } 0646 0647 const QGpgME::Protocol *SignedMessagePart::cryptoProto() const 0648 { 0649 return mCryptoProto; 0650 } 0651 0652 void SignedMessagePart::startVerification() 0653 { 0654 if (!mSignedData) { 0655 return; 0656 } 0657 0658 mMetaData.isSigned = false; 0659 mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In."); 0660 mMetaData.isEncrypted = false; 0661 mMetaData.isDecryptable = false; 0662 0663 auto codec = QStringDecoder(mOtp->codecNameFor(mSignedData).constData()); 0664 0665 // If we have a mNode, this is a detached signature 0666 if (mNode) { 0667 const auto signature = mNode->decodedContent(); 0668 0669 // This is necessary in case the original data contained CRLF's. Otherwise the signature will not match the data (since KMIME normalizes to LF) 0670 const QByteArray signedData = KMime::LFtoCRLF(mSignedData->encodedContent()); 0671 0672 const auto job = mCryptoProto->verifyDetachedJob(); 0673 setVerificationResult(job->exec(signature, signedData), signedData); 0674 job->deleteLater(); 0675 setText(codec.decode(KMime::CRLFtoLF(signedData))); 0676 } else { 0677 QByteArray outdata; 0678 const auto job = mCryptoProto->verifyOpaqueJob(); 0679 setVerificationResult(job->exec(mSignedData->decodedContent(), outdata), outdata); 0680 job->deleteLater(); 0681 setText(codec.decode(KMime::CRLFtoLF(outdata))); 0682 } 0683 0684 if (!mMetaData.isSigned) { 0685 mMetaData.creationTime = QDateTime(); 0686 } 0687 } 0688 0689 static int signatureToStatus(const GpgME::Signature &sig) 0690 { 0691 switch (sig.status().code()) { 0692 case GPG_ERR_NO_ERROR: 0693 return GPGME_SIG_STAT_GOOD; 0694 case GPG_ERR_BAD_SIGNATURE: 0695 return GPGME_SIG_STAT_BAD; 0696 case GPG_ERR_NO_PUBKEY: 0697 return GPGME_SIG_STAT_NOKEY; 0698 case GPG_ERR_NO_DATA: 0699 return GPGME_SIG_STAT_NOSIG; 0700 case GPG_ERR_SIG_EXPIRED: 0701 return GPGME_SIG_STAT_GOOD_EXP; 0702 case GPG_ERR_KEY_EXPIRED: 0703 return GPGME_SIG_STAT_GOOD_EXPKEY; 0704 default: 0705 return GPGME_SIG_STAT_ERROR; 0706 } 0707 } 0708 0709 void SignedMessagePart::sigStatusToMetaData() 0710 { 0711 GpgME::Key key; 0712 if (partMetaData()->isSigned) { 0713 GpgME::Signature signature = mSignatures.front(); 0714 mMetaData.status_code = signatureToStatus(signature); 0715 mMetaData.isGoodSignature = partMetaData()->status_code == GPGME_SIG_STAT_GOOD; 0716 // save extended signature status flags 0717 mMetaData.sigSummary = signature.summary(); 0718 0719 if (partMetaData()->isGoodSignature && !key.keyID()) { 0720 // Search for the key by its fingerprint so that we can check for 0721 // trust etc. 0722 key = Kleo::KeyCache::instance()->findByFingerprint(signature.fingerprint()); 0723 if (key.isNull() && signature.fingerprint()) { 0724 // try to find a subkey that was used for signing; 0725 // assumes that the key ID is the last 16 characters of the fingerprint 0726 const auto fpr = std::string_view{signature.fingerprint()}; 0727 const auto keyID = std::string{fpr, fpr.size() - 16, 16}; 0728 const auto subkeys = Kleo::KeyCache::instance()->findSubkeysByKeyID({keyID}); 0729 if (subkeys.size() > 0) { 0730 key = subkeys[0].parent(); 0731 } 0732 } 0733 if (key.isNull()) { 0734 qCDebug(MIMETREEPARSER_CORE_LOG) << "Found no key or subkey for fingerprint" << signature.fingerprint(); 0735 } 0736 } 0737 0738 if (key.keyID()) { 0739 partMetaData()->keyId = key.keyID(); 0740 } 0741 if (partMetaData()->keyId.isEmpty()) { 0742 partMetaData()->keyId = signature.fingerprint(); 0743 } 0744 partMetaData()->keyTrust = signature.validity(); 0745 if (key.numUserIDs() > 0 && key.userID(0).id()) { 0746 partMetaData()->signer = prettifyDN(key.userID(0).id()); 0747 } 0748 for (const auto &uid : key.userIDs()) { 0749 // The following if /should/ always result in TRUE but we 0750 // won't trust implicitly the plugin that gave us these data. 0751 if (uid.email()) { 0752 QString email = QString::fromUtf8(uid.email()); 0753 if (!email.isEmpty()) { 0754 partMetaData()->signerMailAddresses.append(email); 0755 } 0756 } 0757 } 0758 0759 if (signature.creationTime()) { 0760 partMetaData()->creationTime.setSecsSinceEpoch(signature.creationTime()); 0761 } else { 0762 partMetaData()->creationTime = QDateTime(); 0763 } 0764 if (partMetaData()->signer.isEmpty()) { 0765 if (key.numUserIDs() > 0 && key.userID(0).name()) { 0766 partMetaData()->signer = prettifyDN(key.userID(0).name()); 0767 } 0768 if (!partMetaData()->signerMailAddresses.empty()) { 0769 if (partMetaData()->signer.isEmpty()) { 0770 partMetaData()->signer = partMetaData()->signerMailAddresses.front(); 0771 } else { 0772 partMetaData()->signer += QLatin1StringView(" <") + partMetaData()->signerMailAddresses.front() + QLatin1Char('>'); 0773 } 0774 } 0775 } 0776 if (Kleo::DeVSCompliance::isCompliant()) { 0777 partMetaData()->isCompliant = signature.isDeVs(); 0778 partMetaData()->compliance = Kleo::DeVSCompliance::name(signature.isDeVs()); 0779 } else { 0780 partMetaData()->isCompliant = true; 0781 } 0782 } 0783 } 0784 0785 void SignedMessagePart::setVerificationResult(const GpgME::VerificationResult &result, const QByteArray &signedData) 0786 { 0787 mSignatures = result.signatures(); 0788 // FIXME 0789 // mMetaData.auditLogError = result.error; 0790 mMetaData.isSigned = !mSignatures.empty(); 0791 if (mMetaData.isSigned) { 0792 sigStatusToMetaData(); 0793 if (!signedData.isEmpty() && mParseAfterDecryption) { 0794 parseInternal(signedData); 0795 } 0796 } 0797 } 0798 0799 QString SignedMessagePart::plaintextContent() const 0800 { 0801 if (!mNode) { 0802 return MessagePart::text(); 0803 } else { 0804 return QString(); 0805 } 0806 } 0807 0808 QString SignedMessagePart::htmlContent() const 0809 { 0810 if (!mNode) { 0811 return MessagePart::text(); 0812 } else { 0813 return QString(); 0814 } 0815 } 0816 0817 //-----CryptMessageBlock--------------------- 0818 EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp, 0819 const QString &text, 0820 const QGpgME::Protocol *cryptoProto, 0821 KMime::Content *node, 0822 KMime::Content *encryptedNode, 0823 bool parseAfterDecryption) 0824 : MessagePart(otp, text, node) 0825 , mParseAfterDecryption(parseAfterDecryption) 0826 , mPassphraseError(false) 0827 , mNoSecKey(false) 0828 , mDecryptMessage(false) 0829 , mCryptoProto(cryptoProto) 0830 , mEncryptedNode(encryptedNode) 0831 { 0832 mMetaData.isSigned = false; 0833 mMetaData.isGoodSignature = false; 0834 mMetaData.isEncrypted = false; 0835 mMetaData.isDecryptable = false; 0836 mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In."); 0837 } 0838 0839 void EncryptedMessagePart::setIsEncrypted(bool encrypted) 0840 { 0841 mMetaData.isEncrypted = encrypted; 0842 } 0843 0844 bool EncryptedMessagePart::isEncrypted() const 0845 { 0846 return mMetaData.isEncrypted; 0847 } 0848 0849 const QGpgME::Protocol *EncryptedMessagePart::cryptoProto() const 0850 { 0851 return mCryptoProto; 0852 } 0853 0854 void EncryptedMessagePart::setDecryptMessage(bool decrypt) 0855 { 0856 mDecryptMessage = decrypt; 0857 } 0858 0859 bool EncryptedMessagePart::decryptMessage() const 0860 { 0861 return mDecryptMessage; 0862 } 0863 0864 bool EncryptedMessagePart::isDecryptable() const 0865 { 0866 return mMetaData.isDecryptable; 0867 } 0868 0869 bool EncryptedMessagePart::isNoSecKey() const 0870 { 0871 return mNoSecKey; 0872 } 0873 0874 bool EncryptedMessagePart::passphraseError() const 0875 { 0876 return mPassphraseError; 0877 } 0878 0879 bool EncryptedMessagePart::decrypt(KMime::Content &data) 0880 { 0881 mError = NoError; 0882 mMetaData.errorText.clear(); 0883 // FIXME 0884 // mMetaData.auditLogError = GpgME::Error(); 0885 mMetaData.auditLog.clear(); 0886 0887 const QByteArray ciphertext = data.decodedContent(); 0888 QByteArray plainText; 0889 auto job = mCryptoProto->decryptVerifyJob(); 0890 const std::pair<GpgME::DecryptionResult, GpgME::VerificationResult> p = job->exec(ciphertext, plainText); 0891 job->deleteLater(); 0892 auto decryptResult = p.first; 0893 auto verifyResult = p.second; 0894 mMetaData.isSigned = verifyResult.signatures().size() > 0; 0895 0896 // Normalize CRLF's 0897 plainText = KMime::CRLFtoLF(plainText); 0898 auto codec = QStringDecoder(mOtp->codecNameFor(&data).constData()); 0899 const auto decoded = codec.decode(plainText); 0900 0901 partMetaData()->isSigned = verifyResult.signatures().size() > 0; 0902 0903 if (partMetaData()->isSigned) { 0904 // We simply attach a signed message part to indicate that this content is also signed 0905 // We're forwarding mNode to not loose the encoding information 0906 auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, mCryptoProto, mNode, nullptr)); 0907 subPart->setText(decoded); 0908 subPart->setVerificationResult(verifyResult, plainText); 0909 appendSubPart(subPart); 0910 } 0911 0912 mDecryptRecipients.clear(); 0913 bool cannotDecrypt = false; 0914 bool bDecryptionOk = !decryptResult.error(); 0915 0916 for (const auto &recipient : decryptResult.recipients()) { 0917 if (!recipient.status()) { 0918 bDecryptionOk = true; 0919 } 0920 GpgME::Key key; 0921 key = Kleo::KeyCache::instance()->findByKeyIDOrFingerprint(recipient.keyID()); 0922 if (key.isNull()) { 0923 auto ret = Kleo::KeyCache::instance()->findSubkeysByKeyID({recipient.keyID()}); 0924 if (ret.size() == 1) { 0925 key = ret.front().parent(); 0926 } 0927 if (key.isNull()) { 0928 qCDebug(MIMETREEPARSER_CORE_LOG) << "Found no Key for KeyID " << recipient.keyID(); 0929 } 0930 } 0931 mDecryptRecipients.emplace_back(recipient, key); 0932 } 0933 0934 if (!bDecryptionOk && partMetaData()->isSigned) { 0935 // Only a signed part 0936 partMetaData()->isEncrypted = false; 0937 bDecryptionOk = true; 0938 mDecryptedData = plainText; 0939 } else { 0940 mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_BAD_PASSPHRASE; 0941 mMetaData.isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA; 0942 0943 if (decryptResult.error().isCanceled()) { 0944 setDecryptMessage(false); 0945 } 0946 0947 partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString()); 0948 if (Kleo::DeVSCompliance::isCompliant()) { 0949 partMetaData()->isCompliant = decryptResult.isDeVs(); 0950 partMetaData()->compliance = Kleo::DeVSCompliance::name(decryptResult.isDeVs()); 0951 } else { 0952 partMetaData()->isCompliant = true; 0953 } 0954 if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) { 0955 partMetaData()->keyId = decryptResult.recipient(0).keyID(); 0956 } 0957 0958 if (bDecryptionOk) { 0959 mDecryptedData = plainText; 0960 } else { 0961 mNoSecKey = true; 0962 const auto decryRecipients = decryptResult.recipients(); 0963 for (const GpgME::DecryptionResult::Recipient &recipient : decryRecipients) { 0964 mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY); 0965 } 0966 if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly 0967 mPassphraseError = true; 0968 } 0969 } 0970 } 0971 0972 if (!bDecryptionOk) { 0973 QString cryptPlugLibName; 0974 mError = UnknownError; 0975 if (mCryptoProto) { 0976 cryptPlugLibName = mCryptoProto->name(); 0977 } 0978 0979 if (mNoSecKey) { 0980 mError = NoKeyError; 0981 } 0982 0983 if (mPassphraseError) { 0984 mError = PassphraseError; 0985 } 0986 0987 if (!mCryptoProto) { 0988 partMetaData()->errorText = i18n("No appropriate crypto plug-in was found."); 0989 } else if (cannotDecrypt) { 0990 partMetaData()->errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName); 0991 } else if (!passphraseError()) { 0992 partMetaData()->errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1StringView("<br />") 0993 + i18n("Error: %1", partMetaData()->errorText); 0994 } 0995 } 0996 return bDecryptionOk; 0997 } 0998 0999 void EncryptedMessagePart::startDecryption(KMime::Content *data) 1000 { 1001 mMetaData.isEncrypted = true; 1002 mMetaData.isDecryptable = decrypt(*data); 1003 1004 if (mParseAfterDecryption && !mMetaData.isSigned) { 1005 parseInternal(mDecryptedData); 1006 } else { 1007 setText(QString::fromUtf8(mDecryptedData.constData())); 1008 } 1009 } 1010 1011 void EncryptedMessagePart::startDecryption() 1012 { 1013 if (mEncryptedNode) { 1014 startDecryption(mEncryptedNode); 1015 } else { 1016 startDecryption(mNode); 1017 } 1018 } 1019 1020 std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> EncryptedMessagePart::decryptRecipients() const 1021 { 1022 return mDecryptRecipients; 1023 } 1024 1025 QString EncryptedMessagePart::plaintextContent() const 1026 { 1027 if (!mNode) { 1028 return MessagePart::text(); 1029 } else { 1030 return QString(); 1031 } 1032 } 1033 1034 QString EncryptedMessagePart::htmlContent() const 1035 { 1036 if (!mNode) { 1037 return MessagePart::text(); 1038 } else { 1039 return QString(); 1040 } 1041 } 1042 1043 QString EncryptedMessagePart::text() const 1044 { 1045 if (hasSubParts()) { 1046 auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>(); 1047 if (_mp) { 1048 return _mp->text(); 1049 } 1050 } 1051 1052 return MessagePart::text(); 1053 } 1054 1055 EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message) 1056 : MessagePart(otp, QString(), node) 1057 , mMessage(message) 1058 { 1059 mMetaData.isEncrypted = false; 1060 mMetaData.isSigned = false; 1061 mMetaData.isEncapsulatedRfc822Message = true; 1062 1063 if (!mMessage) { 1064 qCWarning(MIMETREEPARSER_CORE_LOG) << "Node is of type message/rfc822 but doesn't have a message!"; 1065 return; 1066 } 1067 1068 parseInternal(message.data()); 1069 } 1070 1071 QString EncapsulatedRfc822MessagePart::text() const 1072 { 1073 return renderInternalText(); 1074 } 1075 1076 QString EncapsulatedRfc822MessagePart::from() const 1077 { 1078 if (auto from = mMessage->from(false)) { 1079 return from->asUnicodeString(); 1080 } 1081 return {}; 1082 } 1083 1084 QDateTime EncapsulatedRfc822MessagePart::date() const 1085 { 1086 if (auto date = mMessage->date(false)) { 1087 return date->dateTime(); 1088 } 1089 return {}; 1090 } 1091 1092 HeadersPart::HeadersPart(ObjectTreeParser *otp, KMime::Content *node) 1093 : MessagePart(otp, QString(), node) 1094 { 1095 } 1096 1097 #include "moc_messagepart.cpp"