QtPass 1.6.0
Multi-platform GUI for pass, the standard unix password manager.
Loading...
Searching...
No Matches
/home/annejan/Projects/QtPass/src/pass.cpp

Finds the path to the gpgconf executable in the same directory as the given GPG path.

QString result = findGpgconfInGpgDir(gpgPath); std::cout << result.toStdString() << std::endl; // Expected output: path to gpgconf or empty string.

Finds the path to the gpgconf executable in the same directory as the given GPG path.

QString result = findGpgconfInGpgDir(gpgPath); std::cout << result.toStdString() << std::endl; // Expected output: path to gpgconf or empty string

Parameters
gpgPath- Absolute path to a GPG executable or related file used to locate gpgconf.
Returns
QString - The full path to gpgconf if found and executable; otherwise an empty QString.
// SPDX-FileCopyrightText: 2016 Anne Jan Brouwer
// SPDX-License-Identifier: GPL-3.0-or-later
#include "pass.h"
#include "gpgkeystate.h"
#include "helpers.h"
#include "qtpasssettings.h"
#include "util.h"
#include <QDir>
#include <QFileInfo>
#include <QProcess>
#include <QRandomGenerator>
#include <QRegularExpression>
#include <utility>
#ifdef QT_DEBUG
#include "debughelper.h"
#endif
Pass::Pass() : wrapperRunning(false), env(QProcess::systemEnvironment()) {
connect(&exec,
static_cast<void (Executor::*)(int, int, const QString &,
const QString &)>(&Executor::finished),
this, &Pass::finished);
// This was previously using direct QProcess signals.
// The code now uses Executor instead of raw QProcess for better control.
// connect(&process, SIGNAL(error(QProcess::ProcessError)), this,
// SIGNAL(error(QProcess::ProcessError)));
env.append("WSLENV=PASSWORD_STORE_DIR/p");
}
void Pass::executeWrapper(PROCESS id, const QString &app,
const QStringList &args, bool readStdout,
bool readStderr) {
executeWrapper(id, app, args, QString(), readStdout, readStderr);
}
void Pass::executeWrapper(PROCESS id, const QString &app,
const QStringList &args, QString input,
bool readStdout, bool readStderr) {
#ifdef QT_DEBUG
dbg() << app << args;
#endif
exec.execute(id, QtPassSettings::getPassStore(), app, args, std::move(input),
readStdout, readStderr);
}
void Pass::init() {
#ifdef __APPLE__
// If it exists, add the gpgtools to PATH
if (QFile("/usr/local/MacGPG2/bin").exists())
env.replaceInStrings("PATH=", "PATH=/usr/local/MacGPG2/bin:");
// Add missing /usr/local/bin
if (env.filter("/usr/local/bin").isEmpty())
env.replaceInStrings("PATH=", "PATH=/usr/local/bin:");
#endif
if (!QtPassSettings::getGpgHome().isEmpty()) {
QDir absHome(QtPassSettings::getGpgHome());
absHome.makeAbsolute();
env << "GNUPGHOME=" + absHome.path();
}
}
auto Pass::generatePassword(unsigned int length, const QString &charset)
-> QString {
QString passwd;
// --secure goes first as it overrides --no-* otherwise
QStringList args;
args.append("-1");
args.append("--secure");
}
args.append(QtPassSettings::isAvoidCapitals() ? "--no-capitalize"
: "--capitalize");
args.append(QtPassSettings::isAvoidNumbers() ? "--no-numerals"
: "--numerals");
args.append("--symbols");
}
args.append(QString::number(length));
// executeBlocking returns 0 on success, non-zero on failure
&passwd) == 0) {
static const QRegularExpression literalNewLines{"[\\n\\r]"};
passwd.remove(literalNewLines);
} else {
passwd.clear();
#ifdef QT_DEBUG
qDebug() << __FILE__ << ":" << __LINE__ << "\t"
<< "pwgen fail";
#endif
// Error is already handled by clearing passwd; no need for critical
// signal here
}
} else {
// Validate charset - if CUSTOM is selected but chars are empty,
// fall back to ALLCHARS to prevent weak passwords (issue #780)
QString effectiveCharset = charset;
if (effectiveCharset.isEmpty()) {
}
if (effectiveCharset.length() > 0) {
passwd = generateRandomPassword(effectiveCharset, length);
} else {
emit critical(
tr("No characters chosen"),
tr("Can't generate password, there are no characters to choose from "
"set in the configuration!"));
}
}
return passwd;
}
QString out, err;
{"--version"}, &out, &err) != 0) {
return false;
}
QRegularExpression versionRegex(R"(gpg \‍(GnuPG\) (\d+)\.(\d+))");
QRegularExpressionMatch match = versionRegex.match(out);
if (!match.hasMatch()) {
return false;
}
int major = match.captured(1).toInt();
int minor = match.captured(2).toInt();
return major > 2 || (major == 2 && minor >= 1);
}
return QStringLiteral("%echo Generating a default key\n"
"Key-Type: EdDSA\n"
"Key-Curve: Ed25519\n"
"Subkey-Type: ECDH\n"
"Subkey-Curve: Curve25519\n"
"Name-Real: \n"
"Name-Comment: QtPass\n"
"Name-Email: \n"
"Expire-Date: 0\n"
"%no-protection\n"
"%commit\n"
"%echo done");
}
return QStringLiteral("%echo Generating a default key\n"
"Key-Type: RSA\n"
"Subkey-Type: RSA\n"
"Name-Real: \n"
"Name-Comment: QtPass\n"
"Name-Email: \n"
"Expire-Date: 0\n"
"%no-protection\n"
"%commit\n"
"%echo done");
}
namespace {
auto resolveWslGpgconfPath(const QString &lastPart) -> QString {
int lastSep = lastPart.lastIndexOf('/');
if (lastSep < 0) {
lastSep = lastPart.lastIndexOf('\\');
}
if (lastSep >= 0) {
return lastPart.left(lastSep + 1) + "gpgconf";
}
return QStringLiteral("gpgconf");
}
QString findGpgconfInGpgDir(const QString &gpgPath) {
QFileInfo gpgInfo(gpgPath);
if (!gpgInfo.isAbsolute()) {
return QString();
}
QDir dir(gpgInfo.absolutePath());
#ifdef Q_OS_WIN
QFileInfo candidateExe(dir.filePath("gpgconf.exe"));
if (candidateExe.isExecutable()) {
return candidateExe.filePath();
}
#endif
QFileInfo candidate(dir.filePath("gpgconf"));
if (candidate.isExecutable()) {
return candidate.filePath();
}
return QString();
}
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
QStringList splitCommandCompat(const QString &command) {
QStringList result;
QString current;
bool inSingleQuote = false;
bool inDoubleQuote = false;
bool escaping = false;
for (QChar ch : command) {
if (escaping) {
current.append(ch);
escaping = false;
continue;
}
if (ch == '\\') {
escaping = true;
continue;
}
if (ch == '\'' && !inDoubleQuote) {
inSingleQuote = !inSingleQuote;
continue;
}
if (ch == '"' && !inSingleQuote) {
inDoubleQuote = !inDoubleQuote;
continue;
}
if (ch.isSpace() && !inSingleQuote && !inDoubleQuote) {
if (!current.isEmpty()) {
result.append(current);
current.clear();
}
continue;
}
current.append(ch);
}
if (escaping) {
current.append('\\');
}
if (!current.isEmpty()) {
result.append(current);
}
return result;
}
#endif
} // namespace
auto Pass::resolveGpgconfCommand(const QString &gpgPath)
if (gpgPath.trimmed().isEmpty()) {
return {"gpgconf", {}};
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
QStringList parts = QProcess::splitCommand(gpgPath);
#else
QStringList parts = splitCommandCompat(gpgPath);
#endif
if (parts.isEmpty()) {
return {"gpgconf", {}};
}
const QString first = parts.first();
if (first == "wsl" || first == "wsl.exe") {
if (parts.size() >= 2 && parts.at(1).startsWith("sh")) {
return {"gpgconf", {}};
}
if (parts.size() >= 2 &&
QFileInfo(parts.last()).fileName().startsWith("gpg")) {
QString wslGpgconf = resolveWslGpgconfPath(parts.last());
parts.removeLast();
parts.append(wslGpgconf);
return {parts.first(), parts.mid(1)};
}
return {"gpgconf", {}};
}
if (!first.contains('/') && !first.contains('\\')) {
return {"gpgconf", {}};
}
QString gpgconfPath = findGpgconfInGpgDir(gpgPath);
if (!gpgconfPath.isEmpty()) {
return {gpgconfPath, {}};
}
return {"gpgconf", {}};
}
void Pass::GenerateGPGKeys(QString batch) {
// Kill any stale GPG agents that might be holding locks on the key database
// This helps avoid "database locked" timeouts during key generation
QString gpgPath = QtPassSettings::getGpgExecutable();
if (!gpgPath.isEmpty()) {
ResolvedGpgconfCommand gpgconf = resolveGpgconfCommand(gpgPath);
QStringList killArgs = gpgconf.arguments;
killArgs << "--kill";
killArgs << "gpg-agent";
// Use same environment as key generation to target correct gpg-agent
Executor::executeBlocking(env, gpgconf.program, killArgs);
}
executeWrapper(GPG_GENKEYS, gpgPath, {"--gen-key", "--no-tty", "--batch"},
std::move(batch));
}
auto Pass::listKeys(QStringList keystrings, bool secret) -> QList<UserInfo> {
QStringList args = {"--no-tty", "--with-colons", "--with-fingerprint"};
args.append(secret ? "--list-secret-keys" : "--list-keys");
for (const QString &keystring : AS_CONST(keystrings)) {
if (!keystring.isEmpty()) {
args.append(keystring);
}
}
QString p_out;
&p_out) != 0) {
return QList<UserInfo>();
}
return parseGpgColonOutput(p_out, secret);
}
auto Pass::listKeys(const QString &keystring, bool secret) -> QList<UserInfo> {
return listKeys(QStringList(keystring), secret);
}
void Pass::finished(int id, int exitCode, const QString &out,
const QString &err) {
auto pid = static_cast<PROCESS>(id);
if (exitCode != 0) {
emit processErrorExit(exitCode, err);
return;
}
switch (pid) {
case GIT_INIT:
emit finishedGitInit(out, err);
break;
case GIT_PULL:
emit finishedGitPull(out, err);
break;
case GIT_PUSH:
emit finishedGitPush(out, err);
break;
case PASS_SHOW:
emit finishedShow(out);
break;
break;
emit finishedInsert(out, err);
break;
emit finishedRemove(out, err);
break;
case PASS_INIT:
emit finishedInit(out, err);
break;
case PASS_MOVE:
emit finishedMove(out, err);
break;
case PASS_COPY:
emit finishedCopy(out, err);
break;
emit finishedGenerateGPGKeys(out, err);
break;
default:
#ifdef QT_DEBUG
dbg() << "Unhandled process type" << pid;
#endif
break;
}
}
// put PASSWORD_STORE_SIGNING_KEY in env
QStringList envSigningKey = env.filter("PASSWORD_STORE_SIGNING_KEY=");
QString currentSigningKey = QtPassSettings::getPassSigningKey();
if (envSigningKey.isEmpty()) {
if (!currentSigningKey.isEmpty()) {
// dbg()<< "Added
// PASSWORD_STORE_SIGNING_KEY with" + currentSigningKey;
env.append("PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey);
}
} else {
if (currentSigningKey.isEmpty()) {
env.removeAll(envSigningKey.first());
} else {
// dbg()<< "Update
// PASSWORD_STORE_SIGNING_KEY with " + currentSigningKey;
env.replaceInStrings(envSigningKey.first(),
"PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey);
}
}
// put PASSWORD_STORE_DIR in env
QStringList store = env.filter("PASSWORD_STORE_DIR=");
if (store.isEmpty()) {
env.append("PASSWORD_STORE_DIR=" + QtPassSettings::getPassStore());
} else {
// dbg()<< "Update
// PASSWORD_STORE_DIR with " + passStore;
env.replaceInStrings(store.first(), "PASSWORD_STORE_DIR=" +
}
exec.setEnvironment(env);
}
auto Pass::getGpgIdPath(const QString &for_file) -> QString {
QString passStore =
QDir::fromNativeSeparators(QtPassSettings::getPassStore());
QString normalizedFile = QDir::fromNativeSeparators(for_file);
QString fullPath = normalizedFile.startsWith(passStore)
? normalizedFile
: passStore + "/" + normalizedFile;
QDir gpgIdDir(QFileInfo(fullPath).absoluteDir());
bool found = false;
while (gpgIdDir.exists() && gpgIdDir.absolutePath().startsWith(passStore)) {
if (QFile(gpgIdDir.absoluteFilePath(".gpg-id")).exists()) {
found = true;
break;
}
if (!gpgIdDir.cdUp()) {
break;
}
}
QString gpgIdPath(
found ? gpgIdDir.absoluteFilePath(".gpg-id")
: QDir(QtPassSettings::getPassStore()).filePath(".gpg-id"));
return gpgIdPath;
}
auto Pass::getRecipientList(const QString &for_file) -> QStringList {
QFile gpgId(getGpgIdPath(for_file));
if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text)) {
return {};
}
QStringList recipients;
while (!gpgId.atEnd()) {
QString recipient(gpgId.readLine());
recipient = recipient.split("#")[0].trimmed();
if (!recipient.isEmpty() && Util::isValidKeyId(recipient)) {
recipients += recipient;
}
}
return recipients;
}
auto Pass::getRecipientString(const QString &for_file, const QString &separator,
int *count) -> QStringList {
Q_UNUSED(separator)
QStringList recipients = Pass::getRecipientList(for_file);
if (count) {
*count = recipients.size();
}
return recipients;
}
/* Copyright (C) 2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
auto Pass::boundedRandom(quint32 bound) -> quint32 {
if (bound < 2) {
return 0;
}
quint32 randval;
// Rejection-sampling threshold to avoid modulo bias:
// In quint32 arithmetic, (1 + ~bound) wraps to (2^32 - bound), so
// (1 + ~bound) % bound == 2^32 % bound.
// Values randval < max_mod_bound are rejected; accepted values produce a
// uniform distribution when reduced with (randval % bound).
const quint32 max_mod_bound = (1 + ~bound) % bound;
do {
randval = QRandomGenerator::system()->generate();
} while (randval < max_mod_bound);
return randval % bound;
}
auto Pass::generateRandomPassword(const QString &charset, unsigned int length)
-> QString {
if (charset.isEmpty() || length == 0U) {
return {};
}
QString out;
for (unsigned int i = 0; i < length; ++i) {
out.append(charset.at(static_cast<int>(
boundedRandom(static_cast<quint32>(charset.length())))));
}
return out;
}
id Identifier provided by the caller for this queued request.
Definition executor.h:76
static auto executeBlocking(QString app, const QStringList &args, const QString &input=QString(), QString *process_out=nullptr, QString *process_err=nullptr) -> int
Executor::executeBlocking blocking version of the executor, takes input and presents it as stdin.
Definition executor.cpp:223
void starting()
starting signal that is emited when process starts
void init()
Initialize the Pass instance.
Definition pass.cpp:76
void startingExecuteWrapper()
Emitted before executing a command.
void GenerateGPGKeys(QString batch)
Generate GPG keys using batch script.
Definition pass.cpp:369
void finishedCopy(const QString &, const QString &)
Emitted when copy finishes.
void finishedShow(const QString &)
Emitted when show finishes.
void finishedRemove(const QString &, const QString &)
Emitted when remove finishes.
Enums::PROCESS PROCESS
Definition pass.h:44
virtual auto generatePassword(unsigned int length, const QString &charset) -> QString
Generate random password.
Definition pass.cpp:100
void finishedMove(const QString &, const QString &)
Emitted when move finishes.
static bool gpgSupportsEd25519()
Check if GPG supports Ed25519 encryption.
Definition pass.cpp:157
auto boundedRandom(quint32 bound) -> quint32
Generate random number in range.
Definition pass.cpp:591
void finishedGitInit(const QString &, const QString &)
Emitted when Git init finishes.
Pass()
Construct a Pass instance.
Definition pass.cpp:34
void executeWrapper(PROCESS id, const QString &app, const QStringList &args, bool readStdout=true, bool readStderr=true)
Execute external wrapper command.
Definition pass.cpp:57
static auto getRecipientList(const QString &for_file) -> QStringList
Get list of recipients for a password file.
Definition pass.cpp:550
static auto resolveGpgconfCommand(const QString &gpgPath) -> ResolvedGpgconfCommand
Resolve the gpgconf command to kill agents.
Definition pass.cpp:322
auto listKeys(QStringList keystrings, bool secret=false) -> QList< UserInfo >
List GPG keys matching patterns.
Definition pass.cpp:392
void finishedInsert(const QString &, const QString &)
Emitted when insert finishes.
void finishedInit(const QString &, const QString &)
Emitted when init finishes.
static auto getGpgIdPath(const QString &for_file) -> QString
Get .gpg-id file path for a password file.
Definition pass.cpp:520
void finishedOtpGenerate(const QString &)
Emitted when OTP generation finishes.
Executor exec
Definition pass.h:42
void processErrorExit(int exitCode, const QString &err)
Emitted on process error exit.
void finishedGitPull(const QString &, const QString &)
Emitted when Git pull finishes.
void finishedGitPush(const QString &, const QString &)
Emitted when Git push finishes.
void updateEnv()
Update environment for subprocesses.
Definition pass.cpp:482
virtual void finished(int id, int exitCode, const QString &out, const QString &err)
Handle process completion.
Definition pass.cpp:429
static auto getRecipientString(const QString &for_file, const QString &separator=" ", int *count=nullptr) -> QStringList
Get recipients as string.
Definition pass.cpp:573
static QString getDefaultKeyTemplate()
Get default key template for new GPG keys.
Definition pass.cpp:178
void finishedGenerateGPGKeys(const QString &, const QString &)
Emitted when GPG key generation finishes.
auto generateRandomPassword(const QString &charset, unsigned int length) -> QString
Generate random password from charset.
Definition pass.cpp:617
static auto getPwgenExecutable(const QString &defaultValue=QVariant().toString()) -> QString
Get pwgen executable path.
static auto getPasswordConfiguration() -> PasswordConfiguration
Get complete password generation configuration.
static auto getGpgHome(const QString &defaultValue=QVariant().toString()) -> QString
Get GPG home directory.
static auto getPassStore(const QString &defaultValue=QVariant().toString()) -> QString
Get password store directory path.
static auto isAvoidCapitals(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether uppercase characters should be avoided.
static auto getGpgExecutable(const QString &defaultValue=QVariant().toString()) -> QString
Get GPG executable path.
static auto getPassSigningKey(const QString &defaultValue=QVariant().toString()) -> QString
Get GPG signing key for pass.
static auto isUseSymbols(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether symbol characters are enabled.
static auto isLessRandom(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether less random password generation is enabled.
static auto isUsePwgen(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether pwgen support is enabled.
static auto isAvoidNumbers(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether numeric characters should be avoided.
static auto isValidKeyId(const QString &keyId) -> bool
Check if a string looks like a valid GPG key ID. Validates a GPG key ID after normalization:
Definition util.cpp:277
Debug utilities for QtPass.
#define dbg()
Simple debug macro that includes file and line number.
Definition debughelper.h:21
auto parseGpgColonOutput(const QString &output, bool secret) -> QList< UserInfo >
Parse GPG –with-colons output into a list of UserInfo.
Utility macros for QtPass.
#define AS_CONST(x)
Cross-platform const_cast for range-based for loops.
Definition helpers.h:20
@ PASS_INIT
Definition enums.h:36
@ PASS_OTP_GENERATE
Definition enums.h:44
@ PASS_INSERT
Definition enums.h:34
@ GIT_INIT
Definition enums.h:27
@ PASS_COPY
Definition enums.h:39
@ PASS_MOVE
Definition enums.h:38
@ GPG_GENKEYS
Definition enums.h:37
@ PASS_REMOVE
Definition enums.h:35
@ PASS_SHOW
Definition enums.h:33
@ GIT_PULL
Definition enums.h:31
@ GIT_PUSH
Definition enums.h:32
@ PASS_INIT
Definition enums.h:36
@ PASS_OTP_GENERATE
Definition enums.h:44
@ PASS_INSERT
Definition enums.h:34
@ GIT_INIT
Definition enums.h:27
@ PASS_COPY
Definition enums.h:39
@ PASS_MOVE
Definition enums.h:38
@ GPG_GENKEYS
Definition enums.h:37
@ PASS_REMOVE
Definition enums.h:35
@ PASS_SHOW
Definition enums.h:33
@ GIT_PULL
Definition enums.h:31
@ GIT_PUSH
Definition enums.h:32
PROCESS
Identifies different subprocess operations used in QtPass.
Definition enums.h:26
QStringList arguments
Definition pass.h:19