File indexing completed on 2024-04-28 09:46:10

0001 /*
0002     SPDX-FileCopyrightText: 2002 Jean-Baptiste Mardelle <bj@altern.org>
0003     SPDX-FileCopyrightText: 2008, 2009, 2010, 2011, 2012, 2013 Rolf Eike Beer <kde@opensource.sf-tec.de>
0004     SPDX-FileCopyrightText: 2016 Andrius Ć tikoans <andrius@stikonas.eu>
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "kgpgexternalactions.h"
0009 
0010 #include "detailedconsole.h"
0011 #include "foldercompressjob.h"
0012 #include "keyservers.h"
0013 #include "keysmanager.h"
0014 #include "kgpgfirstassistant.h"
0015 #include "kgpginterface.h"
0016 #include "kgpgsettings.h"
0017 #include "kgpgtextinterface.h"
0018 #include "selectpublickeydialog.h"
0019 #include "selectsecretkey.h"
0020 #include "core/images.h"
0021 #include "editor/kgpgeditor.h"
0022 #include "editor/kgpgtextedit.h"
0023 #include "transactions/kgpgdecrypt.h"
0024 #include "transactions/kgpgencrypt.h"
0025 #include "transactions/kgpgsigntext.h"
0026 #include "transactions/kgpgtransactionjob.h"
0027 #include "transactions/kgpgverify.h"
0028 
0029 #include <KActionCollection>
0030 #include <KHelpClient>
0031 #include <KMessageBox>
0032 
0033 #include <KJobTrackerInterface>
0034 #include <QComboBox>
0035 #include <QFont>
0036 #include <QHBoxLayout>
0037 #include <QProcess>
0038 #include <QStringListModel>
0039 #include <QTemporaryFile>
0040 #include <KIO/Global>
0041 #include <KIO/RenameDialog>
0042 #include <KIO/JobTracker>
0043 
0044 KGpgExternalActions::KGpgExternalActions(KeysManager *parent, KGpgItemModel *model)
0045     : QObject(parent),
0046     compressionScheme(0),
0047     m_model(model),
0048     m_kgpgfoldertmp(nullptr),
0049     m_keysmanager(parent)
0050 {
0051     readOptions();
0052 }
0053 
0054 KGpgExternalActions::~KGpgExternalActions()
0055 {
0056     delete m_kgpgfoldertmp;
0057 }
0058 
0059 void KGpgExternalActions::encryptFiles(KeysManager *parent, const QList<QUrl> &urls)
0060 {
0061     Q_ASSERT(!urls.isEmpty());
0062 
0063     KGpgExternalActions *encActions = new KGpgExternalActions(parent, parent->getModel());
0064 
0065     KgpgSelectPublicKeyDlg *dialog = new KgpgSelectPublicKeyDlg(parent, parent->getModel(), encActions->goDefaultKey(), false, urls);
0066     connect(dialog, &KgpgSelectPublicKeyDlg::accepted, encActions, &KGpgExternalActions::slotEncryptionKeySelected);
0067     connect(dialog, &KgpgSelectPublicKeyDlg::rejected, dialog, &KgpgSelectPublicKeyDlg::deleteLater);
0068     connect(dialog, &KgpgSelectPublicKeyDlg::rejected, encActions, &KGpgExternalActions::deleteLater);
0069     dialog->show();
0070 }
0071 
0072 void KGpgExternalActions::slotEncryptionKeySelected()
0073 {
0074     KgpgSelectPublicKeyDlg *dialog = qobject_cast<KgpgSelectPublicKeyDlg *>(sender());
0075     Q_ASSERT(dialog != nullptr);
0076     sender()->deleteLater();
0077 
0078     QStringList opts;
0079     QString defaultKey;
0080 
0081     if (KGpgSettings::encryptFilesTo()) {
0082         if (KGpgSettings::pgpCompatibility())
0083             opts << QLatin1String( "--pgp6" );
0084 
0085         defaultKey = KGpgSettings::fileEncryptionKey();
0086     }
0087 
0088     KGpgEncrypt::EncryptOptions eopt = KGpgEncrypt::DefaultEncryption;
0089 
0090     if (dialog->getUntrusted())
0091         eopt |= KGpgEncrypt::AllowUntrustedEncryption;
0092     if (dialog->getArmor())
0093         eopt |= KGpgEncrypt::AsciiArmored;
0094     if (dialog->getHideId())
0095         eopt |= KGpgEncrypt::HideKeyId;
0096 
0097     if (KGpgSettings::allowCustomEncryptionOptions()) {
0098         const QString customopts = dialog->getCustomOptions();
0099 
0100         if (!customopts.isEmpty())
0101             opts << customopts.split(QLatin1Char(' '), Qt::SkipEmptyParts);
0102     }
0103 
0104     QStringList keys = dialog->selectedKeys();
0105 
0106     if (!defaultKey.isEmpty() && !keys.contains(defaultKey))
0107         keys.append(defaultKey);
0108 
0109     if (dialog->getSymmetric())
0110         keys.clear();
0111 
0112     KGpgEncrypt *enc = new KGpgEncrypt(dialog->parent(), keys, dialog->getFiles(), eopt, opts);
0113     KGpgTransactionJob *encjob = new KGpgTransactionJob(enc);
0114 
0115     KIO::getJobTracker()->registerJob(encjob);
0116     encjob->start();
0117 
0118     deleteLater();
0119 }
0120 
0121 void KGpgExternalActions::encryptFolders(KeysManager *parent, const QList<QUrl> &urls)
0122 {
0123     QTemporaryFile *tmpfolder = new QTemporaryFile();
0124 
0125     if (!tmpfolder->open()) {
0126         delete tmpfolder;
0127         KMessageBox::error(parent, i18n("Cannot create temporary file for folder compression."), i18n("Temporary File Creation"));
0128         return;
0129     }
0130 
0131     if (KMessageBox::Continue != KMessageBox::warningContinueCancel(parent,
0132                 i18n("<qt>KGpg will now create a temporary archive file:<br /><b>%1</b> to process the encryption. "
0133                 "The file will be deleted after the encryption is finished.</qt>",
0134                 tmpfolder->fileName()), i18n("Temporary File Creation"), KStandardGuiItem::cont(),
0135                 KStandardGuiItem::cancel(), QLatin1String( "FolderTmpFile" ))) {
0136         delete tmpfolder;
0137         return;
0138     }
0139 
0140     KGpgExternalActions *encActions = new KGpgExternalActions(parent, parent->getModel());
0141     KgpgSelectPublicKeyDlg *dialog = new KgpgSelectPublicKeyDlg(parent, parent->getModel(), encActions->goDefaultKey(), false, urls);
0142     encActions->m_kgpgfoldertmp = tmpfolder;
0143 
0144     QWidget *bGroup = new QWidget(dialog->optionsbox);
0145     QHBoxLayout *bGroupHBoxLayout = new QHBoxLayout(bGroup);
0146     bGroupHBoxLayout->setContentsMargins(0, 0, 0, 0);
0147 
0148     (void) new QLabel(i18n("Compression method for archive:"), bGroup);
0149 
0150     QComboBox *optionbx = new QComboBox(bGroup);
0151     bGroupHBoxLayout->addWidget(optionbx);
0152     optionbx->setModel(new QStringListModel(FolderCompressJob::archiveNames(), bGroup));
0153 
0154     connect(optionbx, QOverload<int>::of(&QComboBox::activated), encActions, &KGpgExternalActions::slotSetCompression);
0155     connect(dialog, &KgpgSelectPublicKeyDlg::accepted, encActions, &KGpgExternalActions::startFolderEncode);
0156     connect(dialog, &KgpgSelectPublicKeyDlg::rejected, encActions, &KGpgExternalActions::deleteLater);
0157     connect(dialog, &KgpgSelectPublicKeyDlg::rejected, dialog, &KgpgSelectPublicKeyDlg::deleteLater);
0158     
0159     dialog->show();
0160 }
0161 
0162 void KGpgExternalActions::slotSetCompression(int cp)
0163 {
0164     compressionScheme = cp;
0165 }
0166 
0167 void KGpgExternalActions::startFolderEncode()
0168 {
0169     KgpgSelectPublicKeyDlg *dialog = qobject_cast<KgpgSelectPublicKeyDlg *>(sender());
0170     Q_ASSERT(dialog != nullptr);
0171     dialog->deleteLater();
0172 
0173     const QList<QUrl> urls = dialog->getFiles();
0174 
0175     QStringList selec = dialog->selectedKeys();
0176     KGpgEncrypt::EncryptOptions encOptions = KGpgEncrypt::DefaultEncryption;
0177     const QStringList encryptOptions = dialog->getCustomOptions().split(QLatin1Char(' '),  Qt::SkipEmptyParts);
0178     if (dialog->getSymmetric()) {
0179         selec.clear();
0180     } else {
0181         Q_ASSERT(!selec.isEmpty());
0182     }
0183 
0184     QString extension = FolderCompressJob::extensionForArchive(compressionScheme);
0185 
0186     if (dialog->getArmor())
0187         extension += QLatin1String( ".asc" );
0188     else if (KGpgSettings::pgpExtension())
0189         extension += QLatin1String( ".pgp" );
0190     else
0191         extension += QLatin1String( ".gpg" );
0192 
0193     if (dialog->getArmor())
0194         encOptions |= KGpgEncrypt::AsciiArmored;
0195     if (dialog->getHideId())
0196         encOptions |= KGpgEncrypt::HideKeyId;
0197     if (dialog->getUntrusted())
0198         encOptions |= KGpgEncrypt::AllowUntrustedEncryption;
0199 
0200     QUrl encryptedFile(QUrl::fromLocalFile(urls.first().adjusted(QUrl::StripTrailingSlash).path() + extension));
0201     QFile encryptedFolder(encryptedFile.path());
0202     dialog->hide();
0203     if (encryptedFolder.exists()) {
0204         QPointer<KIO::RenameDialog> over = new KIO::RenameDialog(m_keysmanager, i18n("File Already Exists"),
0205                 QUrl(), encryptedFile, KIO::RenameDialog_Overwrite);
0206         if (over->exec() == QDialog::Rejected) {
0207             dialog = nullptr;
0208             delete over;
0209             deleteLater();
0210             return;
0211         }
0212         encryptedFile = over->newDestUrl();
0213         delete over;
0214     }
0215 
0216     FolderCompressJob *trayinfo = new FolderCompressJob(m_keysmanager, urls, encryptedFile, m_kgpgfoldertmp,
0217             selec, encryptOptions, encOptions, compressionScheme);
0218     connect(trayinfo, &FolderCompressJob::result, this, &KGpgExternalActions::slotFolderFinished);
0219     KIO::getJobTracker()->registerJob(trayinfo);
0220     trayinfo->start();
0221 }
0222 
0223 void KGpgExternalActions::slotFolderFinished(KJob *job)
0224 {
0225     FolderCompressJob *trayinfo = qobject_cast<FolderCompressJob *>(job);
0226     Q_ASSERT(trayinfo != nullptr);
0227 
0228     if (trayinfo->error())
0229         KMessageBox::error(m_keysmanager, trayinfo->errorString());
0230 
0231     deleteLater();
0232 }
0233 
0234 void KGpgExternalActions::verifyFile(QUrl url)
0235 {
0236     // check file signature
0237     if (url.isEmpty())
0238         return;
0239 
0240     QString sigfile;
0241     // try to find detached signature.
0242     if (!url.fileName().endsWith(QLatin1String(".sig"))) {
0243         sigfile = url.path() + QLatin1String( ".sig" );
0244         if (!QFile::exists(sigfile)) {
0245             sigfile = url.path() + QLatin1String( ".asc" );
0246             // if no .asc or .sig signature file included, assume the file is internally signed
0247             if (!QFile::exists(sigfile))
0248                 sigfile.clear();
0249         }
0250     } else {
0251         sigfile = url.path();
0252         sigfile.chop(4);
0253     }
0254 
0255     KGpgVerify *kgpv = new KGpgVerify(parent(), QList<QUrl>({QUrl(sigfile)}));
0256     connect(kgpv, &KGpgVerify::done, this, &KGpgExternalActions::slotVerificationDone);
0257     kgpv->start();
0258 }
0259 
0260 void KGpgExternalActions::slotVerificationDone(int result)
0261 {
0262     KGpgVerify *kgpv = qobject_cast<KGpgVerify *>(sender());
0263     Q_ASSERT(kgpv != nullptr);
0264     kgpv->deleteLater();
0265 
0266     if (result == KGpgVerify::TS_MISSING_KEY) {
0267         KeyServer *kser = new KeyServer(m_keysmanager, m_model);
0268         kser->slotSetText(kgpv->missingId());
0269         kser->slotImport();
0270     } else {
0271         const QStringList messages = kgpv->getMessages();
0272 
0273         if (messages.isEmpty())
0274             return;
0275 
0276         QStringList msglist;
0277         for (QString rawmsg : messages)
0278             msglist << rawmsg.replace(QLatin1Char('<'), QLatin1String("&lt;"));
0279 
0280         (void) new KgpgDetailedInfo(m_keysmanager, KGpgVerify::getReport(messages, m_model),
0281                 msglist.join(QLatin1String("<br/>")),
0282                 QStringList(), i18nc("Caption of message box", "Verification Finished"));
0283     }
0284 }
0285 
0286 void KGpgExternalActions::signFiles(KeysManager* parent, const QList<QUrl>& urls)
0287 {
0288     Q_ASSERT(!urls.isEmpty());
0289 
0290     KGpgExternalActions *signActions = new KGpgExternalActions(parent, parent->getModel());
0291 
0292     signActions->droppedUrls = urls;
0293 
0294     KgpgSelectSecretKey *keydlg = new KgpgSelectSecretKey(parent, parent->getModel(), false);
0295     connect(keydlg, &KgpgSelectSecretKey::accepted, signActions, &KGpgExternalActions::slotSignFiles);
0296     connect(keydlg, &KgpgSelectSecretKey::rejected, keydlg, &KgpgSelectSecretKey::deleteLater);
0297     connect(keydlg, &KgpgSelectSecretKey::rejected, signActions, &KGpgExternalActions::deleteLater);
0298     keydlg->show();
0299 }
0300 
0301 void KGpgExternalActions::slotSignFiles()
0302 {
0303     KgpgSelectSecretKey *keydlg = qobject_cast<KgpgSelectSecretKey *>(sender());
0304     Q_ASSERT(keydlg != nullptr);
0305     sender()->deleteLater();
0306 
0307     const QString signKeyID = keydlg->getKeyID();
0308 
0309     QStringList Options;
0310     KGpgSignText::SignOptions sopts = KGpgSignText::DetachedSignature;
0311     if (KGpgSettings::asciiArmor()) {
0312         Options << QLatin1String( "--armor" );
0313         sopts |= KGpgSignText::AsciiArmored;
0314     }
0315     if (KGpgSettings::pgpCompatibility())
0316         Options << QLatin1String( "--pgp6" );
0317 
0318     if (droppedUrls.count() > 1) {
0319         KGpgTextInterface *signFileProcess = new KGpgTextInterface(parent(), signKeyID, Options);
0320         connect(signFileProcess, &KGpgTextInterface::fileSignFinished, signFileProcess, &KGpgTextInterface::deleteLater);
0321         signFileProcess->signFiles(droppedUrls);
0322     } else {
0323         KGpgSignText *signt = new KGpgSignText(parent(), signKeyID, droppedUrls, sopts);
0324         connect(signt, &KGpgSignText::done, signt, &KGpgSignText::deleteLater);
0325         signt->start();
0326     }
0327 
0328     deleteLater();
0329 }
0330 
0331 void KGpgExternalActions::decryptFiles(KeysManager* parent, const QList<QUrl> &urls)
0332 {
0333     KGpgExternalActions *decActions = new KGpgExternalActions(parent, parent->getModel());
0334 
0335     decActions->decryptFile(urls);
0336 }
0337 
0338 void KGpgExternalActions::decryptFile(QList<QUrl> urls)
0339 {
0340     if (urls.isEmpty()) {
0341         deleteLater();
0342         return;
0343     }
0344 
0345     while (!urls.first().isLocalFile()) {
0346         showDroppedFile(urls.takeFirst());
0347     }
0348 
0349     QUrl first = urls.first();
0350 
0351     QString oldname(first.fileName());
0352     if (oldname.endsWith(QLatin1String(".gpg"), Qt::CaseInsensitive) ||
0353             oldname.endsWith(QLatin1String(".asc"), Qt::CaseInsensitive) ||
0354             oldname.endsWith(QLatin1String(".pgp"), Qt::CaseInsensitive))
0355         oldname.chop(4);
0356     else
0357         oldname.append(QLatin1String( ".clear" ));
0358 
0359     QUrl swapname = QUrl::fromLocalFile(first.adjusted(QUrl::RemoveFilename).path() + oldname);
0360     QFile fgpg(swapname.path());
0361     if (fgpg.exists()) {
0362         QPointer<KIO::RenameDialog> over = new KIO::RenameDialog(m_keysmanager,
0363                 i18n("File Already Exists"), QUrl(), swapname, KIO::RenameDialog_Overwrite);
0364         if (over->exec() != QDialog::Accepted) {
0365             delete over;
0366             urls.pop_front();
0367             decryptFile(urls);
0368             return;
0369         }
0370 
0371         swapname = over->newDestUrl();
0372         delete over;
0373     }
0374 
0375     droppedUrls = urls;
0376     KGpgDecrypt *decr = new KGpgDecrypt(this, droppedUrls.first(), swapname);
0377     connect(decr, &KGpgDecrypt::done, this, &KGpgExternalActions::slotDecryptionDone);
0378     decr->start();
0379 }
0380 
0381 void KGpgExternalActions::slotDecryptionDone(int status)
0382 {
0383     KGpgDecrypt *decr = qobject_cast<KGpgDecrypt *>(sender());
0384     Q_ASSERT(decr != nullptr);
0385 
0386     if (status != KGpgTransaction::TS_OK)
0387         m_decryptionFailed << droppedUrls.first();
0388 
0389     decr->deleteLater();
0390 
0391     droppedUrls.pop_front();
0392 
0393     if (!droppedUrls.isEmpty()) {
0394         decryptFile(droppedUrls);
0395     } else {
0396         if (!m_decryptionFailed.isEmpty()) {
0397             QStringList failedFiles;
0398             for (const QUrl &url : std::as_const(m_decryptionFailed))
0399                 failedFiles.append(url.toDisplayString());
0400             KMessageBox::errorList(nullptr,
0401                     i18np("Decryption of this file failed:", "Decryption of these files failed:",
0402                     m_decryptionFailed.count()), failedFiles,
0403                     i18n("Decryption failed."));
0404         }
0405         deleteLater();
0406     }
0407 }
0408 
0409 void KGpgExternalActions::showDroppedFile(const QUrl &file)
0410 {
0411     KgpgEditor *kgpgtxtedit = new KgpgEditor(m_keysmanager, m_model, {});
0412     connect(m_keysmanager, &KeysManager::fontChanged, kgpgtxtedit, &KgpgEditor::slotSetFont);
0413 
0414     kgpgtxtedit->m_editor->openDroppedFile(file, false);
0415 
0416     kgpgtxtedit->show();
0417 }
0418 
0419 void KGpgExternalActions::readOptions()
0420 {
0421     if (KGpgSettings::firstRun()) {
0422         firstRun();
0423     } else if (KGpgSettings::gpgConfigPath().isEmpty()) {
0424                 if (KMessageBox::PrimaryAction == KMessageBox::questionTwoActions(nullptr,
0425                 i18n("<qt>You have not set a path to your GnuPG config file.<br />This may cause some surprising results in KGpg's execution."
0426                 "<br />Would you like to start KGpg's assistant to fix this problem?</qt>"),
0427                 QString(), KGuiItem(i18n("Start Assistant")), KGuiItem(i18n("Do Not Start"))))
0428             startAssistant();
0429     }
0430 }
0431 
0432 void KGpgExternalActions::firstRun()
0433 {
0434     QProcess *createConfigProc = new QProcess(this);
0435     QStringList args;
0436     args << QLatin1String( "--no-tty" ) << QLatin1String( "--list-secret-keys" );
0437     createConfigProc->start(QLatin1String( "gpg" ), args);  // start GnuPG so that it will create a config file
0438     createConfigProc->waitForFinished();
0439     startAssistant();
0440 }
0441 
0442 void KGpgExternalActions::startAssistant()
0443 {
0444     if (m_assistant.isNull()) {
0445         m_assistant = new KGpgFirstAssistant(m_keysmanager);
0446 
0447         connect(m_assistant.data(), &KGpgFirstAssistant::accepted, this, &KGpgExternalActions::slotSaveOptionsPath);
0448         connect(m_assistant.data(), &KGpgFirstAssistant::rejected, m_assistant.data(), &KGpgFirstAssistant::deleteLater);
0449         connect(m_assistant->button(QDialogButtonBox::Help), &QPushButton::clicked, this, &KGpgExternalActions::help);
0450     }
0451 
0452     m_assistant->show();
0453 }
0454 
0455 void KGpgExternalActions::slotSaveOptionsPath()
0456 {
0457     KGpgSettings::setAutoStart(m_assistant->getAutoStart());
0458     KGpgSettings::setGpgConfigPath(m_assistant->getConfigPath());
0459     KGpgSettings::setFirstRun(false);
0460 
0461     const QString gpgConfServer(KgpgInterface::getGpgSetting(QLatin1String( "keyserver" ), KGpgSettings::gpgConfigPath()));
0462     if (!gpgConfServer.isEmpty()) {
0463         // The user already had configured a keyserver, set this one as default.
0464         QStringList serverList(KGpgSettings::keyServers());
0465         serverList.prepend(gpgConfServer);
0466         KGpgSettings::setKeyServers(serverList);
0467     }
0468 
0469     const QString defaultID(m_assistant->getDefaultKey());
0470 
0471     KGpgSettings::self()->save();
0472     Q_EMIT updateDefault(defaultID);
0473     if (m_assistant->runKeyGenerate())
0474         Q_EMIT createNewKey();
0475     m_assistant->deleteLater();
0476 }
0477 
0478 void KGpgExternalActions::help()
0479 {
0480     KHelpClient::invokeHelp(QString(), QLatin1String( "kgpg" ));
0481 }
0482 
0483 QKeySequence KGpgExternalActions::goDefaultKey() const
0484 {
0485     return QKeySequence(qobject_cast<QAction *>(m_keysmanager->actionCollection()->action(QLatin1String( "go_default_key" )))->shortcut());
0486 }