File indexing completed on 2024-12-22 05:07:41

0001 /*
0002     SPDX-FileCopyrightText: 2016 Daniel Vrátil <dvratil@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "key.h"
0008 #include "setupmanager.h"
0009 #include "transport.h"
0010 
0011 #include <QTimer>
0012 
0013 #include <QGpgME/CryptoConfig>
0014 #include <QGpgME/Protocol>
0015 #include <QGpgME/WKSPublishJob>
0016 
0017 #include <gpgme++/engineinfo.h>
0018 
0019 #include <MailTransport/Transport>
0020 #include <MailTransport/TransportManager>
0021 #include <MailTransportAkonadi/MessageQueueJob>
0022 
0023 #include <KIdentityManagement/Identity>
0024 #include <KIdentityManagement/IdentityManager>
0025 
0026 #include <KMime/Headers>
0027 #include <KMime/Message>
0028 #include <KMime/Util>
0029 
0030 #include <KEmailAddress>
0031 
0032 #include <KLocalizedString>
0033 
0034 // This code was taken from kmail-account-wizard
0035 
0036 Key::Key(QObject *parent)
0037     : SetupObject(parent)
0038 {
0039 }
0040 
0041 Key::~Key() = default;
0042 
0043 void Key::setKey(const GpgME::Key &key)
0044 {
0045     m_key = key;
0046 }
0047 
0048 void Key::setMailBox(const QString &mailbox)
0049 {
0050     m_mailbox = KEmailAddress::extractEmailAddress(mailbox);
0051 }
0052 
0053 void Key::setTransportId(int transportId)
0054 {
0055     m_transportId = transportId;
0056 }
0057 
0058 void Key::setPublishingMethod(PublishingMethod method)
0059 {
0060     m_publishingMethod = method;
0061 }
0062 
0063 void Key::create()
0064 {
0065     switch (m_publishingMethod) {
0066     case NoPublishing:
0067         QTimer::singleShot(0, this, [this]() {
0068             Q_EMIT finished(i18n("Skipping key publishing"));
0069         });
0070         break;
0071     case WKS:
0072         publishWKS();
0073         break;
0074     case PKS:
0075         publishPKS();
0076         break;
0077     }
0078 }
0079 
0080 void Key::publishWKS()
0081 {
0082     Q_EMIT info(i18n("Publishing OpenPGP key..."));
0083 
0084     auto job = QGpgME::openpgp()->wksPublishJob();
0085     mJob = job;
0086     connect(job, &QGpgME::WKSPublishJob::result, this, &Key::onWKSPublishingCheckDone);
0087     job->startCheck(m_mailbox);
0088 }
0089 
0090 void Key::onWKSPublishingCheckDone(const GpgME::Error &gpgMeError, const QByteArray &, const QByteArray &returnedError)
0091 {
0092     mJob = nullptr;
0093 
0094     if (gpgMeError) {
0095         if (gpgMeError.isCanceled()) {
0096             Q_EMIT error(i18n("Key publishing was canceled."));
0097             return;
0098         }
0099 
0100         qWarning() << "Check error:" << returnedError;
0101         if (gpgMeError.code() == GPG_ERR_NOT_SUPPORTED) {
0102             Q_EMIT info(i18n("Key publishing failed: not online, or GnuPG too old."));
0103             Q_EMIT finished(QString());
0104         } else {
0105             Q_EMIT info(i18n("Your email provider does not support key publishing."));
0106             Q_EMIT finished(QString());
0107         }
0108         return;
0109     }
0110 
0111     auto job = QGpgME::openpgp()->wksPublishJob();
0112     mJob = job;
0113     connect(job, &QGpgME::WKSPublishJob::result, this, &Key::onWKSPublishingRequestCreated);
0114     job->startCreate(m_key.primaryFingerprint(), m_mailbox);
0115 }
0116 
0117 void Key::onWKSPublishingRequestCreated(const GpgME::Error &gpgMeError, const QByteArray &returnedData, const QByteArray &returnedError)
0118 {
0119     mJob = nullptr;
0120 
0121     if (gpgMeError) {
0122         if (gpgMeError.isCanceled()) {
0123             Q_EMIT error(i18n("Key publishing was canceled."));
0124             return;
0125         }
0126 
0127         qWarning() << "Publishing error:" << returnedData << returnedError;
0128         Q_EMIT error(i18n("An error occurred while creating key publishing request."));
0129         return;
0130     }
0131 
0132     if (m_transportId == 0 && qobject_cast<SetupManager *>(parent())) {
0133         const auto setupManager = qobject_cast<SetupManager *>(parent());
0134         const auto setupObjects = setupManager->setupObjects();
0135         auto it = std::find_if(setupObjects.cbegin(), setupObjects.cend(), [](SetupObject *obj) -> bool {
0136             return qobject_cast<Transport *>(obj);
0137         });
0138         if (it != setupObjects.cend()) {
0139             m_transportId = qobject_cast<Transport *>(*it)->transportId();
0140         }
0141     } else if (m_transportId) {
0142         auto ident = KIdentityManagement::IdentityManager::self()->identityForAddress(m_mailbox);
0143         if (!ident.transport().isEmpty()) {
0144             m_transportId = ident.transport().toInt();
0145         }
0146     }
0147 
0148     auto transport = MailTransport::TransportManager::self()->transportById(m_transportId, true);
0149     if (!transport) {
0150         qWarning() << "No MailTransport::Transport available?!?!?!";
0151         Q_EMIT error(i18n("Key publishing error: mail transport is not configured"));
0152         return;
0153     }
0154 
0155     qDebug() << returnedData;
0156 
0157     // Parse the data so that we can get "To" and "From" headers
0158     auto msg = KMime::Message::Ptr::create();
0159     msg->setContent(KMime::CRLFtoLF(returnedData));
0160     msg->parse();
0161 
0162     if (!msg->from(false) || !msg->to(false)) {
0163         qWarning() << "No FROM or TO in parsed message, source data were:" << returnedData;
0164         Q_EMIT error(i18n("Key publishing error: failed to create request email"));
0165         return;
0166     }
0167 
0168     auto header = new KMime::Headers::Generic("X-KMail-Transport");
0169     header->fromUnicodeString(QString::number(m_transportId), "utf-8");
0170     msg->setHeader(header);
0171 
0172     // Build the message
0173     msg->assemble();
0174 
0175     // Move to outbox
0176     auto job = new MailTransport::MessageQueueJob;
0177     mJob = job;
0178     job->addressAttribute().setTo({msg->to(false)->asUnicodeString()});
0179     job->transportAttribute().setTransportId(transport->id());
0180     job->addressAttribute().setFrom(msg->from(false)->asUnicodeString());
0181     // Don't leave any evidence :-)
0182     job->sentBehaviourAttribute().setSentBehaviour(MailTransport::SentBehaviourAttribute::Delete);
0183     job->sentBehaviourAttribute().setSendSilently(true);
0184     job->setMessage(msg);
0185     connect(job, &KJob::result, this, &Key::onWKSPublishingRequestSent);
0186     job->start();
0187 }
0188 
0189 void Key::onWKSPublishingRequestSent(KJob *job)
0190 {
0191     mJob = nullptr;
0192     if (job->error() == KJob::KilledJobError) {
0193         Q_EMIT error(i18n("Key publishing was canceled."));
0194     } else if (job->error()) {
0195         Q_EMIT error(i18n("Failed to send key publishing request: %1", job->errorString()));
0196     } else {
0197         Q_EMIT finished(i18n("Key publishing request sent."));
0198     }
0199 }
0200 
0201 void Key::publishPKS()
0202 {
0203     Q_EMIT info(i18n("Publishing OpenPGP key..."));
0204 
0205     // default
0206     QString keyServer = QStringLiteral("key.gnupg.net");
0207 
0208     const auto config = QGpgME::cryptoConfig();
0209     if (config) {
0210         const auto entry = config->entry(QStringLiteral("gpg"), QStringLiteral("Keyserver"), QStringLiteral("keyserver"));
0211         if (entry && !entry->stringValue().isEmpty()) {
0212             keyServer = entry->stringValue();
0213         }
0214     }
0215 
0216     const char *gpgName = GpgME::engineInfo(GpgME::OpenPGP).fileName();
0217     auto gpgProcess = new QProcess;
0218     gpgProcess->setProperty("keyServer", keyServer);
0219     connect(gpgProcess, &QProcess::finished, this, &Key::onPKSPublishingFinished);
0220     mJob = gpgProcess;
0221     gpgProcess->start(QString::fromLatin1(gpgName),
0222                       {QStringLiteral("--keyserver"), keyServer, QStringLiteral("--send-keys"), QString::fromLatin1(m_key.primaryFingerprint())});
0223 }
0224 
0225 void Key::onPKSPublishingFinished(int code, QProcess::ExitStatus status)
0226 {
0227     auto process = qobject_cast<QProcess *>(mJob);
0228     mJob = nullptr;
0229     process->deleteLater();
0230 
0231     if (status != QProcess::NormalExit || code != 0) {
0232         qWarning() << "PKS Publishing error:" << process->readAll();
0233         Q_EMIT info(i18n("Failed to publish the key."));
0234         Q_EMIT finished(QString());
0235         return;
0236     }
0237 
0238     const auto keyServer = process->property("keyServer").toString();
0239     Q_EMIT finished(i18n("Key has been published on %1", keyServer));
0240 }
0241 
0242 void Key::destroy()
0243 {
0244     // This is all we can do, there's no unpublish...
0245     if (auto job = qobject_cast<QGpgME::Job *>(mJob)) {
0246         job->slotCancel();
0247     } else if (auto job = qobject_cast<KJob *>(mJob)) {
0248         job->kill();
0249     } else if (auto job = qobject_cast<QProcess *>(mJob)) {
0250         job->kill();
0251     }
0252 }