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 }