QtPass 2.0.0
Developer documentation
Loading...
Searching...
No Matches
sshauthsock.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
2// SPDX-License-Identifier: GPL-3.0-or-later
8#include "sshauthsock.h"
9#include "executor.h"
10#include <QFileInfo>
11#include <QProcessEnvironment>
12#ifndef Q_OS_WIN
13#include <sys/stat.h>
14#endif
15
16#include "qtpasslogging.h"
17
18namespace {
40auto isSshAgentReachable(const QString &candidate) -> bool {
41 if (candidate.isEmpty()) {
42 return false;
43 }
44 // Build a minimal env that overrides SSH_AUTH_SOCK without polluting the
45 // parent process. systemEnvironment() captures whatever was set when
46 // QtPass started, then we override the one var of interest.
47 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
48 env.insert(QStringLiteral("SSH_AUTH_SOCK"), candidate);
49 QString out;
50 QString err;
51 const int exitCode = Executor::executeBlocking(
52 env, QStringLiteral("ssh-add"), {QStringLiteral("-l")}, &out, &err);
53 // OpenSSH ssh-add(1) exit codes: 0 = success / has keys, 1 = no
54 // identities present, 2 = couldn't open a connection. Anything else is
55 // also treated as unreachable (e.g. ssh-add not on PATH).
56 return exitCode == 0 || exitCode == 1;
57}
60auto adoptSocketFrom(const QString &program, const QStringList &args,
61 const char *source) -> bool {
62 QString out;
63 QString err;
64 if (Executor::executeBlocking(program, args, &out, &err) != 0) {
65 return false;
66 }
67 const QString socket = out.trimmed();
68 if (socket.isEmpty()) {
69 return false;
70 }
71 if (!isSshAgentReachable(socket)) {
72 qCDebug(lcQtPass) << "SshAuthSock::initialise():" << source << "reported"
73 << socket << "but ssh-add -l rejected it; not adopting";
74 return false;
75 }
76 qputenv("SSH_AUTH_SOCK", socket.toUtf8());
77 qCDebug(lcQtPass) << "SshAuthSock::initialise(): set from" << source << ":"
78 << socket;
79 return true;
80}
81
82} // namespace
83
84auto SshAuthSock::overrideStatus(const QString &path) -> OverrideStatus {
85 const QFileInfo fi(path);
86 if (!fi.exists()) {
88 }
89 if (!fi.isReadable()) {
91 }
92#ifdef Q_OS_UNIX
93 // S_ISSOCK isn't exposed by QFileInfo; go through stat(2). On Windows
94 // ssh-agent uses a named pipe (\\.\pipe\openssh-ssh-agent.<sid>) and the
95 // socket check would always fail, so skip it there — exists + readable is
96 // the best we can do.
97 struct stat st{};
98 if (::stat(path.toLocal8Bit().constData(), &st) != 0 ||
99 !S_ISSOCK(st.st_mode)) {
101 }
102#endif
104}
105
106// The rules are in the header. All probes go through
107// Executor::executeBlocking with no timeout or cancel flag, so each one
108// waits for its child (gpgconf, ssh-add -l) however long that takes. Any
109// failure is silently absorbed (the user just doesn't get the auto-fix).
110void SshAuthSock::initialise(const QString &override) {
111 // Honour any value already in the environment — terminal launches,
112 // explicit `.desktop` Exec= overrides, and parent-process exports must win.
113 if (!qgetenv("SSH_AUTH_SOCK").isEmpty()) {
114 return;
115 }
116
117 // Manual override from settings takes precedence over auto-probe.
118 if (!override.isEmpty()) {
119 qputenv("SSH_AUTH_SOCK", override.toUtf8());
120 qCDebug(lcQtPass)
121 << "SshAuthSock::initialise(): set from settings override:" << override;
122 return;
123 }
124
125 // Auto-probe via gpgconf (canonical for gpg-agent's SSH support).
126 if (adoptSocketFrom(
127 QStringLiteral("gpgconf"),
128 {QStringLiteral("--list-dirs"), QStringLiteral("agent-ssh-socket")},
129 "gpgconf")) {
130 return;
131 }
132#ifdef Q_OS_MACOS
133 // On macOS, GUI-launched apps may have SSH_AUTH_SOCK in launchd's
134 // per-session environment but not in the inherited process env.
135 adoptSocketFrom(QStringLiteral("launchctl"),
136 {QStringLiteral("getenv"), QStringLiteral("SSH_AUTH_SOCK")},
137 "launchctl");
138#endif
139}
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.
Definition executor.cpp:422
OverrideStatus
Classify a non-empty SSH_AUTH_SOCK override path supplied by the user.
Definition sshauthsock.h:33
@ Valid
Exists, readable, and on Unix a Unix domain socket.
Definition sshauthsock.h:34
@ NotUnixDomainSocket
Unix only: stat() says the path is not S_ISSOCK.
Definition sshauthsock.h:38
@ DoesNotExist
QFileInfo::exists() returned false.
Definition sshauthsock.h:35
static auto overrideStatus(const QString &path) -> OverrideStatus
Validate an SSH_AUTH_SOCK override path. Caller is expected to skip validation when the input is empt...
static void initialise(const QString &override={})
Ensure SSH_AUTH_SOCK is set for child processes.
Executor, the FIFO queue every gpg, git and pass child process of QtPass goes through,...
The "qtpass" logging category.
SshAuthSock: finding an SSH agent socket for child processes and judging a configured override.