LCOV - code coverage report
Current view: top level - src - pass.cpp (source / functions) Coverage Total Hit
Test: .lcov.total Lines: 5.5 % 164 9
Test Date: 2026-03-23 21:55:57 Functions: 13.3 % 15 2

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2016 Anne Jan Brouwer
       2              : // SPDX-License-Identifier: GPL-3.0-or-later
       3              : #include "pass.h"
       4              : #include "helpers.h"
       5              : #include "qtpasssettings.h"
       6              : #include "util.h"
       7              : #include <QDir>
       8              : #include <QRandomGenerator>
       9              : #include <QRegularExpression>
      10              : #include <utility>
      11              : 
      12              : #ifdef QT_DEBUG
      13              : #include "debughelper.h"
      14              : #endif
      15              : 
      16              : using Enums::GIT_INIT;
      17              : using Enums::GIT_PULL;
      18              : using Enums::GIT_PUSH;
      19              : using Enums::GPG_GENKEYS;
      20              : using Enums::PASS_COPY;
      21              : using Enums::PASS_INIT;
      22              : using Enums::PASS_INSERT;
      23              : using Enums::PASS_MOVE;
      24              : using Enums::PASS_OTP_GENERATE;
      25              : using Enums::PASS_REMOVE;
      26              : using Enums::PASS_SHOW;
      27              : 
      28              : /**
      29              :  * @brief Pass::Pass wrapper for using either pass or the pass imitation
      30              :  */
      31           78 : Pass::Pass() : wrapperRunning(false), env(QProcess::systemEnvironment()) {
      32           78 :   connect(&exec,
      33              :           static_cast<void (Executor::*)(int, int, const QString &,
      34              :                                          const QString &)>(&Executor::finished),
      35           78 :           this, &Pass::finished);
      36              : 
      37              :   // TODO(bezet): stop using process
      38              :   // connect(&process, SIGNAL(error(QProcess::ProcessError)), this,
      39              :   //        SIGNAL(error(QProcess::ProcessError)));
      40              : 
      41           78 :   connect(&exec, &Executor::starting, this, &Pass::startingExecuteWrapper);
      42           78 :   env.append("WSLENV=PASSWORD_STORE_DIR/p");
      43           78 : }
      44              : 
      45            0 : void Pass::executeWrapper(PROCESS id, const QString &app,
      46              :                           const QStringList &args, bool readStdout,
      47              :                           bool readStderr) {
      48            0 :   executeWrapper(id, app, args, QString(), readStdout, readStderr);
      49            0 : }
      50              : 
      51            0 : void Pass::executeWrapper(PROCESS id, const QString &app,
      52              :                           const QStringList &args, QString input,
      53              :                           bool readStdout, bool readStderr) {
      54              : #ifdef QT_DEBUG
      55              :   dbg() << app << args;
      56              : #endif
      57            0 :   exec.execute(id, QtPassSettings::getPassStore(), app, args, std::move(input),
      58              :                readStdout, readStderr);
      59            0 : }
      60              : 
      61           32 : void Pass::init() {
      62              : #ifdef __APPLE__
      63              :   // If it exists, add the gpgtools to PATH
      64              :   if (QFile("/usr/local/MacGPG2/bin").exists())
      65              :     env.replaceInStrings("PATH=", "PATH=/usr/local/MacGPG2/bin:");
      66              :   // Add missing /usr/local/bin
      67              :   if (env.filter("/usr/local/bin").isEmpty())
      68              :     env.replaceInStrings("PATH=", "PATH=/usr/local/bin:");
      69              : #endif
      70              : 
      71           64 :   if (!QtPassSettings::getGpgHome().isEmpty()) {
      72            0 :     QDir absHome(QtPassSettings::getGpgHome());
      73            0 :     absHome.makeAbsolute();
      74            0 :     env << "GNUPGHOME=" + absHome.path();
      75            0 :   }
      76           32 : }
      77              : 
      78              : /**
      79              :  * @brief Pass::Generate use either pwgen or internal password
      80              :  * generator
      81              :  * @param length of the desired password
      82              :  * @param charset to use for generation
      83              :  * @return the password
      84              :  */
      85            0 : auto Pass::Generate_b(unsigned int length, const QString &charset) -> QString {
      86            0 :   QString passwd;
      87            0 :   if (QtPassSettings::isUsePwgen()) {
      88              :     // --secure goes first as it overrides --no-* otherwise
      89            0 :     QStringList args;
      90            0 :     args.append("-1");
      91            0 :     if (!QtPassSettings::isLessRandom()) {
      92            0 :       args.append("--secure");
      93              :     }
      94            0 :     args.append(QtPassSettings::isAvoidCapitals() ? "--no-capitalize"
      95              :                                                   : "--capitalize");
      96            0 :     args.append(QtPassSettings::isAvoidNumbers() ? "--no-numerals"
      97              :                                                  : "--numerals");
      98            0 :     if (QtPassSettings::isUseSymbols()) {
      99            0 :       args.append("--symbols");
     100              :     }
     101            0 :     args.append(QString::number(length));
     102              :     // TODO(bezet): try-catch here(2 statuses to merge o_O)
     103            0 :     if (Executor::executeBlocking(QtPassSettings::getPwgenExecutable(), args,
     104              :                                   &passwd) == 0) {
     105            0 :       static const QRegularExpression literalNewLines{"[\\n\\r]"};
     106            0 :       passwd.remove(literalNewLines);
     107              :     } else {
     108            0 :       passwd.clear();
     109              : #ifdef QT_DEBUG
     110              :       qDebug() << __FILE__ << ":" << __LINE__ << "\t"
     111              :                << "pwgen fail";
     112              : #endif
     113              :       // TODO(bezet): emit critical ?
     114              :     }
     115              :   } else {
     116            0 :     if (charset.length() > 0) {
     117            0 :       passwd = generateRandomPassword(charset, length);
     118              :     } else {
     119            0 :       emit critical(
     120            0 :           tr("No characters chosen"),
     121            0 :           tr("Can't generate password, there are no characters to choose from "
     122              :              "set in the configuration!"));
     123              :     }
     124              :   }
     125            0 :   return passwd;
     126              : }
     127              : 
     128              : /**
     129              :  * @brief Pass::GenerateGPGKeys internal gpg keypair generator . .
     130              :  * @param batch GnuPG style configuration string
     131              :  */
     132            0 : void Pass::GenerateGPGKeys(QString batch) {
     133            0 :   executeWrapper(GPG_GENKEYS, QtPassSettings::getGpgExecutable(),
     134              :                  {"--gen-key", "--no-tty", "--batch"}, std::move(batch));
     135              :   // TODO(annejan): check status / error messages - probably not here, it's just
     136              :   // started
     137              :   // here, see finished for details
     138              :   // https://github.com/IJHack/QtPass/issues/202#issuecomment-251081688
     139            0 : }
     140              : 
     141              : /**
     142              :  * @brief Pass::listKeys list users
     143              :  * @param keystrings
     144              :  * @param secret list private keys
     145              :  * @return QList<UserInfo> users
     146              :  */
     147            0 : auto Pass::listKeys(QStringList keystrings, bool secret) -> QList<UserInfo> {
     148            0 :   QList<UserInfo> users;
     149            0 :   QStringList args = {"--no-tty", "--with-colons", "--with-fingerprint"};
     150            0 :   args.append(secret ? "--list-secret-keys" : "--list-keys");
     151              : 
     152            0 :   for (const QString &keystring : AS_CONST(keystrings)) {
     153            0 :     if (!keystring.isEmpty()) {
     154              :       args.append(keystring);
     155              :     }
     156              :   }
     157            0 :   QString p_out;
     158            0 :   if (Executor::executeBlocking(QtPassSettings::getGpgExecutable(), args,
     159              :                                 &p_out) != 0) {
     160              :     return users;
     161              :   }
     162              : #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
     163              :   const QStringList keys =
     164            0 :       p_out.split(Util::newLinesRegex(), Qt::SkipEmptyParts);
     165              : #else
     166              :   const QStringList keys =
     167              :       p_out.split(Util::newLinesRegex(), QString::SkipEmptyParts);
     168              : #endif
     169            0 :   UserInfo current_user;
     170            0 :   for (const QString &key : keys) {
     171            0 :     QStringList props = key.split(':');
     172            0 :     if (props.size() < 10) {
     173              :       continue;
     174              :     }
     175            0 :     if (props[0] == (secret ? "sec" : "pub")) {
     176            0 :       if (!current_user.key_id.isEmpty()) {
     177              :         users.append(current_user);
     178              :       }
     179            0 :       current_user = UserInfo();
     180            0 :       current_user.key_id = props[4];
     181            0 :       current_user.name = props[9].toUtf8();
     182            0 :       current_user.validity = props[1][0].toLatin1();
     183            0 :       current_user.created.setSecsSinceEpoch(props[5].toUInt());
     184            0 :       current_user.expiry.setSecsSinceEpoch(props[6].toUInt());
     185            0 :     } else if (current_user.name.isEmpty() && props[0] == "uid") {
     186            0 :       current_user.name = props[9];
     187            0 :     } else if ((props[0] == "fpr") && props[9].endsWith(current_user.key_id)) {
     188            0 :       current_user.key_id = props[9];
     189              :     }
     190              :   }
     191            0 :   if (!current_user.key_id.isEmpty()) {
     192              :     users.append(current_user);
     193              :   }
     194              :   return users;
     195            0 : }
     196              : 
     197              : /**
     198              :  * @brief Pass::listKeys list users
     199              :  * @param keystring
     200              :  * @param secret list private keys
     201              :  * @return QList<UserInfo> users
     202              :  */
     203            0 : auto Pass::listKeys(const QString &keystring, bool secret) -> QList<UserInfo> {
     204            0 :   return listKeys(QStringList(keystring), secret);
     205              : }
     206              : 
     207              : /**
     208              :  * @brief Pass::processFinished reemits specific signal based on what process
     209              :  * has finished
     210              :  * @param id    id of Pass process that was scheduled and finished
     211              :  * @param exitCode  return code of a process
     212              :  * @param out   output generated by process(if capturing was requested, empty
     213              :  *              otherwise)
     214              :  * @param err   error output generated by process(if capturing was requested,
     215              :  *              or error occurred)
     216              :  */
     217            0 : void Pass::finished(int id, int exitCode, const QString &out,
     218              :                     const QString &err) {
     219              :   auto pid = static_cast<PROCESS>(id);
     220            0 :   if (exitCode != 0) {
     221            0 :     emit processErrorExit(exitCode, err);
     222            0 :     return;
     223              :   }
     224            0 :   switch (pid) {
     225            0 :   case GIT_INIT:
     226            0 :     emit finishedGitInit(out, err);
     227            0 :     break;
     228            0 :   case GIT_PULL:
     229            0 :     emit finishedGitPull(out, err);
     230            0 :     break;
     231            0 :   case GIT_PUSH:
     232            0 :     emit finishedGitPush(out, err);
     233            0 :     break;
     234            0 :   case PASS_SHOW:
     235            0 :     emit finishedShow(out);
     236            0 :     break;
     237            0 :   case PASS_OTP_GENERATE:
     238            0 :     emit finishedOtpGenerate(out);
     239            0 :     break;
     240            0 :   case PASS_INSERT:
     241            0 :     emit finishedInsert(out, err);
     242            0 :     break;
     243            0 :   case PASS_REMOVE:
     244            0 :     emit finishedRemove(out, err);
     245            0 :     break;
     246            0 :   case PASS_INIT:
     247            0 :     emit finishedInit(out, err);
     248            0 :     break;
     249            0 :   case PASS_MOVE:
     250            0 :     emit finishedMove(out, err);
     251            0 :     break;
     252            0 :   case PASS_COPY:
     253            0 :     emit finishedCopy(out, err);
     254            0 :     break;
     255            0 :   case GPG_GENKEYS:
     256            0 :     emit finishedGenerateGPGKeys(out, err);
     257            0 :     break;
     258              :   default:
     259              : #ifdef QT_DEBUG
     260              :     dbg() << "Unhandled process type" << pid;
     261              : #endif
     262              :     break;
     263              :   }
     264              : }
     265              : 
     266              : /**
     267              :  * @brief Pass::updateEnv update the execution environment (used when
     268              :  * switching profiles)
     269              :  */
     270            0 : void Pass::updateEnv() {
     271              :   // put PASSWORD_STORE_SIGNING_KEY in env
     272            0 :   QStringList envSigningKey = env.filter("PASSWORD_STORE_SIGNING_KEY=");
     273            0 :   QString currentSigningKey = QtPassSettings::getPassSigningKey();
     274            0 :   if (envSigningKey.isEmpty()) {
     275            0 :     if (!currentSigningKey.isEmpty()) {
     276              :       // dbg()<< "Added
     277              :       // PASSWORD_STORE_SIGNING_KEY with" + currentSigningKey;
     278            0 :       env.append("PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey);
     279              :     }
     280              :   } else {
     281            0 :     if (currentSigningKey.isEmpty()) {
     282              :       // dbg() << "Removed
     283              :       // PASSWORD_STORE_SIGNING_KEY";
     284              :       env.removeAll(envSigningKey.first());
     285              :     } else {
     286              :       // dbg()<< "Update
     287              :       // PASSWORD_STORE_SIGNING_KEY with " + currentSigningKey;
     288            0 :       env.replaceInStrings(envSigningKey.first(),
     289            0 :                            "PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey);
     290              :     }
     291              :   }
     292              :   // put PASSWORD_STORE_DIR in env
     293            0 :   QStringList store = env.filter("PASSWORD_STORE_DIR=");
     294            0 :   if (store.isEmpty()) {
     295              :     // dbg()<< "Added
     296              :     // PASSWORD_STORE_DIR";
     297            0 :     env.append("PASSWORD_STORE_DIR=" + QtPassSettings::getPassStore());
     298              :   } else {
     299              :     // dbg()<< "Update
     300              :     // PASSWORD_STORE_DIR with " + passStore;
     301            0 :     env.replaceInStrings(store.first(), "PASSWORD_STORE_DIR=" +
     302            0 :                                             QtPassSettings::getPassStore());
     303              :   }
     304            0 :   exec.setEnvironment(env);
     305            0 : }
     306              : 
     307              : /**
     308              :  * @brief Pass::getGpgIdPath return gpgid file path for some file (folder).
     309              :  * @param for_file which file (folder) would you like the gpgid file path for.
     310              :  * @return path to the gpgid file.
     311              :  */
     312            0 : auto Pass::getGpgIdPath(const QString &for_file) -> QString {
     313              :   QString passStore =
     314            0 :       QDir::fromNativeSeparators(QtPassSettings::getPassStore());
     315            0 :   QString normalizedFile = QDir::fromNativeSeparators(for_file);
     316            0 :   QString fullPath = normalizedFile.startsWith(passStore)
     317            0 :                          ? normalizedFile
     318            0 :                          : passStore + "/" + normalizedFile;
     319            0 :   QDir gpgIdDir(QFileInfo(fullPath).absoluteDir());
     320              :   bool found = false;
     321            0 :   while (gpgIdDir.exists() && gpgIdDir.absolutePath().startsWith(passStore)) {
     322            0 :     if (QFile(gpgIdDir.absoluteFilePath(".gpg-id")).exists()) {
     323              :       found = true;
     324              :       break;
     325              :     }
     326            0 :     if (!gpgIdDir.cdUp()) {
     327              :       break;
     328              :     }
     329              :   }
     330            0 :   QString gpgIdPath(found ? gpgIdDir.absoluteFilePath(".gpg-id")
     331            0 :                           : QtPassSettings::getPassStore() + ".gpg-id");
     332              : 
     333            0 :   return gpgIdPath;
     334            0 : }
     335              : 
     336              : /**
     337              :  * @brief Pass::getRecipientList return list of gpg-id's to encrypt for
     338              :  * @param for_file which file (folder) would you like recepients for
     339              :  * @return recepients gpg-id contents
     340              :  */
     341            0 : auto Pass::getRecipientList(const QString &for_file) -> QStringList {
     342            0 :   QFile gpgId(getGpgIdPath(for_file));
     343            0 :   if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text)) {
     344            0 :     return {};
     345              :   }
     346            0 :   QStringList recipients;
     347            0 :   while (!gpgId.atEnd()) {
     348            0 :     QString recipient(gpgId.readLine());
     349            0 :     recipient = recipient.split("#")[0].trimmed();
     350            0 :     if (!recipient.isEmpty()) {
     351              :       recipients += recipient;
     352              :     }
     353              :   }
     354              :   return recipients;
     355            0 : }
     356              : 
     357              : /**
     358              :  * @brief Pass::getRecipientString formated string for use with GPG
     359              :  * @param for_file which file (folder) would you like recepients for
     360              :  * @param separator formating separator eg: " -r "
     361              :  * @param count
     362              :  * @return recepient string
     363              :  */
     364            0 : auto Pass::getRecipientString(const QString &for_file, const QString &separator,
     365              :                               int *count) -> QStringList {
     366              :   Q_UNUSED(separator)
     367              :   Q_UNUSED(count)
     368            0 :   return Pass::getRecipientList(for_file);
     369              : }
     370              : 
     371              : /* Copyright (C) 2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
     372              :  */
     373              : 
     374            0 : auto Pass::boundedRandom(quint32 bound) -> quint32 {
     375            0 :   if (bound < 2) {
     376              :     return 0;
     377              :   }
     378              : 
     379              :   quint32 randval;
     380            0 :   const quint32 max_mod_bound = (1 + ~bound) % bound;
     381              : 
     382              :   do {
     383              :     randval = QRandomGenerator::system()->generate();
     384            0 :   } while (randval < max_mod_bound);
     385              : 
     386            0 :   return randval % bound;
     387              : }
     388              : 
     389            0 : auto Pass::generateRandomPassword(const QString &charset, unsigned int length)
     390              :     -> QString {
     391            0 :   QString out;
     392            0 :   for (unsigned int i = 0; i < length; ++i) {
     393            0 :     out.append(charset.at(static_cast<int>(
     394            0 :         boundedRandom(static_cast<quint32>(charset.length())))));
     395              :   }
     396            0 :   return out;
     397              : }
        

Generated by: LCOV version 2.4-beta