aboutsummaryrefslogtreecommitdiffstats
path: root/kgpg/transactions/kgpgtransaction.h
blob: 8fc76a570b00475349544f2baa25a97d53f06ae6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
/*
 * Copyright (C) 2008,2009 Rolf Eike Beer <[email protected]>
 */

/***************************************************************************
 *                                                                         *
 *   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 <QObject>
#include <QString>
#include <QStringList>

class GPGProc;
class KGpgSignTransactionHelper;
class KGpgTransactionPrivate;
class QByteArray;
class QProcess;

/**
 * @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 actions to do after a passphrase was sent to GnuPG
	 */
	enum ts_passphrase_actions {
		PA_NONE = 0,			///< do nothing special
		PA_CLOSE_GOOD = 1,		///< close command channel if passphrase was accepted
		PA_USER_ABORTED = 2		///< the user has cancelled the dialog, abort the transaction
	};

	/**
	 * @brief KGpgTransaction constructor
	 */
	explicit KGpgTransaction(QObject *parent = 0, 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
	 * @param proc process to read data from
	 *
	 * 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 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 password
	 *
	 * This is called after askNewPassphrase() was called, the user has
	 * entered a new password and it was sent to the GnuPG process.
	 *
	 * The default implementation does nothing.
	 */
	virtual void newPasswordEntered();
	/**
	 * @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 true if "quit" should be sent to process
	 *
	 * 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 ts_passphrase_actions passphraseRequested();

private:
	KGpgTransactionPrivate* const d;

	Q_PRIVATE_SLOT(d, void slotReadReady())
	Q_PRIVATE_SLOT(d, void slotProcessExited())
	Q_PRIVATE_SLOT(d, void slotProcessStarted())
	Q_PRIVATE_SLOT(d, void slotInputTransactionDone(int))
	Q_PRIVATE_SLOT(d, void slotPasswordEntered(const QString &))
	Q_PRIVATE_SLOT(d, void slotPasswordAborted())

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 password and write
	 * it to the gpg process. If the password is wrong the user is prompted
	 * again for the correct password. If the user aborts the password
	 * entry the gpg process will be killed and the transaction result will
	 * be set to TS_USER_ABORTED.
	 *
	 * In contrast to sendPassphrase() this function will not block, but
	 * handle everything using signals and slots.
	 *
	 * @see KgpgInterface::sendPassphrase
	 * @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 password
	 * @param message message to display to the user. If message is empty
	 * "Enter password for [UID]" will be used.
	 * @return true if the authorization was successful
	 *
	 * This function handles user authorization for key changes. It will
	 * take care to display the message asking the user for the password
	 * and the number of tries left.
	 */
	 bool askPassphrase(const QString &message = QString());
};

class KGpgTransactionPrivate: QObject {
public:
    KGpgTransactionPrivate(KGpgTransaction *parent, bool allowChaining);
    ~KGpgTransactionPrivate();

    KGpgTransaction *m_parent;
    GPGProc *m_process;
    KGpgTransaction *m_inputTransaction;
    //KNewPasswordDialog *m_passwordDialog;
    int m_success;
    int m_tries;
    QString m_description;
    bool m_chainingAllowed;
    KGpgTransaction::ts_passphrase_actions m_passphraseAction;	///< ignore further request for passphrase

    QStringList m_idhints;

    void slotReadReady();
    void slotProcessExited();
    void slotProcessStarted();
    void slotInputTransactionDone(int result);
    void slotPasswordEntered(const QString &password);
    void slotPasswordAborted();

    QList<int *> m_argRefs;
    bool m_inputProcessDone;
    int m_inputProcessResult;
    bool m_ownProcessFinished;

    /**
     * terminate GnuPG session
     */
    void sendQuit(void);

    void write(const QByteArray &a);

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 // KGPGTRANSACTION_H