QtPass  1.2.3
Multi-platform GUI for pass, the standard unix password manager.
imitatepass.cpp
Go to the documentation of this file.
1 #include "imitatepass.h"
2 #include "qtpasssettings.h"
3 #include <QDirIterator>
4 
5 #ifdef QT_DEBUG
6 #include "debughelper.h"
7 #endif
8 
9 using namespace Enums;
10 
16 
17 static QString pgit(const QString &path) {
18  if (!QtPassSettings::getGitExecutable().startsWith("wsl "))
19  return path;
20  QString res = "$(wslpath " + path + ")";
21  return res.replace('\\', '/');
22 }
23 
24 static QString pgpg(const QString &path) {
25  if (!QtPassSettings::getGpgExecutable().startsWith("wsl "))
26  return path;
27  QString res = "$(wslpath " + path + ")";
28  return res.replace('\\', '/');
29 }
30 
35  executeGit(GIT_INIT, {"init", pgit(QtPassSettings::getPassStore())});
36 }
37 
41 void ImitatePass::GitPull() { executeGit(GIT_PULL, {"pull"}); }
42 
47  exec.executeBlocking(QtPassSettings::getGitExecutable(), {"pull"});
48 }
49 
55  executeGit(GIT_PUSH, {"push"});
56  }
57 }
58 
62 void ImitatePass::Show(QString file) {
63  file = QtPassSettings::getPassStore() + file + ".gpg";
64  QStringList args = {"-d", "--quiet", "--yes", "--no-encrypt-to",
65  "--batch", "--use-agent", pgpg(file)};
66  executeGpg(PASS_SHOW, args);
67 }
68 
72 void ImitatePass::OtpGenerate(QString file) {
73 #ifdef QT_DEBUG
74  dbg() << "No OTP generation code for fake pass yet, attempting for file: " +
75  file;
76 #else
77  Q_UNUSED(file)
78 #endif
79 }
80 
88 void ImitatePass::Insert(QString file, QString newValue, bool overwrite) {
89  file = file + ".gpg";
90  transactionHelper trans(this, PASS_INSERT);
91  QStringList recipients = Pass::getRecipientList(file);
92  if (recipients.isEmpty()) {
93  // TODO(bezet): probably throw here
94  emit critical(tr("Can not edit"),
95  tr("Could not read encryption key to use, .gpg-id "
96  "file missing or invalid."));
97  return;
98  }
99  QStringList args = {"--batch", "-eq", "--output", pgpg(file)};
100  for (auto &r : recipients) {
101  args.append("-r");
102  args.append(r);
103  };
104  if (overwrite)
105  args.append("--yes");
106  args.append("-");
107  executeGpg(PASS_INSERT, args, newValue);
109  // TODO(bezet) why not?
110  if (!overwrite)
111  executeGit(GIT_ADD, {"add", pgit(file)});
112  QString path = QDir(QtPassSettings::getPassStore()).relativeFilePath(file);
113  path.replace(QRegExp("\\.gpg$"), "");
114  QString msg =
115  QString(overwrite ? "Edit" : "Add") + " for " + path + " using QtPass.";
116  GitCommit(file, msg);
117  }
118 }
119 
126 void ImitatePass::GitCommit(const QString &file, const QString &msg) {
127  executeGit(GIT_COMMIT, {"commit", "-m", msg, "--", pgit(file)});
128 }
129 
133 void ImitatePass::Remove(QString file, bool isDir) {
134  file = QtPassSettings::getPassStore() + file;
135  transactionHelper trans(this, PASS_REMOVE);
136  if (!isDir)
137  file += ".gpg";
138  if (QtPassSettings::isUseGit()) {
139  executeGit(GIT_RM, {"rm", (isDir ? "-rf" : "-f"), pgit(file)});
140  // TODO(bezet): commit message used to have pass-like file name inside(ie.
141  // getFile(file, true)
142  GitCommit(file, "Remove for " + file + " using QtPass.");
143  } else {
144  if (isDir) {
145 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
146  QDir dir(file);
147  dir.removeRecursively();
148 #else
149  removeDir(QtPassSettings::getPassStore() + file);
150 #endif
151  } else
152  QFile(file).remove();
153  }
154 }
155 
163 void ImitatePass::Init(QString path, const QList<UserInfo> &users) {
164  QString gpgIdFile = path + ".gpg-id";
165  QFile gpgId(gpgIdFile);
166  bool addFile = false;
167  transactionHelper trans(this, PASS_INIT);
168  if (QtPassSettings::isAddGPGId(true)) {
169  QFileInfo checkFile(gpgIdFile);
170  if (!checkFile.exists() || !checkFile.isFile())
171  addFile = true;
172  }
173  if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
174  emit critical(tr("Cannot update"),
175  tr("Failed to open .gpg-id for writing."));
176  return;
177  }
178  bool secret_selected = false;
179  foreach (const UserInfo &user, users) {
180  if (user.enabled) {
181  gpgId.write((user.key_id + "\n").toUtf8());
182  secret_selected |= user.have_secret;
183  }
184  }
185  gpgId.close();
186  if (!secret_selected) {
187  emit critical(
188  tr("Check selected users!"),
189  tr("None of the selected keys have a secret key available.\n"
190  "You will not be able to decrypt any newly added passwords!"));
191  return;
192  }
193 
195  !QtPassSettings::getGitExecutable().isEmpty()) {
196  if (addFile)
197  executeGit(GIT_ADD, {"add", pgit(gpgIdFile)});
198  QString path = gpgIdFile;
199  path.replace(QRegExp("\\.gpg$"), "");
200  GitCommit(gpgIdFile, "Added " + path + " using QtPass.");
201  }
202  reencryptPath(path);
203 }
204 
210 bool ImitatePass::removeDir(const QString &dirName) {
211  bool result = true;
212  QDir dir(dirName);
213 
214  if (dir.exists(dirName)) {
215  Q_FOREACH (QFileInfo info,
216  dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System |
217  QDir::Hidden | QDir::AllDirs | QDir::Files,
218  QDir::DirsFirst)) {
219  if (info.isDir())
220  result = removeDir(info.absoluteFilePath());
221  else
222  result = QFile::remove(info.absoluteFilePath());
223 
224  if (!result)
225  return result;
226  }
227  result = dir.rmdir(dirName);
228  }
229  return result;
230 }
231 
239 void ImitatePass::reencryptPath(QString dir) {
240  emit statusMsg(tr("Re-encrypting from folder %1").arg(dir), 3000);
241  emit startReencryptPath();
243  // TODO(bezet): move statuses inside actions?
244  emit statusMsg(tr("Updating password-store"), 2000);
245  GitPull_b();
246  }
247  QDir currentDir;
248  QDirIterator gpgFiles(dir, QStringList() << "*.gpg", QDir::Files,
249  QDirIterator::Subdirectories);
250  QStringList gpgId;
251  while (gpgFiles.hasNext()) {
252  QString fileName = gpgFiles.next();
253  if (gpgFiles.fileInfo().path() != currentDir.path()) {
254  gpgId = getRecipientList(fileName);
255  gpgId.sort();
256  }
257  // TODO(bezet): enable --with-colons for better future-proofness?
258  QStringList args = {
259  "-v", "--no-secmem-warning", "--no-permission-warning",
260  "--list-only", "--keyid-format=long", pgpg(fileName)};
261  QString keys, err;
262  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, &keys, &err);
263  QStringList actualKeys;
264  keys += err;
265  QStringList key = keys.split(QRegExp("[\r\n]"), QString::SkipEmptyParts);
266  QListIterator<QString> itr(key);
267  while (itr.hasNext()) {
268  QString current = itr.next();
269  QStringList cur = current.split(" ");
270  if (cur.length() > 4) {
271  QString actualKey = cur.takeAt(4);
272  if (actualKey.length() == 16) {
273  actualKeys << actualKey;
274  }
275  }
276  }
277  actualKeys.sort();
278  if (actualKeys != gpgId) {
279  // dbg()<< actualKeys << gpgId << getRecipientList(fileName);
280 #ifdef QT_DEBUG
281  dbg() << "reencrypt " << fileName << " for " << gpgId;
282 #endif
283  QString local_lastDecrypt = "Could not decrypt";
284  args = QStringList{
285  "-d", "--quiet", "--yes", "--no-encrypt-to",
286  "--batch", "--use-agent", pgpg(fileName)};
287  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
288  &local_lastDecrypt);
289 
290  if (!local_lastDecrypt.isEmpty() &&
291  local_lastDecrypt != "Could not decrypt") {
292  if (local_lastDecrypt.right(1) != "\n")
293  local_lastDecrypt += "\n";
294 
295  QStringList recipients = Pass::getRecipientList(fileName);
296  if (recipients.isEmpty()) {
297  emit critical(tr("Can not edit"),
298  tr("Could not read encryption key to use, .gpg-id "
299  "file missing or invalid."));
300  return;
301  }
302  args =
303  QStringList{"--yes", "--batch", "-eq", "--output", pgpg(fileName)};
304  for (auto &i : recipients) {
305  args.append("-r");
306  args.append(i);
307  }
308  args.append("-");
309  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
310  local_lastDecrypt);
311 
313  exec.executeBlocking(QtPassSettings::getGitExecutable(),
314  {"add", pgit(fileName)});
315  QString path =
316  QDir(QtPassSettings::getPassStore()).relativeFilePath(fileName);
317  path.replace(QRegExp("\\.gpg$"), "");
318  exec.executeBlocking(QtPassSettings::getGitExecutable(),
319  {"commit", pgit(fileName), "-m",
320  "Edit for " + path + " using QtPass."});
321  }
322 
323  } else {
324 #ifdef QT_DEBUG
325  dbg() << "Decrypt error on re-encrypt";
326 #endif
327  }
328  }
329  }
331  emit statusMsg(tr("Updating password-store"), 2000);
332  // TODO(bezet): this is non-blocking and shall be done outside
333  GitPush();
334  }
335  emit endReencryptPath();
336 }
337 
338 void ImitatePass::Move(const QString src, const QString dest,
339  const bool force) {
340  QFileInfo destFileInfo(dest);
341  transactionHelper trans(this, PASS_MOVE);
342  if (QtPassSettings::isUseGit()) {
343  QStringList args;
344  args << "mv";
345  if (force) {
346  args << "-f";
347  }
348  args << pgit(src);
349  args << pgit(dest);
350  executeGit(GIT_MOVE, args);
351 
352  QString message = QString("moved from %1 to %2 using QTPass.");
353  message = message.arg(src).arg(dest);
354  GitCommit("", message);
355  } else {
356  QDir qDir;
357  QFileInfo srcFileInfo(src);
358  QString destCopy = dest;
359  if (srcFileInfo.isFile() && destFileInfo.isDir()) {
360  destCopy = destFileInfo.absoluteFilePath() + QDir::separator() +
361  srcFileInfo.fileName();
362  }
363  if (force) {
364  qDir.remove(destCopy);
365  }
366  qDir.rename(src, destCopy);
367  }
368  // reecrypt all files under the new folder
369  if (destFileInfo.isDir()) {
370  reencryptPath(destFileInfo.absoluteFilePath());
371  } else if (destFileInfo.isFile()) {
372  reencryptPath(destFileInfo.dir().path());
373  }
374 }
375 
376 void ImitatePass::Copy(const QString src, const QString dest,
377  const bool force) {
378  QFileInfo destFileInfo(dest);
379  transactionHelper trans(this, PASS_COPY);
380  if (QtPassSettings::isUseGit()) {
381  QStringList args;
382  args << "cp";
383  if (force) {
384  args << "-f";
385  }
386  args << pgit(src);
387  args << pgit(dest);
388  executeGit(GIT_COPY, args);
389 
390  QString message = QString("copied from %1 to %2 using QTPass.");
391  message = message.arg(src).arg(dest);
392  GitCommit("", message);
393  } else {
394  QDir qDir;
395  if (force) {
396  qDir.remove(dest);
397  }
398  QFile::copy(src, dest);
399  }
400  // reecrypt all files under the new folder
401  if (destFileInfo.isDir()) {
402  reencryptPath(destFileInfo.absoluteFilePath());
403  } else if (destFileInfo.isFile()) {
404  reencryptPath(destFileInfo.dir().path());
405  }
406 }
407 
412 void ImitatePass::executeGpg(PROCESS id, const QStringList &args, QString input,
413  bool readStdout, bool readStderr) {
414  executeWrapper(id, QtPassSettings::getGpgExecutable(), args, input,
415  readStdout, readStderr);
416 }
421 void ImitatePass::executeGit(PROCESS id, const QStringList &args, QString input,
422  bool readStdout, bool readStderr) {
423  executeWrapper(id, QtPassSettings::getGitExecutable(), args, input,
424  readStdout, readStderr);
425 }
426 
437 void ImitatePass::finished(int id, int exitCode, const QString &out,
438  const QString &err) {
439 #ifdef QT_DEBUG
440  dbg() << "Imitate Pass";
441 #endif
442  static QString transactionOutput;
443  PROCESS pid = transactionIsOver(static_cast<PROCESS>(id));
444  transactionOutput.append(out);
445 
446  if (exitCode == 0) {
447  if (pid == INVALID)
448  return;
449  } else {
450  while (pid == INVALID) {
451  id = exec.cancelNext();
452  if (id == -1) {
453  // this is probably irrecoverable and shall not happen
454 #ifdef QT_DEBUG
455  dbg() << "No such transaction!";
456 #endif
457  return;
458  }
459  pid = transactionIsOver(static_cast<PROCESS>(id));
460  }
461  }
462  Pass::finished(pid, exitCode, transactionOutput, err);
463  transactionOutput.clear();
464 }
465 
475 void ImitatePass::executeWrapper(PROCESS id, const QString &app,
476  const QStringList &args, QString input,
477  bool readStdout, bool readStderr) {
478  transactionAdd(id);
479  Pass::executeWrapper(id, app, args, input, readStdout, readStderr);
480 }
void reencryptPath(QString dir)
ImitatePass::reencryptPath reencrypt all files under the chosen directory.
virtual void Init(QString path, const QList< UserInfo > &list) Q_DECL_OVERRIDE
ImitatePass::Init initialize pass repository.
virtual void OtpGenerate(QString file) Q_DECL_OVERRIDE
ImitatePass::OtpGenerate generates an otp code.
Definition: imitatepass.cpp:72
virtual void finished(int id, int exitCode, const QString &out, const QString &err) Q_DECL_OVERRIDE
ImitatePass::finished this function is overloaded to ensure identical behaviour to RealPass ie...
virtual void GitInit() Q_DECL_OVERRIDE
ImitatePass::GitInit git init wrapper.
Definition: imitatepass.cpp:34
virtual void executeWrapper(PROCESS id, const QString &app, const QStringList &args, QString input, bool readStdout=true, bool readStderr=true) Q_DECL_OVERRIDE
executeWrapper overrided so that every execution is a transaction
PROCESS
Definition: enums.h:16
bool enabled
UserInfo::enabled.
Definition: userinfo.h:50
#define dbg()
Definition: debughelper.h:7
QString key_id
UserInfo::key_id hexadecimal representation.
Definition: userinfo.h:36
bool have_secret
UserInfo::have_secret secret key is available (can decrypt with this key)
Definition: userinfo.h:46
ImitatePass()
ImitatePass::ImitatePass for situaions when pass is not available we imitate the behavior of pass htt...
Definition: imitatepass.cpp:15
static bool isUseWebDav(const bool &defaultValue=QVariant().toBool())
virtual void Remove(QString file, bool isDir=false) Q_DECL_OVERRIDE
ImitatePass::Remove custom implementation of "pass remove".
static bool isAddGPGId(const bool &defaultValue=QVariant().toBool())
virtual void Insert(QString file, QString value, bool overwrite=false) Q_DECL_OVERRIDE
ImitatePass::Insert create new file with encrypted content.
Definition: imitatepass.cpp:88
void Move(const QString src, const QString dest, const bool force=false) Q_DECL_OVERRIDE
static bool isUseGit(const bool &defaultValue=QVariant().toBool())
static QString getPassStore(const QString &defaultValue=QVariant().toString())
virtual void GitPull() Q_DECL_OVERRIDE
ImitatePass::GitPull git init wrapper.
Definition: imitatepass.cpp:41
Enumerators for configuration and runtime items.
static QString getGitExecutable(const QString &defaultValue=QVariant().toString())
virtual void finished(int id, int exitCode, const QString &out, const QString &err)
Pass::processFinished reemits specific signal based on what process has finished. ...
Definition: pass.cpp:186
static QStringList getRecipientList(QString for_file)
Pass::getRecipientList return list of gpg-id&#39;s to encrypt for.
Definition: pass.cpp:257
virtual void GitPush() Q_DECL_OVERRIDE
ImitatePass::GitPush git init wrapper.
Definition: imitatepass.cpp:53
void executeWrapper(PROCESS id, const QString &app, const QStringList &args, bool readStdout=true, bool readStderr=true)
Definition: pass.cpp:29
Stores key info lines including validity, creation date and more.
Definition: userinfo.h:11
static QString getGpgExecutable(const QString &defaultValue=QVariant().toString())
virtual void Show(QString file) Q_DECL_OVERRIDE
ImitatePass::Show shows content of file.
Definition: imitatepass.cpp:62
virtual void GitPull_b() Q_DECL_OVERRIDE
ImitatePass::GitPull_b git pull wrapper.
Definition: imitatepass.cpp:46
void Copy(const QString src, const QString dest, const bool force=false) Q_DECL_OVERRIDE
static bool isAutoPull(const bool &defaultValue=QVariant().toBool())
static bool isAutoPush(const bool &defaultValue=QVariant().toBool())