aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@qt.io>2017-06-14 16:31:33 +0200
committerChristian Kandeler <christian.kandeler@qt.io>2018-11-19 09:12:31 +0000
commit424923817cd018767671e0e88b90deef3fd4917a (patch)
tree12f060fee0a92018b318c881636b2722bad48857
parent3d1d9aae2e977ef599582074e0f4faaef454a595 (diff)
SSH: Implement X11 forwarding
Change-Id: Ia7b15e784cb098bc7c6c6be2748d772192187e97 Reviewed-by: hjk <hjk@qt.io> Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
-rw-r--r--src/libs/ssh/ssh.pro9
-rw-r--r--src/libs/ssh/ssh.qbs5
-rw-r--r--src/libs/ssh/ssh_global.h6
-rw-r--r--src/libs/ssh/sshchannelmanager.cpp62
-rw-r--r--src/libs/ssh/sshchannelmanager_p.h8
-rw-r--r--src/libs/ssh/sshconnection.cpp5
-rw-r--r--src/libs/ssh/sshconnection.h2
-rw-r--r--src/libs/ssh/sshincomingpacket.cpp17
-rw-r--r--src/libs/ssh/sshincomingpacket_p.h8
-rw-r--r--src/libs/ssh/sshoutgoingpacket.cpp8
-rw-r--r--src/libs/ssh/sshoutgoingpacket_p.h2
-rw-r--r--src/libs/ssh/sshremoteprocess.cpp58
-rw-r--r--src/libs/ssh/sshremoteprocess.h1
-rw-r--r--src/libs/ssh/sshremoteprocess_p.h7
-rw-r--r--src/libs/ssh/sshsendfacility.cpp7
-rw-r--r--src/libs/ssh/sshsendfacility_p.h2
-rw-r--r--src/libs/ssh/sshx11channel.cpp220
-rw-r--r--src/libs/ssh/sshx11channel_p.h73
-rw-r--r--src/libs/ssh/sshx11displayinfo_p.h49
-rw-r--r--src/libs/ssh/sshx11inforetriever.cpp150
-rw-r--r--src/libs/ssh/sshx11inforetriever_p.h65
-rw-r--r--tests/auto/ssh/ssh.pro2
-rw-r--r--tests/auto/ssh/ssh.qbs1
-rw-r--r--tests/auto/ssh/tst_ssh.cpp61
24 files changed, 811 insertions, 17 deletions
diff --git a/src/libs/ssh/ssh.pro b/src/libs/ssh/ssh.pro
index 40efcaf96b..27407b7f2e 100644
--- a/src/libs/ssh/ssh.pro
+++ b/src/libs/ssh/ssh.pro
@@ -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
diff --git a/src/libs/ssh/ssh.qbs b/src/libs/ssh/ssh.qbs
index c93e5d3a1c..f433fdea56 100644
--- a/src/libs/ssh/ssh.qbs
+++ b/src/libs/ssh/ssh.qbs
@@ -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"] : []
diff --git a/src/libs/ssh/ssh_global.h b/src/libs/ssh/ssh_global.h
index 35fed0fc0d..3941ffbfcd 100644
--- a/src/libs/ssh/ssh_global.h
+++ b/src/libs/ssh/ssh_global.h
@@ -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)
diff --git a/src/libs/ssh/sshchannelmanager.cpp b/src/libs/ssh/sshchannelmanager.cpp
index 78672d2549..f4bfb8cf12 100644
--- a/src/libs/ssh/sshchannelmanager.cpp
+++ b/src/libs/ssh/sshchannelmanager.cpp
@@ -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;
diff --git a/src/libs/ssh/sshchannelmanager_p.h b/src/libs/ssh/sshchannelmanager_p.h
index 33f7af9ef0..10e31e7d9e 100644
--- a/src/libs/ssh/sshchannelmanager_p.h
+++ b/src/libs/ssh/sshchannelmanager_p.h
@@ -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
diff --git a/src/libs/ssh/sshconnection.cpp b/src/libs/ssh/sshconnection.cpp
index 7981a65cb9..8495da64c4 100644
--- a/src/libs/ssh/sshconnection.cpp
+++ b/src/libs/ssh/sshconnection.cpp
@@ -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,
diff --git a/src/libs/ssh/sshconnection.h b/src/libs/ssh/sshconnection.h
index 78f7692c61..ca1e3a461a 100644
--- a/src/libs/ssh/sshconnection.h
+++ b/src/libs/ssh/sshconnection.h
@@ -142,6 +142,8 @@ public:
int channelCount() const;
+ QString x11DisplayName() const;
+
signals:
void connected();
void disconnected();
diff --git a/src/libs/ssh/sshincomingpacket.cpp b/src/libs/ssh/sshincomingpacket.cpp
index c97e18c857..aa3beedf6b 100644
--- a/src/libs/ssh/sshincomingpacket.cpp
+++ b/src/libs/ssh/sshincomingpacket.cpp
@@ -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());
diff --git a/src/libs/ssh/sshincomingpacket_p.h b/src/libs/ssh/sshincomingpacket_p.h
index 6b3a54abf3..d495f9ca95 100644
--- a/src/libs/ssh/sshincomingpacket_p.h
+++ b/src/libs/ssh/sshincomingpacket_p.h
@@ -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;
diff --git a/src/libs/ssh/sshoutgoingpacket.cpp b/src/libs/ssh/sshoutgoingpacket.cpp
index ae505700e0..ac25e0c980 100644
--- a/src/libs/ssh/sshoutgoingpacket.cpp
+++ b/src/libs/ssh/sshoutgoingpacket.cpp
@@ -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)
{
diff --git a/src/libs/ssh/sshoutgoingpacket_p.h b/src/libs/ssh/sshoutgoingpacket_p.h
index 8e3da5aef6..b9392279aa 100644
--- a/src/libs/ssh/sshoutgoingpacket_p.h
+++ b/src/libs/ssh/sshoutgoingpacket_p.h
@@ -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);
diff --git a/src/libs/ssh/sshremoteprocess.cpp b/src/libs/ssh/sshremoteprocess.cpp
index 3b81e0409c..f4c43bdce2 100644
--- a/src/libs/ssh/sshremoteprocess.cpp
+++ b/src/libs/ssh/sshremoteprocess.cpp
@@ -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);
+}
+
+void SshRemoteProcessPrivate::startProcess(const X11DisplayInfo &displayInfo)
+{
+ if (m_procState != NotYetStarted)
+ return;
+
+ 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_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);
+ 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()
diff --git a/src/libs/ssh/sshremoteprocess.h b/src/libs/ssh/sshremoteprocess.h
index cd54a03ede..85f8018227 100644
--- a/src/libs/ssh/sshremoteprocess.h
+++ b/src/libs/ssh/sshremoteprocess.h
@@ -78,6 +78,7 @@ public:
void clearEnvironment();
void requestTerminal(const SshPseudoTerminal &terminal);
+ void requestX11Forwarding(const QString &displayName);
void start();
bool isRunning() const;
diff --git a/src/libs/ssh/sshremoteprocess_p.h b/src/libs/ssh/sshremoteprocess_p.h
index edf0648329..459da8c593 100644
--- a/src/libs/ssh/sshremoteprocess_p.h
+++ b/src/libs/ssh/sshremoteprocess_p.h
@@ -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;
diff --git a/src/libs/ssh/sshsendfacility.cpp b/src/libs/ssh/sshsendfacility.cpp
index 9552d3e020..f92a1220fb 100644
--- a/src/libs/ssh/sshsendfacility.cpp
+++ b/src/libs/ssh/sshsendfacility.cpp
@@ -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)
{
diff --git a/src/libs/ssh/sshsendfacility_p.h b/src/libs/ssh/sshsendfacility_p.h
index f54a2a9463..b3e0e44e1f 100644
--- a/src/libs/ssh/sshsendfacility_p.h
+++ b/src/libs/ssh/sshsendfacility_p.h
@@ -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);
diff --git a/src/libs/ssh/sshx11channel.cpp b/src/libs/ssh/sshx11channel.cpp
new file mode 100644
index 0000000000..d531142ceb
--- /dev/null
+++ b/src/libs/ssh/sshx11channel.cpp
@@ -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>
diff --git a/src/libs/ssh/sshx11channel_p.h b/src/libs/ssh/sshx11channel_p.h
new file mode 100644
index 0000000000..918129172e
--- /dev/null
+++ b/src/libs/ssh/sshx11channel_p.h
@@ -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
diff --git a/src/libs/ssh/sshx11displayinfo_p.h b/src/libs/ssh/sshx11displayinfo_p.h
new file mode 100644
index 0000000000..48ba05218a
--- /dev/null
+++ b/src/libs/ssh/sshx11displayinfo_p.h
@@ -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
diff --git a/src/libs/ssh/sshx11inforetriever.cpp b/src/libs/ssh/sshx11inforetriever.cpp
new file mode 100644
index 0000000000..f329a6b8cd
--- /dev/null
+++ b/src/libs/ssh/sshx11inforetriever.cpp
@@ -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
diff --git a/src/libs/ssh/sshx11inforetriever_p.h b/src/libs/ssh/sshx11inforetriever_p.h
new file mode 100644
index 0000000000..fc27672f4f
--- /dev/null
+++ b/src/libs/ssh/sshx11inforetriever_p.h
@@ -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
diff --git a/tests/auto/ssh/ssh.pro b/tests/auto/ssh/ssh.pro
index 31469ad3ad..8ba850177d 100644
--- a/tests/auto/ssh/ssh.pro
+++ b/tests/auto/ssh/ssh.pro
@@ -1,5 +1,5 @@
QT = core network
-QTC_LIB_DEPENDS += ssh
+QTC_LIB_DEPENDS += ssh utils
include(../qttest.pri)
SOURCES += tst_ssh.cpp
diff --git a/tests/auto/ssh/ssh.qbs b/tests/auto/ssh/ssh.qbs
index b580f05433..c2b7072b55 100644
--- a/tests/auto/ssh/ssh.qbs
+++ b/tests/auto/ssh/ssh.qbs
@@ -3,5 +3,6 @@ import qbs
QtcAutotest {
name: "SSH autotest"
Depends { name: "QtcSsh" }
+ Depends { name: "Utils" }
files: "tst_ssh.cpp"
}
diff --git a/tests/auto/ssh/tst_ssh.cpp b/tests/auto/ssh/tst_ssh.cpp
index b15e411066..93b8a85c13 100644
--- a/tests/auto/ssh/tst_ssh.cpp
+++ b/tests/auto/ssh/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;