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/sshtcpiptunnel.cpp \
|
||||
$$PWD/sshforwardedtcpiptunnel.cpp \
|
||||
$$PWD/sshagent.cpp
|
||||
$$PWD/sshagent.cpp \
|
||||
$$PWD/sshx11channel.cpp \
|
||||
$$PWD/sshx11inforetriever.cpp
|
||||
|
||||
HEADERS = $$PWD/sshsendfacility_p.h \
|
||||
$$PWD/sshremoteprocess.h \
|
||||
@@ -76,7 +78,10 @@ HEADERS = $$PWD/sshsendfacility_p.h \
|
||||
$$PWD/sshtcpiptunnel_p.h \
|
||||
$$PWD/sshforwardedtcpiptunnel.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
|
||||
|
||||
|
||||
@@ -95,6 +95,11 @@ Project {
|
||||
"sshtcpipforwardserver_p.h",
|
||||
"sshtcpiptunnel.cpp",
|
||||
"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"] : []
|
||||
|
||||
@@ -33,6 +33,12 @@
|
||||
# define QSSH_EXPORT Q_DECL_IMPORT
|
||||
#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_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)
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
#include "sshsendfacility_p.h"
|
||||
#include "sshtcpipforwardserver.h"
|
||||
#include "sshtcpipforwardserver_p.h"
|
||||
#include "sshx11channel_p.h"
|
||||
#include "sshx11inforetriever_p.h"
|
||||
|
||||
#include <QList>
|
||||
|
||||
@@ -63,6 +65,10 @@ void SshChannelManager::handleChannelOpen(const SshIncomingPacket &packet)
|
||||
handleChannelOpenForwardedTcpIp(channelOpen);
|
||||
return;
|
||||
}
|
||||
if (channelOpen.channelType == "x11") {
|
||||
handleChannelOpenX11(channelOpen);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
m_sendFacility.sendChannelOpenFailurePacket(channelOpen.commonData.remoteChannel,
|
||||
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));
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -303,6 +346,25 @@ void SshChannelManager::handleChannelOpenForwardedTcpIp(
|
||||
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 count = 0;
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sshx11displayinfo_p.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
@@ -41,6 +43,7 @@ class AbstractSshChannel;
|
||||
struct SshChannelOpenGeneric;
|
||||
class SshIncomingPacket;
|
||||
class SshSendFacility;
|
||||
class SshRemoteProcessPrivate;
|
||||
|
||||
class SshChannelManager : public QObject
|
||||
{
|
||||
@@ -59,6 +62,7 @@ public:
|
||||
int channelCount() const;
|
||||
enum CloseAllMode { CloseAllRegular, CloseAllAndReset };
|
||||
int closeAllChannels(CloseAllMode mode);
|
||||
QString x11DisplayName() const { return m_x11DisplayInfo.displayName; }
|
||||
|
||||
void handleChannelRequest(const SshIncomingPacket &packet);
|
||||
void handleChannelOpen(const SshIncomingPacket &packet);
|
||||
@@ -89,12 +93,16 @@ private:
|
||||
const QSharedPointer<QObject> &pub);
|
||||
|
||||
void handleChannelOpenForwardedTcpIp(const SshChannelOpenGeneric &channelOpenGeneric);
|
||||
void handleChannelOpenX11(const SshChannelOpenGeneric &channelOpenGeneric);
|
||||
|
||||
SshSendFacility &m_sendFacility;
|
||||
QHash<quint32, AbstractSshChannel *> m_channels;
|
||||
QHash<AbstractSshChannel *, QSharedPointer<QObject> > m_sessions;
|
||||
quint32 m_nextLocalChannelId;
|
||||
QList<QSharedPointer<SshTcpIpForwardServer>> m_waitingForwardServers;
|
||||
QList<QSharedPointer<SshTcpIpForwardServer>> m_listeningForwardServers;
|
||||
QList<SshRemoteProcessPrivate *> m_x11ForwardingRequests;
|
||||
X11DisplayInfo m_x11DisplayInfo;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
@@ -207,6 +207,11 @@ int SshConnection::channelCount() const
|
||||
return d->m_channelManager->channelCount();
|
||||
}
|
||||
|
||||
QString SshConnection::x11DisplayName() const
|
||||
{
|
||||
return d->m_channelManager->x11DisplayName();
|
||||
}
|
||||
|
||||
namespace Internal {
|
||||
|
||||
SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn,
|
||||
|
||||
@@ -142,6 +142,8 @@ public:
|
||||
|
||||
int channelCount() const;
|
||||
|
||||
QString x11DisplayName() const;
|
||||
|
||||
signals:
|
||||
void connected();
|
||||
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
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
|
||||
@@ -131,6 +131,13 @@ struct SshChannelOpenForwardedTcpIp
|
||||
quint32 originatorPort;
|
||||
};
|
||||
|
||||
struct SshChannelOpenX11
|
||||
{
|
||||
SshChannelOpenCommon common;
|
||||
QByteArray originatorAddress;
|
||||
quint32 originatorPort;
|
||||
};
|
||||
|
||||
struct SshChannelOpenFailure
|
||||
{
|
||||
quint32 localChannel;
|
||||
@@ -204,6 +211,7 @@ public:
|
||||
SshChannelOpenGeneric extractChannelOpen() const;
|
||||
static SshChannelOpenForwardedTcpIp extractChannelOpenForwardedTcpIp(
|
||||
const SshChannelOpenGeneric &genericData);
|
||||
static SshChannelOpenX11 extractChannelOpenX11(const SshChannelOpenGeneric &genericData);
|
||||
SshChannelOpenFailure extractChannelOpenFailure() const;
|
||||
SshChannelOpenConfirmation extractChannelOpenConfirmation() const;
|
||||
SshChannelWindowAdjust extractWindowAdjust() const;
|
||||
|
||||
@@ -225,6 +225,14 @@ void SshOutgoingPacket::generateEnvPacket(quint32 remoteChannel,
|
||||
.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,
|
||||
const SshPseudoTerminal &terminal)
|
||||
{
|
||||
|
||||
@@ -71,6 +71,8 @@ public:
|
||||
void generateCancelTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort);
|
||||
void generateEnvPacket(quint32 remoteChannel, const QByteArray &var,
|
||||
const QByteArray &value);
|
||||
void generateX11ForwardingPacket(quint32 remoteChannel, const QByteArray &protocol,
|
||||
const QByteArray &cookie, quint32 screenNumber);
|
||||
void generatePtyRequestPacket(quint32 remoteChannel,
|
||||
const SshPseudoTerminal &terminal);
|
||||
void generateExecPacket(quint32 remoteChannel, const QByteArray &command);
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "sshincomingpacket_p.h"
|
||||
#include "sshlogging_p.h"
|
||||
#include "sshsendfacility_p.h"
|
||||
#include "sshx11displayinfo_p.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
@@ -186,6 +187,12 @@ void SshRemoteProcess::requestTerminal(const SshPseudoTerminal &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()
|
||||
{
|
||||
if (d->channelState() == Internal::SshRemoteProcessPrivate::Inactive) {
|
||||
@@ -227,6 +234,14 @@ SshRemoteProcess::Signal SshRemoteProcess::exitSignal() const
|
||||
|
||||
namespace Internal {
|
||||
|
||||
void SshRemoteProcessPrivate::failToStart(const QString &reason)
|
||||
{
|
||||
if (m_procState != NotYetStarted)
|
||||
return;
|
||||
m_proc->setErrorString(reason);
|
||||
setProcState(StartFailed);
|
||||
}
|
||||
|
||||
SshRemoteProcessPrivate::SshRemoteProcessPrivate(const QByteArray &command,
|
||||
quint32 channelId, SshSendFacility &sendFacility, SshRemoteProcess *proc)
|
||||
: AbstractSshChannel(channelId, sendFacility),
|
||||
@@ -286,26 +301,41 @@ void SshRemoteProcessPrivate::closeHook()
|
||||
|
||||
void SshRemoteProcessPrivate::handleOpenSuccessInternal()
|
||||
{
|
||||
foreach (const EnvVar &envVar, m_env) {
|
||||
m_sendFacility.sendEnvPacket(remoteChannel(), envVar.first,
|
||||
envVar.second);
|
||||
}
|
||||
if (m_x11DisplayName.isEmpty())
|
||||
startProcess(X11DisplayInfo());
|
||||
else
|
||||
emit x11ForwardingRequested(m_x11DisplayName);
|
||||
}
|
||||
|
||||
if (m_useTerminal)
|
||||
m_sendFacility.sendPtyRequestPacket(remoteChannel(), m_terminal);
|
||||
void SshRemoteProcessPrivate::startProcess(const X11DisplayInfo &displayInfo)
|
||||
{
|
||||
if (m_procState != NotYetStarted)
|
||||
return;
|
||||
|
||||
if (m_isShell)
|
||||
m_sendFacility.sendShellPacket(remoteChannel());
|
||||
else
|
||||
m_sendFacility.sendExecPacket(remoteChannel(), m_command);
|
||||
setProcState(ExecRequested);
|
||||
m_timeoutTimer.start(ReplyTimeout);
|
||||
foreach (const EnvVar &envVar, m_env) {
|
||||
m_sendFacility.sendEnvPacket(remoteChannel(), envVar.first,
|
||||
envVar.second);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
setProcState(StartFailed);
|
||||
m_proc->setErrorString(reason);
|
||||
failToStart(reason);
|
||||
}
|
||||
|
||||
void SshRemoteProcessPrivate::handleChannelSuccess()
|
||||
|
||||
@@ -78,6 +78,7 @@ public:
|
||||
void clearEnvironment();
|
||||
|
||||
void requestTerminal(const SshPseudoTerminal &terminal);
|
||||
void requestX11Forwarding(const QString &displayName);
|
||||
void start();
|
||||
|
||||
bool isRunning() const;
|
||||
|
||||
@@ -38,6 +38,7 @@ class SshRemoteProcess;
|
||||
|
||||
namespace Internal {
|
||||
class SshSendFacility;
|
||||
class X11DisplayInfo;
|
||||
|
||||
class SshRemoteProcessPrivate : public AbstractSshChannel
|
||||
{
|
||||
@@ -48,12 +49,16 @@ public:
|
||||
NotYetStarted, ExecRequested, StartFailed, Running, Exited
|
||||
};
|
||||
|
||||
void failToStart(const QString &reason);
|
||||
void startProcess(const X11DisplayInfo &displayInfo);
|
||||
|
||||
signals:
|
||||
void started();
|
||||
void readyRead();
|
||||
void readyReadStandardOutput();
|
||||
void readyReadStandardError();
|
||||
void closed(int exitStatus);
|
||||
void x11ForwardingRequested(const QString &display);
|
||||
|
||||
private:
|
||||
SshRemoteProcessPrivate(const QByteArray &command, quint32 channelId,
|
||||
@@ -93,6 +98,8 @@ private:
|
||||
bool m_useTerminal;
|
||||
SshPseudoTerminal m_terminal;
|
||||
|
||||
QString m_x11DisplayName;
|
||||
|
||||
QByteArray m_stdout;
|
||||
QByteArray m_stderr;
|
||||
|
||||
|
||||
@@ -205,6 +205,13 @@ void SshSendFacility::sendEnvPacket(quint32 remoteChannel,
|
||||
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,
|
||||
const QByteArray &command)
|
||||
{
|
||||
|
||||
@@ -82,6 +82,8 @@ public:
|
||||
const SshPseudoTerminal &terminal);
|
||||
void sendEnvPacket(quint32 remoteChannel, const QByteArray &var,
|
||||
const QByteArray &value);
|
||||
void sendX11ForwardingPacket(quint32 remoteChannel, const QByteArray &protocol,
|
||||
const QByteArray &cookie, quint32 screenNumber);
|
||||
void sendExecPacket(quint32 remoteChannel, const QByteArray &command);
|
||||
void sendShellPacket(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
|
||||
QTC_LIB_DEPENDS += ssh
|
||||
QTC_LIB_DEPENDS += ssh utils
|
||||
include(../qttest.pri)
|
||||
|
||||
SOURCES += tst_ssh.cpp
|
||||
|
||||
@@ -3,5 +3,6 @@ import qbs
|
||||
QtcAutotest {
|
||||
name: "SSH autotest"
|
||||
Depends { name: "QtcSsh" }
|
||||
Depends { name: "Utils" }
|
||||
files: "tst_ssh.cpp"
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
#include <ssh/sshpseudoterminal.h>
|
||||
#include <ssh/sshremoteprocessrunner.h>
|
||||
#include <ssh/sshtcpipforwardserver.h>
|
||||
#include <ssh/sshx11displayinfo_p.h>
|
||||
#include <ssh/sshx11inforetriever_p.h>
|
||||
#include <utils/environment.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
@@ -152,6 +155,8 @@ private slots:
|
||||
void remoteProcessChannels();
|
||||
void remoteProcessInput();
|
||||
void sftp();
|
||||
void x11InfoRetriever_data();
|
||||
void x11InfoRetriever();
|
||||
|
||||
private:
|
||||
bool waitForConnection(SshConnection &connection);
|
||||
@@ -826,6 +831,62 @@ void tst_Ssh::sftp()
|
||||
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)
|
||||
{
|
||||
QEventLoop loop;
|
||||
|
||||
Reference in New Issue
Block a user