1 /*
2 SPDX-FileCopyrightText: 2002 Jean-Baptiste Mardelle <bj@altern.org>
3 SPDX-FileCopyrightText: 2008, 2009, 2010, 2011, 2012, 2013 Rolf Eike Beer <kde@opensource.sf-tec.de>
4 SPDX-FileCopyrightText: 2016 Andrius Štikoans <andrius@stikonas.eu>
5 SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "kgpgexternalactions.h"
9
10 #include "detailedconsole.h"
11 #include "foldercompressjob.h"
12 #include "keyservers.h"
13 #include "keysmanager.h"
14 #include "kgpgfirstassistant.h"
15 #include "kgpginterface.h"
16 #include "kgpgsettings.h"
17 #include "kgpgtextinterface.h"
18 #include "selectpublickeydialog.h"
19 #include "selectsecretkey.h"
20 #include "core/images.h"
21 #include "editor/kgpgeditor.h"
22 #include "editor/kgpgtextedit.h"
23 #include "transactions/kgpgdecrypt.h"
24 #include "transactions/kgpgencrypt.h"
25 #include "transactions/kgpgsigntext.h"
26 #include "transactions/kgpgtransactionjob.h"
27 #include "transactions/kgpgverify.h"
28
29 #include <KActionCollection>
30 #include <KHelpClient>
31 #include <KMessageBox>
32
33 #include <QComboBox>
34 #include <QFont>
35 #include <QHBoxLayout>
36 #include <QProcess>
37 #include <QStringListModel>
38 #include <QTemporaryFile>
39 #include <kio/global.h>
40 #include <kio/renamedialog.h>
41 #include <kjobtrackerinterface.h>
42
KGpgExternalActions(KeysManager * parent,KGpgItemModel * model)43 KGpgExternalActions::KGpgExternalActions(KeysManager *parent, KGpgItemModel *model)
44 : QObject(parent),
45 compressionScheme(0),
46 m_model(model),
47 m_kgpgfoldertmp(nullptr),
48 m_keysmanager(parent)
49 {
50 readOptions();
51 }
52
~KGpgExternalActions()53 KGpgExternalActions::~KGpgExternalActions()
54 {
55 delete m_kgpgfoldertmp;
56 }
57
encryptFiles(KeysManager * parent,const QList<QUrl> & urls)58 void KGpgExternalActions::encryptFiles(KeysManager *parent, const QList<QUrl> &urls)
59 {
60 Q_ASSERT(!urls.isEmpty());
61
62 KGpgExternalActions *encActions = new KGpgExternalActions(parent, parent->getModel());
63
64 KgpgSelectPublicKeyDlg *dialog = new KgpgSelectPublicKeyDlg(parent, parent->getModel(), encActions->goDefaultKey(), false, urls);
65 connect(dialog, &KgpgSelectPublicKeyDlg::accepted, encActions, &KGpgExternalActions::slotEncryptionKeySelected);
66 connect(dialog, &KgpgSelectPublicKeyDlg::rejected, dialog, &KgpgSelectPublicKeyDlg::deleteLater);
67 connect(dialog, &KgpgSelectPublicKeyDlg::rejected, encActions, &KGpgExternalActions::deleteLater);
68 dialog->show();
69 }
70
slotEncryptionKeySelected()71 void KGpgExternalActions::slotEncryptionKeySelected()
72 {
73 KgpgSelectPublicKeyDlg *dialog = qobject_cast<KgpgSelectPublicKeyDlg *>(sender());
74 Q_ASSERT(dialog != nullptr);
75 sender()->deleteLater();
76
77 QStringList opts;
78 QString defaultKey;
79
80 if (KGpgSettings::encryptFilesTo()) {
81 if (KGpgSettings::pgpCompatibility())
82 opts << QLatin1String( "--pgp6" );
83
84 defaultKey = KGpgSettings::fileEncryptionKey();
85 }
86
87 KGpgEncrypt::EncryptOptions eopt = KGpgEncrypt::DefaultEncryption;
88
89 if (dialog->getUntrusted())
90 eopt |= KGpgEncrypt::AllowUntrustedEncryption;
91 if (dialog->getArmor())
92 eopt |= KGpgEncrypt::AsciiArmored;
93 if (dialog->getHideId())
94 eopt |= KGpgEncrypt::HideKeyId;
95
96 if (KGpgSettings::allowCustomEncryptionOptions()) {
97 const QString customopts = dialog->getCustomOptions();
98
99 if (!customopts.isEmpty())
100 opts << customopts.split(QLatin1Char(' '), Qt::SkipEmptyParts);
101 }
102
103 QStringList keys = dialog->selectedKeys();
104
105 if (!defaultKey.isEmpty() && !keys.contains(defaultKey))
106 keys.append(defaultKey);
107
108 if (dialog->getSymmetric())
109 keys.clear();
110
111 KGpgEncrypt *enc = new KGpgEncrypt(dialog->parent(), keys, dialog->getFiles(), eopt, opts);
112 KGpgTransactionJob *encjob = new KGpgTransactionJob(enc);
113
114 KIO::getJobTracker()->registerJob(encjob);
115 encjob->start();
116
117 deleteLater();
118 }
119
encryptFolders(KeysManager * parent,const QList<QUrl> & urls)120 void KGpgExternalActions::encryptFolders(KeysManager *parent, const QList<QUrl> &urls)
121 {
122 QTemporaryFile *tmpfolder = new QTemporaryFile();
123
124 if (!tmpfolder->open()) {
125 delete tmpfolder;
126 KMessageBox::sorry(parent, i18n("Cannot create temporary file for folder compression."), i18n("Temporary File Creation"));
127 return;
128 }
129
130 if (KMessageBox::Continue != KMessageBox::warningContinueCancel(parent,
131 i18n("<qt>KGpg will now create a temporary archive file:<br /><b>%1</b> to process the encryption. "
132 "The file will be deleted after the encryption is finished.</qt>",
133 tmpfolder->fileName()), i18n("Temporary File Creation"), KStandardGuiItem::cont(),
134 KStandardGuiItem::cancel(), QLatin1String( "FolderTmpFile" ))) {
135 delete tmpfolder;
136 return;
137 }
138
139 KGpgExternalActions *encActions = new KGpgExternalActions(parent, parent->getModel());
140 KgpgSelectPublicKeyDlg *dialog = new KgpgSelectPublicKeyDlg(parent, parent->getModel(), encActions->goDefaultKey(), false, urls);
141 encActions->m_kgpgfoldertmp = tmpfolder;
142
143 QWidget *bGroup = new QWidget(dialog->optionsbox);
144 QHBoxLayout *bGroupHBoxLayout = new QHBoxLayout(bGroup);
145 bGroupHBoxLayout->setContentsMargins(0, 0, 0, 0);
146
147 (void) new QLabel(i18n("Compression method for archive:"), bGroup);
148
149 QComboBox *optionbx = new QComboBox(bGroup);
150 bGroupHBoxLayout->addWidget(optionbx);
151 optionbx->setModel(new QStringListModel(FolderCompressJob::archiveNames(), bGroup));
152
153 connect(optionbx, QOverload<int>::of(&QComboBox::activated), encActions, &KGpgExternalActions::slotSetCompression);
154 connect(dialog, &KgpgSelectPublicKeyDlg::accepted, encActions, &KGpgExternalActions::startFolderEncode);
155 connect(dialog, &KgpgSelectPublicKeyDlg::rejected, encActions, &KGpgExternalActions::deleteLater);
156 connect(dialog, &KgpgSelectPublicKeyDlg::rejected, dialog, &KgpgSelectPublicKeyDlg::deleteLater);
157
158 dialog->show();
159 }
160
slotSetCompression(int cp)161 void KGpgExternalActions::slotSetCompression(int cp)
162 {
163 compressionScheme = cp;
164 }
165
startFolderEncode()166 void KGpgExternalActions::startFolderEncode()
167 {
168 KgpgSelectPublicKeyDlg *dialog = qobject_cast<KgpgSelectPublicKeyDlg *>(sender());
169 Q_ASSERT(dialog != nullptr);
170 dialog->deleteLater();
171
172 const QList<QUrl> urls = dialog->getFiles();
173
174 QStringList selec = dialog->selectedKeys();
175 KGpgEncrypt::EncryptOptions encOptions = KGpgEncrypt::DefaultEncryption;
176 const QStringList encryptOptions = dialog->getCustomOptions().split(QLatin1Char(' '), Qt::SkipEmptyParts);
177 if (dialog->getSymmetric()) {
178 selec.clear();
179 } else {
180 Q_ASSERT(!selec.isEmpty());
181 }
182
183 QString extension = FolderCompressJob::extensionForArchive(compressionScheme);
184
185 if (dialog->getArmor())
186 extension += QLatin1String( ".asc" );
187 else if (KGpgSettings::pgpExtension())
188 extension += QLatin1String( ".pgp" );
189 else
190 extension += QLatin1String( ".gpg" );
191
192 if (dialog->getArmor())
193 encOptions |= KGpgEncrypt::AsciiArmored;
194 if (dialog->getHideId())
195 encOptions |= KGpgEncrypt::HideKeyId;
196 if (dialog->getUntrusted())
197 encOptions |= KGpgEncrypt::AllowUntrustedEncryption;
198
199 QUrl encryptedFile(QUrl::fromLocalFile(urls.first().adjusted(QUrl::StripTrailingSlash).path() + extension));
200 QFile encryptedFolder(encryptedFile.path());
201 dialog->hide();
202 if (encryptedFolder.exists()) {
203 QPointer<KIO::RenameDialog> over = new KIO::RenameDialog(m_keysmanager, i18n("File Already Exists"),
204 QUrl(), encryptedFile, KIO::RenameDialog_Overwrite);
205 if (over->exec() == QDialog::Rejected) {
206 dialog = nullptr;
207 delete over;
208 deleteLater();
209 return;
210 }
211 encryptedFile = over->newDestUrl();
212 delete over;
213 }
214
215 FolderCompressJob *trayinfo = new FolderCompressJob(m_keysmanager, urls, encryptedFile, m_kgpgfoldertmp,
216 selec, encryptOptions, encOptions, compressionScheme);
217 connect(trayinfo, &FolderCompressJob::result, this, &KGpgExternalActions::slotFolderFinished);
218 KIO::getJobTracker()->registerJob(trayinfo);
219 trayinfo->start();
220 }
221
slotFolderFinished(KJob * job)222 void KGpgExternalActions::slotFolderFinished(KJob *job)
223 {
224 FolderCompressJob *trayinfo = qobject_cast<FolderCompressJob *>(job);
225 Q_ASSERT(trayinfo != nullptr);
226
227 if (trayinfo->error())
228 KMessageBox::sorry(m_keysmanager, trayinfo->errorString());
229
230 deleteLater();
231 }
232
verifyFile(QUrl url)233 void KGpgExternalActions::verifyFile(QUrl url)
234 {
235 // check file signature
236 if (url.isEmpty())
237 return;
238
239 QString sigfile;
240 // try to find detached signature.
241 if (!url.fileName().endsWith(QLatin1String(".sig"))) {
242 sigfile = url.path() + QLatin1String( ".sig" );
243 QFile fsig(sigfile);
244 if (!fsig.exists()) {
245 sigfile = url.path() + QLatin1String( ".asc" );
246 QFile fsig(sigfile);
247 // if no .asc or .sig signature file included, assume the file is internally signed
248 if (!fsig.exists())
249 sigfile.clear();
250 }
251 } else {
252 sigfile = url.path();
253 sigfile.chop(4);
254 url = QUrl(sigfile);
255 }
256
257 KGpgVerify *kgpv = new KGpgVerify(parent(), QList<QUrl>({QUrl(sigfile)}));
258 connect(kgpv, &KGpgVerify::done, this, &KGpgExternalActions::slotVerificationDone);
259 kgpv->start();
260 }
261
slotVerificationDone(int result)262 void KGpgExternalActions::slotVerificationDone(int result)
263 {
264 KGpgVerify *kgpv = qobject_cast<KGpgVerify *>(sender());
265 Q_ASSERT(kgpv != nullptr);
266 kgpv->deleteLater();
267
268 if (result == KGpgVerify::TS_MISSING_KEY) {
269 KeyServer *kser = new KeyServer(m_keysmanager, m_model);
270 kser->slotSetText(kgpv->missingId());
271 kser->slotImport();
272 } else {
273 const QStringList messages = kgpv->getMessages();
274
275 if (messages.isEmpty())
276 return;
277
278 QStringList msglist;
279 for (QString rawmsg : messages)
280 msglist << rawmsg.replace(QLatin1Char('<'), QLatin1String("<"));
281
282 (void) new KgpgDetailedInfo(m_keysmanager, KGpgVerify::getReport(messages, m_model),
283 msglist.join(QLatin1String("<br/>")),
284 QStringList(), i18nc("Caption of message box", "Verification Finished"));
285 }
286 }
287
signFiles(KeysManager * parent,const QList<QUrl> & urls)288 void KGpgExternalActions::signFiles(KeysManager* parent, const QList<QUrl>& urls)
289 {
290 Q_ASSERT(!urls.isEmpty());
291
292 KGpgExternalActions *signActions = new KGpgExternalActions(parent, parent->getModel());
293
294 signActions->droppedUrls = urls;
295
296 KgpgSelectSecretKey *keydlg = new KgpgSelectSecretKey(parent, parent->getModel(), false);
297 connect(keydlg, &KgpgSelectSecretKey::accepted, signActions, &KGpgExternalActions::slotSignFiles);
298 connect(keydlg, &KgpgSelectSecretKey::rejected, keydlg, &KgpgSelectSecretKey::deleteLater);
299 connect(keydlg, &KgpgSelectSecretKey::rejected, signActions, &KGpgExternalActions::deleteLater);
300 keydlg->show();
301 }
302
slotSignFiles()303 void KGpgExternalActions::slotSignFiles()
304 {
305 KgpgSelectSecretKey *keydlg = qobject_cast<KgpgSelectSecretKey *>(sender());
306 Q_ASSERT(keydlg != nullptr);
307 sender()->deleteLater();
308
309 const QString signKeyID = keydlg->getKeyID();
310
311 QStringList Options;
312 KGpgSignText::SignOptions sopts = KGpgSignText::DetachedSignature;
313 if (KGpgSettings::asciiArmor()) {
314 Options << QLatin1String( "--armor" );
315 sopts |= KGpgSignText::AsciiArmored;
316 }
317 if (KGpgSettings::pgpCompatibility())
318 Options << QLatin1String( "--pgp6" );
319
320 if (droppedUrls.count() > 1) {
321 KGpgTextInterface *signFileProcess = new KGpgTextInterface(parent(), signKeyID, Options);
322 connect(signFileProcess, &KGpgTextInterface::fileSignFinished, signFileProcess, &KGpgTextInterface::deleteLater);
323 signFileProcess->signFiles(droppedUrls);
324 } else {
325 KGpgSignText *signt = new KGpgSignText(parent(), signKeyID, droppedUrls, sopts);
326 connect(signt, &KGpgSignText::done, signt, &KGpgSignText::deleteLater);
327 signt->start();
328 }
329
330 deleteLater();
331 }
332
decryptFiles(KeysManager * parent,const QList<QUrl> & urls)333 void KGpgExternalActions::decryptFiles(KeysManager* parent, const QList<QUrl> &urls)
334 {
335 KGpgExternalActions *decActions = new KGpgExternalActions(parent, parent->getModel());
336
337 decActions->decryptFile(urls);
338 }
339
decryptFile(QList<QUrl> urls)340 void KGpgExternalActions::decryptFile(QList<QUrl> urls)
341 {
342 if (urls.isEmpty()) {
343 deleteLater();
344 return;
345 }
346
347 while (!urls.first().isLocalFile()) {
348 showDroppedFile(urls.takeFirst());
349 }
350
351 QUrl first = urls.first();
352
353 QString oldname(first.fileName());
354 if (oldname.endsWith(QLatin1String(".gpg"), Qt::CaseInsensitive) ||
355 oldname.endsWith(QLatin1String(".asc"), Qt::CaseInsensitive) ||
356 oldname.endsWith(QLatin1String(".pgp"), Qt::CaseInsensitive))
357 oldname.chop(4);
358 else
359 oldname.append(QLatin1String( ".clear" ));
360
361 QUrl swapname = QUrl::fromLocalFile(first.adjusted(QUrl::RemoveFilename).path() + oldname);
362 QFile fgpg(swapname.path());
363 if (fgpg.exists()) {
364 QPointer<KIO::RenameDialog> over = new KIO::RenameDialog(m_keysmanager,
365 i18n("File Already Exists"), QUrl(), swapname, KIO::RenameDialog_Overwrite);
366 if (over->exec() != QDialog::Accepted) {
367 delete over;
368 urls.pop_front();
369 decryptFile(urls);
370 return;
371 }
372
373 swapname = over->newDestUrl();
374 delete over;
375 }
376
377 droppedUrls = urls;
378 KGpgDecrypt *decr = new KGpgDecrypt(this, droppedUrls.first(), swapname);
379 connect(decr, &KGpgDecrypt::done, this, &KGpgExternalActions::slotDecryptionDone);
380 decr->start();
381 }
382
slotDecryptionDone(int status)383 void KGpgExternalActions::slotDecryptionDone(int status)
384 {
385 KGpgDecrypt *decr = qobject_cast<KGpgDecrypt *>(sender());
386 Q_ASSERT(decr != nullptr);
387
388 if (status != KGpgTransaction::TS_OK)
389 m_decryptionFailed << droppedUrls.first();
390
391 decr->deleteLater();
392
393 droppedUrls.pop_front();
394
395 if (!droppedUrls.isEmpty()) {
396 decryptFile(droppedUrls);
397 } else {
398 if (!m_decryptionFailed.isEmpty()) {
399 QStringList failedFiles;
400 for (const QUrl &url : qAsConst(m_decryptionFailed))
401 failedFiles.append(url.toDisplayString());
402 KMessageBox::errorList(nullptr,
403 i18np("Decryption of this file failed:", "Decryption of these files failed:",
404 m_decryptionFailed.count()), failedFiles,
405 i18n("Decryption failed."));
406 }
407 deleteLater();
408 }
409 }
410
showDroppedFile(const QUrl & file)411 void KGpgExternalActions::showDroppedFile(const QUrl &file)
412 {
413 KgpgEditor *kgpgtxtedit = new KgpgEditor(m_keysmanager, m_model, {});
414 connect(m_keysmanager, &KeysManager::fontChanged, kgpgtxtedit, &KgpgEditor::slotSetFont);
415
416 kgpgtxtedit->m_editor->openDroppedFile(file, false);
417
418 kgpgtxtedit->show();
419 }
420
readOptions()421 void KGpgExternalActions::readOptions()
422 {
423 if (KGpgSettings::firstRun()) {
424 firstRun();
425 } else if (KGpgSettings::gpgConfigPath().isEmpty()) {
426 if (KMessageBox::Yes == KMessageBox::questionYesNo(nullptr,
427 i18n("<qt>You have not set a path to your GnuPG config file.<br />This may cause some surprising results in KGpg's execution."
428 "<br />Would you like to start KGpg's assistant to fix this problem?</qt>"),
429 QString(), KGuiItem(i18n("Start Assistant")), KGuiItem(i18n("Do Not Start"))))
430 startAssistant();
431 }
432 }
433
firstRun()434 void KGpgExternalActions::firstRun()
435 {
436 QProcess *createConfigProc = new QProcess(this);
437 QStringList args;
438 args << QLatin1String( "--no-tty" ) << QLatin1String( "--list-secret-keys" );
439 createConfigProc->start(QLatin1String( "gpg" ), args); // start GnuPG so that it will create a config file
440 createConfigProc->waitForFinished();
441 startAssistant();
442 }
443
startAssistant()444 void KGpgExternalActions::startAssistant()
445 {
446 if (m_assistant.isNull()) {
447 m_assistant = new KGpgFirstAssistant(m_keysmanager);
448
449 connect(m_assistant.data(), &KGpgFirstAssistant::accepted, this, &KGpgExternalActions::slotSaveOptionsPath);
450 connect(m_assistant.data(), &KGpgFirstAssistant::rejected, m_assistant.data(), &KGpgFirstAssistant::deleteLater);
451 connect(m_assistant->button(QDialogButtonBox::Help), &QPushButton::clicked, this, &KGpgExternalActions::help);
452 }
453
454 m_assistant->show();
455 }
456
slotSaveOptionsPath()457 void KGpgExternalActions::slotSaveOptionsPath()
458 {
459 KGpgSettings::setAutoStart(m_assistant->getAutoStart());
460 KGpgSettings::setGpgConfigPath(m_assistant->getConfigPath());
461 KGpgSettings::setFirstRun(false);
462
463 const QString gpgConfServer(KgpgInterface::getGpgSetting(QLatin1String( "keyserver" ), KGpgSettings::gpgConfigPath()));
464 if (!gpgConfServer.isEmpty()) {
465 // The user already had configured a keyserver, set this one as default.
466 QStringList serverList(KGpgSettings::keyServers());
467 serverList.prepend(gpgConfServer);
468 KGpgSettings::setKeyServers(serverList);
469 }
470
471 const QString defaultID(m_assistant->getDefaultKey());
472
473 KGpgSettings::self()->save();
474 Q_EMIT updateDefault(defaultID);
475 if (m_assistant->runKeyGenerate())
476 Q_EMIT createNewKey();
477 m_assistant->deleteLater();
478 }
479
help()480 void KGpgExternalActions::help()
481 {
482 KHelpClient::invokeHelp(QString(), QLatin1String( "kgpg" ));
483 }
484
goDefaultKey() const485 QKeySequence KGpgExternalActions::goDefaultKey() const
486 {
487 return QKeySequence(qobject_cast<QAction *>(m_keysmanager->actionCollection()->action(QLatin1String( "go_default_key" )))->shortcut());
488 }
489