File indexing completed on 2024-04-21 05:50:44

0001 /*
0002     SPDX-FileCopyrightText: 2009, 2010, 2012, 2014, 2016, 2018 Rolf Eike Beer <kde@opensource.sf-tec.de>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "kgpgtransactionprivate.h"
0007 #include "kgpgtransaction.h"
0008 #include "kgpg_transactions_debug.h"
0009 #include "kgpg_general_debug.h"
0010 
0011 #include <QPointer>
0012 #include <QWidget>
0013 
0014 #include <KLocalizedString>
0015 #include <KIO/RenameDialog>
0016 
0017 KGpgTransactionPrivate::KGpgTransactionPrivate(KGpgTransaction *parent, bool allowChaining)
0018     : m_parent(parent),
0019     m_process(new GPGProc()),
0020     m_inputTransaction(nullptr),
0021     m_newPasswordDialog(nullptr),
0022     m_passwordDialog(nullptr),
0023     m_success(KGpgTransaction::TS_OK),
0024     m_tries(3),
0025     m_chainingAllowed(allowChaining),
0026     m_inputProcessDone(false),
0027     m_inputProcessResult(KGpgTransaction::TS_OK),
0028     m_ownProcessFinished(false),
0029     m_quitTries(0)
0030 {
0031     connect(m_process, &GPGProc::readReady, this, &KGpgTransactionPrivate::slotReadReady);
0032     connect(m_process, &GPGProc::processExited, this, &KGpgTransactionPrivate::slotProcessExited);
0033     connect(m_process, &GPGProc::started, this, &KGpgTransactionPrivate::slotProcessStarted);
0034 }
0035 
0036 KGpgTransactionPrivate::~KGpgTransactionPrivate()
0037 {
0038     if (m_newPasswordDialog) {
0039         m_newPasswordDialog->close();
0040         m_newPasswordDialog->deleteLater();
0041     }
0042     if (m_process->state() == QProcess::Running) {
0043         m_process->closeWriteChannel();
0044         m_process->terminate();
0045     }
0046     delete m_inputTransaction;
0047     delete m_process;
0048 }
0049 
0050 void
0051 KGpgTransactionPrivate::slotReadReady()
0052 {
0053     QString line;
0054     QPointer<GPGProc> process(m_process);
0055     QPointer<KGpgTransaction> par(m_parent);
0056 
0057     while (!process.isNull() && (m_process->readln(line, true) >= 0)) {
0058         if (m_quitTries)
0059             m_quitLines << line;
0060 #ifdef KGPG_DEBUG_TRANSACTIONS
0061         qCDebug(KGPG_LOG_TRANSACTIONS) << m_parent << line;
0062 #endif /* KGPG_DEBUG_TRANSACTIONS */
0063 
0064         static const QString getBool = QLatin1String("[GNUPG:] GET_BOOL ");
0065         if (keyConsidered(line)) {
0066             // already handled by keyConsidered - skip the line
0067         } else if (line.startsWith(QLatin1String("[GNUPG:] USERID_HINT "))) {
0068             m_parent->addIdHint(line);
0069         } else if (line.startsWith(QLatin1String("[GNUPG:] BAD_PASSPHRASE "))) {
0070             // the MISSING_PASSPHRASE line comes first, in that case ignore a
0071             // following BAD_PASSPHRASE
0072             if (m_success != KGpgTransaction::TS_USER_ABORTED)
0073                 m_success = KGpgTransaction::TS_BAD_PASSPHRASE;
0074         } else if (line.startsWith(QLatin1String("[GNUPG:] GET_HIDDEN passphrase.enter"))) {
0075             const bool goOn = m_parent->passphraseRequested();
0076 
0077             // Check if the object was deleted while waiting for the result
0078             if (!goOn || par.isNull())
0079                 return;
0080 
0081         } else if (line.startsWith(QLatin1String("[GNUPG:] GOOD_PASSPHRASE"))) {
0082             Q_EMIT m_parent->statusMessage(i18n("Got Passphrase"));
0083 
0084             if (m_passwordDialog != nullptr) {
0085                 m_passwordDialog->close();
0086                 m_passwordDialog->deleteLater();
0087                 m_passwordDialog = nullptr;
0088             }
0089 
0090             if (m_parent->passphraseReceived()) {
0091                 // signal GnuPG that there will be no further input and it can
0092                 // begin sending output.
0093                 m_process->closeWriteChannel();
0094             }
0095 
0096         } else if (line.startsWith(getBool)) {
0097             const QString question = line.mid(getBool.length());
0098 
0099             KGpgTransaction::ts_boolanswer answer;
0100 
0101             if (question.startsWith(QLatin1String("openfile.overwrite.okay"))) {
0102                 m_overwriteUrl.clear();
0103                 answer = m_parent->confirmOverwrite(m_overwriteUrl);
0104 
0105                 if ((answer == KGpgTransaction::BA_UNKNOWN) && !m_overwriteUrl.isEmpty()) {
0106                     QPointer<KIO::RenameDialog> over = new KIO::RenameDialog(qobject_cast<QWidget *>(m_parent->parent()),
0107                             i18n("File Already Exists"), QUrl(),
0108                             m_overwriteUrl, KIO::RenameDialog_Overwrite);
0109 
0110                     m_overwriteUrl.clear();
0111 
0112                     switch (over->exec()) {
0113                     case KIO::Result_Overwrite:
0114                         answer = KGpgTransaction::BA_YES;
0115                         break;
0116                     case KIO::Result_Rename:
0117                         answer = KGpgTransaction::BA_NO;
0118                         m_overwriteUrl = over->newDestUrl();
0119                         break;
0120                     default:
0121                         answer = KGpgTransaction::BA_UNKNOWN;
0122                         m_parent->setSuccess(KGpgTransaction::TS_USER_ABORTED);
0123                         // Close the pipes, otherwise GnuPG will try to answer
0124                         // further questions about this file.
0125                         m_process->closeWriteChannel();
0126                         m_process->closeReadChannel(QProcess::StandardOutput);
0127                         break;
0128                     }
0129 
0130                     delete over;
0131 
0132                     if (answer == KGpgTransaction::BA_UNKNOWN)
0133                         continue;
0134                 }
0135             } else {
0136                 answer = m_parent->boolQuestion(question);
0137             }
0138 
0139             switch (answer) {
0140             case KGpgTransaction::BA_YES:
0141                 write("YES\n");
0142                 break;
0143             case KGpgTransaction::BA_NO:
0144                 write("NO\n");
0145                 break;
0146             case KGpgTransaction::BA_UNKNOWN:
0147                 m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE);
0148                 m_parent->unexpectedLine(line);
0149                 sendQuit();
0150             }
0151         } else if (!m_overwriteUrl.isEmpty() && line.startsWith(QLatin1String("[GNUPG:] GET_LINE openfile.askoutname"))) {
0152             write(m_overwriteUrl.toLocalFile().toUtf8() + '\n');
0153             m_overwriteUrl.clear();
0154         } else if (line.startsWith(QLatin1String("[GNUPG:] MISSING_PASSPHRASE"))) {
0155             m_success = KGpgTransaction::TS_USER_ABORTED;
0156         } else if (line.startsWith(QLatin1String("[GNUPG:] CARDCTRL "))) {
0157             // just ignore them, pinentry should handle that
0158         } else {
0159             // all known hints
0160             int i = 0;
0161             bool matched = false;
0162             for (const QString &hintName : hintNames()) {
0163                 const KGpgTransaction::ts_hintType h = static_cast<KGpgTransaction::ts_hintType>(i++);
0164                 if (!line.startsWith(hintName))
0165                     continue;
0166 
0167                 matched = true;
0168 
0169                 bool r;
0170                 const int skip = hintName.length();
0171                 if (line.length() == skip) {
0172                     r = m_parent->hintLine(h, QString());
0173                 } else {
0174                     r = m_parent->hintLine(h, line.mid(skip + 1).trimmed());
0175                 }
0176 
0177                 if (!r) {
0178                     m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE);
0179                     sendQuit();
0180                 }
0181 
0182                 break;
0183             }
0184 
0185             if (!matched) {
0186                 if (m_parent->nextLine(line))
0187                     sendQuit();
0188             }
0189         }
0190     }
0191 }
0192 
0193 void
0194 KGpgTransactionPrivate::slotProcessExited()
0195 {
0196     Q_ASSERT(sender() == m_process);
0197 
0198     m_ownProcessFinished = true;
0199 
0200     if (m_inputProcessDone)
0201         processDone();
0202 }
0203 
0204 void
0205 KGpgTransactionPrivate::slotProcessStarted()
0206 {
0207     m_parent->postStart();
0208 }
0209 
0210 void
0211 KGpgTransactionPrivate::sendQuit(void)
0212 {
0213     write("quit\n");
0214 
0215 #ifdef KGPG_DEBUG_TRANSACTIONS
0216     if (m_quitTries == 0)
0217         qCDebug(KGPG_LOG_TRANSACTIONS) << "sending quit";
0218 #endif /* KGPG_DEBUG_TRANSACTIONS */
0219 
0220     if (m_quitTries++ >= 3) {
0221         qCDebug(KGPG_LOG_GENERAL) << "tried" << m_quitTries << "times to quit the GnuPG session";
0222         qCDebug(KGPG_LOG_GENERAL) << "last input was" << m_quitLines;
0223         qCDebug(KGPG_LOG_GENERAL) << "please file a bug report at https://bugs.kde.org";
0224         m_process->closeWriteChannel();
0225         m_success = KGpgTransaction::TS_MSG_SEQUENCE;
0226     }
0227 }
0228 
0229 void
0230 KGpgTransactionPrivate::slotInputTransactionDone(int result)
0231 {
0232     Q_ASSERT(sender() == m_inputTransaction);
0233 
0234     m_inputProcessDone = true;
0235     m_inputProcessResult = result;
0236 
0237     if (m_ownProcessFinished)
0238         processDone();
0239 }
0240 
0241 void
0242 KGpgTransactionPrivate::slotPassphraseEntered(const QString &passphrase)
0243 {
0244     // not calling KGpgTransactionPrivate::write() here for obvious privacy reasons
0245     m_process->write(passphrase.toUtf8() + '\n');
0246     if (sender() == m_newPasswordDialog) {
0247         m_newPasswordDialog->deleteLater();
0248         m_newPasswordDialog = nullptr;
0249         m_parent->newPassphraseEntered();
0250     } else {
0251         Q_ASSERT(sender() == m_passwordDialog);
0252     }
0253 }
0254 
0255 void
0256 KGpgTransactionPrivate::slotPassphraseAborted()
0257 {
0258     Q_ASSERT((sender() == m_passwordDialog) ^ (sender() == m_newPasswordDialog));
0259     sender()->deleteLater();
0260     m_newPasswordDialog = nullptr;
0261     m_passwordDialog = nullptr;
0262     handlePassphraseAborted();
0263 }
0264 
0265 void
0266 KGpgTransactionPrivate::handlePassphraseAborted()
0267 {
0268     // sending "quit" here is useless as it would be interpreted as the passphrase
0269     m_process->closeWriteChannel();
0270     m_success = KGpgTransaction::TS_USER_ABORTED;
0271 }
0272 
0273 void
0274 KGpgTransactionPrivate::write(const QByteArray &a)
0275 {
0276     m_process->write(a);
0277 #ifdef KGPG_DEBUG_TRANSACTIONS
0278     qCDebug(KGPG_LOG_TRANSACTIONS) << m_parent << a;
0279 #endif /* KGPG_DEBUG_TRANSACTIONS */
0280 }
0281 
0282 const QStringList &
0283 KGpgTransactionPrivate::hintNames (void)
0284 {
0285     static QStringList hints;
0286 
0287     if (hints.isEmpty()) {
0288         hints.insert(KGpgTransaction::HT_KEYEXPIRED,
0289                 QLatin1String("[GNUPG:] KEYEXPIRED"));
0290         hints.insert(KGpgTransaction::HT_SIGEXPIRED,
0291                 QLatin1String("[GNUPG:] SIGEXPIRED"));
0292         hints.insert(KGpgTransaction::HT_NOSECKEY,
0293                 QLatin1String("[GNUPG:] NO_SECKEY"));
0294         hints.insert(KGpgTransaction::HT_ENCTO,
0295                 QLatin1String("[GNUPG:] ENC_TO"));
0296         hints.insert(KGpgTransaction::HT_PINENTRY_LAUNCHED,
0297                 QLatin1String("[GNUPG:] PINENTRY_LAUNCHED"));
0298     }
0299 
0300     return hints;
0301 }
0302 
0303 void
0304 KGpgTransactionPrivate::processDone()
0305 {
0306     m_parent->finish();
0307     Q_EMIT m_parent->infoProgress(100, 100);
0308     Q_EMIT m_parent->done(m_success);
0309 #ifdef KGPG_DEBUG_TRANSACTIONS
0310     qCDebug(KGPG_LOG_TRANSACTIONS) << this << "result:" << m_success;
0311 #endif /* KGPG_DEBUG_TRANSACTIONS */
0312 }
0313 
0314 bool KGpgTransactionPrivate::keyConsidered(const QString& line)
0315 {
0316     if (!line.startsWith(QLatin1String("[GNUPG:] KEY_CONSIDERED ")))
0317         return false;
0318 
0319     const QStringList &parts = line.split(QLatin1Char(' '), Qt::SkipEmptyParts);
0320     if (parts.count() < 3)
0321         m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE);
0322     else if (!m_expectedFingerprints.isEmpty() &&
0323             !m_expectedFingerprints.contains(parts[2], Qt::CaseInsensitive))
0324         m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE);
0325 
0326     return true;
0327 }