diff --git a/transactions/kgpgtransaction.cpp b/transactions/kgpgtransaction.cpp index 880c1621..fe4f9344 100644 --- a/transactions/kgpgtransaction.cpp +++ b/transactions/kgpgtransaction.cpp @@ -1,405 +1,401 @@ /* * 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 +void 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() { } diff --git a/transactions/kgpgtransaction.h b/transactions/kgpgtransaction.h index a613ae94..396f8a21 100644 --- a/transactions/kgpgtransaction.h +++ b/transactions/kgpgtransaction.h @@ -1,462 +1,462 @@ /* * 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(); /** * @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 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); 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 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); + void 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 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 set the fingerprints that are expected for this transaction * * This will skip any KEY_CONSIDERED messages from GnuPG that contain * any of the given fingerprints. */ void setExpectedFingerprints(const QStringList &fingerprints); }; #endif // KGPGTRANSACTION_H