diff --git a/transactions/kgpgchangedisable.cpp b/transactions/kgpgchangedisable.cpp index 37fc4a5a..1e97022f 100644 --- a/transactions/kgpgchangedisable.cpp +++ b/transactions/kgpgchangedisable.cpp @@ -1,47 +1,48 @@ /* - * Copyright (C) 2008,2009,2012 Rolf Eike Beer + * Copyright (C) 2008,2009,2012,2018 Rolf Eike Beer */ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kgpgchangedisable.h" KGpgChangeDisable::KGpgChangeDisable(QObject *parent, const QString &keyid, const bool disable) : KGpgEditKeyTransaction(parent, keyid, QString(), false) { setDisable(disable); + setExpectedFingerprints( { keyid } ); } KGpgChangeDisable::~KGpgChangeDisable() { } void KGpgChangeDisable::setDisable(bool disable) { QString cmd; if (disable) cmd = QLatin1String( "disable" ); else cmd = QLatin1String( "enable" ); replaceCommand(cmd); } bool KGpgChangeDisable::preStart() { if (!KGpgEditKeyTransaction::preStart()) return false; setSuccess(TS_OK); return true; } diff --git a/transactions/kgpgdelkey.cpp b/transactions/kgpgdelkey.cpp index 2d5143ec..3a94228a 100644 --- a/transactions/kgpgdelkey.cpp +++ b/transactions/kgpgdelkey.cpp @@ -1,94 +1,96 @@ /* - * Copyright (C) 2008,2009,2012,2016,2017 Rolf Eike Beer + * Copyright (C) 2008,2009,2012,2016,2017,2018 Rolf Eike Beer */ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kgpgdelkey.h" #include "gpgproc.h" #include #include static QStringList keyFingerprints(const KGpgKeyNode::List &keys) { QStringList ret; ret.reserve(keys.count()); for (const KGpgKeyNode *key : keys) ret << key->getFingerprint(); return ret; } KGpgDelKey::KGpgDelKey(QObject *parent, KGpgKeyNode *key) : KGpgTransaction(parent) , keys({key}) , fingerprints(keyFingerprints(keys)) { setCmdLine(); + setExpectedFingerprints(fingerprints); } KGpgDelKey::KGpgDelKey(QObject *parent, const KGpgKeyNode::List &keys) : KGpgTransaction(parent) , keys(keys) , fingerprints(keyFingerprints(keys)) { setCmdLine(); + setExpectedFingerprints(fingerprints); } KGpgDelKey::~KGpgDelKey() { } bool KGpgDelKey::nextLine(const QString &line) { if (!line.startsWith(QLatin1String("[GNUPG:] GOT_IT"))) setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); return false; } KGpgTransaction::ts_boolanswer KGpgDelKey::boolQuestion(const QString &line) { if (line.startsWith(QLatin1String("delete_key.okay"))) return KGpgTransaction::BA_YES; if (line.startsWith(QLatin1String("delete_key.secret.okay"))) return KGpgTransaction::BA_YES; return KGpgTransaction::boolQuestion(line); } bool KGpgDelKey::preStart() { GPGProc *proc = getProcess(); const QStringList args = proc->program() + fingerprints; proc->setProgram(args); setSuccess(KGpgTransaction::TS_OK); return true; } void KGpgDelKey::setCmdLine() { addArgument(QLatin1String( "--status-fd=1" )); addArgument(QLatin1String( "--command-fd=0" )); addArgument(QLatin1String( "--delete-secret-and-public-key" )); m_argscount = getProcess()->program().count(); } diff --git a/transactions/kgpgtransaction.cpp b/transactions/kgpgtransaction.cpp index f88e17c7..880c1621 100644 --- a/transactions/kgpgtransaction.cpp +++ b/transactions/kgpgtransaction.cpp @@ -1,413 +1,405 @@ /* - * Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2016 Rolf Eike Beer + * Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2016,2018 Rolf Eike Beer */ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kgpgtransaction.h" #include "kgpg_debug.h" #include "kgpgtransactionprivate.h" #include "gpgproc.h" #include "kgpginterface.h" #include #include #include #include #include #include #include #include #include KGpgTransaction::KGpgTransaction(QObject *parent, const bool allowChaining) : QObject(parent), d(new KGpgTransactionPrivate(this, allowChaining)) { } KGpgTransaction::~KGpgTransaction() { delete d; } void KGpgTransaction::start() { d->m_inputProcessResult = false; d->m_inputProcessDone = (d->m_inputTransaction == nullptr); setSuccess(TS_OK); d->m_idhints.clear(); d->m_tries = 3; if (preStart()) { d->m_ownProcessFinished = false; if (d->m_inputTransaction != nullptr) d->m_inputTransaction->start(); #ifdef KGPG_DEBUG_TRANSACTIONS qCDebug(KGPG_LOG_TRANSACTIONS) << this << d->m_process->program(); #endif /* KGPG_DEBUG_TRANSACTIONS */ d->m_process->start(); emit infoProgress(0, 1); } else { emit done(d->m_success); } } void KGpgTransaction::write(const QByteArray &a, const bool lf) { if (lf) d->write(a + '\n'); else d->write(a); } void KGpgTransaction::write(const int i) { write(QByteArray::number(i)); } void KGpgTransaction::askNewPassphrase(const QString& text) { emit statusMessage(i18n("Requesting Passphrase")); d->m_newPasswordDialog = new KNewPasswordDialog(qobject_cast(parent())); d->m_newPasswordDialog->setPrompt(text); d->m_newPasswordDialog->setAllowEmptyPasswords(false); connect(d->m_newPasswordDialog, &KNewPasswordDialog::newPassword, d, &KGpgTransactionPrivate::slotPassphraseEntered); connect(d->m_newPasswordDialog, &KNewPasswordDialog::rejected, d, &KGpgTransactionPrivate::slotPassphraseAborted); connect(d->m_process, &GPGProc::processExited, d->m_newPasswordDialog, &KNewPasswordDialog::rejected); d->m_newPasswordDialog->show(); } int KGpgTransaction::getSuccess() const { return d->m_success; } void KGpgTransaction::setSuccess(const int v) { #ifdef KGPG_DEBUG_TRANSACTIONS qCDebug(KGPG_LOG_TRANSACTIONS) << "old" << d->m_success << "new" << v; #endif /* KGPG_DEBUG_TRANSACTIONS */ d->m_success = v; } KGpgTransaction::ts_boolanswer KGpgTransaction::boolQuestion(const QString& line) { Q_UNUSED(line) return BA_UNKNOWN; } KGpgTransaction::ts_boolanswer KGpgTransaction::confirmOverwrite(QUrl ¤tFile) { Q_UNUSED(currentFile) return BA_UNKNOWN; } bool KGpgTransaction::hintLine(const ts_hintType hint, const QString &args) { switch (hint) { case HT_KEYEXPIRED: case HT_PINENTRY_LAUNCHED: return !args.isEmpty(); default: return true; } } void KGpgTransaction::finish() { } void KGpgTransaction::setDescription(const QString &description) { d->m_description = description; } void KGpgTransaction::waitForInputTransaction() { Q_ASSERT(d->m_inputTransaction != nullptr); if (d->m_inputProcessDone) return; d->m_inputTransaction->waitForFinished(); } void KGpgTransaction::unexpectedLine(const QString &line) { qCDebug(KGPG_LOG_GENERAL) << this << "unexpected input line" << line << "for command" << d->m_process->program(); } bool KGpgTransaction::passphraseRequested() { return askPassphrase(); } bool KGpgTransaction::passphraseReceived() { return true; } bool KGpgTransaction::preStart() { return true; } void KGpgTransaction::postStart() { } void KGpgTransaction::addIdHint(QString txt) { int cut = txt.indexOf(QLatin1Char( ' ' ), 22, Qt::CaseInsensitive); txt.remove(0, cut); if (txt.contains(QLatin1Char( '(' ), Qt::CaseInsensitive)) txt = txt.section(QLatin1Char( '(' ), 0, 0) + txt.section(QLatin1Char( ')' ), -1); txt.replace(QLatin1Char( '<' ), QLatin1String( "<" )); if (!d->m_idhints.contains(txt)) d->m_idhints << txt; } QString KGpgTransaction::getIdHints() const { return d->m_idhints.join( i18n(" or " )); } GPGProc * KGpgTransaction::getProcess() { return d->m_process; } int KGpgTransaction::addArgument(const QString &arg) { int r = d->m_process->program().count(); *d->m_process << arg; return r; } int KGpgTransaction::addArguments(const QStringList &args) { int r = d->m_process->program().count(); *d->m_process << args; return r; } void KGpgTransaction::replaceArgument(const int pos, const QString &arg) { QStringList args(d->m_process->program()); d->m_process->clearProgram(); args.replace(pos, arg); d->m_process->setProgram(args); } void KGpgTransaction::insertArgument(const int pos, const QString &arg) { insertArguments(pos, QStringList(arg)); } void KGpgTransaction::insertArguments(const int pos, const QStringList &args) { QStringList tmp(d->m_process->program()); int tmppos = pos; for (const QString &s : args) { tmp.insert(tmppos++, s); } d->m_process->setProgram(tmp); int move = args.count(); foreach (int *ref, d->m_argRefs) { if (*ref >= pos) *ref += move; } } void KGpgTransaction::addArgumentRef(int *ref) { d->m_argRefs.append(ref); } bool KGpgTransaction::askPassphrase(const QString &message) { emit statusMessage(i18n("Requesting Passphrase")); if (d->m_passwordDialog == nullptr) { d->m_passwordDialog = new KPasswordDialog(qobject_cast(parent())); QString passdlgmessage; if (message.isEmpty()) { QString userIDs(getIdHints()); if (userIDs.isEmpty()) userIDs = i18n("[No user id found]"); else userIDs.replace(QLatin1Char( '<' ), QLatin1String( "<" )); passdlgmessage = i18n("Enter passphrase for %1", userIDs); } else { passdlgmessage = message; } d->m_passwordDialog->setPrompt(passdlgmessage); connect(d->m_passwordDialog, &KPasswordDialog::gotPassword, d, &KGpgTransactionPrivate::slotPassphraseEntered); connect(d->m_passwordDialog, &KPasswordDialog::rejected, d, &KGpgTransactionPrivate::slotPassphraseAborted); connect(d->m_process, &GPGProc::processExited, d->m_passwordDialog, &KPasswordDialog::rejected); } else { // we already have a dialog, so this is a "bad passphrase" situation --d->m_tries; d->m_passwordDialog->showErrorMessage(i18np("

Bad passphrase. You have 1 try left.

", "

Bad passphrase. You have %1 tries left.

", d->m_tries), KPasswordDialog::PasswordError); } d->m_passwordDialog->show(); return true; } +void +KGpgTransaction::setExpectedFingerprints(const QStringList &fingerprints) +{ + d->m_expectedFingerprints = fingerprints; +} + void KGpgTransaction::setGnuPGHome(const QString &home) { QStringList tmp(d->m_process->program()); Q_ASSERT(tmp.count() > 3); int homepos = tmp.indexOf(QLatin1String("--options"), 1); if (homepos == -1) homepos = tmp.indexOf(QLatin1String("--homedir"), 1); Q_ASSERT(homepos != -1); Q_ASSERT(homepos + 1 < tmp.count()); tmp[homepos] = QLatin1String("--homedir"); tmp[homepos + 1] = home; d->m_process->setProgram(tmp); } int KGpgTransaction::waitForFinished(const int msecs) { int ret = TS_OK; if (d->m_inputTransaction != nullptr) { int ret = d->m_inputTransaction->waitForFinished(msecs); if ((ret != TS_OK) && (msecs != -1)) return ret; } bool b = d->m_process->waitForFinished(msecs); if (ret != TS_OK) return ret; if (!b) return TS_USER_ABORTED; else return getSuccess(); } const QString & KGpgTransaction::getDescription() const { return d->m_description; } void KGpgTransaction::setInputTransaction(KGpgTransaction *ta) { Q_ASSERT(d->m_chainingAllowed); if (d->m_inputTransaction != nullptr) clearInputTransaction(); d->m_inputTransaction = ta; GPGProc *proc = ta->getProcess(); proc->setStandardOutputProcess(d->m_process); connect(ta, &KGpgTransaction::done, d, &KGpgTransactionPrivate::slotInputTransactionDone); } void KGpgTransaction::clearInputTransaction() { disconnect(d->m_inputTransaction, &KGpgTransaction::done, d, &KGpgTransactionPrivate::slotInputTransactionDone); d->m_inputTransaction = nullptr; } bool KGpgTransaction::hasInputTransaction() const { return (d->m_inputTransaction != nullptr); } void KGpgTransaction::kill() { d->m_process->kill(); } void KGpgTransaction::newPassphraseEntered() { } - -bool KGpgTransaction::keyConsidered(const QString& line, const QStringList &fingerprints) -{ - if (!line.startsWith(QLatin1String("[GNUPG:] KEY_CONSIDERED "))) - return false; - - const QStringList &parts = line.split(QLatin1Char(' '), QString::SkipEmptyParts); - if (parts.count() < 3) - setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); - else if (!fingerprints.contains(parts[2], Qt::CaseInsensitive)) - setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); - - return true; -} diff --git a/transactions/kgpgtransaction.h b/transactions/kgpgtransaction.h index 227b4ede..0f75d9db 100644 --- a/transactions/kgpgtransaction.h +++ b/transactions/kgpgtransaction.h @@ -1,465 +1,461 @@ /* - * Copyright (C) 2008,2009,2012,2013 Rolf Eike Beer + * Copyright (C) 2008,2009,2012,2013,2018 Rolf Eike Beer */ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KGPGTRANSACTION_H #define KGPGTRANSACTION_H #include #include class GPGProc; class KGpgSignTransactionHelper; class KGpgTransactionPrivate; class QByteArray; class QStringList; class QUrl; /** * @brief Process one GnuPG operation * * This class encapsulates one GnuPG operation. It will care for all * interaction with the gpg process. Everything you have to care about * is to set up the object properly, call start() and catch the done signal. * * This is an abstract base class for specific operations that implements * the basic I/O loop, the process setup and interaction and some convenience * members to set extra arguments for the process. * * If you want to add a new operation create a child class that implements * nextLine(). Ususally you also need a constructor that takes some information * like the id of the key to modify. * * @author Rolf Eike Beer */ class KGpgTransaction: public QObject { Q_OBJECT friend class KGpgTransactionPrivate; friend class KGpgSignTransactionHelper; Q_DISABLE_COPY(KGpgTransaction) public: /** * @brief return codes common to many transactions * * Every transaction may define additional return codes, which * should start at TS_COMMON_END + 1. */ enum ts_transaction { TS_OK = 0, ///< everything went fine TS_BAD_PASSPHRASE = 1, ///< the passphrase was not correct TS_MSG_SEQUENCE = 2, ///< unexpected sequence of GnuPG messages TS_USER_ABORTED = 3, ///< the user aborted the transaction TS_INVALID_EMAIL = 4, ///< the given email address is invalid TS_INPUT_PROCESS_ERROR = 5, ///< the connected input process returned an error TS_COMMON_END = 100 ///< placeholder for return values of derived classes }; /** * @brief result codes for GnuPG boolean questions * * These are the possible answers to a boolean question of a GnuPG process. */ enum ts_boolanswer { BA_UNKNOWN = 0, ///< the question is not supported (this is an error) BA_YES = 1, ///< answer "YES" BA_NO = 2 ///< answer "NO" }; /** * @brief the known hints sent by GnuPG */ enum ts_hintType { HT_KEYEXPIRED, ///< key is expired HT_SIGEXPIRED, ///< deprecated by GnuPG HT_NOSECKEY, ///< secret key not available HT_ENCTO, ///< message is encrypted for this key HT_PINENTRY_LAUNCHED ///< pinentry was launched }; /** * @brief KGpgTransaction constructor */ explicit KGpgTransaction(QObject *parent = nullptr, const bool allowChaining = false); /** * @brief KGpgTransaction destructor */ virtual ~KGpgTransaction(); /** * @brief Start the operation. */ void start(); /** * @brief sets the home directory of GnuPG called for this transaction */ void setGnuPGHome(const QString &home); /** * @brief blocks until the transaction is complete * @return the result of the transaction like done() would * @retval TS_USER_ABORTED the timeout expired * * If this transaction has another transaction set as input then * it would wait for those transaction to finish first. The msecs * argument is used as limit for both transactions then so you * can end up waiting twice the given time (or longer if you have * more transactions chained). */ int waitForFinished(const int msecs = -1); /** * @brief return description of this transaction * @return string used to describe what's going on * * This is especially useful when using this transaction from a KJob. */ const QString &getDescription() const; /** * @brief connect the standard input of this transaction to another process * * Once the input process is connected this transaction will not emit * the done signal until the input process sends the done signal. * * The basic idea is that when an input transaction is set you only need * to care about this transaction. The other transaction is automatically * started when this one is started and is destroyed when this one is. */ void setInputTransaction(KGpgTransaction *ta); /** * @brief tell the process the standard input is no longer connected * * If you had connected an input process you need to tell the transaction * once this input process is gone. Otherwise you will not get a done * signal from this transaction as it will wait for the finished signal * from the process that will never come. */ void clearInputTransaction(); /** * @brief check if another transaction will sent input to this */ bool hasInputTransaction() const; /** * @brief abort this operation as soon as possible */ void kill(); signals: /** * @brief Emitted when the operation was completed. * @param result return status of the transaction * * @see ts_transaction for the common status codes. Each transaction * may define additional status codes. */ void done(int result); /** * @brief emits textual status information * @param msg the status message */ void statusMessage(const QString &msg); /** * @brief emits procentual status information * @param processedAmount how much of the job is done * @param totalAmount how much needs to be done to complete this job */ void infoProgress(qulonglong processedAmount, qulonglong totalAmount); protected: /** * @brief Called before the gpg process is started. * @return true if the process should be started * * You may reimplement this member if you need to do some special * operations before the process is started. The command line of the * process may be modified for the last time here. * * When you notice that some values passed are invalid or the * transaction does not need to be run for some other reason you should * call setSuccess() to set the return value and return false. In this * case the process is not started but the value is immediately * returned. */ virtual bool preStart(); /** * @brief Called when the gpg process is up and running. * * This functions is connected to the started() signal of the gpg process. */ virtual void postStart(); /** * @brief Called for every line the gpg process writes. * @param line the input from the process * @return true if "quit" should be sent to process * * You need to implement this member to get a usable subclass. * * When this function returns true "quit" is written to the process. */ virtual bool nextLine(const QString &line) = 0; /** * @brief Called for every boolean question GnuPG answers * @param line the question GnuPG asked * @return what to answer GnuPG * * This is called instead of nextLine() if the line contains a boolean * question. Returning BA_UNKNOWN will cancel the current transaction * and will set the transaction result to TS_MSG_SEQUENCE. * * The default implementation will answer BA_UNKNOWN to every question. */ virtual ts_boolanswer boolQuestion(const QString &line); /** * @brief called when GnuPG asks for confirmation for overwriting a file * @param currentFile fill in the current filename for the user dialog * @return what to answer to GnuPG * @retval BA_YES file will be overwritten, @currentFile is ignored * @retval BA_NO file will not be overwritten, if currentFile is given this will automatically be provided as alternative to GnuPG * @retval BA_UNKNOWN ask the user for a choice or abort, currentFile is provided to the user as a hint about the original filename, if currentFile is empty the transaction is aborted * * The default implementation will just return BA_UNKNOWN without setting * a filename, causing a sequence error. */ virtual ts_boolanswer confirmOverwrite(QUrl ¤tFile); /** * @brief Called for a set of hint messages * * @param hint the hint type given by GnuPG * @param args the arguments given to the hint * @return if the hint was parsed correctly * @retval true everything is fine * @retval false something went wrong (e.g. syntax error) * * The default implementation will do nothing but checking for some * argument counts. Override this and handle all interesting hints * yourself. Don't forget to call the default implementation at the end. */ virtual bool hintLine(const ts_hintType hint, const QString &args); /** * @brief Called when the gpg process finishes. * * You may reimplement this member if you need to do some special * operations after process completion. The provided one simply * does nothing which should be enough for most cases. */ virtual void finish(); /** * @brief called when the user entered a new passphrase * * This is called after askNewPassphrase() was called, the user has * entered a new passphrase and it was sent to the GnuPG process. * * The default implementation does nothing. */ virtual void newPassphraseEntered(); /** * @brief set the description returned in getDescription() * @param description the new description of this transaction */ void setDescription(const QString &description); /** * @brief wait until the input transaction has finished */ void waitForInputTransaction(); /** * @brief notify of an unexpected line * * This will print out the line to the console to ease debugging. */ void unexpectedLine(const QString &line); /** * @brief called when GnuPG asks for a passphrase * @return if the processing should continue * @retval true processing should continue * @retval false an error occurred, transaction should be aborted * * This allows a transaction to implement special handling for * passphrases, e.g. when both old and new passphrase must be * requested when changing it. The default implementation will just * call askPassphrase(). */ virtual bool passphraseRequested(); /** * @brief called when GnuPG accepted the passphrase * @return if the input channel to GnuPG should be closed * @retval true close the input channel of the GnuPG process * @retval false keep the GnuPG input channel open * * This allows a transaction to handle passphrase success in a * special way. The default implementation will just return true. */ virtual bool passphraseReceived(); private: KGpgTransactionPrivate* const d; protected: /** * @brief Ask user for passphrase and send it to gpg process. * * If the gpg process asks for a new passphrase this function will do * all necessary steps for you: ask the user for the passphrase and write * it to the gpg process. If the passphrase is wrong the user is prompted * again for the correct passphrase. If the user aborts the passphrase * entry the gpg process will be killed and the transaction result will * be set to TS_USER_ABORTED. * * @see askPassphrase */ void askNewPassphrase(const QString &text); /** * @brief get the success value that will be returned with the done signal */ int getSuccess() const; /** * @brief set the success value that will be returned with the done signal * @param v the new success value * * You should use 0 as success value. Other values can be defined as needed. */ void setSuccess(const int v); /** * @brief add a userid hint * @param txt userid description * * Before GnuPG asks for a passphrase it usually sends out a hint message * for which key the passphrase will be needed. There may be several hint * messages, e.g. if a text was encrypted with multiple keys. */ void addIdHint(QString txt); /** * @brief get string of all userid hints * @returns concatenation of all ids previously added with addIdHint(). */ QString getIdHints() const; /** * @brief get a reference to the gpg process object * @returns gpg process object * * This returns a reference to the gpg process object used internally. * In case you need to do some special things (e.g. changing the output * mode) you can modify this object. * * Usually you will not need this. * * @warning Never free this object! */ GPGProc *getProcess(); /** * @brief add a command line argument to gpg process * @param arg new argument * @returns the position of the new argument * * This is a convenience function that allows adding one additional * argument to the command line of the process. This must be called * before start() is called. Usually you will call this from your * constructor. */ int addArgument(const QString &arg); /** * @brief add command line arguments to gpg process * @param args new arguments * @returns the position of the first argument added * * This is a convenience function that allows adding additional * arguments to the command line of the process. This must be called * before start() is called. */ int addArguments(const QStringList &args); /** * @brief replace the argument at the given position * @param pos position of old argument * @param arg new argument */ void replaceArgument(const int pos, const QString &arg); /** * @brief insert an argument at the given position * @param pos position to insert at * @param arg new argument */ void insertArgument(const int pos, const QString &arg); /** * @brief insert arguments at the given position * @param pos position to insert at * @param args new arguments */ void insertArguments(const int pos, const QStringList &args); /** * @brief make sure the reference to a specific argument is kept up to date * @param ref the value where the position is stored * * You might want to keep the position of a specific argument to * later be able to repace it easily. In that case put it into * this function too so every time someone mofifies the argument * list (especially by insertArgument() and insertArguments()) * this reference will be kept up to date. */ void addArgumentRef(int *ref); /** * @brief write data to standard input of gpg process * @param a data to write * @param lf if line feed should be appended to message * * Use this function to interact with the gpg process. A carriage * return is appended to the data automatically. Usually you will * call this function from nextLine(). */ void write(const QByteArray &a, const bool lf = true); /** * @brief write data to standard input of gpg process * @param i data to write * * @overload */ void write(const int i); /** * @brief ask user for passphrase * @param message message to display to the user. If message is empty * "Enter passphrase for [UID]" will be used. * @return true if the authorization was successful * * This function handles user authorization for key operations. It will * take care to display the message asking the user for the passphrase * and the number of tries left. */ bool askPassphrase(const QString &message = QString()); /** - * @brief handle if this a KEY_CONSIDERED line - * @param line the line from GnuPG output - * @param fingerprints the fingerprints of the expected keys - * @returns if this is a KEY_CONSIDERED line + * @brief set the fingerprints that are expected for this transaction * - * In case this is a KEY_CONSIDERED line (i.e. the return value is true), - * but either it was malformed or the given fingerprint does not match any - * key in fingerprints, the success value will be set to TS_MSG_SEQUENCE. + * This will skip any KEY_CONSIDERED messages from GnuPG that contain + * any of the given fingerprints. */ - bool keyConsidered(const QString &line, const QStringList &fingerprints); + void setExpectedFingerprints(const QStringList &fingerprints); }; #endif // KGPGTRANSACTION_H diff --git a/transactions/kgpgtransactionprivate.cpp b/transactions/kgpgtransactionprivate.cpp index 6c6b86f8..2163a05a 100644 --- a/transactions/kgpgtransactionprivate.cpp +++ b/transactions/kgpgtransactionprivate.cpp @@ -1,321 +1,336 @@ /* - * Copyright (C) 2009,2010,2012,2014,2016 Rolf Eike Beer + * Copyright (C) 2009,2010,2012,2014,2016,2018 Rolf Eike Beer */ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kgpgtransactionprivate.h" #include "kgpgtransaction.h" #include "kgpg_debug.h" #include #include #include #include #include KGpgTransactionPrivate::KGpgTransactionPrivate(KGpgTransaction *parent, bool allowChaining) : m_parent(parent), m_process(new GPGProc()), m_inputTransaction(nullptr), m_newPasswordDialog(nullptr), m_passwordDialog(nullptr), m_success(KGpgTransaction::TS_OK), m_tries(3), m_chainingAllowed(allowChaining), m_inputProcessDone(false), m_inputProcessResult(KGpgTransaction::TS_OK), m_ownProcessFinished(false), m_quitTries(0) { connect(m_process, &GPGProc::readReady, this, &KGpgTransactionPrivate::slotReadReady); connect(m_process, &GPGProc::processExited, this, &KGpgTransactionPrivate::slotProcessExited); connect(m_process, &GPGProc::started, this, &KGpgTransactionPrivate::slotProcessStarted); } KGpgTransactionPrivate::~KGpgTransactionPrivate() { if (m_newPasswordDialog) { m_newPasswordDialog->close(); m_newPasswordDialog->deleteLater(); } if (m_process->state() == QProcess::Running) { m_process->closeWriteChannel(); m_process->terminate(); } delete m_inputTransaction; delete m_process; } void KGpgTransactionPrivate::slotReadReady() { QString line; QPointer process(m_process); QPointer par(m_parent); while (!process.isNull() && (m_process->readln(line, true) >= 0)) { if (m_quitTries) m_quitLines << line; #ifdef KGPG_DEBUG_TRANSACTIONS qCDebug(KGPG_LOG_TRANSACTIONS) << m_parent << line; #endif /* KGPG_DEBUG_TRANSACTIONS */ static const QString getBool = QLatin1String("[GNUPG:] GET_BOOL "); - if (m_parent->keyConsidered(line, QStringList())) { + if (keyConsidered(line)) { // already handled by keyConsidered - skip the line } else if (line.startsWith(QLatin1String("[GNUPG:] USERID_HINT "))) { m_parent->addIdHint(line); } else if (line.startsWith(QLatin1String("[GNUPG:] BAD_PASSPHRASE "))) { // the MISSING_PASSPHRASE line comes first, in that case ignore a // following BAD_PASSPHRASE if (m_success != KGpgTransaction::TS_USER_ABORTED) m_success = KGpgTransaction::TS_BAD_PASSPHRASE; } else if (line.startsWith(QLatin1String("[GNUPG:] GET_HIDDEN passphrase.enter"))) { const bool goOn = m_parent->passphraseRequested(); // Check if the object was deleted while waiting for the result if (!goOn || par.isNull()) return; } else if (line.startsWith(QLatin1String("[GNUPG:] GOOD_PASSPHRASE"))) { emit m_parent->statusMessage(i18n("Got Passphrase")); if (m_passwordDialog != nullptr) { m_passwordDialog->close(); m_passwordDialog->deleteLater(); m_passwordDialog = nullptr; } if (m_parent->passphraseReceived()) { // signal GnuPG that there will be no further input and it can // begin sending output. m_process->closeWriteChannel(); } } else if (line.startsWith(getBool)) { static const QString overwrite = QLatin1String("openfile.overwrite.okay"); const QString question = line.mid(getBool.length()); KGpgTransaction::ts_boolanswer answer; if (question.startsWith(overwrite)) { m_overwriteUrl.clear(); answer = m_parent->confirmOverwrite(m_overwriteUrl); if ((answer == KGpgTransaction::BA_UNKNOWN) && !m_overwriteUrl.isEmpty()) { QPointer over = new KIO::RenameDialog(qobject_cast(m_parent->parent()), i18n("File Already Exists"), QUrl(), m_overwriteUrl, KIO::RenameDialog_Overwrite); m_overwriteUrl.clear(); switch (over->exec()) { case KIO::R_OVERWRITE: answer = KGpgTransaction::BA_YES; break; case KIO::R_RENAME: answer = KGpgTransaction::BA_NO; m_overwriteUrl = over->newDestUrl(); break; default: answer = KGpgTransaction::BA_UNKNOWN; m_parent->setSuccess(KGpgTransaction::TS_USER_ABORTED); // Close the pipes, otherwise GnuPG will try to answer // further questions about this file. m_process->closeWriteChannel(); m_process->closeReadChannel(QProcess::StandardOutput); break; } delete over; if (answer == KGpgTransaction::BA_UNKNOWN) continue; } } else { answer = m_parent->boolQuestion(question); } switch (answer) { case KGpgTransaction::BA_YES: write("YES\n"); break; case KGpgTransaction::BA_NO: write("NO\n"); break; case KGpgTransaction::BA_UNKNOWN: m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); m_parent->unexpectedLine(line); sendQuit(); } } else if (!m_overwriteUrl.isEmpty() && line.startsWith(QLatin1String("[GNUPG:] GET_LINE openfile.askoutname"))) { write(m_overwriteUrl.toLocalFile().toUtf8() + '\n'); m_overwriteUrl.clear(); } else if (line.startsWith(QLatin1String("[GNUPG:] MISSING_PASSPHRASE"))) { m_success = KGpgTransaction::TS_USER_ABORTED; } else if (line.startsWith(QLatin1String("[GNUPG:] CARDCTRL "))) { // just ignore them, pinentry should handle that } else { // all known hints int i = 0; bool matched = false; for (const QString &hintName : hintNames()) { const KGpgTransaction::ts_hintType h = static_cast(i++); if (!line.startsWith(hintName)) continue; matched = true; bool r; const int skip = hintName.length(); if (line.length() == skip) { r = m_parent->hintLine(h, QString()); } else { r = m_parent->hintLine(h, line.mid(skip + 1).trimmed()); } if (!r) { m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); sendQuit(); } break; } if (!matched) { if (m_parent->nextLine(line)) sendQuit(); } } } } void KGpgTransactionPrivate::slotProcessExited() { Q_ASSERT(sender() == m_process); m_ownProcessFinished = true; if (m_inputProcessDone) processDone(); } void KGpgTransactionPrivate::slotProcessStarted() { m_parent->postStart(); } void KGpgTransactionPrivate::sendQuit(void) { write("quit\n"); #ifdef KGPG_DEBUG_TRANSACTIONS if (m_quitTries == 0) qCDebug(KGPG_LOG_TRANSACTIONS) << "sending quit"; #endif /* KGPG_DEBUG_TRANSACTIONS */ if (m_quitTries++ >= 3) { qCDebug(KGPG_LOG_GENERAL) << "tried" << m_quitTries << "times to quit the GnuPG session"; qCDebug(KGPG_LOG_GENERAL) << "last input was" << m_quitLines; qCDebug(KGPG_LOG_GENERAL) << "please file a bug report at https://bugs.kde.org"; m_process->closeWriteChannel(); m_success = KGpgTransaction::TS_MSG_SEQUENCE; } } void KGpgTransactionPrivate::slotInputTransactionDone(int result) { Q_ASSERT(sender() == m_inputTransaction); m_inputProcessDone = true; m_inputProcessResult = result; if (m_ownProcessFinished) processDone(); } void KGpgTransactionPrivate::slotPassphraseEntered(const QString &passphrase) { // not calling KGpgTransactionPrivate::write() here for obvious privacy reasons m_process->write(passphrase.toUtf8() + '\n'); if (sender() == m_newPasswordDialog) { m_newPasswordDialog->deleteLater(); m_newPasswordDialog = nullptr; m_parent->newPassphraseEntered(); } else { Q_ASSERT(sender() == m_passwordDialog); } } void KGpgTransactionPrivate::slotPassphraseAborted() { Q_ASSERT((sender() == m_passwordDialog) ^ (sender() == m_newPasswordDialog)); sender()->deleteLater(); m_newPasswordDialog = nullptr; m_passwordDialog = nullptr; handlePassphraseAborted(); } void KGpgTransactionPrivate::handlePassphraseAborted() { // sending "quit" here is useless as it would be interpreted as the passphrase m_process->closeWriteChannel(); m_success = KGpgTransaction::TS_USER_ABORTED; } void KGpgTransactionPrivate::write(const QByteArray &a) { m_process->write(a); #ifdef KGPG_DEBUG_TRANSACTIONS qCDebug(KGPG_LOG_TRANSACTIONS) << m_parent << a; #endif /* KGPG_DEBUG_TRANSACTIONS */ } const QStringList & KGpgTransactionPrivate::hintNames (void) { static QStringList hints; if (hints.isEmpty()) { hints.insert(KGpgTransaction::HT_KEYEXPIRED, QLatin1String("[GNUPG:] KEYEXPIRED")); hints.insert(KGpgTransaction::HT_SIGEXPIRED, QLatin1String("[GNUPG:] SIGEXPIRED")); hints.insert(KGpgTransaction::HT_NOSECKEY, QLatin1String("[GNUPG:] NO_SECKEY")); hints.insert(KGpgTransaction::HT_ENCTO, QLatin1String("[GNUPG:] ENC_TO")); hints.insert(KGpgTransaction::HT_PINENTRY_LAUNCHED, QLatin1String("[GNUPG:] PINENTRY_LAUNCHED")); } return hints; } void KGpgTransactionPrivate::processDone() { m_parent->finish(); emit m_parent->infoProgress(100, 100); emit m_parent->done(m_success); #ifdef KGPG_DEBUG_TRANSACTIONS qCDebug(KGPG_LOG_TRANSACTIONS) << this << "result:" << m_success; #endif /* KGPG_DEBUG_TRANSACTIONS */ } + +bool KGpgTransactionPrivate::keyConsidered(const QString& line) +{ + if (!line.startsWith(QLatin1String("[GNUPG:] KEY_CONSIDERED "))) + return false; + + const QStringList &parts = line.split(QLatin1Char(' '), QString::SkipEmptyParts); + if (parts.count() < 3) + m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); + else if (!m_expectedFingerprints.isEmpty() && + !m_expectedFingerprints.contains(parts[2], Qt::CaseInsensitive)) + m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); + + return true; +} diff --git a/transactions/kgpgtransactionprivate.h b/transactions/kgpgtransactionprivate.h index e6075c63..53f6626b 100644 --- a/transactions/kgpgtransactionprivate.h +++ b/transactions/kgpgtransactionprivate.h @@ -1,84 +1,98 @@ /* - * Copyright (C) 2008,2009,2012,2013 Rolf Eike Beer + * Copyright (C) 2008,2009,2012,2013,2018 Rolf Eike Beer */ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KGPGTRANSACTIONPRIVATE_H #define KGPGTRANSACTIONPRIVATE_H #include "gpgproc.h" #include #include #include class KGpgTransaction; class KGpgSignTransactionHelper; class KGpgTransactionPrivate : public QObject { public: KGpgTransactionPrivate(KGpgTransaction *parent, bool allowChaining); ~KGpgTransactionPrivate(); KGpgTransaction *m_parent; GPGProc *m_process; KGpgTransaction *m_inputTransaction; KNewPasswordDialog *m_newPasswordDialog; KPasswordDialog *m_passwordDialog; int m_success; int m_tries; QString m_description; bool m_chainingAllowed; QStringList m_idhints; + QStringList m_expectedFingerprints; QUrl m_overwriteUrl; ///< the file to overwrite or it's new name void slotReadReady(); void slotProcessExited(); void slotProcessStarted(); void slotInputTransactionDone(int result); void slotPassphraseEntered(const QString &passphrase); /** * @brief a slot to handle the case that the passphrase entry was aborted by the user * * This will delete the sender as well as do the internal passphrase aborted handling. */ void slotPassphraseAborted(); /** * @brief do the internal passphrase aborted handling */ void handlePassphraseAborted(); QList m_argRefs; bool m_inputProcessDone; int m_inputProcessResult; bool m_ownProcessFinished; /** * terminate GnuPG session */ void sendQuit(void); void write(const QByteArray &a); static const QStringList &hintNames(void); + /** + * @brief handle if this a KEY_CONSIDERED line + * @param line the line from GnuPG output + * @returns if this is a KEY_CONSIDERED line + * + * In case this is a KEY_CONSIDERED line (i.e. the return value is true), + * but either it was malformed or the given fingerprint does not match any + * key in m_expectedFingerprints, the success value will be set to TS_MSG_SEQUENCE. + * + * @see setExpectedIds + */ + bool keyConsidered(const QString &line); + private: void processDone(); unsigned int m_quitTries; ///< how many times we tried to quit QStringList m_quitLines; ///< what we received after we tried to quit }; #endif // KGPGTRANSACTIONPRIVATE_H