File indexing completed on 2025-01-19 04:46:49

0001 /*
0002   SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "gnupgwksurlhandler.h"
0008 #include "gnupgwks_debug.h"
0009 #include "gnupgwksmessagepart.h"
0010 
0011 #include <QProcess>
0012 #include <QString>
0013 #include <QUrlQuery>
0014 
0015 #include <MessageViewer/Viewer>
0016 #include <MimeTreeParser/BodyPart>
0017 #include <MimeTreeParser/NodeHelper>
0018 
0019 #include <QGpgME/Protocol>
0020 #include <QGpgME/WKSPublishJob>
0021 
0022 #include <Akonadi/MessageQueueJob>
0023 #include <MailTransport/Transport>
0024 #include <MailTransport/TransportManager>
0025 
0026 #include <KIdentityManagementCore/Identity>
0027 #include <KIdentityManagementCore/IdentityManager>
0028 
0029 #include <KMime/Util>
0030 
0031 #include <Akonadi/ItemDeleteJob>
0032 
0033 #include <KLocalizedString>
0034 
0035 using namespace MimeTreeParser::Interface;
0036 
0037 bool ApplicationGnuPGWKSUrlHandler::handleContextMenuRequest(BodyPart *, const QString &, const QPoint &) const
0038 {
0039     return false;
0040 }
0041 
0042 QString ApplicationGnuPGWKSUrlHandler::name() const
0043 {
0044     return QStringLiteral("ApplicationGnuPGWKSUrlHandler");
0045 }
0046 
0047 bool ApplicationGnuPGWKSUrlHandler::handleClick(MessageViewer::Viewer *viewerInstance, BodyPart *part, const QString &path) const
0048 {
0049     Q_UNUSED(viewerInstance)
0050 
0051     if (!path.startsWith(QLatin1StringView("gnupgwks?"))) {
0052         return false;
0053     }
0054 
0055     const QUrlQuery q(path.mid(sizeof("gnupgwks?") - 1));
0056     if (q.queryItemValue(QStringLiteral("action")) == QLatin1StringView("show")) {
0057         const QString progFullPath = QStandardPaths::findExecutable(QStringLiteral("kleopatra"));
0058         if (progFullPath.isEmpty()
0059             || !QProcess::startDetached(QStringLiteral("kleopatra"), {QStringLiteral("--query"), q.queryItemValue(QStringLiteral("fpr"))})) {
0060             return false;
0061         }
0062         return true;
0063     } else if (q.queryItemValue(QStringLiteral("action")) == QLatin1StringView("confirm")) {
0064         GnuPGWKSMessagePart mp(part);
0065         if (!sendConfirmation(viewerInstance, mp)) {
0066             part->nodeHelper()->setProperty((QStringLiteral("__GnuPGWKS") + mp.fingerprint()).toLatin1().constData(), QStringLiteral("error"));
0067         }
0068         return true;
0069     }
0070 
0071     return false;
0072 }
0073 
0074 QString ApplicationGnuPGWKSUrlHandler::statusBarMessage(BodyPart *part, const QString &path) const
0075 {
0076     Q_UNUSED(part)
0077 
0078     if (!path.startsWith(QLatin1StringView("gnupgwks?"))) {
0079         return {};
0080     }
0081 
0082     const QUrlQuery q(path.mid(sizeof("gnupgwks?") - 1));
0083     const QString actionStr = q.queryItemValue(QStringLiteral("action"));
0084     if (actionStr == QLatin1StringView("show")) {
0085         return i18n("Display key details");
0086     } else if (actionStr == QLatin1StringView("confirm")) {
0087         return i18n("Publish the key");
0088     }
0089     return {};
0090 }
0091 
0092 QByteArray ApplicationGnuPGWKSUrlHandler::createConfirmation(const KMime::Message::Ptr &msg) const
0093 {
0094     auto job = QGpgME::openpgp()->wksPublishJob();
0095     QEventLoop el;
0096     QByteArray result;
0097     QObject::connect(job,
0098                      &QGpgME::WKSPublishJob::result,
0099                      [&el, &result](const GpgME::Error &, const QByteArray &returnedData, const QByteArray &returnedError) {
0100                          if (returnedData.isEmpty()) {
0101                              qCWarning(GNUPGWKS_LOG) << "GPG:" << returnedError;
0102                          }
0103                          result = returnedData;
0104                          el.quit();
0105                      });
0106     job->startReceive(msg->encodedContent());
0107     el.exec();
0108 
0109     return result;
0110 }
0111 
0112 bool ApplicationGnuPGWKSUrlHandler::sendConfirmation(MessageViewer::Viewer *viewerInstance, const GnuPGWKSMessagePart &mp) const
0113 {
0114     const QByteArray data = createConfirmation(viewerInstance->message());
0115     if (data.isEmpty()) {
0116         return false;
0117     }
0118 
0119     auto msg = KMime::Message::Ptr::create();
0120     msg->setContent(KMime::CRLFtoLF(data));
0121     msg->parse();
0122 
0123     // Find identity
0124     const auto identity = KIdentityManagementCore::IdentityManager::self()->identityForAddress(mp.address());
0125     const bool nullIdentity = (identity == KIdentityManagementCore::Identity::null());
0126     if (!nullIdentity) {
0127         auto x_header = new KMime::Headers::Generic("X-KMail-Identity");
0128         x_header->from7BitString(QByteArray::number(identity.uoid()));
0129         msg->setHeader(x_header);
0130     }
0131 
0132     // Find transport set in the identity, fallback to default transport
0133     auto transportMgr = MailTransport::TransportManager::self();
0134     const bool identityHasTransport = !identity.transport().isEmpty();
0135     int transportId = -1;
0136     if (!nullIdentity && identityHasTransport) {
0137         transportId = identity.transport().toInt();
0138     } else {
0139         transportId = transportMgr->defaultTransportId();
0140     }
0141     // No transport exists, ask user to create one
0142     if (transportId == -1) {
0143         if (!transportMgr->showTransportCreationDialog(nullptr, MailTransport::TransportManager::IfNoTransportExists)) {
0144             return false;
0145         }
0146         transportId = transportMgr->defaultTransportId();
0147     }
0148     auto header = new KMime::Headers::Generic("X-KMail-Transport");
0149     header->fromUnicodeString(QString::number(transportId), "utf-8");
0150     msg->setHeader(header);
0151 
0152     // Build the message
0153     msg->assemble();
0154 
0155     // Move to outbox
0156     auto transport = transportMgr->transportById(transportId);
0157     auto job = new Akonadi::MessageQueueJob;
0158     job->addressAttribute().setTo({msg->to(false)->asUnicodeString()});
0159     job->transportAttribute().setTransportId(transport->id());
0160     job->addressAttribute().setFrom(msg->from(false)->asUnicodeString());
0161     job->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::Delete);
0162     job->sentBehaviourAttribute().setSendSilently(true);
0163     job->setMessage(msg);
0164 
0165     // Send
0166     if (!job->exec()) {
0167         qCWarning(GNUPGWKS_LOG) << "Error queuing message in output:" << job->errorText();
0168         return false;
0169     }
0170 
0171     // Delete the original request
0172     // Don't use viewerInstance->deleteMessage(), which triggers Move To Trash,
0173     // we want to get rid of the message for good.
0174     new Akonadi::ItemDeleteJob(viewerInstance->messageItem());
0175     return true;
0176 }