Line data Source code
1 : #include "imitatepass.h"
2 : #include "debughelper.h"
3 : #include "qtpasssettings.h"
4 : #include <QDirIterator>
5 :
6 : using namespace Enums;
7 :
8 : /**
9 : * @brief ImitatePass::ImitatePass for situaions when pass is not available
10 : * we imitate the behavior of pass https://www.passwordstore.org/
11 : */
12 8 : ImitatePass::ImitatePass() {}
13 :
14 : /**
15 : * @brief ImitatePass::GitInit git init wrapper
16 : */
17 0 : void ImitatePass::GitInit() {
18 0 : executeGit(GIT_INIT, {"init", QtPassSettings::getPassStore()});
19 0 : }
20 :
21 : /**
22 : * @brief ImitatePass::GitPull git init wrapper
23 : */
24 0 : void ImitatePass::GitPull() { executeGit(GIT_PULL, {"pull"}); }
25 :
26 : /**
27 : * @brief ImitatePass::GitPull_b git pull wrapper
28 : */
29 0 : void ImitatePass::GitPull_b() {
30 0 : exec.executeBlocking(QtPassSettings::getGitExecutable(), {"pull"});
31 0 : }
32 :
33 : /**
34 : * @brief ImitatePass::GitPush git init wrapper
35 : */
36 0 : void ImitatePass::GitPush() {
37 0 : if (QtPassSettings::isUseGit()) {
38 0 : executeGit(GIT_PUSH, {"push"});
39 0 : }
40 0 : }
41 :
42 : /**
43 : * @brief ImitatePass::Show shows content of file
44 : */
45 0 : void ImitatePass::Show(QString file) {
46 0 : file = QtPassSettings::getPassStore() + file + ".gpg";
47 0 : QStringList args = {"-d", "--quiet", "--yes", "--no-encrypt-to",
48 0 : "--batch", "--use-agent", file};
49 0 : executeGpg(PASS_SHOW, args);
50 :
51 0 : }
52 :
53 : /**
54 : * @brief ImitatePass::OtpGenerate generates an otp code
55 : */
56 0 : void ImitatePass::OtpGenerate(QString file) {}
57 :
58 : /**
59 : * @brief ImitatePass::Insert create new file with encrypted content
60 : *
61 : * @param file file to be created
62 : * @param newValue value to be stored in file
63 : * @param overwrite whether to overwrite existing file
64 : */
65 0 : void ImitatePass::Insert(QString file, QString newValue, bool overwrite) {
66 0 : file = file + ".gpg";
67 0 : transactionHelper trans(this, PASS_INSERT);
68 0 : QStringList recipients = Pass::getRecipientList(file);
69 0 : if (recipients.isEmpty()) {
70 : // TODO(bezet): probably throw here
71 0 : emit critical(tr("Can not edit"),
72 0 : tr("Could not read encryption key to use, .gpg-id "
73 : "file missing or invalid."));
74 0 : return;
75 : }
76 0 : QStringList args = {"--batch", "-eq", "--output", file};
77 0 : for (auto &r : recipients) {
78 0 : args.append("-r");
79 0 : args.append(r);
80 0 : };
81 0 : if (overwrite)
82 0 : args.append("--yes");
83 0 : args.append("-");
84 0 : executeGpg(PASS_INSERT, args, newValue);
85 0 : if (!QtPassSettings::isUseWebDav() && QtPassSettings::isUseGit()) {
86 : // TODO(bezet) why not?
87 0 : if (!overwrite)
88 0 : executeGit(GIT_ADD, {"add", file});
89 0 : QString path = QDir(QtPassSettings::getPassStore()).relativeFilePath(file);
90 0 : path.replace(QRegExp("\\.gpg$"), "");
91 0 : QString msg =
92 0 : QString(overwrite ? "Edit" : "Add") + " for " + path + " using QtPass.";
93 0 : GitCommit(file, msg);
94 0 : }
95 0 : }
96 :
97 : /**
98 : * @brief ImitatePass::GitCommit commit a file to git with an appropriate commit
99 : * message
100 : * @param file
101 : * @param msg
102 : */
103 0 : void ImitatePass::GitCommit(const QString &file, const QString &msg) {
104 0 : executeGit(GIT_COMMIT, {"commit", "-m", msg, "--", file});
105 0 : }
106 :
107 : /**
108 : * @brief ImitatePass::Remove custom implementation of "pass remove"
109 : */
110 0 : void ImitatePass::Remove(QString file, bool isDir) {
111 0 : file = QtPassSettings::getPassStore() + file;
112 0 : transactionHelper trans(this, PASS_REMOVE);
113 0 : if (!isDir)
114 0 : file += ".gpg";
115 0 : if (QtPassSettings::isUseGit()) {
116 0 : executeGit(GIT_RM, {"rm", (isDir ? "-rf" : "-f"), file});
117 : // TODO(bezet): commit message used to have pass-like file name inside(ie.
118 : // getFile(file, true)
119 0 : GitCommit(file, "Remove for " + file + " using QtPass.");
120 0 : } else {
121 0 : if (isDir) {
122 : #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
123 0 : QDir dir(file);
124 0 : dir.removeRecursively();
125 : #else
126 : removeDir(QtPassSettings::getPassStore() + file);
127 : #endif
128 0 : } else
129 0 : QFile(file).remove();
130 : }
131 0 : }
132 :
133 : /**
134 : * @brief ImitatePass::Init initialize pass repository
135 : *
136 : * @param path path in which new password-store will be created
137 : * @param users list of users who shall be able to decrypt passwords in
138 : * path
139 : */
140 0 : void ImitatePass::Init(QString path, const QList<UserInfo> &users) {
141 0 : QString gpgIdFile = path + ".gpg-id";
142 0 : QFile gpgId(gpgIdFile);
143 : bool addFile = false;
144 0 : transactionHelper trans(this, PASS_INIT);
145 0 : if (QtPassSettings::isAddGPGId(true)) {
146 0 : QFileInfo checkFile(gpgIdFile);
147 0 : if (!checkFile.exists() || !checkFile.isFile())
148 0 : addFile = true;
149 0 : }
150 0 : if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
151 0 : emit critical(tr("Cannot update"),
152 0 : tr("Failed to open .gpg-id for writing."));
153 0 : return;
154 : }
155 : bool secret_selected = false;
156 0 : foreach (const UserInfo &user, users) {
157 0 : if (user.enabled) {
158 0 : gpgId.write((user.key_id + "\n").toUtf8());
159 0 : secret_selected |= user.have_secret;
160 0 : }
161 : }
162 0 : gpgId.close();
163 0 : if (!secret_selected) {
164 0 : emit critical(
165 0 : tr("Check selected users!"),
166 0 : tr("None of the selected keys have a secret key available.\n"
167 : "You will not be able to decrypt any newly added passwords!"));
168 0 : return;
169 : }
170 :
171 0 : if (!QtPassSettings::isUseWebDav() && QtPassSettings::isUseGit() &&
172 0 : !QtPassSettings::getGitExecutable().isEmpty()) {
173 0 : if (addFile)
174 0 : executeGit(GIT_ADD, {"add", gpgIdFile});
175 0 : QString path = gpgIdFile;
176 0 : path.replace(QRegExp("\\.gpg$"), "");
177 0 : GitCommit(gpgIdFile, "Added " + path + " using QtPass.");
178 0 : }
179 0 : reencryptPath(path);
180 0 : }
181 :
182 : /**
183 : * @brief ImitatePass::removeDir delete folder recursive.
184 : * @param dirName which folder.
185 : * @return was removal succesful?
186 : */
187 0 : bool ImitatePass::removeDir(const QString &dirName) {
188 : bool result = true;
189 0 : QDir dir(dirName);
190 :
191 0 : if (dir.exists(dirName)) {
192 0 : Q_FOREACH (QFileInfo info,
193 : dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System |
194 : QDir::Hidden | QDir::AllDirs | QDir::Files,
195 : QDir::DirsFirst)) {
196 0 : if (info.isDir())
197 0 : result = removeDir(info.absoluteFilePath());
198 : else
199 0 : result = QFile::remove(info.absoluteFilePath());
200 :
201 0 : if (!result)
202 0 : return result;
203 : }
204 0 : result = dir.rmdir(dirName);
205 0 : }
206 0 : return result;
207 0 : }
208 :
209 : /**
210 : * @brief ImitatePass::reencryptPath reencrypt all files under the chosen
211 : * directory
212 : *
213 : * This is stil quite experimental..
214 : * @param dir
215 : */
216 0 : void ImitatePass::reencryptPath(QString dir) {
217 0 : emit statusMsg(tr("Re-encrypting from folder %1").arg(dir), 3000);
218 0 : emit startReencryptPath();
219 0 : if (QtPassSettings::isAutoPull()) {
220 : // TODO(bezet): move statuses inside actions?
221 0 : emit statusMsg(tr("Updating password-store"), 2000);
222 0 : GitPull_b();
223 0 : }
224 0 : QDir currentDir;
225 0 : QDirIterator gpgFiles(dir, QStringList() << "*.gpg", QDir::Files,
226 0 : QDirIterator::Subdirectories);
227 0 : QStringList gpgId;
228 0 : while (gpgFiles.hasNext()) {
229 0 : QString fileName = gpgFiles.next();
230 0 : if (gpgFiles.fileInfo().path() != currentDir.path()) {
231 0 : gpgId = getRecipientList(fileName);
232 0 : gpgId.sort();
233 : }
234 : // TODO(bezet): enable --with-colons for better future-proofness?
235 0 : QStringList args = {
236 0 : "-v", "--no-secmem-warning", "--no-permission-warning",
237 0 : "--list-only", "--keyid-format=long", fileName};
238 0 : QString keys, err;
239 0 : exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, &keys, &err);
240 0 : QStringList actualKeys;
241 0 : keys += err;
242 0 : QStringList key = keys.split("\n");
243 0 : QListIterator<QString> itr(key);
244 0 : while (itr.hasNext()) {
245 0 : QString current = itr.next();
246 0 : QStringList cur = current.split(" ");
247 0 : if (cur.length() > 4) {
248 0 : QString actualKey = cur.takeAt(4);
249 0 : if (actualKey.length() == 16) {
250 0 : actualKeys << actualKey;
251 : }
252 0 : }
253 0 : }
254 0 : actualKeys.sort();
255 0 : if (actualKeys != gpgId) {
256 : // dbg()<< actualKeys << gpgId << getRecipientList(fileName);
257 : dbg() << "reencrypt " << fileName << " for " << gpgId;
258 0 : QString local_lastDecrypt = "Could not decrypt";
259 0 : args = QStringList{"-d", "--quiet", "--yes", "--no-encrypt-to",
260 0 : "--batch", "--use-agent", fileName};
261 0 : exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
262 : &local_lastDecrypt);
263 :
264 0 : if (!local_lastDecrypt.isEmpty() &&
265 0 : local_lastDecrypt != "Could not decrypt") {
266 0 : if (local_lastDecrypt.right(1) != "\n")
267 0 : local_lastDecrypt += "\n";
268 :
269 0 : QStringList recipients = Pass::getRecipientList(fileName);
270 0 : if (recipients.isEmpty()) {
271 0 : emit critical(tr("Can not edit"),
272 0 : tr("Could not read encryption key to use, .gpg-id "
273 : "file missing or invalid."));
274 0 : return;
275 : }
276 0 : args = QStringList{"--yes", "--batch", "-eq", "--output", fileName};
277 0 : for (auto &i : recipients) {
278 0 : args.append("-r");
279 0 : args.append(i);
280 0 : }
281 0 : args.append("-");
282 0 : exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
283 0 : local_lastDecrypt);
284 :
285 0 : if (!QtPassSettings::isUseWebDav() && QtPassSettings::isUseGit()) {
286 0 : exec.executeBlocking(QtPassSettings::getGitExecutable(),
287 0 : {"add", fileName});
288 0 : QString path =
289 0 : QDir(QtPassSettings::getPassStore()).relativeFilePath(fileName);
290 0 : path.replace(QRegExp("\\.gpg$"), "");
291 0 : exec.executeBlocking(QtPassSettings::getGitExecutable(),
292 0 : {"commit", fileName, "-m",
293 0 : "Edit for " + path + " using QtPass."});
294 0 : }
295 :
296 0 : } else {
297 : dbg() << "Decrypt error on re-encrypt";
298 : }
299 0 : }
300 0 : }
301 0 : if (QtPassSettings::isAutoPush()) {
302 0 : emit statusMsg(tr("Updating password-store"), 2000);
303 : // TODO(bezet): this is non-blocking and shall be done outside
304 0 : GitPush();
305 : }
306 0 : emit endReencryptPath();
307 0 : }
308 :
309 0 : void ImitatePass::Move(const QString src, const QString dest,
310 : const bool force) {
311 0 : QFileInfo destFileInfo(dest);
312 0 : transactionHelper trans(this, PASS_MOVE);
313 0 : if (QtPassSettings::isUseGit()) {
314 0 : QStringList args;
315 0 : args << "mv";
316 0 : if (force) {
317 0 : args << "-f";
318 0 : }
319 0 : args << src;
320 0 : args << dest;
321 0 : executeGit(GIT_MOVE, args);
322 :
323 0 : QString message = QString("moved from %1 to %2 using QTPass.");
324 0 : message = message.arg(src).arg(dest);
325 0 : GitCommit("", message);
326 0 : } else {
327 0 : QDir qDir;
328 0 : QFileInfo srcFileInfo(src);
329 0 : QString destCopy = dest;
330 0 : if (srcFileInfo.isFile() && destFileInfo.isDir()) {
331 0 : destCopy = destFileInfo.absoluteFilePath() + QDir::separator() +
332 0 : srcFileInfo.fileName();
333 0 : }
334 0 : if (force) {
335 0 : qDir.remove(destCopy);
336 : }
337 0 : qDir.rename(src, destCopy);
338 0 : }
339 : // reecrypt all files under the new folder
340 0 : if (destFileInfo.isDir()) {
341 0 : reencryptPath(destFileInfo.absoluteFilePath());
342 0 : } else if (destFileInfo.isFile()) {
343 0 : reencryptPath(destFileInfo.dir().path());
344 0 : }
345 0 : }
346 :
347 0 : void ImitatePass::Copy(const QString src, const QString dest,
348 : const bool force) {
349 0 : QFileInfo destFileInfo(dest);
350 0 : transactionHelper trans(this, PASS_COPY);
351 0 : if (QtPassSettings::isUseGit()) {
352 0 : QStringList args;
353 0 : args << "cp";
354 0 : if (force) {
355 0 : args << "-f";
356 0 : }
357 0 : args << src;
358 0 : args << dest;
359 0 : executeGit(GIT_COPY, args);
360 :
361 0 : QString message = QString("copied from %1 to %2 using QTPass.");
362 0 : message = message.arg(src).arg(dest);
363 0 : GitCommit("", message);
364 0 : } else {
365 0 : QDir qDir;
366 0 : if (force) {
367 0 : qDir.remove(dest);
368 : }
369 0 : QFile::copy(src, dest);
370 0 : }
371 : // reecrypt all files under the new folder
372 0 : if (destFileInfo.isDir()) {
373 0 : reencryptPath(destFileInfo.absoluteFilePath());
374 0 : } else if (destFileInfo.isFile()) {
375 0 : reencryptPath(destFileInfo.dir().path());
376 0 : }
377 0 : }
378 :
379 : /**
380 : * @brief ImitatePass::executeGpg easy wrapper for running gpg commands
381 : * @param args
382 : */
383 0 : void ImitatePass::executeGpg(PROCESS id, const QStringList &args, QString input,
384 : bool readStdout, bool readStderr) {
385 0 : executeWrapper(id, QtPassSettings::getGpgExecutable(), args, input,
386 : readStdout, readStderr);
387 0 : }
388 : /**
389 : * @brief ImitatePass::executeGit easy wrapper for running git commands
390 : * @param args
391 : */
392 0 : void ImitatePass::executeGit(PROCESS id, const QStringList &args, QString input,
393 : bool readStdout, bool readStderr) {
394 0 : executeWrapper(id, QtPassSettings::getGitExecutable(), args, input,
395 : readStdout, readStderr);
396 0 : }
397 :
398 : /**
399 : * @brief ImitatePass::finished this function is overloaded to ensure
400 : * identical behaviour to RealPass ie. only PASS_*
401 : * processes are visible inside Pass::finish, so
402 : * that interface-wise it all looks the same
403 : * @param id
404 : * @param exitCode
405 : * @param out
406 : * @param err
407 : */
408 0 : void ImitatePass::finished(int id, int exitCode, const QString &out,
409 : const QString &err) {
410 : dbg() << "Imitate Pass";
411 0 : static QString transactionOutput;
412 0 : PROCESS pid = transactionIsOver(static_cast<PROCESS>(id));
413 0 : transactionOutput.append(out);
414 :
415 0 : if (exitCode == 0) {
416 0 : if (pid == INVALID)
417 0 : return;
418 : } else {
419 0 : while (pid == INVALID) {
420 0 : id = exec.cancelNext();
421 0 : if (id == -1) {
422 : // this is probably irrecoverable and shall not happen
423 : dbg() << "No such transaction!";
424 0 : return;
425 : }
426 0 : pid = transactionIsOver(static_cast<PROCESS>(id));
427 : }
428 : }
429 0 : Pass::finished(pid, exitCode, transactionOutput, err);
430 0 : transactionOutput.clear();
431 0 : }
432 :
433 : /**
434 : * @brief executeWrapper overrided so that every execution is a transaction
435 : * @param id
436 : * @param app
437 : * @param args
438 : * @param input
439 : * @param readStdout
440 : * @param readStderr
441 : */
442 0 : void ImitatePass::executeWrapper(PROCESS id, const QString &app,
443 : const QStringList &args, QString input,
444 : bool readStdout, bool readStderr) {
445 0 : transactionAdd(id);
446 0 : Pass::executeWrapper(id, app, args, input, readStdout, readStderr);
447 0 : }
|