QtPass
2.0.0
Developer documentation
Toggle main menu visibility
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
18
namespace
{
40
auto
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
}
60
auto
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
84
auto
SshAuthSock::overrideStatus
(
const
QString &path) ->
OverrideStatus
{
85
const
QFileInfo fi(path);
86
if
(!fi.exists()) {
87
return
OverrideStatus::DoesNotExist
;
88
}
89
if
(!fi.isReadable()) {
90
return
OverrideStatus::NotReadable
;
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)) {
100
return
OverrideStatus::NotUnixDomainSocket
;
101
}
102
#endif
103
return
OverrideStatus::Valid
;
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).
110
void
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
}
Executor::executeBlocking
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
SshAuthSock::OverrideStatus
OverrideStatus
Classify a non-empty SSH_AUTH_SOCK override path supplied by the user.
Definition
sshauthsock.h:33
SshAuthSock::OverrideStatus::NotReadable
@ NotReadable
Definition
sshauthsock.h:36
SshAuthSock::OverrideStatus::Valid
@ Valid
Exists, readable, and on Unix a Unix domain socket.
Definition
sshauthsock.h:34
SshAuthSock::OverrideStatus::NotUnixDomainSocket
@ NotUnixDomainSocket
Unix only: stat() says the path is not S_ISSOCK.
Definition
sshauthsock.h:38
SshAuthSock::OverrideStatus::DoesNotExist
@ DoesNotExist
QFileInfo::exists() returned false.
Definition
sshauthsock.h:35
SshAuthSock::overrideStatus
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...
Definition
sshauthsock.cpp:84
SshAuthSock::initialise
static void initialise(const QString &override={})
Ensure SSH_AUTH_SOCK is set for child processes.
Definition
sshauthsock.cpp:110
executor.h
Executor, the FIFO queue every gpg, git and pass child process of QtPass goes through,...
qtpasslogging.h
The "qtpass" logging category.
sshauthsock.h
SshAuthSock: finding an SSH agent socket for child processes and judging a configured override.
src
sshauthsock.cpp
Generated by
1.18.0