Restore built-in askpass helper for SSH password auth
This commit is contained in:
@@ -1,7 +1,20 @@
|
|||||||
#include "ssh_session_backend.h"
|
#include "ssh_session_backend.h"
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QProcessEnvironment>
|
#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)
|
SshSessionBackend::SshSessionBackend(const Profile& profile, QObject* parent)
|
||||||
: SessionBackend(profile, parent),
|
: SessionBackend(profile, parent),
|
||||||
@@ -45,6 +58,7 @@ SshSessionBackend::~SshSessionBackend()
|
|||||||
m_process->kill();
|
m_process->kill();
|
||||||
m_process->waitForFinished(500);
|
m_process->waitForFinished(500);
|
||||||
}
|
}
|
||||||
|
cleanupAskPassScript();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SshSessionBackend::connectSession(const SessionConnectOptions& options)
|
void SshSessionBackend::connectSession(const SessionConnectOptions& options)
|
||||||
@@ -161,6 +175,7 @@ void SshSessionBackend::onProcessErrorOccurred(QProcess::ProcessError)
|
|||||||
void SshSessionBackend::onProcessFinished(int exitCode, QProcess::ExitStatus)
|
void SshSessionBackend::onProcessFinished(int exitCode, QProcess::ExitStatus)
|
||||||
{
|
{
|
||||||
m_connectedProbeTimer->stop();
|
m_connectedProbeTimer->stop();
|
||||||
|
cleanupAskPassScript();
|
||||||
|
|
||||||
if (m_reconnectPending) {
|
if (m_reconnectPending) {
|
||||||
m_reconnectPending = false;
|
m_reconnectPending = false;
|
||||||
@@ -311,6 +326,8 @@ bool SshSessionBackend::startSshProcess(const SessionConnectOptions& options)
|
|||||||
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=yes");
|
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=yes");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
||||||
|
|
||||||
if (p.authMode.compare(QStringLiteral("Password"), Qt::CaseInsensitive) == 0) {
|
if (p.authMode.compare(QStringLiteral("Password"), Qt::CaseInsensitive) == 0) {
|
||||||
if (options.password.isEmpty()) {
|
if (options.password.isEmpty()) {
|
||||||
const QString message = QStringLiteral("Password is required for password authentication.");
|
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")
|
args << QStringLiteral("-o") << QStringLiteral("PreferredAuthentications=password")
|
||||||
<< QStringLiteral("-o") << QStringLiteral("PubkeyAuthentication=no")
|
<< QStringLiteral("-o") << QStringLiteral("PubkeyAuthentication=no")
|
||||||
<< QStringLiteral("-o") << QStringLiteral("NumberOfPasswordPrompts=1");
|
<< 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) {
|
} else if (p.authMode.compare(QStringLiteral("Private Key"), Qt::CaseInsensitive) == 0) {
|
||||||
QString keyPath = options.privateKeyPath.trimmed();
|
QString keyPath = options.privateKeyPath.trimmed();
|
||||||
if (keyPath.isEmpty()) {
|
if (keyPath.isEmpty()) {
|
||||||
@@ -354,7 +378,7 @@ bool SshSessionBackend::startSshProcess(const SessionConnectOptions& options)
|
|||||||
: QStringLiteral("%1@%2").arg(p.username.trimmed(), p.host.trimmed());
|
: QStringLiteral("%1@%2").arg(p.username.trimmed(), p.host.trimmed());
|
||||||
args << target;
|
args << target;
|
||||||
|
|
||||||
m_process->setProcessEnvironment(QProcessEnvironment::systemEnvironment());
|
m_process->setProcessEnvironment(environment);
|
||||||
m_process->setProgram(QStringLiteral("ssh"));
|
m_process->setProgram(QStringLiteral("ssh"));
|
||||||
m_process->setArguments(args);
|
m_process->setArguments(args);
|
||||||
m_process->setProcessChannelMode(QProcess::SeparateChannels);
|
m_process->setProcessChannelMode(QProcess::SeparateChannels);
|
||||||
@@ -371,6 +395,66 @@ bool SshSessionBackend::startSshProcess(const SessionConnectOptions& options)
|
|||||||
return true;
|
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
|
QString SshSessionBackend::mapSshError(const QString& rawError) const
|
||||||
{
|
{
|
||||||
const QString raw = rawError.trimmed();
|
const QString raw = rawError.trimmed();
|
||||||
@@ -398,6 +482,9 @@ QString SshSessionBackend::mapSshError(const QString& rawError) const
|
|||||||
return QStringLiteral("Private key file is not accessible.");
|
return QStringLiteral("Private key file is not accessible.");
|
||||||
}
|
}
|
||||||
if (raw.contains(QStringLiteral("No such file or directory"), Qt::CaseInsensitive)) {
|
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.");
|
return QStringLiteral("Required file was not found.");
|
||||||
}
|
}
|
||||||
if (raw.isEmpty()) {
|
if (raw.isEmpty()) {
|
||||||
|
|||||||
@@ -39,12 +39,17 @@ private:
|
|||||||
SessionConnectOptions m_reconnectOptions;
|
SessionConnectOptions m_reconnectOptions;
|
||||||
SessionConnectOptions m_activeOptions;
|
SessionConnectOptions m_activeOptions;
|
||||||
QString m_lastRawError;
|
QString m_lastRawError;
|
||||||
|
QString m_askPassScriptPath;
|
||||||
bool m_waitingForPasswordPrompt;
|
bool m_waitingForPasswordPrompt;
|
||||||
bool m_waitingForHostKeyConfirmation;
|
bool m_waitingForHostKeyConfirmation;
|
||||||
bool m_passwordSubmitted;
|
bool m_passwordSubmitted;
|
||||||
|
|
||||||
void setState(SessionState state, const QString& message);
|
void setState(SessionState state, const QString& message);
|
||||||
bool startSshProcess(const SessionConnectOptions& options);
|
bool startSshProcess(const SessionConnectOptions& options);
|
||||||
|
bool configureAskPass(const SessionConnectOptions& options,
|
||||||
|
QProcessEnvironment& environment,
|
||||||
|
QString& error);
|
||||||
|
void cleanupAskPassScript();
|
||||||
QString mapSshError(const QString& rawError) const;
|
QString mapSshError(const QString& rawError) const;
|
||||||
QString knownHostsFileForNullDevice() const;
|
QString knownHostsFileForNullDevice() const;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user