1 /*
2 SPDX-FileCopyrightText: 2009, 2010, 2012, 2014, 2016, 2018 Rolf Eike Beer <kde@opensource.sf-tec.de>
3 SPDX-License-Identifier: GPL-2.0-or-later
4 */
5
6 #include "kgpgtransactionprivate.h"
7 #include "kgpgtransaction.h"
8 #include "kgpg_transactions_debug.h"
9 #include "kgpg_general_debug.h"
10
11 #include <QPointer>
12 #include <QWidget>
13
14 #include <KLocalizedString>
15 #include <KIO/RenameDialog>
16
KGpgTransactionPrivate(KGpgTransaction * parent,bool allowChaining)17 KGpgTransactionPrivate::KGpgTransactionPrivate(KGpgTransaction *parent, bool allowChaining)
18 : m_parent(parent),
19 m_process(new GPGProc()),
20 m_inputTransaction(nullptr),
21 m_newPasswordDialog(nullptr),
22 m_passwordDialog(nullptr),
23 m_success(KGpgTransaction::TS_OK),
24 m_tries(3),
25 m_chainingAllowed(allowChaining),
26 m_inputProcessDone(false),
27 m_inputProcessResult(KGpgTransaction::TS_OK),
28 m_ownProcessFinished(false),
29 m_quitTries(0)
30 {
31 connect(m_process, &GPGProc::readReady, this, &KGpgTransactionPrivate::slotReadReady);
32 connect(m_process, &GPGProc::processExited, this, &KGpgTransactionPrivate::slotProcessExited);
33 connect(m_process, &GPGProc::started, this, &KGpgTransactionPrivate::slotProcessStarted);
34 }
35
~KGpgTransactionPrivate()36 KGpgTransactionPrivate::~KGpgTransactionPrivate()
37 {
38 if (m_newPasswordDialog) {
39 m_newPasswordDialog->close();
40 m_newPasswordDialog->deleteLater();
41 }
42 if (m_process->state() == QProcess::Running) {
43 m_process->closeWriteChannel();
44 m_process->terminate();
45 }
46 delete m_inputTransaction;
47 delete m_process;
48 }
49
50 void
slotReadReady()51 KGpgTransactionPrivate::slotReadReady()
52 {
53 QString line;
54 QPointer<GPGProc> process(m_process);
55 QPointer<KGpgTransaction> par(m_parent);
56
57 while (!process.isNull() && (m_process->readln(line, true) >= 0)) {
58 if (m_quitTries)
59 m_quitLines << line;
60 #ifdef KGPG_DEBUG_TRANSACTIONS
61 qCDebug(KGPG_LOG_TRANSACTIONS) << m_parent << line;
62 #endif /* KGPG_DEBUG_TRANSACTIONS */
63
64 static const QString getBool = QLatin1String("[GNUPG:] GET_BOOL ");
65 if (keyConsidered(line)) {
66 // already handled by keyConsidered - skip the line
67 } else if (line.startsWith(QLatin1String("[GNUPG:] USERID_HINT "))) {
68 m_parent->addIdHint(line);
69 } else if (line.startsWith(QLatin1String("[GNUPG:] BAD_PASSPHRASE "))) {
70 // the MISSING_PASSPHRASE line comes first, in that case ignore a
71 // following BAD_PASSPHRASE
72 if (m_success != KGpgTransaction::TS_USER_ABORTED)
73 m_success = KGpgTransaction::TS_BAD_PASSPHRASE;
74 } else if (line.startsWith(QLatin1String("[GNUPG:] GET_HIDDEN passphrase.enter"))) {
75 const bool goOn = m_parent->passphraseRequested();
76
77 // Check if the object was deleted while waiting for the result
78 if (!goOn || par.isNull())
79 return;
80
81 } else if (line.startsWith(QLatin1String("[GNUPG:] GOOD_PASSPHRASE"))) {
82 Q_EMIT m_parent->statusMessage(i18n("Got Passphrase"));
83
84 if (m_passwordDialog != nullptr) {
85 m_passwordDialog->close();
86 m_passwordDialog->deleteLater();
87 m_passwordDialog = nullptr;
88 }
89
90 if (m_parent->passphraseReceived()) {
91 // signal GnuPG that there will be no further input and it can
92 // begin sending output.
93 m_process->closeWriteChannel();
94 }
95
96 } else if (line.startsWith(getBool)) {
97 const QString question = line.mid(getBool.length());
98
99 KGpgTransaction::ts_boolanswer answer;
100
101 if (question.startsWith(QLatin1String("openfile.overwrite.okay"))) {
102 m_overwriteUrl.clear();
103 answer = m_parent->confirmOverwrite(m_overwriteUrl);
104
105 if ((answer == KGpgTransaction::BA_UNKNOWN) && !m_overwriteUrl.isEmpty()) {
106 QPointer<KIO::RenameDialog> over = new KIO::RenameDialog(qobject_cast<QWidget *>(m_parent->parent()),
107 i18n("File Already Exists"), QUrl(),
108 m_overwriteUrl, KIO::RenameDialog_Overwrite);
109
110 m_overwriteUrl.clear();
111
112 switch (over->exec()) {
113 case KIO::Result_Overwrite:
114 answer = KGpgTransaction::BA_YES;
115 break;
116 case KIO::Result_Rename:
117 answer = KGpgTransaction::BA_NO;
118 m_overwriteUrl = over->newDestUrl();
119 break;
120 default:
121 answer = KGpgTransaction::BA_UNKNOWN;
122 m_parent->setSuccess(KGpgTransaction::TS_USER_ABORTED);
123 // Close the pipes, otherwise GnuPG will try to answer
124 // further questions about this file.
125 m_process->closeWriteChannel();
126 m_process->closeReadChannel(QProcess::StandardOutput);
127 break;
128 }
129
130 delete over;
131
132 if (answer == KGpgTransaction::BA_UNKNOWN)
133 continue;
134 }
135 } else {
136 answer = m_parent->boolQuestion(question);
137 }
138
139 switch (answer) {
140 case KGpgTransaction::BA_YES:
141 write("YES\n");
142 break;
143 case KGpgTransaction::BA_NO:
144 write("NO\n");
145 break;
146 case KGpgTransaction::BA_UNKNOWN:
147 m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE);
148 m_parent->unexpectedLine(line);
149 sendQuit();
150 }
151 } else if (!m_overwriteUrl.isEmpty() && line.startsWith(QLatin1String("[GNUPG:] GET_LINE openfile.askoutname"))) {
152 write(m_overwriteUrl.toLocalFile().toUtf8() + '\n');
153 m_overwriteUrl.clear();
154 } else if (line.startsWith(QLatin1String("[GNUPG:] MISSING_PASSPHRASE"))) {
155 m_success = KGpgTransaction::TS_USER_ABORTED;
156 } else if (line.startsWith(QLatin1String("[GNUPG:] CARDCTRL "))) {
157 // just ignore them, pinentry should handle that
158 } else {
159 // all known hints
160 int i = 0;
161 bool matched = false;
162 for (const QString &hintName : hintNames()) {
163 const KGpgTransaction::ts_hintType h = static_cast<KGpgTransaction::ts_hintType>(i++);
164 if (!line.startsWith(hintName))
165 continue;
166
167 matched = true;
168
169 bool r;
170 const int skip = hintName.length();
171 if (line.length() == skip) {
172 r = m_parent->hintLine(h, QString());
173 } else {
174 r = m_parent->hintLine(h, line.mid(skip + 1).trimmed());
175 }
176
177 if (!r) {
178 m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE);
179 sendQuit();
180 }
181
182 break;
183 }
184
185 if (!matched) {
186 if (m_parent->nextLine(line))
187 sendQuit();
188 }
189 }
190 }
191 }
192
193 void
slotProcessExited()194 KGpgTransactionPrivate::slotProcessExited()
195 {
196 Q_ASSERT(sender() == m_process);
197
198 m_ownProcessFinished = true;
199
200 if (m_inputProcessDone)
201 processDone();
202 }
203
204 void
slotProcessStarted()205 KGpgTransactionPrivate::slotProcessStarted()
206 {
207 m_parent->postStart();
208 }
209
210 void
sendQuit(void)211 KGpgTransactionPrivate::sendQuit(void)
212 {
213 write("quit\n");
214
215 #ifdef KGPG_DEBUG_TRANSACTIONS
216 if (m_quitTries == 0)
217 qCDebug(KGPG_LOG_TRANSACTIONS) << "sending quit";
218 #endif /* KGPG_DEBUG_TRANSACTIONS */
219
220 if (m_quitTries++ >= 3) {
221 qCDebug(KGPG_LOG_GENERAL) << "tried" << m_quitTries << "times to quit the GnuPG session";
222 qCDebug(KGPG_LOG_GENERAL) << "last input was" << m_quitLines;
223 qCDebug(KGPG_LOG_GENERAL) << "please file a bug report at https://bugs.kde.org";
224 m_process->closeWriteChannel();
225 m_success = KGpgTransaction::TS_MSG_SEQUENCE;
226 }
227 }
228
229 void
slotInputTransactionDone(int result)230 KGpgTransactionPrivate::slotInputTransactionDone(int result)
231 {
232 Q_ASSERT(sender() == m_inputTransaction);
233
234 m_inputProcessDone = true;
235 m_inputProcessResult = result;
236
237 if (m_ownProcessFinished)
238 processDone();
239 }
240
241 void
slotPassphraseEntered(const QString & passphrase)242 KGpgTransactionPrivate::slotPassphraseEntered(const QString &passphrase)
243 {
244 // not calling KGpgTransactionPrivate::write() here for obvious privacy reasons
245 m_process->write(passphrase.toUtf8() + '\n');
246 if (sender() == m_newPasswordDialog) {
247 m_newPasswordDialog->deleteLater();
248 m_newPasswordDialog = nullptr;
249 m_parent->newPassphraseEntered();
250 } else {
251 Q_ASSERT(sender() == m_passwordDialog);
252 }
253 }
254
255 void
slotPassphraseAborted()256 KGpgTransactionPrivate::slotPassphraseAborted()
257 {
258 Q_ASSERT((sender() == m_passwordDialog) ^ (sender() == m_newPasswordDialog));
259 sender()->deleteLater();
260 m_newPasswordDialog = nullptr;
261 m_passwordDialog = nullptr;
262 handlePassphraseAborted();
263 }
264
265 void
handlePassphraseAborted()266 KGpgTransactionPrivate::handlePassphraseAborted()
267 {
268 // sending "quit" here is useless as it would be interpreted as the passphrase
269 m_process->closeWriteChannel();
270 m_success = KGpgTransaction::TS_USER_ABORTED;
271 }
272
273 void
write(const QByteArray & a)274 KGpgTransactionPrivate::write(const QByteArray &a)
275 {
276 m_process->write(a);
277 #ifdef KGPG_DEBUG_TRANSACTIONS
278 qCDebug(KGPG_LOG_TRANSACTIONS) << m_parent << a;
279 #endif /* KGPG_DEBUG_TRANSACTIONS */
280 }
281
282 const QStringList &
hintNames(void)283 KGpgTransactionPrivate::hintNames (void)
284 {
285 static QStringList hints;
286
287 if (hints.isEmpty()) {
288 hints.insert(KGpgTransaction::HT_KEYEXPIRED,
289 QLatin1String("[GNUPG:] KEYEXPIRED"));
290 hints.insert(KGpgTransaction::HT_SIGEXPIRED,
291 QLatin1String("[GNUPG:] SIGEXPIRED"));
292 hints.insert(KGpgTransaction::HT_NOSECKEY,
293 QLatin1String("[GNUPG:] NO_SECKEY"));
294 hints.insert(KGpgTransaction::HT_ENCTO,
295 QLatin1String("[GNUPG:] ENC_TO"));
296 hints.insert(KGpgTransaction::HT_PINENTRY_LAUNCHED,
297 QLatin1String("[GNUPG:] PINENTRY_LAUNCHED"));
298 }
299
300 return hints;
301 }
302
303 void
processDone()304 KGpgTransactionPrivate::processDone()
305 {
306 m_parent->finish();
307 Q_EMIT m_parent->infoProgress(100, 100);
308 Q_EMIT m_parent->done(m_success);
309 #ifdef KGPG_DEBUG_TRANSACTIONS
310 qCDebug(KGPG_LOG_TRANSACTIONS) << this << "result:" << m_success;
311 #endif /* KGPG_DEBUG_TRANSACTIONS */
312 }
313
keyConsidered(const QString & line)314 bool KGpgTransactionPrivate::keyConsidered(const QString& line)
315 {
316 if (!line.startsWith(QLatin1String("[GNUPG:] KEY_CONSIDERED ")))
317 return false;
318
319 const QStringList &parts = line.split(QLatin1Char(' '), Qt::SkipEmptyParts);
320 if (parts.count() < 3)
321 m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE);
322 else if (!m_expectedFingerprints.isEmpty() &&
323 !m_expectedFingerprints.contains(parts[2], Qt::CaseInsensitive))
324 m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE);
325
326 return true;
327 }
328