forked from qt-creator/qt-creator
SSH: Implement X11 forwarding
Change-Id: Ia7b15e784cb098bc7c6c6be2748d772192187e97 Reviewed-by: hjk <hjk@qt.io> Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
@@ -33,7 +33,9 @@ SOURCES = $$PWD/sshsendfacility.cpp \
|
|||||||
$$PWD/sshtcpipforwardserver.cpp \
|
$$PWD/sshtcpipforwardserver.cpp \
|
||||||
$$PWD/sshtcpiptunnel.cpp \
|
$$PWD/sshtcpiptunnel.cpp \
|
||||||
$$PWD/sshforwardedtcpiptunnel.cpp \
|
$$PWD/sshforwardedtcpiptunnel.cpp \
|
||||||
$$PWD/sshagent.cpp
|
$$PWD/sshagent.cpp \
|
||||||
|
$$PWD/sshx11channel.cpp \
|
||||||
|
$$PWD/sshx11inforetriever.cpp
|
||||||
|
|
||||||
HEADERS = $$PWD/sshsendfacility_p.h \
|
HEADERS = $$PWD/sshsendfacility_p.h \
|
||||||
$$PWD/sshremoteprocess.h \
|
$$PWD/sshremoteprocess.h \
|
||||||
@@ -76,7 +78,10 @@ HEADERS = $$PWD/sshsendfacility_p.h \
|
|||||||
$$PWD/sshtcpiptunnel_p.h \
|
$$PWD/sshtcpiptunnel_p.h \
|
||||||
$$PWD/sshforwardedtcpiptunnel.h \
|
$$PWD/sshforwardedtcpiptunnel.h \
|
||||||
$$PWD/sshforwardedtcpiptunnel_p.h \
|
$$PWD/sshforwardedtcpiptunnel_p.h \
|
||||||
$$PWD/sshagent_p.h
|
$$PWD/sshagent_p.h \
|
||||||
|
$$PWD/sshx11channel_p.h \
|
||||||
|
$$PWD/sshx11displayinfo_p.h \
|
||||||
|
$$PWD/sshx11inforetriever_p.h
|
||||||
|
|
||||||
FORMS = $$PWD/sshkeycreationdialog.ui
|
FORMS = $$PWD/sshkeycreationdialog.ui
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,11 @@ Project {
|
|||||||
"sshtcpipforwardserver_p.h",
|
"sshtcpipforwardserver_p.h",
|
||||||
"sshtcpiptunnel.cpp",
|
"sshtcpiptunnel.cpp",
|
||||||
"sshtcpiptunnel_p.h",
|
"sshtcpiptunnel_p.h",
|
||||||
|
"sshx11channel.cpp",
|
||||||
|
"sshx11channel_p.h",
|
||||||
|
"sshx11displayinfo_p.h",
|
||||||
|
"sshx11inforetriever.cpp",
|
||||||
|
"sshx11inforetriever_p.h",
|
||||||
]
|
]
|
||||||
|
|
||||||
property var botanIncludes: qtc.useSystemBotan ? ["/usr/include/botan-2"] : []
|
property var botanIncludes: qtc.useSystemBotan ? ["/usr/include/botan-2"] : []
|
||||||
|
|||||||
@@ -33,6 +33,12 @@
|
|||||||
# define QSSH_EXPORT Q_DECL_IMPORT
|
# define QSSH_EXPORT Q_DECL_IMPORT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_TESTS
|
||||||
|
# define QSSH_AUTOTEST_EXPORT QSSH_EXPORT
|
||||||
|
#else
|
||||||
|
# define QSSH_AUTOTEST_EXPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
#define QSSH_PRINT_WARNING qWarning("Soft assert at %s:%d", __FILE__, __LINE__)
|
#define QSSH_PRINT_WARNING qWarning("Soft assert at %s:%d", __FILE__, __LINE__)
|
||||||
#define QSSH_ASSERT(cond) do { if (!(cond)) { QSSH_PRINT_WARNING; } } while (false)
|
#define QSSH_ASSERT(cond) do { if (!(cond)) { QSSH_PRINT_WARNING; } } while (false)
|
||||||
#define QSSH_ASSERT_AND_RETURN(cond) do { if (!(cond)) { QSSH_PRINT_WARNING; return; } } while (false)
|
#define QSSH_ASSERT_AND_RETURN(cond) do { if (!(cond)) { QSSH_PRINT_WARNING; return; } } while (false)
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
#include "sshsendfacility_p.h"
|
#include "sshsendfacility_p.h"
|
||||||
#include "sshtcpipforwardserver.h"
|
#include "sshtcpipforwardserver.h"
|
||||||
#include "sshtcpipforwardserver_p.h"
|
#include "sshtcpipforwardserver_p.h"
|
||||||
|
#include "sshx11channel_p.h"
|
||||||
|
#include "sshx11inforetriever_p.h"
|
||||||
|
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
|
||||||
@@ -63,6 +65,10 @@ void SshChannelManager::handleChannelOpen(const SshIncomingPacket &packet)
|
|||||||
handleChannelOpenForwardedTcpIp(channelOpen);
|
handleChannelOpenForwardedTcpIp(channelOpen);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (channelOpen.channelType == "x11") {
|
||||||
|
handleChannelOpenX11(channelOpen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
m_sendFacility.sendChannelOpenFailurePacket(channelOpen.commonData.remoteChannel,
|
m_sendFacility.sendChannelOpenFailurePacket(channelOpen.commonData.remoteChannel,
|
||||||
SSH_OPEN_UNKNOWN_CHANNEL_TYPE, QByteArray());
|
SSH_OPEN_UNKNOWN_CHANNEL_TYPE, QByteArray());
|
||||||
@@ -195,6 +201,43 @@ QSsh::SshRemoteProcess::Ptr SshChannelManager::createRemoteProcess(const QByteAr
|
|||||||
{
|
{
|
||||||
SshRemoteProcess::Ptr proc(new SshRemoteProcess(command, m_nextLocalChannelId++, m_sendFacility));
|
SshRemoteProcess::Ptr proc(new SshRemoteProcess(command, m_nextLocalChannelId++, m_sendFacility));
|
||||||
insertChannel(proc->d, proc);
|
insertChannel(proc->d, proc);
|
||||||
|
connect(proc->d, &SshRemoteProcessPrivate::destroyed, this, [this] {
|
||||||
|
m_x11ForwardingRequests.removeOne(static_cast<SshRemoteProcessPrivate *>(sender()));
|
||||||
|
});
|
||||||
|
connect(proc->d, &SshRemoteProcessPrivate::x11ForwardingRequested, this,
|
||||||
|
[this, proc = proc->d](const QString &displayName) {
|
||||||
|
if (!x11DisplayName().isEmpty()) {
|
||||||
|
if (x11DisplayName() != displayName) {
|
||||||
|
proc->failToStart(tr("Cannot forward to display %1 on SSH connection that is "
|
||||||
|
"already forwarding to display %2.")
|
||||||
|
.arg(displayName, x11DisplayName()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!m_x11DisplayInfo.cookie.isEmpty())
|
||||||
|
proc->startProcess(m_x11DisplayInfo);
|
||||||
|
else
|
||||||
|
m_x11ForwardingRequests << proc;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_x11DisplayInfo.displayName = displayName;
|
||||||
|
m_x11ForwardingRequests << proc;
|
||||||
|
auto * const x11InfoRetriever = new SshX11InfoRetriever(displayName, this);
|
||||||
|
const auto failureHandler = [this](const QString &errorMessage) {
|
||||||
|
for (SshRemoteProcessPrivate * const proc : qAsConst(m_x11ForwardingRequests))
|
||||||
|
proc->failToStart(errorMessage);
|
||||||
|
m_x11ForwardingRequests.clear();
|
||||||
|
};
|
||||||
|
connect(x11InfoRetriever, &SshX11InfoRetriever::failure, this, failureHandler);
|
||||||
|
const auto successHandler = [this](const X11DisplayInfo &displayInfo) {
|
||||||
|
m_x11DisplayInfo = displayInfo;
|
||||||
|
for (SshRemoteProcessPrivate * const proc : qAsConst(m_x11ForwardingRequests))
|
||||||
|
proc->startProcess(displayInfo);
|
||||||
|
m_x11ForwardingRequests.clear();
|
||||||
|
};
|
||||||
|
connect(x11InfoRetriever, &SshX11InfoRetriever::success, this, successHandler);
|
||||||
|
qCDebug(sshLog) << "starting x11 info retriever";
|
||||||
|
x11InfoRetriever->start();
|
||||||
|
});
|
||||||
return proc;
|
return proc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,6 +346,25 @@ void SshChannelManager::handleChannelOpenForwardedTcpIp(
|
|||||||
insertChannel(tunnel->d, tunnel);
|
insertChannel(tunnel->d, tunnel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SshChannelManager::handleChannelOpenX11(const SshChannelOpenGeneric &channelOpenGeneric)
|
||||||
|
{
|
||||||
|
qCDebug(sshLog) << "incoming X11 channel open request";
|
||||||
|
const SshChannelOpenX11 channelOpen
|
||||||
|
= SshIncomingPacket::extractChannelOpenX11(channelOpenGeneric);
|
||||||
|
if (m_x11DisplayInfo.cookie.isEmpty()) {
|
||||||
|
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||||
|
"Server attempted to open an unrequested X11 channel.");
|
||||||
|
}
|
||||||
|
SshX11Channel * const x11Channel = new SshX11Channel(m_x11DisplayInfo,
|
||||||
|
m_nextLocalChannelId++,
|
||||||
|
m_sendFacility);
|
||||||
|
x11Channel->setParent(this);
|
||||||
|
x11Channel->handleOpenSuccess(channelOpen.common.remoteChannel,
|
||||||
|
channelOpen.common.remoteWindowSize,
|
||||||
|
channelOpen.common.remoteMaxPacketSize);
|
||||||
|
insertChannel(x11Channel, QSharedPointer<QObject>());
|
||||||
|
}
|
||||||
|
|
||||||
int SshChannelManager::closeAllChannels(CloseAllMode mode)
|
int SshChannelManager::closeAllChannels(CloseAllMode mode)
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "sshx11displayinfo_p.h"
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
@@ -41,6 +43,7 @@ class AbstractSshChannel;
|
|||||||
struct SshChannelOpenGeneric;
|
struct SshChannelOpenGeneric;
|
||||||
class SshIncomingPacket;
|
class SshIncomingPacket;
|
||||||
class SshSendFacility;
|
class SshSendFacility;
|
||||||
|
class SshRemoteProcessPrivate;
|
||||||
|
|
||||||
class SshChannelManager : public QObject
|
class SshChannelManager : public QObject
|
||||||
{
|
{
|
||||||
@@ -59,6 +62,7 @@ public:
|
|||||||
int channelCount() const;
|
int channelCount() const;
|
||||||
enum CloseAllMode { CloseAllRegular, CloseAllAndReset };
|
enum CloseAllMode { CloseAllRegular, CloseAllAndReset };
|
||||||
int closeAllChannels(CloseAllMode mode);
|
int closeAllChannels(CloseAllMode mode);
|
||||||
|
QString x11DisplayName() const { return m_x11DisplayInfo.displayName; }
|
||||||
|
|
||||||
void handleChannelRequest(const SshIncomingPacket &packet);
|
void handleChannelRequest(const SshIncomingPacket &packet);
|
||||||
void handleChannelOpen(const SshIncomingPacket &packet);
|
void handleChannelOpen(const SshIncomingPacket &packet);
|
||||||
@@ -89,12 +93,16 @@ private:
|
|||||||
const QSharedPointer<QObject> &pub);
|
const QSharedPointer<QObject> &pub);
|
||||||
|
|
||||||
void handleChannelOpenForwardedTcpIp(const SshChannelOpenGeneric &channelOpenGeneric);
|
void handleChannelOpenForwardedTcpIp(const SshChannelOpenGeneric &channelOpenGeneric);
|
||||||
|
void handleChannelOpenX11(const SshChannelOpenGeneric &channelOpenGeneric);
|
||||||
|
|
||||||
SshSendFacility &m_sendFacility;
|
SshSendFacility &m_sendFacility;
|
||||||
QHash<quint32, AbstractSshChannel *> m_channels;
|
QHash<quint32, AbstractSshChannel *> m_channels;
|
||||||
QHash<AbstractSshChannel *, QSharedPointer<QObject> > m_sessions;
|
QHash<AbstractSshChannel *, QSharedPointer<QObject> > m_sessions;
|
||||||
quint32 m_nextLocalChannelId;
|
quint32 m_nextLocalChannelId;
|
||||||
QList<QSharedPointer<SshTcpIpForwardServer>> m_waitingForwardServers;
|
QList<QSharedPointer<SshTcpIpForwardServer>> m_waitingForwardServers;
|
||||||
QList<QSharedPointer<SshTcpIpForwardServer>> m_listeningForwardServers;
|
QList<QSharedPointer<SshTcpIpForwardServer>> m_listeningForwardServers;
|
||||||
|
QList<SshRemoteProcessPrivate *> m_x11ForwardingRequests;
|
||||||
|
X11DisplayInfo m_x11DisplayInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
|
|||||||
@@ -207,6 +207,11 @@ int SshConnection::channelCount() const
|
|||||||
return d->m_channelManager->channelCount();
|
return d->m_channelManager->channelCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString SshConnection::x11DisplayName() const
|
||||||
|
{
|
||||||
|
return d->m_channelManager->x11DisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn,
|
SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn,
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ public:
|
|||||||
|
|
||||||
int channelCount() const;
|
int channelCount() const;
|
||||||
|
|
||||||
|
QString x11DisplayName() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connected();
|
void connected();
|
||||||
void disconnected();
|
void disconnected();
|
||||||
|
|||||||
@@ -406,6 +406,23 @@ SshChannelOpenForwardedTcpIp SshIncomingPacket::extractChannelOpenForwardedTcpIp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SshChannelOpenX11 SshIncomingPacket::extractChannelOpenX11(const SshChannelOpenGeneric &genericData)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
SshChannelOpenX11 specificData;
|
||||||
|
specificData.common = genericData.commonData;
|
||||||
|
quint32 offset = 0;
|
||||||
|
specificData.originatorAddress = SshPacketParser::asString(genericData.typeSpecificData,
|
||||||
|
&offset);
|
||||||
|
specificData.originatorPort = SshPacketParser::asUint32(genericData.typeSpecificData,
|
||||||
|
&offset);
|
||||||
|
return specificData;
|
||||||
|
} catch (const SshPacketParseException &) {
|
||||||
|
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||||
|
"Server sent invalid SSH_MSG_CHANNEL_OPEN packet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SshChannelOpenFailure SshIncomingPacket::extractChannelOpenFailure() const
|
SshChannelOpenFailure SshIncomingPacket::extractChannelOpenFailure() const
|
||||||
{
|
{
|
||||||
Q_ASSERT(isComplete());
|
Q_ASSERT(isComplete());
|
||||||
|
|||||||
@@ -131,6 +131,13 @@ struct SshChannelOpenForwardedTcpIp
|
|||||||
quint32 originatorPort;
|
quint32 originatorPort;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SshChannelOpenX11
|
||||||
|
{
|
||||||
|
SshChannelOpenCommon common;
|
||||||
|
QByteArray originatorAddress;
|
||||||
|
quint32 originatorPort;
|
||||||
|
};
|
||||||
|
|
||||||
struct SshChannelOpenFailure
|
struct SshChannelOpenFailure
|
||||||
{
|
{
|
||||||
quint32 localChannel;
|
quint32 localChannel;
|
||||||
@@ -204,6 +211,7 @@ public:
|
|||||||
SshChannelOpenGeneric extractChannelOpen() const;
|
SshChannelOpenGeneric extractChannelOpen() const;
|
||||||
static SshChannelOpenForwardedTcpIp extractChannelOpenForwardedTcpIp(
|
static SshChannelOpenForwardedTcpIp extractChannelOpenForwardedTcpIp(
|
||||||
const SshChannelOpenGeneric &genericData);
|
const SshChannelOpenGeneric &genericData);
|
||||||
|
static SshChannelOpenX11 extractChannelOpenX11(const SshChannelOpenGeneric &genericData);
|
||||||
SshChannelOpenFailure extractChannelOpenFailure() const;
|
SshChannelOpenFailure extractChannelOpenFailure() const;
|
||||||
SshChannelOpenConfirmation extractChannelOpenConfirmation() const;
|
SshChannelOpenConfirmation extractChannelOpenConfirmation() const;
|
||||||
SshChannelWindowAdjust extractWindowAdjust() const;
|
SshChannelWindowAdjust extractWindowAdjust() const;
|
||||||
|
|||||||
@@ -225,6 +225,14 @@ void SshOutgoingPacket::generateEnvPacket(quint32 remoteChannel,
|
|||||||
.appendBool(false).appendString(var).appendString(value).finalize();
|
.appendBool(false).appendString(var).appendString(value).finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SshOutgoingPacket::generateX11ForwardingPacket(quint32 remoteChannel,
|
||||||
|
const QByteArray &protocol, const QByteArray &cookie, quint32 screenNumber)
|
||||||
|
{
|
||||||
|
init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("x11-req")
|
||||||
|
.appendBool(false).appendBool(false).appendString(protocol)
|
||||||
|
.appendString(cookie).appendInt(screenNumber).finalize();
|
||||||
|
}
|
||||||
|
|
||||||
void SshOutgoingPacket::generatePtyRequestPacket(quint32 remoteChannel,
|
void SshOutgoingPacket::generatePtyRequestPacket(quint32 remoteChannel,
|
||||||
const SshPseudoTerminal &terminal)
|
const SshPseudoTerminal &terminal)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ public:
|
|||||||
void generateCancelTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort);
|
void generateCancelTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort);
|
||||||
void generateEnvPacket(quint32 remoteChannel, const QByteArray &var,
|
void generateEnvPacket(quint32 remoteChannel, const QByteArray &var,
|
||||||
const QByteArray &value);
|
const QByteArray &value);
|
||||||
|
void generateX11ForwardingPacket(quint32 remoteChannel, const QByteArray &protocol,
|
||||||
|
const QByteArray &cookie, quint32 screenNumber);
|
||||||
void generatePtyRequestPacket(quint32 remoteChannel,
|
void generatePtyRequestPacket(quint32 remoteChannel,
|
||||||
const SshPseudoTerminal &terminal);
|
const SshPseudoTerminal &terminal);
|
||||||
void generateExecPacket(quint32 remoteChannel, const QByteArray &command);
|
void generateExecPacket(quint32 remoteChannel, const QByteArray &command);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "sshincomingpacket_p.h"
|
#include "sshincomingpacket_p.h"
|
||||||
#include "sshlogging_p.h"
|
#include "sshlogging_p.h"
|
||||||
#include "sshsendfacility_p.h"
|
#include "sshsendfacility_p.h"
|
||||||
|
#include "sshx11displayinfo_p.h"
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
@@ -186,6 +187,12 @@ void SshRemoteProcess::requestTerminal(const SshPseudoTerminal &terminal)
|
|||||||
d->m_terminal = terminal;
|
d->m_terminal = terminal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SshRemoteProcess::requestX11Forwarding(const QString &displayName)
|
||||||
|
{
|
||||||
|
QSSH_ASSERT_AND_RETURN(d->channelState() == Internal::SshRemoteProcessPrivate::Inactive);
|
||||||
|
d->m_x11DisplayName = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
void SshRemoteProcess::start()
|
void SshRemoteProcess::start()
|
||||||
{
|
{
|
||||||
if (d->channelState() == Internal::SshRemoteProcessPrivate::Inactive) {
|
if (d->channelState() == Internal::SshRemoteProcessPrivate::Inactive) {
|
||||||
@@ -227,6 +234,14 @@ SshRemoteProcess::Signal SshRemoteProcess::exitSignal() const
|
|||||||
|
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
|
void SshRemoteProcessPrivate::failToStart(const QString &reason)
|
||||||
|
{
|
||||||
|
if (m_procState != NotYetStarted)
|
||||||
|
return;
|
||||||
|
m_proc->setErrorString(reason);
|
||||||
|
setProcState(StartFailed);
|
||||||
|
}
|
||||||
|
|
||||||
SshRemoteProcessPrivate::SshRemoteProcessPrivate(const QByteArray &command,
|
SshRemoteProcessPrivate::SshRemoteProcessPrivate(const QByteArray &command,
|
||||||
quint32 channelId, SshSendFacility &sendFacility, SshRemoteProcess *proc)
|
quint32 channelId, SshSendFacility &sendFacility, SshRemoteProcess *proc)
|
||||||
: AbstractSshChannel(channelId, sendFacility),
|
: AbstractSshChannel(channelId, sendFacility),
|
||||||
@@ -286,26 +301,41 @@ void SshRemoteProcessPrivate::closeHook()
|
|||||||
|
|
||||||
void SshRemoteProcessPrivate::handleOpenSuccessInternal()
|
void SshRemoteProcessPrivate::handleOpenSuccessInternal()
|
||||||
{
|
{
|
||||||
foreach (const EnvVar &envVar, m_env) {
|
if (m_x11DisplayName.isEmpty())
|
||||||
m_sendFacility.sendEnvPacket(remoteChannel(), envVar.first,
|
startProcess(X11DisplayInfo());
|
||||||
envVar.second);
|
else
|
||||||
}
|
emit x11ForwardingRequested(m_x11DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_useTerminal)
|
void SshRemoteProcessPrivate::startProcess(const X11DisplayInfo &displayInfo)
|
||||||
m_sendFacility.sendPtyRequestPacket(remoteChannel(), m_terminal);
|
{
|
||||||
|
if (m_procState != NotYetStarted)
|
||||||
|
return;
|
||||||
|
|
||||||
if (m_isShell)
|
foreach (const EnvVar &envVar, m_env) {
|
||||||
m_sendFacility.sendShellPacket(remoteChannel());
|
m_sendFacility.sendEnvPacket(remoteChannel(), envVar.first,
|
||||||
else
|
envVar.second);
|
||||||
m_sendFacility.sendExecPacket(remoteChannel(), m_command);
|
}
|
||||||
setProcState(ExecRequested);
|
|
||||||
m_timeoutTimer.start(ReplyTimeout);
|
if (!m_x11DisplayName.isEmpty()) {
|
||||||
|
m_sendFacility.sendX11ForwardingPacket(remoteChannel(), displayInfo.protocol,
|
||||||
|
displayInfo.randomCookie.toHex(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_useTerminal)
|
||||||
|
m_sendFacility.sendPtyRequestPacket(remoteChannel(), m_terminal);
|
||||||
|
|
||||||
|
if (m_isShell)
|
||||||
|
m_sendFacility.sendShellPacket(remoteChannel());
|
||||||
|
else
|
||||||
|
m_sendFacility.sendExecPacket(remoteChannel(), m_command);
|
||||||
|
setProcState(ExecRequested);
|
||||||
|
m_timeoutTimer.start(ReplyTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SshRemoteProcessPrivate::handleOpenFailureInternal(const QString &reason)
|
void SshRemoteProcessPrivate::handleOpenFailureInternal(const QString &reason)
|
||||||
{
|
{
|
||||||
setProcState(StartFailed);
|
failToStart(reason);
|
||||||
m_proc->setErrorString(reason);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SshRemoteProcessPrivate::handleChannelSuccess()
|
void SshRemoteProcessPrivate::handleChannelSuccess()
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ public:
|
|||||||
void clearEnvironment();
|
void clearEnvironment();
|
||||||
|
|
||||||
void requestTerminal(const SshPseudoTerminal &terminal);
|
void requestTerminal(const SshPseudoTerminal &terminal);
|
||||||
|
void requestX11Forwarding(const QString &displayName);
|
||||||
void start();
|
void start();
|
||||||
|
|
||||||
bool isRunning() const;
|
bool isRunning() const;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class SshRemoteProcess;
|
|||||||
|
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
class SshSendFacility;
|
class SshSendFacility;
|
||||||
|
class X11DisplayInfo;
|
||||||
|
|
||||||
class SshRemoteProcessPrivate : public AbstractSshChannel
|
class SshRemoteProcessPrivate : public AbstractSshChannel
|
||||||
{
|
{
|
||||||
@@ -48,12 +49,16 @@ public:
|
|||||||
NotYetStarted, ExecRequested, StartFailed, Running, Exited
|
NotYetStarted, ExecRequested, StartFailed, Running, Exited
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void failToStart(const QString &reason);
|
||||||
|
void startProcess(const X11DisplayInfo &displayInfo);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void started();
|
void started();
|
||||||
void readyRead();
|
void readyRead();
|
||||||
void readyReadStandardOutput();
|
void readyReadStandardOutput();
|
||||||
void readyReadStandardError();
|
void readyReadStandardError();
|
||||||
void closed(int exitStatus);
|
void closed(int exitStatus);
|
||||||
|
void x11ForwardingRequested(const QString &display);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SshRemoteProcessPrivate(const QByteArray &command, quint32 channelId,
|
SshRemoteProcessPrivate(const QByteArray &command, quint32 channelId,
|
||||||
@@ -93,6 +98,8 @@ private:
|
|||||||
bool m_useTerminal;
|
bool m_useTerminal;
|
||||||
SshPseudoTerminal m_terminal;
|
SshPseudoTerminal m_terminal;
|
||||||
|
|
||||||
|
QString m_x11DisplayName;
|
||||||
|
|
||||||
QByteArray m_stdout;
|
QByteArray m_stdout;
|
||||||
QByteArray m_stderr;
|
QByteArray m_stderr;
|
||||||
|
|
||||||
|
|||||||
@@ -205,6 +205,13 @@ void SshSendFacility::sendEnvPacket(quint32 remoteChannel,
|
|||||||
sendPacket();
|
sendPacket();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SshSendFacility::sendX11ForwardingPacket(quint32 remoteChannel, const QByteArray &protocol,
|
||||||
|
const QByteArray &cookie, quint32 screenNumber)
|
||||||
|
{
|
||||||
|
m_outgoingPacket.generateX11ForwardingPacket(remoteChannel, protocol, cookie, screenNumber);
|
||||||
|
sendPacket();
|
||||||
|
}
|
||||||
|
|
||||||
void SshSendFacility::sendExecPacket(quint32 remoteChannel,
|
void SshSendFacility::sendExecPacket(quint32 remoteChannel,
|
||||||
const QByteArray &command)
|
const QByteArray &command)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ public:
|
|||||||
const SshPseudoTerminal &terminal);
|
const SshPseudoTerminal &terminal);
|
||||||
void sendEnvPacket(quint32 remoteChannel, const QByteArray &var,
|
void sendEnvPacket(quint32 remoteChannel, const QByteArray &var,
|
||||||
const QByteArray &value);
|
const QByteArray &value);
|
||||||
|
void sendX11ForwardingPacket(quint32 remoteChannel, const QByteArray &protocol,
|
||||||
|
const QByteArray &cookie, quint32 screenNumber);
|
||||||
void sendExecPacket(quint32 remoteChannel, const QByteArray &command);
|
void sendExecPacket(quint32 remoteChannel, const QByteArray &command);
|
||||||
void sendShellPacket(quint32 remoteChannel);
|
void sendShellPacket(quint32 remoteChannel);
|
||||||
void sendSftpPacket(quint32 remoteChannel);
|
void sendSftpPacket(quint32 remoteChannel);
|
||||||
|
|||||||
220
src/libs/ssh/sshx11channel.cpp
Normal file
220
src/libs/ssh/sshx11channel.cpp
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2018 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "sshx11channel_p.h"
|
||||||
|
|
||||||
|
#include "sshincomingpacket_p.h"
|
||||||
|
#include "sshlogging_p.h"
|
||||||
|
#include "sshsendfacility_p.h"
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QTcpSocket>
|
||||||
|
|
||||||
|
namespace QSsh {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
class X11Socket : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
X11Socket(QObject *parent) : QObject(parent) { }
|
||||||
|
|
||||||
|
void establishConnection(const X11DisplayInfo &displayInfo)
|
||||||
|
{
|
||||||
|
const bool hostNameIsPath = displayInfo.hostName.startsWith('/'); // macOS
|
||||||
|
const bool hasActualHostName = !displayInfo.hostName.isEmpty()
|
||||||
|
&& displayInfo.hostName != "unix" && !displayInfo.hostName.endsWith("/unix")
|
||||||
|
&& !hostNameIsPath;
|
||||||
|
if (hasActualHostName) {
|
||||||
|
QTcpSocket * const socket = new QTcpSocket(this);
|
||||||
|
connect(socket, &QTcpSocket::connected, this, &X11Socket::connected);
|
||||||
|
connect(socket,
|
||||||
|
static_cast<void(QTcpSocket::*)(QTcpSocket::SocketError)>(&QTcpSocket::error),
|
||||||
|
[this, socket] {
|
||||||
|
emit error(socket->errorString());
|
||||||
|
});
|
||||||
|
socket->connectToHost(displayInfo.hostName, 6000 + displayInfo.display);
|
||||||
|
m_socket = socket;
|
||||||
|
} else {
|
||||||
|
const QString serverBasePath = hostNameIsPath ? QString(displayInfo.hostName + ':')
|
||||||
|
: "/tmp/.X11-unix/X";
|
||||||
|
QLocalSocket * const socket = new QLocalSocket(this);
|
||||||
|
connect(socket, &QLocalSocket::connected, this, &X11Socket::connected);
|
||||||
|
connect(socket,
|
||||||
|
static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),
|
||||||
|
[this, socket] {
|
||||||
|
emit error(socket->errorString());
|
||||||
|
});
|
||||||
|
socket->connectToServer(serverBasePath + QString::number(displayInfo.display));
|
||||||
|
m_socket = socket;
|
||||||
|
}
|
||||||
|
connect(m_socket, &QIODevice::readyRead,
|
||||||
|
[this] { emit dataAvailable(m_socket->readAll()); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void closeConnection()
|
||||||
|
{
|
||||||
|
m_socket->disconnect();
|
||||||
|
if (localSocket())
|
||||||
|
localSocket()->disconnectFromServer();
|
||||||
|
else
|
||||||
|
tcpSocket()->disconnectFromHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(const QByteArray &data)
|
||||||
|
{
|
||||||
|
m_socket->write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasError() const
|
||||||
|
{
|
||||||
|
return (localSocket() && localSocket()->error() != QLocalSocket::UnknownSocketError)
|
||||||
|
|| (tcpSocket() && tcpSocket()->error() != QTcpSocket::UnknownSocketError);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isConnected() const
|
||||||
|
{
|
||||||
|
return (localSocket() && localSocket()->state() == QLocalSocket::ConnectedState)
|
||||||
|
|| (tcpSocket() && tcpSocket()->state() == QTcpSocket::ConnectedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void connected();
|
||||||
|
void error(const QString &message);
|
||||||
|
void dataAvailable(const QByteArray &data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLocalSocket *localSocket() const { return qobject_cast<QLocalSocket *>(m_socket); }
|
||||||
|
QTcpSocket *tcpSocket() const { return qobject_cast<QTcpSocket *>(m_socket); }
|
||||||
|
|
||||||
|
QIODevice *m_socket = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
SshX11Channel::SshX11Channel(const X11DisplayInfo &displayInfo, quint32 channelId,
|
||||||
|
SshSendFacility &sendFacility)
|
||||||
|
: AbstractSshChannel (channelId, sendFacility),
|
||||||
|
m_x11Socket(new X11Socket(this)),
|
||||||
|
m_displayInfo(displayInfo)
|
||||||
|
{
|
||||||
|
setChannelState(SessionRequested); // Invariant for parent class.
|
||||||
|
}
|
||||||
|
|
||||||
|
void SshX11Channel::handleChannelSuccess()
|
||||||
|
{
|
||||||
|
qCWarning(sshLog) << "unexpected channel success message for X11 channel";
|
||||||
|
}
|
||||||
|
|
||||||
|
void SshX11Channel::handleChannelFailure()
|
||||||
|
{
|
||||||
|
qCWarning(sshLog) << "unexpected channel failure message for X11 channel";
|
||||||
|
}
|
||||||
|
|
||||||
|
void SshX11Channel::handleOpenSuccessInternal()
|
||||||
|
{
|
||||||
|
m_sendFacility.sendChannelOpenConfirmationPacket(remoteChannel(), localChannelId(),
|
||||||
|
initialWindowSize(), maxPacketSize());
|
||||||
|
connect(m_x11Socket, &X11Socket::connected, [this] {
|
||||||
|
qCDebug(sshLog) << "x11 socket connected for channel" << localChannelId();
|
||||||
|
if (!m_queuedRemoteData.isEmpty())
|
||||||
|
handleRemoteData(QByteArray());
|
||||||
|
});
|
||||||
|
connect(m_x11Socket, &X11Socket::error,
|
||||||
|
[this](const QString &msg) { emit error(tr("X11 socket error: %1").arg(msg)); });
|
||||||
|
connect(m_x11Socket, &X11Socket::dataAvailable, [this](const QByteArray &data) {
|
||||||
|
qCDebug(sshLog) << "sending " << data.size() << "bytes from x11 socket to remote side "
|
||||||
|
"in channel" << localChannelId();
|
||||||
|
sendData(data);
|
||||||
|
});
|
||||||
|
m_x11Socket->establishConnection(m_displayInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SshX11Channel::handleOpenFailureInternal(const QString &reason)
|
||||||
|
{
|
||||||
|
qCWarning(sshLog) << "unexpected channel open failure message for X11 channel:" << reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SshX11Channel::handleChannelDataInternal(const QByteArray &data)
|
||||||
|
{
|
||||||
|
handleRemoteData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SshX11Channel::handleChannelExtendedDataInternal(quint32 type, const QByteArray &data)
|
||||||
|
{
|
||||||
|
qCWarning(sshLog) << "unexpected extended data for X11 channel" << type << data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SshX11Channel::handleExitStatus(const SshChannelExitStatus &exitStatus)
|
||||||
|
{
|
||||||
|
qCWarning(sshLog) << "unexpected exit status message on X11 channel" << exitStatus.exitStatus;
|
||||||
|
closeChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SshX11Channel::handleExitSignal(const SshChannelExitSignal &signal)
|
||||||
|
{
|
||||||
|
qCWarning(sshLog) << "unexpected exit signal message on X11 channel" << signal.error;
|
||||||
|
closeChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SshX11Channel::closeHook()
|
||||||
|
{
|
||||||
|
m_x11Socket->disconnect();
|
||||||
|
m_x11Socket->closeConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SshX11Channel::handleRemoteData(const QByteArray &data)
|
||||||
|
{
|
||||||
|
if (m_x11Socket->hasError())
|
||||||
|
return;
|
||||||
|
qCDebug(sshLog) << "received" << data.size() << "bytes from remote side in x11 channel"
|
||||||
|
<< localChannelId();
|
||||||
|
if (!m_x11Socket->isConnected()) {
|
||||||
|
qCDebug(sshLog) << "x11 socket not yet connected, queueing data";
|
||||||
|
m_queuedRemoteData += data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_haveReplacedRandomCookie) {
|
||||||
|
qCDebug(sshLog) << "forwarding data to x11 socket";
|
||||||
|
m_x11Socket->write(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_queuedRemoteData += data;
|
||||||
|
const int randomCookieOffset = m_queuedRemoteData.indexOf(m_displayInfo.randomCookie);
|
||||||
|
if (randomCookieOffset == -1) {
|
||||||
|
qCDebug(sshLog) << "random cookie has not appeared in remote data yet, queueing data";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_queuedRemoteData.replace(randomCookieOffset, m_displayInfo.cookie.size(),
|
||||||
|
m_displayInfo.cookie);
|
||||||
|
qCDebug(sshLog) << "found and replaced random cookie, forwarding data to x11 socket";
|
||||||
|
m_x11Socket->write(m_queuedRemoteData);
|
||||||
|
m_queuedRemoteData.clear();
|
||||||
|
m_haveReplacedRandomCookie = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace QSsh
|
||||||
|
|
||||||
|
#include <sshx11channel.moc>
|
||||||
73
src/libs/ssh/sshx11channel_p.h
Normal file
73
src/libs/ssh/sshx11channel_p.h
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2018 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sshchannel_p.h"
|
||||||
|
|
||||||
|
#include "sshx11displayinfo_p.h"
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
|
||||||
|
namespace QSsh {
|
||||||
|
namespace Internal {
|
||||||
|
class SshChannelManager;
|
||||||
|
class SshSendFacility;
|
||||||
|
class X11Socket;
|
||||||
|
|
||||||
|
class SshX11Channel : public AbstractSshChannel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
friend class Internal::SshChannelManager;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void error(const QString &message);
|
||||||
|
|
||||||
|
private:
|
||||||
|
SshX11Channel(const X11DisplayInfo &displayInfo, quint32 channelId,
|
||||||
|
SshSendFacility &sendFacility);
|
||||||
|
|
||||||
|
void handleChannelSuccess() override;
|
||||||
|
void handleChannelFailure() override;
|
||||||
|
|
||||||
|
void handleOpenSuccessInternal() override;
|
||||||
|
void handleOpenFailureInternal(const QString &reason) override;
|
||||||
|
void handleChannelDataInternal(const QByteArray &data) override;
|
||||||
|
void handleChannelExtendedDataInternal(quint32 type, const QByteArray &data) override;
|
||||||
|
void handleExitStatus(const SshChannelExitStatus &exitStatus) override;
|
||||||
|
void handleExitSignal(const SshChannelExitSignal &signal) override;
|
||||||
|
void closeHook() override;
|
||||||
|
|
||||||
|
void handleRemoteData(const QByteArray &data);
|
||||||
|
|
||||||
|
X11Socket * const m_x11Socket;
|
||||||
|
const X11DisplayInfo m_displayInfo;
|
||||||
|
QByteArray m_queuedRemoteData;
|
||||||
|
bool m_haveReplacedRandomCookie = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace QSsh
|
||||||
49
src/libs/ssh/sshx11displayinfo_p.h
Normal file
49
src/libs/ssh/sshx11displayinfo_p.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2018 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ssh_global.h"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QByteArray>
|
||||||
|
|
||||||
|
namespace QSsh {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
class QSSH_AUTOTEST_EXPORT X11DisplayInfo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString displayName;
|
||||||
|
QString hostName;
|
||||||
|
QByteArray protocol;
|
||||||
|
QByteArray cookie;
|
||||||
|
QByteArray randomCookie;
|
||||||
|
int display = 0;
|
||||||
|
int screen = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace QSsh
|
||||||
150
src/libs/ssh/sshx11inforetriever.cpp
Normal file
150
src/libs/ssh/sshx11inforetriever.cpp
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2018 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "sshx11inforetriever_p.h"
|
||||||
|
|
||||||
|
#include "sshlogging_p.h"
|
||||||
|
#include "sshx11displayinfo_p.h"
|
||||||
|
|
||||||
|
#include <QByteArrayList>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QTemporaryFile>
|
||||||
|
|
||||||
|
#include <botan/auto_rng.h>
|
||||||
|
|
||||||
|
namespace QSsh {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
static QByteArray xauthProtocol() { return "MIT-MAGIC-COOKIE-1"; }
|
||||||
|
|
||||||
|
SshX11InfoRetriever::SshX11InfoRetriever(const QString &displayName, QObject *parent)
|
||||||
|
: QObject(parent),
|
||||||
|
m_displayName(displayName),
|
||||||
|
m_xauthProc(new QProcess(this)),
|
||||||
|
m_xauthFile(new QTemporaryFile(this))
|
||||||
|
{
|
||||||
|
connect(m_xauthProc, &QProcess::errorOccurred, [this] {
|
||||||
|
if (m_xauthProc->error() == QProcess::FailedToStart) {
|
||||||
|
emitFailure(tr("Could not start xauth: %1").arg(m_xauthProc->errorString()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_xauthProc,
|
||||||
|
static_cast<void (QProcess::*)(int,QProcess::ExitStatus)>(&QProcess::finished),
|
||||||
|
[this] {
|
||||||
|
if (m_xauthProc->exitStatus() != QProcess::NormalExit) {
|
||||||
|
emitFailure(tr("xauth crashed: %1").arg(m_xauthProc->errorString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_xauthProc->exitCode() != 0) {
|
||||||
|
emitFailure(tr("xauth failed with exit code %1.").arg(m_xauthProc->exitCode()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (m_state) {
|
||||||
|
case State::RunningGenerate:
|
||||||
|
m_state = State::RunningList;
|
||||||
|
m_xauthProc->start("xauth", QStringList{"-f", m_xauthFile->fileName(), "list",
|
||||||
|
m_displayName});
|
||||||
|
break;
|
||||||
|
case State::RunningList: {
|
||||||
|
const QByteArrayList outputLines = m_xauthProc->readAllStandardOutput().split('\n');
|
||||||
|
if (outputLines.empty()) {
|
||||||
|
emitFailure(tr("Unexpected xauth output."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QByteArrayList data = outputLines.first().simplified().split(' ');
|
||||||
|
if (data.size() < 3 || data.at(1) != xauthProtocol() || data.at(2).isEmpty()) {
|
||||||
|
emitFailure(tr("Unexpected xauth output."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
X11DisplayInfo displayInfo;
|
||||||
|
displayInfo.displayName = m_displayName;
|
||||||
|
const int colonIndex = m_displayName.indexOf(':');
|
||||||
|
if (colonIndex == -1) {
|
||||||
|
emitFailure(tr("Invalid display name \"%1\"").arg(m_displayName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
displayInfo.hostName = m_displayName.mid(0, colonIndex);
|
||||||
|
const int dotIndex = m_displayName.indexOf('.', colonIndex + 1);
|
||||||
|
const QString display = m_displayName.mid(colonIndex + 1,
|
||||||
|
dotIndex == -1 ? -1
|
||||||
|
: dotIndex - colonIndex - 1);
|
||||||
|
if (display.isEmpty()) {
|
||||||
|
emitFailure(tr("Invalid display name \"%1\"").arg(m_displayName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool ok;
|
||||||
|
displayInfo.display = display.toInt(&ok);
|
||||||
|
if (!ok) {
|
||||||
|
emitFailure(tr("Invalid display name \"%1\"").arg(m_displayName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (dotIndex != -1) {
|
||||||
|
displayInfo.screen = m_displayName.mid(dotIndex + 1).toInt(&ok);
|
||||||
|
if (!ok) {
|
||||||
|
emitFailure(tr("Invalid display name \"%1\"").arg(m_displayName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
displayInfo.protocol = data.at(1);
|
||||||
|
displayInfo.cookie = QByteArray::fromHex(data.at(2));
|
||||||
|
displayInfo.randomCookie.resize(displayInfo.cookie.size());
|
||||||
|
try {
|
||||||
|
Botan::AutoSeeded_RNG rng;
|
||||||
|
rng.randomize(reinterpret_cast<Botan::uint8_t *>(displayInfo.randomCookie.data()),
|
||||||
|
displayInfo.randomCookie.size());
|
||||||
|
} catch (const std::exception &ex) {
|
||||||
|
emitFailure(tr("Failed to generate random cookie: %1")
|
||||||
|
.arg(QLatin1String(ex.what())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit success(displayInfo);
|
||||||
|
deleteLater();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
emitFailure(tr("Internal error"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SshX11InfoRetriever::start()
|
||||||
|
{
|
||||||
|
if (!m_xauthFile->open()) {
|
||||||
|
emitFailure(tr("Could not create temporary file: %1").arg(m_xauthFile->errorString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_state = State::RunningGenerate;
|
||||||
|
m_xauthProc->start("xauth", QStringList{"-f", m_xauthFile->fileName(), "generate",
|
||||||
|
m_displayName, QString::fromLatin1(xauthProtocol())});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SshX11InfoRetriever::emitFailure(const QString &reason)
|
||||||
|
{
|
||||||
|
emit failure(tr("Could not retrieve X11 authentication cookie: %1").arg(reason));
|
||||||
|
deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace QSsh
|
||||||
65
src/libs/ssh/sshx11inforetriever_p.h
Normal file
65
src/libs/ssh/sshx11inforetriever_p.h
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2018 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ssh_global.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QByteArray;
|
||||||
|
class QProcess;
|
||||||
|
class QTemporaryFile;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
namespace QSsh {
|
||||||
|
namespace Internal {
|
||||||
|
class X11DisplayInfo;
|
||||||
|
|
||||||
|
class QSSH_AUTOTEST_EXPORT SshX11InfoRetriever : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
SshX11InfoRetriever(const QString &displayName, QObject *parent = nullptr);
|
||||||
|
void start();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void failure(const QString &message);
|
||||||
|
void success(const X11DisplayInfo &displayInfo);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void emitFailure(const QString &reason);
|
||||||
|
|
||||||
|
const QString m_displayName;
|
||||||
|
QProcess * const m_xauthProc;
|
||||||
|
QTemporaryFile * const m_xauthFile;
|
||||||
|
|
||||||
|
enum class State { Inactive, RunningGenerate, RunningList } m_state = State::Inactive;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace QSsh
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
QT = core network
|
QT = core network
|
||||||
QTC_LIB_DEPENDS += ssh
|
QTC_LIB_DEPENDS += ssh utils
|
||||||
include(../qttest.pri)
|
include(../qttest.pri)
|
||||||
|
|
||||||
SOURCES += tst_ssh.cpp
|
SOURCES += tst_ssh.cpp
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ import qbs
|
|||||||
QtcAutotest {
|
QtcAutotest {
|
||||||
name: "SSH autotest"
|
name: "SSH autotest"
|
||||||
Depends { name: "QtcSsh" }
|
Depends { name: "QtcSsh" }
|
||||||
|
Depends { name: "Utils" }
|
||||||
files: "tst_ssh.cpp"
|
files: "tst_ssh.cpp"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,9 @@
|
|||||||
#include <ssh/sshpseudoterminal.h>
|
#include <ssh/sshpseudoterminal.h>
|
||||||
#include <ssh/sshremoteprocessrunner.h>
|
#include <ssh/sshremoteprocessrunner.h>
|
||||||
#include <ssh/sshtcpipforwardserver.h>
|
#include <ssh/sshtcpipforwardserver.h>
|
||||||
|
#include <ssh/sshx11displayinfo_p.h>
|
||||||
|
#include <ssh/sshx11inforetriever_p.h>
|
||||||
|
#include <utils/environment.h>
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
@@ -152,6 +155,8 @@ private slots:
|
|||||||
void remoteProcessChannels();
|
void remoteProcessChannels();
|
||||||
void remoteProcessInput();
|
void remoteProcessInput();
|
||||||
void sftp();
|
void sftp();
|
||||||
|
void x11InfoRetriever_data();
|
||||||
|
void x11InfoRetriever();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool waitForConnection(SshConnection &connection);
|
bool waitForConnection(SshConnection &connection);
|
||||||
@@ -826,6 +831,62 @@ void tst_Ssh::sftp()
|
|||||||
QCOMPARE(sftpChannel->state(), SftpChannel::Closed);
|
QCOMPARE(sftpChannel->state(), SftpChannel::Closed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_Ssh::x11InfoRetriever_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("displayName");
|
||||||
|
QTest::addColumn<bool>("successExpected");
|
||||||
|
|
||||||
|
const Utils::FileName xauthCommand
|
||||||
|
= Utils::Environment::systemEnvironment().searchInPath("xauth");
|
||||||
|
const QString displayName = QLatin1String(qgetenv("DISPLAY"));
|
||||||
|
const bool canSucceed = xauthCommand.exists() && !displayName.isEmpty();
|
||||||
|
QTest::newRow(canSucceed ? "suitable host" : "unsuitable host") << displayName << canSucceed;
|
||||||
|
QTest::newRow("invalid display name") << QString("dummy") << false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_Ssh::x11InfoRetriever()
|
||||||
|
{
|
||||||
|
QFETCH(QString, displayName);
|
||||||
|
QFETCH(bool, successExpected);
|
||||||
|
using namespace QSsh::Internal;
|
||||||
|
SshX11InfoRetriever x11InfoRetriever(displayName);
|
||||||
|
QEventLoop loop;
|
||||||
|
bool success;
|
||||||
|
X11DisplayInfo displayInfo;
|
||||||
|
QString errorMessage;
|
||||||
|
const auto successHandler = [&loop, &success, &displayInfo](const X11DisplayInfo &di) {
|
||||||
|
success = true;
|
||||||
|
displayInfo = di;
|
||||||
|
loop.quit();
|
||||||
|
};
|
||||||
|
connect(&x11InfoRetriever, &SshX11InfoRetriever::success, successHandler);
|
||||||
|
const auto failureHandler = [&loop, &success, &errorMessage](const QString &error) {
|
||||||
|
success = false;
|
||||||
|
errorMessage = error;
|
||||||
|
loop.quit();
|
||||||
|
};
|
||||||
|
connect(&x11InfoRetriever, &SshX11InfoRetriever::failure, failureHandler);
|
||||||
|
QTimer timer;
|
||||||
|
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||||
|
timer.setSingleShot(true);
|
||||||
|
timer.setInterval(40000);
|
||||||
|
timer.start();
|
||||||
|
x11InfoRetriever.start();
|
||||||
|
loop.exec();
|
||||||
|
QVERIFY(timer.isActive());
|
||||||
|
timer.stop();
|
||||||
|
if (successExpected) {
|
||||||
|
QVERIFY2(success, qPrintable(errorMessage));
|
||||||
|
QVERIFY(!displayInfo.protocol.isEmpty());
|
||||||
|
QVERIFY(!displayInfo.cookie.isEmpty());
|
||||||
|
QCOMPARE(displayInfo.cookie.size(), displayInfo.randomCookie.size());
|
||||||
|
QCOMPARE(displayInfo.displayName, displayName);
|
||||||
|
} else {
|
||||||
|
QVERIFY(!success);
|
||||||
|
QVERIFY(!errorMessage.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool tst_Ssh::waitForConnection(SshConnection &connection)
|
bool tst_Ssh::waitForConnection(SshConnection &connection)
|
||||||
{
|
{
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
|
|||||||
Reference in New Issue
Block a user