16#include <QCoreApplication>
17#include <QCryptographicHash>
22#include <QRegularExpression>
49 qCDebug(lcQtPass) <<
"Git pull failed with code:" << result;
69 [
this](
const QString &app,
const QStringList &args,
70 const QString &input, QString *out, QString *err) {
72 gpg.setProcessEnvironment(
exec.environment());
78 QSet<QString> &verified,
const QString &within,
79 const QString &except) ->
bool {
84 const QString absolute =
85 QDir::cleanPath(QFileInfo(gpgIdFile).absoluteFilePath());
86 if (verified.contains(gpgIdFile) ||
88 absolute == QDir::cleanPath(QFileInfo(except).absoluteFilePath())) ||
89 (!within.isEmpty() && !absolute.startsWith(within))) {
93 QCoreApplication::translate(
"ImitatePass",
"Signature for %1 is invalid.")
97 emit
critical(QCoreApplication::translate(
"ImitatePass",
98 "Check .gpg-id file signature!"),
102 verified.insert(gpgIdFile);
117 QSet<QString> verified;
126auto passFoldersUnder(
const QString &dir) -> QStringList {
127 QStringList folders{QDir::cleanPath(dir)};
128 const QFileInfoList children = QDir(dir).entryInfoList(
129 QDir::Dirs | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot);
130 for (
const QFileInfo &child : children) {
131 if (child.isSymLink() || child.isJunction() ||
132 child.fileName() == QLatin1String(
".git")) {
135 folders << passFoldersUnder(child.absoluteFilePath());
142 const QString &except) ->
bool {
149 emit
statusMsg(tr(
"Checking the recipient lists under %1…")
150 .arg(QDir::toNativeSeparators(QDir::cleanPath(dir))),
161 const QString within =
162 onlyListsWithin ? QDir::cleanPath(QFileInfo(dir).absoluteFilePath()) +
165 QSet<QString> verified;
170 for (
const QString &folder : passFoldersUnder(dir)) {
171 if (
refuseList(folder, signer, verified, within, except)) {
187 QStringList{QStringLiteral(
"git")} + args, out, &err) == 0;
194 const QString entry =
195 QDir::cleanPath(QDir::isAbsolutePath(file)
207 QStringList args = {
"insert",
"-m"};
233 if (err.contains(QLatin1String(
"Password encryption aborted."))) {
240 return !after.isEmpty() && after !=
m_insertDone.before;
248 QCryptographicHash hash(QCryptographicHash::Sha256);
249 if (!hash.addData(&file)) {
252 return hash.result();
269 const QString why = tr(
"Could not remove the link %1.").arg(full);
270 emit
critical(tr(
"Delete failed"), why);
285 {
"git",
"ls-files",
"--", rel}, &known, &err) == 0;
286 if (recorded && !known.trimmed().isEmpty()) {
290 {
"git",
"rm",
"-q",
"--cached",
"--", rel}, &known, &err) == 0;
298 {
"git",
"config",
"--bool",
"--get",
"pass.signcommits"},
299 &signs,
nullptr) == 0 &&
300 signs.trimmed() == QLatin1String(
"true");
301 QStringList commit{
"git",
"commit",
"-q"};
313 commit + QStringList{
"-m",
"Remove for " + rel +
" using QtPass.",
320 tr(
"The link %1 was removed, but git could not record it: %2")
321 .arg(rel, err.trimmed());
322 emit
critical(tr(
"Delete incomplete"), why);
335 const QString &err) {
351 const QString &err) {
354 const QString &rel = commit.
rel;
356 const bool committed =
359 {
"git",
"diff",
"--cached",
"--name-only",
"--", rel}, &staged,
361 staged.trimmed().isEmpty();
369 const QString why = tr(
"The link %1 was removed and git no longer tracks "
370 "it, but the commit failed: %2")
371 .arg(rel, err.trimmed());
372 emit
critical(tr(
"Delete incomplete"), why);
381 const QString folder = QDir::cleanPath(
382 QDir::isAbsolutePath(path) ? path
390 QString dirWithoutPassdir;
391 const auto refusePlaced = [
this](
const QString &placed) {
395 if (refusePlaced(folder) ||
396 refusePlaced(folder + QStringLiteral(
"/.gpg-id")) ||
397 refusePlaced(folder + QStringLiteral(
"/.gpg-id.sig")) ||
408 const QString inStore =
411 QDir(inStore).filePath(QStringLiteral(
".gpg-id")))) {
417 QStringList args = {
"init",
"--path=" + dirWithoutPassdir};
418 for (
const UserInfo &user : users) {
420 args.append(user.key_id);
437 .relativeFilePath(QDir::cleanPath(QDir(path).absolutePath()));
445 const QString &src,
const QString &dest,
450 QFileInfo srcFileInfo = QFileInfo(src);
451 QFileInfo destFileInfo = QFileInfo(dest);
454 if (destFileInfo.isDir() &&
460 if (!force && srcFileInfo.isFile() && destFileInfo.isFile()) {
469 ? QDir(dest).filePath(srcFileInfo.fileName())
481 const QString passSrc =
482 passName(src, srcFileInfo.isFile() &&
483 srcFileInfo.suffix() == QLatin1String(
"gpg"));
484 const QString passDest =
485 passName(dest, !destFileInfo.isDir() &&
486 destFileInfo.suffix() == QLatin1String(
"gpg"));
502 emit
statusMsg(tr(
"Search needs the GPG executable to be configured."),
512 bool readStdout,
bool readStderr) {
514 readStdout, readStderr);
void dequeuing(int id, bool completes)
Emitted, synchronously, when the queue's head leaves it: just before it is started,...
static auto executeBlocking(const QString &app, const QStringList &args, const QString &input=QString(), QString *process_out=nullptr, QString *process_err=nullptr) -> int
Run a command synchronously and return its exit code.
auto environment() const -> QProcessEnvironment
Return the environment passed to child processes.
The detached signature on a store's .gpg-id: whether the configured signing key is usable,...
auto enabled() const -> bool
Whether any signing key is configured at all.
static auto keysFromSetting(const QString &passSigningKey) -> QStringList
Split the settings value into keys, upper-cased the way gpg prints fingerprints.
void finished(const QList< QPair< QString, QStringList > > &results)
The newest search is over.
void search(const QString &pattern, bool caseInsensitive, const QString &gpgExe, const QString &storeDir, const QProcessEnvironment &env)
Start a search; finished() follows asynchronously, also for an empty or invalid pattern (with no resu...
void cancel()
Ask running searches to stop without waiting for them: the walk ends at the next file and a gpg that ...
void finishedAnyWithPid(const QString &out, const QString &err, Enums::PROCESS pid)
A process of any kind finished successfully and its output cannot contain a secret.
AppSettings m_settings
Application settings snapshot injected at init() time.
static auto getGpgIdPath(const QString &for_file, const QString &passStore) -> QString
Get .gpg-id file path for a password file.
auto refusePendingPull() -> bool
Refuse a store change while a Git pull is still queued or running.
Enums::PROCESS PROCESS
Alias for Enums::PROCESS used throughout this class.
void queueShow(const QString &file)
Remember what a Show() asked for, so its completion can say so.
void statusMsg(const QString &msg, int timeout)
Progress or a non-fatal problem worth a line in the status bar ("Creating backup commit",...
auto refuseSkippedGpgId(const QString &file, bool signing, const QString &except=QString()) -> bool
With signing on, refuse to encrypt file when the lookup passed over its folder's own list (skippedGpg...
@ PlacedByCaller
Through the target of a linked store root, by a caller that places such a folder itself (Init()): not...
void executeWrapper(PROCESS id, const QString &app, const QStringList &args, bool readStdout=true, bool readStderr=true)
Execute external wrapper command.
auto refuseLinkedPath(const QString &path, LinkScope scope=LinkScope::EntryAndParents, PathSpelling spelling=PathSpelling::Configured) -> bool
Refuse an operation on something that is, or lies behind, a symbolic link or NTFS junction inside the...
void finishedGrep(const QList< QPair< QString, QStringList > > &results)
The newest Grep() is over; an empty list means no entry matched.
Executor exec
Internal command executor for queuing and running subprocesses.
void processErrorExit(int exitCode, const QString &err)
An operation ended in failure: its process exited non-zero, could not be started or crashed,...
auto verifySignedList(const GpgIdSigner &signer, const QString &gpgIdFile, QByteArray *contents, QString *why) const -> bool
Whether gpgIdFile is a signed list QtPass accepts: its signature verifies (GpgIdSigner::verifyFile())...
virtual void finished(int id, int exitCode, const QString &out, const QString &err)
Handle process completion.
void critical(const QString &title, const QString &message)
Something went wrong that the user has to be told about in a dialog, such as a refused path or a fail...
@ ParentsOnly
Only the folders above it: a removal unlinks a link and touches nothing behind it.
@ EntryAndParents
The entry itself and every folder above it in the store.
void finishedRemove(const QString &out, const QString &err)
A Remove() succeeded; the entry or folder is gone from the store.
auto refuseUnfinishedGitOperation() -> bool
Refuse to change the store while git is mid-rebase, -merge, -cherry-pick or -revert.
void queueInsert()
Count one insert that has started queueing but not yet reported.
auto refuseFolderOutsideStore(const QString &folder, QString *relative=nullptr) -> bool
Refuse recipients for a folder outside the store, as pass refuses a path with .. in it.
void finishLinkCommit(const LinkCommit &commit, const QString &out, const QString &err)
Judge a queued linked-folder removal commit by what is left staged in the store it ran in: pass git e...
void Remove(QString file, bool isDir) override
Remove an entry or folder with pass rm.
void Init(QString path, const QList< UserInfo > &users) override
Initialize password store.
void Copy(const QString src, const QString dest, const bool force) override
Copy password file or directory.
auto storeGit(const QStringList &args, QString *out) -> bool override
Run a git command in the store through pass git, blocking.
QQueue< InsertRecord > m_insertPending
auto refuseList(const QString &file, const GpgIdSigner &signer, QSet< QString > &verified, const QString &within=QString(), const QString &except=QString()) -> bool
The checks of refuseUnverifiedList() for file's nearest list, once per list.
void Insert(QString file, QString newValue, bool overwrite) override
Insert new password.
~RealPass() override
Destructor; lets the search workers wind down.
void Show(QString file) override
Show decrypted password.
InsertRecord m_insertDone
The insert whose completion is being handled.
void GitPull_b() override
Pull with rebase.
RealPass()
Construct a RealPass instance.
void onDequeuing(int id, bool completes)
The executor is about to run (or drop) its next item: for an insert, take its entry from m_insertQueu...
void passMoveOrCopy(PROCESS id, const QString &subcommand, const QString &src, const QString &dest, bool force)
Shared implementation of Move and Copy via pass mv/pass cp.
auto passName(const QString &path, bool stripGpg) const -> QString
path as pass names it: relative to the store, without the .gpg pass appends itself.
auto refuseUnverifiedList(const QString &file) -> bool
With signing on, refuse to have pass encrypt file when the signed list it would use is not one QtPass...
static auto entryFingerprint(const QString &path) -> QByteArray
A SHA-256 of the regular file path (opened without following a link or blocking on a FIFO).
void GitInit() override
Initialize Git repository in password store.
void GitPull() override
Pull changes from remote.
QQueue< LinkCommit > m_linkCommits
void Move(const QString src, const QString dest, const bool force) override
Move password file or directory.
void finished(int id, int exitCode, const QString &out, const QString &err) override
Finish a process; a linked-folder removal commit (GIT_COMMIT, which RealPass uses for nothing else) i...
void executePass(PROCESS id, const QStringList &args, QString input=QString(), bool readStdout=true, bool readStderr=true)
Execute pass command with arguments.
void GitPush() override
Push changes to remote.
auto insertWritten(const QString &err) const -> bool override
Whether the failed pass insert wrote its entry before its git step failed. pass does not say,...
void Grep(QString pattern, bool caseInsensitive) override
Search all password content by GPG-decrypting each real .gpg file, as ImitatePass does.
auto refuseUnverifiedTree(const QString &dir, bool onlyListsWithin, const QString &except=QString()) -> bool
For a folder pass is about to re-encrypt in a move or copy: refuse when the signed list of one of its...
auto listSigner() -> GpgIdSigner
The verifier for signed lists, gpg in the store's environment.
QQueue< QString > m_insertQueue
The entries of the queued inserts, oldest first.
static auto removeTree(const QString &dir) -> bool
Remove dir and everything in it, the way rm -rf does: a symbolic link or NTFS junction,...
static auto endsWithGpg() -> const QRegularExpression &
Returns a regex to match .gpg file extensions.
static auto isLinkedFolder(const QString &path) -> bool
Whether path names a symbolic link or NTFS junction, with or without a trailing separator (which woul...
static auto openRegularFile(const QString &path, QFile &file) -> bool
Open path for reading as the regular file it is, not as what a link under that name points to: the ob...
Executor, the FIFO queue every gpg, git and pass child process of QtPass goes through,...
GpgIdSigner, which signs a store's .gpg-id and verifies an existing .gpg-id.sig against the configure...
PathValidator: is a path inside the password store, and how is it spelled relative to the store root.
The "qtpass" logging category.
RealPass, the backend that hands every store operation to the pass command-line tool.
QString gpgExecutable
Path to the gpg/gpg2 executable.
QString passStore
Password-store root path.
QString passExecutable
Path to the pass executable.
bool useGit
Enable git integration.
QString rel
The folder, relative to the store.
QProcessEnvironment environment
Stores GPG key info including validity, creation date, and more.
Util: static helpers for the process environment, binary discovery, store paths, URL safety and the l...