Restore built-in askpass helper for SSH password auth
This commit is contained in:
@@ -1,7 +1,20 @@
|
||||
#include "ssh_session_backend.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QTextStream>
|
||||
#include <QUuid>
|
||||
|
||||
namespace {
|
||||
QString escapeForShellSingleQuotes(const QString& value)
|
||||
{
|
||||
QString escaped = value;
|
||||
escaped.replace(QStringLiteral("'"), QStringLiteral("'\"'\"'"));
|
||||
return escaped;
|
||||
}
|
||||
}
|
||||
|
||||
SshSessionBackend::SshSessionBackend(const Profile& profile, QObject* parent)
|
||||
: SessionBackend(profile, parent),
|
||||
@@ -45,6 +58,7 @@ SshSessionBackend::~SshSessionBackend()
|
||||
m_process->kill();
|
||||
m_process->waitForFinished(500);
|
||||
}
|
||||
cleanupAskPassScript();
|
||||
}
|
||||
|
||||
void SshSessionBackend::connectSession(const SessionConnectOptions& options)
|
||||
@@ -161,6 +175,7 @@ void SshSessionBackend::onProcessErrorOccurred(QProcess::ProcessError)
|
||||
void SshSessionBackend::onProcessFinished(int exitCode, QProcess::ExitStatus)
|
||||
{
|
||||
m_connectedProbeTimer->stop();
|
||||
cleanupAskPassScript();
|
||||
|
||||
if (m_reconnectPending) {
|
||||
m_reconnectPending = false;
|
||||
@@ -311,6 +326,8 @@ bool SshSessionBackend::startSshProcess(const SessionConnectOptions& options)
|
||||
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=yes");
|
||||
}
|
||||
|
||||
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
||||
|
||||
if (p.authMode.compare(QStringLiteral("Password"), Qt::CaseInsensitive) == 0) {
|
||||
if (options.password.isEmpty()) {
|
||||
const QString message = QStringLiteral("Password is required for password authentication.");
|
||||
@@ -322,7 +339,14 @@ bool SshSessionBackend::startSshProcess(const SessionConnectOptions& options)
|
||||
args << QStringLiteral("-o") << QStringLiteral("PreferredAuthentications=password")
|
||||
<< QStringLiteral("-o") << QStringLiteral("PubkeyAuthentication=no")
|
||||
<< QStringLiteral("-o") << QStringLiteral("NumberOfPasswordPrompts=1");
|
||||
m_waitingForPasswordPrompt = true;
|
||||
m_waitingForPasswordPrompt = false;
|
||||
|
||||
QString askPassError;
|
||||
if (!configureAskPass(options, environment, askPassError)) {
|
||||
setState(SessionState::Failed, askPassError);
|
||||
emit connectionError(askPassError, askPassError);
|
||||
return false;
|
||||
}
|
||||
} else if (p.authMode.compare(QStringLiteral("Private Key"), Qt::CaseInsensitive) == 0) {
|
||||
QString keyPath = options.privateKeyPath.trimmed();
|
||||
if (keyPath.isEmpty()) {
|
||||
@@ -354,7 +378,7 @@ bool SshSessionBackend::startSshProcess(const SessionConnectOptions& options)
|
||||
: QStringLiteral("%1@%2").arg(p.username.trimmed(), p.host.trimmed());
|
||||
args << target;
|
||||
|
||||
m_process->setProcessEnvironment(QProcessEnvironment::systemEnvironment());
|
||||
m_process->setProcessEnvironment(environment);
|
||||
m_process->setProgram(QStringLiteral("ssh"));
|
||||
m_process->setArguments(args);
|
||||
m_process->setProcessChannelMode(QProcess::SeparateChannels);
|
||||
@@ -371,6 +395,66 @@ bool SshSessionBackend::startSshProcess(const SessionConnectOptions& options)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SshSessionBackend::configureAskPass(const SessionConnectOptions& options,
|
||||
QProcessEnvironment& environment,
|
||||
QString& error)
|
||||
{
|
||||
cleanupAskPassScript();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
m_askPassScriptPath = QDir::temp().filePath(
|
||||
QStringLiteral("orbithub_askpass_%1.cmd")
|
||||
.arg(QUuid::createUuid().toString(QUuid::WithoutBraces)));
|
||||
#else
|
||||
m_askPassScriptPath = QDir::temp().filePath(
|
||||
QStringLiteral("orbithub_askpass_%1.sh")
|
||||
.arg(QUuid::createUuid().toString(QUuid::WithoutBraces)));
|
||||
#endif
|
||||
|
||||
QFile script(m_askPassScriptPath);
|
||||
if (!script.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
|
||||
error = QStringLiteral("Failed to create temporary askpass helper script.");
|
||||
cleanupAskPassScript();
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream out(&script);
|
||||
#ifdef Q_OS_WIN
|
||||
out << "@echo off\r\n";
|
||||
out << "echo " << options.password << "\r\n";
|
||||
#else
|
||||
const QString escapedPassword = escapeForShellSingleQuotes(options.password);
|
||||
out << "#!/bin/sh\n";
|
||||
out << "printf '%s\\n' '" << escapedPassword << "'\n";
|
||||
#endif
|
||||
out.flush();
|
||||
script.close();
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
if (!QFile::setPermissions(m_askPassScriptPath,
|
||||
QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) {
|
||||
error = QStringLiteral("Failed to set permissions on askpass helper script.");
|
||||
cleanupAskPassScript();
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
environment.insert(QStringLiteral("SSH_ASKPASS"), m_askPassScriptPath);
|
||||
environment.insert(QStringLiteral("SSH_ASKPASS_REQUIRE"), QStringLiteral("force"));
|
||||
if (!environment.contains(QStringLiteral("DISPLAY"))) {
|
||||
environment.insert(QStringLiteral("DISPLAY"), QStringLiteral(":0"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SshSessionBackend::cleanupAskPassScript()
|
||||
{
|
||||
if (!m_askPassScriptPath.isEmpty()) {
|
||||
QFile::remove(m_askPassScriptPath);
|
||||
m_askPassScriptPath.clear();
|
||||
}
|
||||
}
|
||||
|
||||
QString SshSessionBackend::mapSshError(const QString& rawError) const
|
||||
{
|
||||
const QString raw = rawError.trimmed();
|
||||
@@ -398,6 +482,9 @@ QString SshSessionBackend::mapSshError(const QString& rawError) const
|
||||
return QStringLiteral("Private key file is not accessible.");
|
||||
}
|
||||
if (raw.contains(QStringLiteral("No such file or directory"), Qt::CaseInsensitive)) {
|
||||
if (raw.contains(QStringLiteral("ssh-askpass"), Qt::CaseInsensitive)) {
|
||||
return QStringLiteral("SSH password helper is missing or failed to launch.");
|
||||
}
|
||||
return QStringLiteral("Required file was not found.");
|
||||
}
|
||||
if (raw.isEmpty()) {
|
||||
|
||||
@@ -39,12 +39,17 @@ private:
|
||||
SessionConnectOptions m_reconnectOptions;
|
||||
SessionConnectOptions m_activeOptions;
|
||||
QString m_lastRawError;
|
||||
QString m_askPassScriptPath;
|
||||
bool m_waitingForPasswordPrompt;
|
||||
bool m_waitingForHostKeyConfirmation;
|
||||
bool m_passwordSubmitted;
|
||||
|
||||
void setState(SessionState state, const QString& message);
|
||||
bool startSshProcess(const SessionConnectOptions& options);
|
||||
bool configureAskPass(const SessionConnectOptions& options,
|
||||
QProcessEnvironment& environment,
|
||||
QString& error);
|
||||
void cleanupAskPassScript();
|
||||
QString mapSshError(const QString& rawError) const;
|
||||
QString knownHostsFileForNullDevice() const;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user