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("&lt;"));
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