File indexing completed on 2023-12-03 09:19:03

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