summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@nokia.com>2012-06-19 13:03:48 +0200
committerChristian Kandeler <christian.kandeler@nokia.com>2012-08-28 15:12:04 +0200
commitedcf76613b82d4b04ad9d0797515dc42d983eb68 (patch)
tree5229324612e8489ebe70e304641ea0781893dfdd
parenta57b4cf93efcb3fe30623566115b29e925367f7f (diff)
downloadqt-creator-edcf76613b82d4b04ad9d0797515dc42d983eb68.tar.gz
SSH: Implement tunneling.
This is the "direct-tcpip" port forwarding specified in RFC 4254. Change-Id: I1ffa2e923b4479c7211b1b4304e66895b565fb64 Reviewed-by: hjk <qthjk@ovi.com> Reviewed-by: Christian Kandeler <christian.kandeler@nokia.com>
-rw-r--r--src/libs/ssh/sftpchannel_p.h11
-rw-r--r--src/libs/ssh/ssh.pro7
-rw-r--r--src/libs/ssh/ssh.qbs1
-rw-r--r--src/libs/ssh/sshchannel.cpp33
-rw-r--r--src/libs/ssh/sshchannel_p.h20
-rw-r--r--src/libs/ssh/sshchannelmanager.cpp11
-rw-r--r--src/libs/ssh/sshchannelmanager_p.h7
-rw-r--r--src/libs/ssh/sshconnection.cpp13
-rw-r--r--src/libs/ssh/sshconnection.h2
-rw-r--r--src/libs/ssh/sshconnection_p.h5
-rw-r--r--src/libs/ssh/sshdirecttcpiptunnel.cpp194
-rw-r--r--src/libs/ssh/sshdirecttcpiptunnel.h90
-rw-r--r--src/libs/ssh/sshdirecttcpiptunnel_p.h84
-rw-r--r--src/libs/ssh/sshoutgoingpacket.cpp11
-rw-r--r--src/libs/ssh/sshoutgoingpacket_p.h3
-rw-r--r--src/libs/ssh/sshremoteprocess.cpp1
-rw-r--r--src/libs/ssh/sshremoteprocess_p.h13
-rw-r--r--src/libs/ssh/sshsendfacility.cpp9
-rw-r--r--src/libs/ssh/sshsendfacility_p.h3
-rw-r--r--tests/manual/ssh/remoteprocess/remoteprocesstest.h2
-rw-r--r--tests/manual/ssh/ssh.pro2
-rw-r--r--tests/manual/ssh/tunnel/argumentscollector.cpp174
-rw-r--r--tests/manual/ssh/tunnel/argumentscollector.h63
-rw-r--r--tests/manual/ssh/tunnel/main.cpp55
-rw-r--r--tests/manual/ssh/tunnel/tunnel.cpp165
-rw-r--r--tests/manual/ssh/tunnel/tunnel.h81
-rw-r--r--tests/manual/ssh/tunnel/tunnel.pro5
27 files changed, 1022 insertions, 43 deletions
diff --git a/src/libs/ssh/sftpchannel_p.h b/src/libs/ssh/sftpchannel_p.h
index e3979312b2..9597e28e2d 100644
--- a/src/libs/ssh/sftpchannel_p.h
+++ b/src/libs/ssh/sftpchannel_p.h
@@ -49,14 +49,8 @@ class SftpChannelPrivate : public AbstractSshChannel
Q_OBJECT
friend class QSsh::SftpChannel;
public:
-
enum SftpState { Inactive, SubsystemRequested, InitSent, Initialized };
- virtual void handleChannelSuccess();
- virtual void handleChannelFailure();
-
- virtual void closeHook();
-
signals:
void initialized();
void initializationFailed(const QString &reason);
@@ -72,6 +66,9 @@ private:
SftpChannel *sftp);
SftpJobId createJob(const AbstractSftpOperation::Ptr &job);
+ virtual void handleChannelSuccess();
+ virtual void handleChannelFailure();
+
virtual void handleOpenSuccessInternal();
virtual void handleOpenFailureInternal(const QString &reason);
virtual void handleChannelDataInternal(const QByteArray &data);
@@ -80,6 +77,8 @@ private:
virtual void handleExitStatus(const SshChannelExitStatus &exitStatus);
virtual void handleExitSignal(const SshChannelExitSignal &signal);
+ virtual void closeHook();
+
void handleCurrentPacket();
void handleServerVersion();
void handleHandle();
diff --git a/src/libs/ssh/ssh.pro b/src/libs/ssh/ssh.pro
index 2df5b0430b..3b4ce04962 100644
--- a/src/libs/ssh/ssh.pro
+++ b/src/libs/ssh/ssh.pro
@@ -29,7 +29,8 @@ SOURCES = $$PWD/sshsendfacility.cpp \
$$PWD/sshconnectionmanager.cpp \
$$PWD/sshkeypasswordretriever.cpp \
$$PWD/sftpfilesystemmodel.cpp \
- $$PWD/sshkeycreationdialog.cpp
+ $$PWD/sshkeycreationdialog.cpp \
+ $$PWD/sshdirecttcpiptunnel.cpp
HEADERS = $$PWD/sshsendfacility_p.h \
$$PWD/sshremoteprocess.h \
@@ -62,6 +63,8 @@ HEADERS = $$PWD/sshsendfacility_p.h \
$$PWD/sshkeypasswordretriever_p.h \
$$PWD/sftpfilesystemmodel.h \
$$PWD/sshkeycreationdialog.h \
- $$PWD/ssh_global.h
+ $$PWD/ssh_global.h \
+ $$PWD/sshdirecttcpiptunnel_p.h \
+ $$PWD/sshdirecttcpiptunnel.h
FORMS = $$PWD/sshkeycreationdialog.ui
diff --git a/src/libs/ssh/ssh.qbs b/src/libs/ssh/ssh.qbs
index cb8603327c..42b84abcc5 100644
--- a/src/libs/ssh/ssh.qbs
+++ b/src/libs/ssh/ssh.qbs
@@ -35,6 +35,7 @@ QtcLibrary {
"sshpacket.cpp", "sshpacket_p.h",
"sshpacketparser.cpp", "sshpacketparser_p.h",
"sshremoteprocess.cpp", "sshremoteprocess.h", "sshremoteprocess_p.h",
+ "sshdirecttcpiptunnel.h", "sshdirecttcpiptunnel_p.h", "sshdirecttcpiptunnel.cpp",
"sshremoteprocessrunner.cpp", "sshremoteprocessrunner.h",
"sshsendfacility.cpp", "sshsendfacility_p.h",
"sshkeypasswordretriever.cpp",
diff --git a/src/libs/ssh/sshchannel.cpp b/src/libs/ssh/sshchannel.cpp
index f513f5121f..a2dfe78b5c 100644
--- a/src/libs/ssh/sshchannel.cpp
+++ b/src/libs/ssh/sshchannel.cpp
@@ -40,18 +40,14 @@
namespace QSsh {
namespace Internal {
-namespace {
- const quint32 MinMaxPacketSize = 32768;
- const quint32 MaxPacketSize = 16 * 1024 * 1024;
- const quint32 InitialWindowSize = MaxPacketSize;
- const quint32 NoChannel = 0xffffffffu;
-} // anonymous namespace
+const quint32 MinMaxPacketSize = 32768;
+const quint32 NoChannel = 0xffffffffu;
AbstractSshChannel::AbstractSshChannel(quint32 channelId,
SshSendFacility &sendFacility)
: m_sendFacility(sendFacility), m_timeoutTimer(new QTimer(this)),
m_localChannel(channelId), m_remoteChannel(NoChannel),
- m_localWindowSize(InitialWindowSize), m_remoteWindowSize(0),
+ m_localWindowSize(initialWindowSize()), m_remoteWindowSize(0),
m_state(Inactive)
{
m_timeoutTimer->setSingleShot(true);
@@ -77,8 +73,7 @@ void AbstractSshChannel::requestSessionStart()
// with our cryptography stuff, it would have hit us before, on
// establishing the connection.
try {
- m_sendFacility.sendSessionPacket(m_localChannel, InitialWindowSize,
- MaxPacketSize);
+ m_sendFacility.sendSessionPacket(m_localChannel, initialWindowSize(), maxPacketSize());
setChannelState(SessionRequested);
m_timeoutTimer->start(ReplyTimeout);
} catch (Botan::Exception &e) {
@@ -98,6 +93,16 @@ void AbstractSshChannel::sendData(const QByteArray &data)
}
}
+quint32 AbstractSshChannel::initialWindowSize()
+{
+ return maxPacketSize();
+}
+
+quint32 AbstractSshChannel::maxPacketSize()
+{
+ return 16 * 1024 * 1024;
+}
+
void AbstractSshChannel::handleWindowAdjust(quint32 bytesToAdd)
{
checkChannelActive();
@@ -174,6 +179,7 @@ void AbstractSshChannel::handleChannelEof()
"Unexpected SSH_MSG_CHANNEL_EOF message.");
}
m_localWindowSize = 0;
+ emit eof();
}
void AbstractSshChannel::handleChannelClose()
@@ -224,10 +230,9 @@ int AbstractSshChannel::handleChannelOrExtendedChannelData(const QByteArray &dat
qWarning("Misbehaving server does not respect local window, clipping.");
m_localWindowSize -= bytesToDeliver;
- if (m_localWindowSize < MaxPacketSize) {
- m_localWindowSize += MaxPacketSize;
- m_sendFacility.sendWindowAdjustPacket(m_remoteChannel,
- MaxPacketSize);
+ if (m_localWindowSize < maxPacketSize()) {
+ m_localWindowSize += maxPacketSize();
+ m_sendFacility.sendWindowAdjustPacket(m_remoteChannel, maxPacketSize());
}
return bytesToDeliver;
}
@@ -256,7 +261,7 @@ void AbstractSshChannel::checkChannelActive()
quint32 AbstractSshChannel::maxDataSize() const
{
- return qMin(m_localWindowSize, MaxPacketSize);
+ return qMin(m_localWindowSize, maxPacketSize());
}
} // namespace Internal
diff --git a/src/libs/ssh/sshchannel_p.h b/src/libs/ssh/sshchannel_p.h
index d1a84e1563..5898bc44b1 100644
--- a/src/libs/ssh/sshchannel_p.h
+++ b/src/libs/ssh/sshchannel_p.h
@@ -53,17 +53,12 @@ public:
Inactive, SessionRequested, SessionEstablished, CloseRequested, Closed
};
- ChannelState channelState() const { return m_state; }
- void setChannelState(ChannelState state);
-
quint32 localChannelId() const { return m_localChannel; }
quint32 remoteChannel() const { return m_remoteChannel; }
virtual void handleChannelSuccess() = 0;
virtual void handleChannelFailure() = 0;
- virtual void closeHook() = 0;
-
void handleOpenSuccess(quint32 remoteChannelId, quint32 remoteWindowSize,
quint32 remoteMaxPacketSize);
void handleOpenFailure(const QString &reason);
@@ -74,8 +69,6 @@ public:
void handleChannelExtendedData(quint32 type, const QByteArray &data);
void handleChannelRequest(const SshIncomingPacket &packet);
- void requestSessionStart();
- void sendData(const QByteArray &data);
void closeChannel();
virtual ~AbstractSshChannel();
@@ -84,10 +77,20 @@ public:
signals:
void timeout();
+ void eof();
protected:
AbstractSshChannel(quint32 channelId, SshSendFacility &sendFacility);
+ void setChannelState(ChannelState state);
+ ChannelState channelState() const { return m_state; }
+
+ void requestSessionStart();
+ void sendData(const QByteArray &data);
+
+ static quint32 initialWindowSize();
+ static quint32 maxPacketSize();
+
quint32 maxDataSize() const;
void checkChannelActive();
@@ -103,7 +106,8 @@ private:
virtual void handleExitStatus(const SshChannelExitStatus &exitStatus) = 0;
virtual void handleExitSignal(const SshChannelExitSignal &signal) = 0;
- void setState(ChannelState newState);
+ virtual void closeHook() = 0;
+
void flushSendBuffer();
int handleChannelOrExtendedChannelData(const QByteArray &data);
diff --git a/src/libs/ssh/sshchannelmanager.cpp b/src/libs/ssh/sshchannelmanager.cpp
index 792963702e..5d7f35f91d 100644
--- a/src/libs/ssh/sshchannelmanager.cpp
+++ b/src/libs/ssh/sshchannelmanager.cpp
@@ -32,6 +32,8 @@
#include "sftpchannel.h"
#include "sftpchannel_p.h"
+#include "sshdirecttcpiptunnel.h"
+#include "sshdirecttcpiptunnel_p.h"
#include "sshincomingpacket_p.h"
#include "sshremoteprocess.h"
#include "sshremoteprocess_p.h"
@@ -168,6 +170,15 @@ QSsh::SftpChannel::Ptr SshChannelManager::createSftpChannel()
return sftp;
}
+SshDirectTcpIpTunnel::Ptr SshChannelManager::createTunnel(quint16 remotePort,
+ const SshConnectionInfo &connectionInfo)
+{
+ SshDirectTcpIpTunnel::Ptr tunnel(new SshDirectTcpIpTunnel(m_nextLocalChannelId++, remotePort,
+ connectionInfo, m_sendFacility));
+ insertChannel(tunnel->d, tunnel);
+ return tunnel;
+}
+
void SshChannelManager::insertChannel(AbstractSshChannel *priv,
const QSharedPointer<QObject> &pub)
{
diff --git a/src/libs/ssh/sshchannelmanager_p.h b/src/libs/ssh/sshchannelmanager_p.h
index 16626eddb1..28f7266deb 100644
--- a/src/libs/ssh/sshchannelmanager_p.h
+++ b/src/libs/ssh/sshchannelmanager_p.h
@@ -36,8 +36,9 @@
#include <QSharedPointer>
namespace QSsh {
-
class SftpChannel;
+class SshConnectionInfo;
+class SshDirectTcpIpTunnel;
class SshRemoteProcess;
namespace Internal {
@@ -55,8 +56,10 @@ public:
QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
QSharedPointer<SshRemoteProcess> createRemoteShell();
QSharedPointer<SftpChannel> createSftpChannel();
- int channelCount() const;
+ QSharedPointer<SshDirectTcpIpTunnel> createTunnel(quint16 remotePort,
+ const SshConnectionInfo &connectionInfo);
+ int channelCount() const;
enum CloseAllMode { CloseAllRegular, CloseAllAndReset };
int closeAllChannels(CloseAllMode mode);
diff --git a/src/libs/ssh/sshconnection.cpp b/src/libs/ssh/sshconnection.cpp
index ebf233d3b6..8dfd475dd5 100644
--- a/src/libs/ssh/sshconnection.cpp
+++ b/src/libs/ssh/sshconnection.cpp
@@ -35,8 +35,10 @@
#include "sshcapabilities_p.h"
#include "sshchannelmanager_p.h"
#include "sshcryptofacility_p.h"
+#include "sshdirecttcpiptunnel.h"
#include "sshexception_p.h"
#include "sshkeyexchange_p.h"
+#include "sshremoteprocess.h"
#include <botan/botan.h>
@@ -191,6 +193,12 @@ QSharedPointer<SftpChannel> SshConnection::createSftpChannel()
return d->createSftpChannel();
}
+SshDirectTcpIpTunnel::Ptr SshConnection::createTunnel(quint16 remotePort)
+{
+ QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, SshDirectTcpIpTunnel::Ptr());
+ return d->createTunnel(remotePort);
+}
+
int SshConnection::closeAllChannels()
{
try {
@@ -730,6 +738,11 @@ QSharedPointer<SftpChannel> SshConnectionPrivate::createSftpChannel()
return m_channelManager->createSftpChannel();
}
+SshDirectTcpIpTunnel::Ptr SshConnectionPrivate::createTunnel(quint16 remotePort)
+{
+ return m_channelManager->createTunnel(remotePort, m_conn->connectionInfo());
+}
+
const quint64 SshConnectionPrivate::InvalidSeqNr = static_cast<quint64>(-1);
} // namespace Internal
diff --git a/src/libs/ssh/sshconnection.h b/src/libs/ssh/sshconnection.h
index 66263c3077..3c982b197e 100644
--- a/src/libs/ssh/sshconnection.h
+++ b/src/libs/ssh/sshconnection.h
@@ -43,6 +43,7 @@
namespace QSsh {
class SftpChannel;
+class SshDirectTcpIpTunnel;
class SshRemoteProcess;
namespace Internal {
@@ -103,6 +104,7 @@ public:
QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
QSharedPointer<SshRemoteProcess> createRemoteShell();
QSharedPointer<SftpChannel> createSftpChannel();
+ QSharedPointer<SshDirectTcpIpTunnel> createTunnel(quint16 remotePort);
// -1 if an error occurred, number of channels closed otherwise.
int closeAllChannels();
diff --git a/src/libs/ssh/sshconnection_p.h b/src/libs/ssh/sshconnection_p.h
index bdf0c26c96..ed74322b4d 100644
--- a/src/libs/ssh/sshconnection_p.h
+++ b/src/libs/ssh/sshconnection_p.h
@@ -34,7 +34,6 @@
#include "sshconnection.h"
#include "sshexception_p.h"
#include "sshincomingpacket_p.h"
-#include "sshremoteprocess.h"
#include "sshsendfacility_p.h"
#include <QHash>
@@ -50,6 +49,8 @@ QT_END_NAMESPACE
namespace QSsh {
class SftpChannel;
+class SshRemoteProcess;
+class SshDirectTcpIpTunnel;
namespace Internal {
class SshChannelManager;
@@ -88,6 +89,8 @@ public:
QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
QSharedPointer<SshRemoteProcess> createRemoteShell();
QSharedPointer<SftpChannel> createSftpChannel();
+ QSharedPointer<SshDirectTcpIpTunnel> createTunnel(quint16 remotePort);
+
SshStateInternal state() const { return m_state; }
SshError error() const { return m_error; }
QString errorString() const { return m_errorString; }
diff --git a/src/libs/ssh/sshdirecttcpiptunnel.cpp b/src/libs/ssh/sshdirecttcpiptunnel.cpp
new file mode 100644
index 0000000000..ef0cada653
--- /dev/null
+++ b/src/libs/ssh/sshdirecttcpiptunnel.cpp
@@ -0,0 +1,194 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#include "sshdirecttcpiptunnel.h"
+#include "sshdirecttcpiptunnel_p.h"
+
+#include "sshincomingpacket_p.h"
+#include "sshsendfacility_p.h"
+
+#include <QTimer>
+
+namespace QSsh {
+namespace Internal {
+
+SshDirectTcpIpTunnelPrivate::SshDirectTcpIpTunnelPrivate(quint32 channelId, quint16 remotePort,
+ const SshConnectionInfo &connectionInfo, SshSendFacility &sendFacility)
+ : AbstractSshChannel(channelId, sendFacility),
+ m_remotePort(remotePort),
+ m_connectionInfo(connectionInfo)
+{
+ connect(this, SIGNAL(eof()), SLOT(handleEof()));
+}
+
+void SshDirectTcpIpTunnelPrivate::handleChannelSuccess()
+{
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_MSG_CHANNEL_SUCCESS message.");
+}
+
+void SshDirectTcpIpTunnelPrivate::handleChannelFailure()
+{
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_MSG_CHANNEL_FAILURE message.");
+}
+
+void SshDirectTcpIpTunnelPrivate::handleOpenSuccessInternal()
+{
+ emit initialized();
+}
+
+void SshDirectTcpIpTunnelPrivate::handleOpenFailureInternal(const QString &reason)
+{
+ emit error(reason);
+ closeChannel();
+}
+
+void SshDirectTcpIpTunnelPrivate::handleChannelDataInternal(const QByteArray &data)
+{
+ m_data += data;
+ emit readyRead();
+}
+
+void SshDirectTcpIpTunnelPrivate::handleChannelExtendedDataInternal(quint32 type,
+ const QByteArray &data)
+{
+ qDebug("%s: Unexpected extended channel data. Type is %u, content is '%s'.", Q_FUNC_INFO, type,
+ data.constData());
+}
+
+void SshDirectTcpIpTunnelPrivate::handleExitStatus(const SshChannelExitStatus &exitStatus)
+{
+ qDebug("%s: Unexpected exit status %d.", Q_FUNC_INFO, exitStatus.exitStatus);
+}
+
+void SshDirectTcpIpTunnelPrivate::handleExitSignal(const SshChannelExitSignal &signal)
+{
+ qDebug("%s: Unexpected exit signal %s.", Q_FUNC_INFO, signal.signal.constData());
+}
+
+void SshDirectTcpIpTunnelPrivate::closeHook()
+{
+ emit closed();
+}
+
+void SshDirectTcpIpTunnelPrivate::handleEof()
+{
+ /*
+ * For some reason, the OpenSSH server only sends EOF when the remote port goes away,
+ * but does not close the channel, even though it becomes useless in that case.
+ * So we close it ourselves.
+ */
+ closeChannel();
+}
+
+} // namespace Internal
+
+using namespace Internal;
+
+SshDirectTcpIpTunnel::SshDirectTcpIpTunnel(quint32 channelId, quint16 remotePort,
+ const SshConnectionInfo &connectionInfo, SshSendFacility &sendFacility)
+ : d(new SshDirectTcpIpTunnelPrivate(channelId, remotePort, connectionInfo, sendFacility))
+{
+ connect(d, SIGNAL(initialized()), SIGNAL(initialized()), Qt::QueuedConnection);
+ connect(d, SIGNAL(readyRead()), SIGNAL(readyRead()), Qt::QueuedConnection);
+ connect(d, SIGNAL(closed()), SIGNAL(tunnelClosed()), Qt::QueuedConnection);
+ connect(d, SIGNAL(error(QString)), SLOT(handleError(QString)), Qt::QueuedConnection);
+}
+
+SshDirectTcpIpTunnel::~SshDirectTcpIpTunnel()
+{
+ d->closeChannel();
+ delete d;
+}
+
+bool SshDirectTcpIpTunnel::atEnd() const
+{
+ return QIODevice::atEnd() && d->m_data.isEmpty();
+}
+
+qint64 SshDirectTcpIpTunnel::bytesAvailable() const
+{
+ return QIODevice::bytesAvailable() + d->m_data.count();
+}
+
+bool SshDirectTcpIpTunnel::canReadLine() const
+{
+ return QIODevice::canReadLine() || d->m_data.contains('\n');
+}
+
+void SshDirectTcpIpTunnel::close()
+{
+ d->closeChannel();
+ QIODevice::close();
+}
+
+void SshDirectTcpIpTunnel::initialize()
+{
+ QSSH_ASSERT_AND_RETURN(d->channelState() == AbstractSshChannel::Inactive);
+
+ try {
+ QIODevice::open(QIODevice::ReadWrite);
+ d->m_sendFacility.sendDirectTcpIpPacket(d->localChannelId(), d->initialWindowSize(),
+ d->maxPacketSize(), d->m_connectionInfo.peerAddress.toString().toUtf8(),
+ d->m_remotePort, d->m_connectionInfo.localAddress.toString().toUtf8(),
+ d->m_connectionInfo.localPort);
+ d->setChannelState(AbstractSshChannel::SessionRequested);
+ d->m_timeoutTimer->start(d->ReplyTimeout);
+ } catch (Botan::Exception &e) { // Won't happen, but let's play it safe.
+ qDebug("Botan error: %s", e.what());
+ d->closeChannel();
+ }
+}
+
+qint64 SshDirectTcpIpTunnel::readData(char *data, qint64 maxlen)
+{
+ const qint64 bytesRead = qMin(qint64(d->m_data.count()), maxlen);
+ memcpy(data, d->m_data.constData(), bytesRead);
+ d->m_data.remove(0, bytesRead);
+ return bytesRead;
+}
+
+qint64 SshDirectTcpIpTunnel::writeData(const char *data, qint64 len)
+{
+ QSSH_ASSERT_AND_RETURN_VALUE(d->channelState() == AbstractSshChannel::SessionEstablished, 0);
+
+ d->sendData(QByteArray(data, len));
+ return len;
+}
+
+void SshDirectTcpIpTunnel::handleError(const QString &reason)
+{
+ setErrorString(reason);
+ emit error(reason);
+}
+
+} // namespace QSsh
diff --git a/src/libs/ssh/sshdirecttcpiptunnel.h b/src/libs/ssh/sshdirecttcpiptunnel.h
new file mode 100644
index 0000000000..b451c96afc
--- /dev/null
+++ b/src/libs/ssh/sshdirecttcpiptunnel.h
@@ -0,0 +1,90 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHDIRECTTCPIPTUNNEL_H
+#define SSHDIRECTTCPIPTUNNEL_H
+
+#include "ssh_global.h"
+
+#include <QIODevice>
+#include <QSharedPointer>
+
+namespace QSsh {
+class SshConnectionInfo;
+
+namespace Internal {
+class SshChannelManager;
+class SshDirectTcpIpTunnelPrivate;
+class SshSendFacility;
+} // namespace Internal
+
+class QSSH_EXPORT SshDirectTcpIpTunnel : public QIODevice
+{
+ Q_OBJECT
+
+ friend class Internal::SshChannelManager;
+
+public:
+ typedef QSharedPointer<SshDirectTcpIpTunnel> Ptr;
+
+ ~SshDirectTcpIpTunnel();
+
+ // QIODevice stuff
+ bool atEnd() const;
+ qint64 bytesAvailable() const;
+ bool canReadLine() const;
+ void close();
+ bool isSequential() const { return true; }
+
+ void initialize();
+
+signals:
+ void initialized();
+ void error(const QString &reason);
+ void tunnelClosed();
+
+private:
+ SshDirectTcpIpTunnel(quint32 channelId, quint16 remotePort,
+ const SshConnectionInfo &connectionInfo, Internal::SshSendFacility &sendFacility);
+
+ // QIODevice stuff
+ qint64 readData(char *data, qint64 maxlen);
+ qint64 writeData(const char *data, qint64 len);
+
+ Q_SLOT void handleError(const QString &reason);
+
+ Internal::SshDirectTcpIpTunnelPrivate * const d;
+};
+
+} // namespace QSsh
+
+#endif // SSHDIRECTTCPIPTUNNEL_H
diff --git a/src/libs/ssh/sshdirecttcpiptunnel_p.h b/src/libs/ssh/sshdirecttcpiptunnel_p.h
new file mode 100644
index 0000000000..7122c4dc2a
--- /dev/null
+++ b/src/libs/ssh/sshdirecttcpiptunnel_p.h
@@ -0,0 +1,84 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#ifndef DIRECTTCPIPCHANNEL_P_H
+#define DIRECTTCPIPCHANNEL_P_H
+
+#include "sshchannel_p.h"
+
+#include "sshconnection.h"
+
+namespace QSsh {
+class SshDirectTcpIpTunnel;
+
+namespace Internal {
+
+class SshDirectTcpIpTunnelPrivate : public AbstractSshChannel
+{
+ Q_OBJECT
+
+ friend class QSsh::SshDirectTcpIpTunnel;
+
+public:
+ explicit SshDirectTcpIpTunnelPrivate(quint32 channelId, quint16 remotePort,
+ const SshConnectionInfo &connectionInfo, SshSendFacility &sendFacility);
+
+signals:
+ void initialized();
+ void readyRead();
+ void error(const QString &reason);
+ void closed();
+
+private slots:
+ void handleEof();
+
+private:
+ void handleChannelSuccess();
+ void handleChannelFailure();
+
+ void handleOpenSuccessInternal();
+ void handleOpenFailureInternal(const QString &reason);
+ void handleChannelDataInternal(const QByteArray &data);
+ void handleChannelExtendedDataInternal(quint32 type, const QByteArray &data);
+ void handleExitStatus(const SshChannelExitStatus &exitStatus);
+ void handleExitSignal(const SshChannelExitSignal &signal);
+
+ void closeHook();
+
+ const quint16 m_remotePort;
+ const SshConnectionInfo m_connectionInfo;
+ QByteArray m_data;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // DIRECTTCPIPCHANNEL_P_H
diff --git a/src/libs/ssh/sshoutgoingpacket.cpp b/src/libs/ssh/sshoutgoingpacket.cpp
index 97b199beed..1be0aa4934 100644
--- a/src/libs/ssh/sshoutgoingpacket.cpp
+++ b/src/libs/ssh/sshoutgoingpacket.cpp
@@ -143,7 +143,16 @@ void SshOutgoingPacket::generateSessionPacket(quint32 channelId,
quint32 windowSize, quint32 maxPacketSize)
{
init(SSH_MSG_CHANNEL_OPEN).appendString("session").appendInt(channelId)
- .appendInt(windowSize).appendInt(maxPacketSize).finalize();
+ .appendInt(windowSize).appendInt(maxPacketSize).finalize();
+}
+
+void SshOutgoingPacket::generateDirectTcpIpPacket(quint32 channelId, quint32 windowSize,
+ quint32 maxPacketSize, const QByteArray &remoteHost, quint32 remotePort,
+ const QByteArray &localIpAddress, quint32 localPort)
+{
+ init(SSH_MSG_CHANNEL_OPEN).appendString("direct-tcpip").appendInt(channelId)
+ .appendInt(windowSize).appendInt(maxPacketSize).appendString(remoteHost)
+ .appendInt(remotePort).appendString(localIpAddress).appendInt(localPort).finalize();
}
void SshOutgoingPacket::generateEnvPacket(quint32 remoteChannel,
diff --git a/src/libs/ssh/sshoutgoingpacket_p.h b/src/libs/ssh/sshoutgoingpacket_p.h
index 60b2d3087a..7c77c749b3 100644
--- a/src/libs/ssh/sshoutgoingpacket_p.h
+++ b/src/libs/ssh/sshoutgoingpacket_p.h
@@ -62,6 +62,9 @@ public:
void generateInvalidMessagePacket();
void generateSessionPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize);
+ void generateDirectTcpIpPacket(quint32 channelId, quint32 windowSize,
+ quint32 maxPacketSize, const QByteArray &remoteHost, quint32 remotePort,
+ const QByteArray &localIpAddress, quint32 localPort);
void generateEnvPacket(quint32 remoteChannel, const QByteArray &var,
const QByteArray &value);
void generatePtyRequestPacket(quint32 remoteChannel,
diff --git a/src/libs/ssh/sshremoteprocess.cpp b/src/libs/ssh/sshremoteprocess.cpp
index 36f71b5b80..a4f4ae76b5 100644
--- a/src/libs/ssh/sshremoteprocess.cpp
+++ b/src/libs/ssh/sshremoteprocess.cpp
@@ -167,6 +167,7 @@ void SshRemoteProcess::init()
connect(d, SIGNAL(readyReadStandardError()), this,
SIGNAL(readyReadStandardError()), Qt::QueuedConnection);
connect(d, SIGNAL(closed(int)), this, SIGNAL(closed(int)), Qt::QueuedConnection);
+ connect(d, SIGNAL(eof()), SIGNAL(readChannelFinished()), Qt::QueuedConnection);
}
void SshRemoteProcess::addToEnvironment(const QByteArray &var, const QByteArray &value)
diff --git a/src/libs/ssh/sshremoteprocess_p.h b/src/libs/ssh/sshremoteprocess_p.h
index 57bf3f9ceb..6cecde117c 100644
--- a/src/libs/ssh/sshremoteprocess_p.h
+++ b/src/libs/ssh/sshremoteprocess_p.h
@@ -54,13 +54,6 @@ public:
NotYetStarted, ExecRequested, StartFailed, Running, Exited
};
- virtual void handleChannelSuccess();
- virtual void handleChannelFailure();
-
- virtual void closeHook();
-
- QByteArray &data();
-
signals:
void started();
void readyRead();
@@ -74,6 +67,9 @@ private:
SshRemoteProcessPrivate(quint32 channelId, SshSendFacility &sendFacility,
SshRemoteProcess *proc);
+ virtual void handleChannelSuccess();
+ virtual void handleChannelFailure();
+
virtual void handleOpenSuccessInternal();
virtual void handleOpenFailureInternal(const QString &reason);
virtual void handleChannelDataInternal(const QByteArray &data);
@@ -82,8 +78,11 @@ private:
virtual void handleExitStatus(const SshChannelExitStatus &exitStatus);
virtual void handleExitSignal(const SshChannelExitSignal &signal);
+ virtual void closeHook();
+
void init();
void setProcState(ProcessState newState);
+ QByteArray &data();
QProcess::ProcessChannel m_readChannel;
diff --git a/src/libs/ssh/sshsendfacility.cpp b/src/libs/ssh/sshsendfacility.cpp
index 07f0a3755f..e90ba62246 100644
--- a/src/libs/ssh/sshsendfacility.cpp
+++ b/src/libs/ssh/sshsendfacility.cpp
@@ -150,6 +150,15 @@ void SshSendFacility::sendSessionPacket(quint32 channelId, quint32 windowSize,
sendPacket();
}
+void SshSendFacility::sendDirectTcpIpPacket(quint32 channelId, quint32 windowSize,
+ quint32 maxPacketSize, const QByteArray &remoteHost, quint32 remotePort,
+ const QByteArray &localIpAddress, quint32 localPort)
+{
+ m_outgoingPacket.generateDirectTcpIpPacket(channelId, windowSize, maxPacketSize, remoteHost,
+ remotePort, localIpAddress, localPort);
+ sendPacket();
+}
+
void SshSendFacility::sendPtyRequestPacket(quint32 remoteChannel,
const SshPseudoTerminal &terminal)
{
diff --git a/src/libs/ssh/sshsendfacility_p.h b/src/libs/ssh/sshsendfacility_p.h
index d7d20d901d..b17484e491 100644
--- a/src/libs/ssh/sshsendfacility_p.h
+++ b/src/libs/ssh/sshsendfacility_p.h
@@ -69,6 +69,9 @@ public:
void sendInvalidPacket();
void sendSessionPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize);
+ void sendDirectTcpIpPacket(quint32 channelId, quint32 windowSize, quint32 maxPacketSize,
+ const QByteArray &remoteHost, quint32 remotePort, const QByteArray &localIpAddress,
+ quint32 localPort);
void sendPtyRequestPacket(quint32 remoteChannel,
const SshPseudoTerminal &terminal);
void sendEnvPacket(quint32 remoteChannel, const QByteArray &var,
diff --git a/tests/manual/ssh/remoteprocess/remoteprocesstest.h b/tests/manual/ssh/remoteprocess/remoteprocesstest.h
index a2d21e3e05..2cef8828dd 100644
--- a/tests/manual/ssh/remoteprocess/remoteprocesstest.h
+++ b/tests/manual/ssh/remoteprocess/remoteprocesstest.h
@@ -71,10 +71,10 @@ private:
const QSsh::SshConnectionParameters m_sshParams;
QTimer * const m_timeoutTimer;
QTextStream *m_textStream;
- QSsh::SshRemoteProcessRunner * const m_remoteRunner;
QSsh::SshRemoteProcess::Ptr m_catProcess;
QSsh::SshRemoteProcess::Ptr m_echoProcess;
QSsh::SshConnection *m_sshConnection;
+ QSsh::SshRemoteProcessRunner * const m_remoteRunner;
QByteArray m_remoteStdout;
QByteArray m_remoteStderr;
QByteArray m_remoteData;
diff --git a/tests/manual/ssh/ssh.pro b/tests/manual/ssh/ssh.pro
index 6284229802..fdfd768132 100644
--- a/tests/manual/ssh/ssh.pro
+++ b/tests/manual/ssh/ssh.pro
@@ -5,4 +5,4 @@
#-------------------------------------------------
TEMPLATE = subdirs
-SUBDIRS = errorhandling sftp remoteprocess shell sftpfsmodel
+SUBDIRS = errorhandling sftp remoteprocess shell sftpfsmodel tunnel
diff --git a/tests/manual/ssh/tunnel/argumentscollector.cpp b/tests/manual/ssh/tunnel/argumentscollector.cpp
new file mode 100644
index 0000000000..a533ac966a
--- /dev/null
+++ b/tests/manual/ssh/tunnel/argumentscollector.cpp
@@ -0,0 +1,174 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#include "argumentscollector.h"
+
+#include <QDir>
+#include <QProcessEnvironment>
+
+#include <iostream>
+
+using namespace QSsh;
+
+using namespace std;
+
+ArgumentsCollector::ArgumentsCollector(const QStringList &args)
+ : m_arguments(args)
+{
+}
+
+QSsh::SshConnectionParameters ArgumentsCollector::collect(bool &success) const
+{
+ SshConnectionParameters parameters;
+ parameters.host = QLatin1String("localhost");
+
+ try {
+ bool authTypeGiven = false;
+ bool portGiven = false;
+ bool timeoutGiven = false;
+ bool proxySettingGiven = false;
+ int pos;
+ int port;
+
+ for (pos = 1; pos < m_arguments.count() - 1; ++pos) {
+ if (checkAndSetStringArg(pos, parameters.userName, "-u"))
+ continue;
+ if (checkAndSetIntArg(pos, port, portGiven, "-p")
+ || checkAndSetIntArg(pos, parameters.timeout, timeoutGiven, "-t"))
+ continue;
+ if (checkAndSetStringArg(pos, parameters.password, "-pwd")) {
+ if (!parameters.privateKeyFile.isEmpty())
+ throw ArgumentErrorException(QLatin1String("-pwd and -k are mutually exclusive."));
+ parameters.authenticationType
+ = SshConnectionParameters::AuthenticationByPassword;
+ authTypeGiven = true;
+ continue;
+ }
+ if (checkAndSetStringArg(pos, parameters.privateKeyFile, "-k")) {
+ if (!parameters.password.isEmpty())
+ throw ArgumentErrorException(QLatin1String("-pwd and -k are mutually exclusive."));
+ parameters.authenticationType
+ = SshConnectionParameters::AuthenticationByKey;
+ authTypeGiven = true;
+ continue;
+ }
+ if (!checkForNoProxy(pos, parameters.proxyType, proxySettingGiven))
+ throw ArgumentErrorException(QLatin1String("unknown option ") + m_arguments.at(pos));
+ }
+
+ Q_ASSERT(pos <= m_arguments.count());
+ if (pos == m_arguments.count() - 1) {
+ if (!checkForNoProxy(pos, parameters.proxyType, proxySettingGiven))
+ throw ArgumentErrorException(QLatin1String("unknown option ") + m_arguments.at(pos));
+ }
+
+ if (!authTypeGiven) {
+ parameters.authenticationType = SshConnectionParameters::AuthenticationByKey;
+ parameters.privateKeyFile = QDir::homePath() + QLatin1String("/.ssh/id_rsa");
+ }
+
+ if (parameters.userName.isEmpty()) {
+ parameters.userName
+ = QProcessEnvironment::systemEnvironment().value(QLatin1String("USER"));
+ }
+ if (parameters.userName.isEmpty())
+ throw ArgumentErrorException(QLatin1String("No user name given."));
+
+ if (parameters.host.isEmpty())
+ throw ArgumentErrorException(QLatin1String("No host given."));
+
+ parameters.port = portGiven ? port : 22;
+ if (!timeoutGiven)
+ parameters.timeout = 30;
+ success = true;
+ } catch (ArgumentErrorException &ex) {
+ cerr << "Error: " << qPrintable(ex.error) << endl;
+ printUsage();
+ success = false;
+ }
+ return parameters;
+}
+
+void ArgumentsCollector::printUsage() const
+{
+ cerr << "Usage: " << qPrintable(m_arguments.first())
+ << "[ -u <user> ] "
+ << "[ -pwd <password> | -k <private key file> ] [ -p <port> ] "
+ << "[ -t <timeout> ] [ -no-proxy ]" << endl;
+}
+
+bool ArgumentsCollector::checkAndSetStringArg(int &pos, QString &arg, const char *opt) const
+{
+ if (m_arguments.at(pos) == QLatin1String(opt)) {
+ if (!arg.isEmpty()) {
+ throw ArgumentErrorException(QLatin1String("option ") + opt
+ + QLatin1String(" was given twice."));
+ }
+ arg = m_arguments.at(++pos);
+ if (arg.isEmpty() && QLatin1String(opt) != QLatin1String("-pwd"))
+ throw ArgumentErrorException(QLatin1String("empty argument not allowed here."));
+ return true;
+ }
+ return false;
+}
+
+bool ArgumentsCollector::checkAndSetIntArg(int &pos, int &val,
+ bool &alreadyGiven, const char *opt) const
+{
+ if (m_arguments.at(pos) == QLatin1String(opt)) {
+ if (alreadyGiven) {
+ throw ArgumentErrorException(QLatin1String("option ") + opt
+ + QLatin1String(" was given twice."));
+ }
+ bool isNumber;
+ val = m_arguments.at(++pos).toInt(&isNumber);
+ if (!isNumber) {
+ throw ArgumentErrorException(QLatin1String("option ") + opt
+ + QLatin1String(" needs integer argument"));
+ }
+ alreadyGiven = true;
+ return true;
+ }
+ return false;
+}
+
+bool ArgumentsCollector::checkForNoProxy(int &pos,
+ SshConnectionParameters::ProxyType &type, bool &alreadyGiven) const
+{
+ if (m_arguments.at(pos) == QLatin1String("-no-proxy")) {
+ if (alreadyGiven)
+ throw ArgumentErrorException(QLatin1String("proxy setting given twice."));
+ type = SshConnectionParameters::NoProxy;
+ alreadyGiven = true;
+ return true;
+ }
+ return false;
+}
diff --git a/tests/manual/ssh/tunnel/argumentscollector.h b/tests/manual/ssh/tunnel/argumentscollector.h
new file mode 100644
index 0000000000..d7189597f1
--- /dev/null
+++ b/tests/manual/ssh/tunnel/argumentscollector.h
@@ -0,0 +1,63 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef ARGUMENTSCOLLECTOR_H
+#define ARGUMENTSCOLLECTOR_H
+
+#include <ssh/sshconnection.h>
+
+#include <QStringList>
+
+class ArgumentsCollector
+{
+public:
+ ArgumentsCollector(const QStringList &args);
+ QSsh::SshConnectionParameters collect(bool &success) const;
+private:
+ struct ArgumentErrorException
+ {
+ ArgumentErrorException(const QString &error) : error(error) {}
+ const QString error;
+ };
+
+ void printUsage() const;
+ bool checkAndSetStringArg(int &pos, QString &arg, const char *opt) const;
+ bool checkAndSetIntArg(int &pos, int &val, bool &alreadyGiven,
+ const char *opt) const;
+ bool checkForNoProxy(int &pos,
+ QSsh::SshConnectionParameters::ProxyType &type,
+ bool &alreadyGiven) const;
+
+ const QStringList m_arguments;
+};
+
+#endif // ARGUMENTSCOLLECTOR_H
diff --git a/tests/manual/ssh/tunnel/main.cpp b/tests/manual/ssh/tunnel/main.cpp
new file mode 100644
index 0000000000..b25beb46ff
--- /dev/null
+++ b/tests/manual/ssh/tunnel/main.cpp
@@ -0,0 +1,55 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#include "../remoteprocess/argumentscollector.h"
+#include "tunnel.h"
+
+#include <ssh/sshconnection.h>
+
+#include <QCoreApplication>
+#include <QObject>
+#include <QStringList>
+
+#include <cstdlib>
+#include <iostream>
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+ bool parseSuccess;
+ const QSsh::SshConnectionParameters &parameters
+ = ArgumentsCollector(app.arguments()).collect(parseSuccess);
+ if (!parseSuccess)
+ return EXIT_FAILURE;
+ Tunnel tunnel(parameters);
+ tunnel.run();
+ return app.exec();
+}
diff --git a/tests/manual/ssh/tunnel/tunnel.cpp b/tests/manual/ssh/tunnel/tunnel.cpp
new file mode 100644
index 0000000000..342102a881
--- /dev/null
+++ b/tests/manual/ssh/tunnel/tunnel.cpp
@@ -0,0 +1,165 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#include "tunnel.h"
+
+#include <ssh/sshconnection.h>
+#include <ssh/sshdirecttcpiptunnel.h>
+
+#include <QCoreApplication>
+#include <QTcpServer>
+#include <QTcpSocket>
+#include <QTimer>
+
+#include <cstdlib>
+#include <iostream>
+
+const QByteArray ServerDataPrefix("Received the following data: ");
+const QByteArray TestData("Urgsblubb?");
+
+using namespace QSsh;
+
+Tunnel::Tunnel(const QSsh::SshConnectionParameters &parameters, QObject *parent)
+ : QObject(parent),
+ m_connection(new SshConnection(parameters, this)),
+ m_tunnelServer(new QTcpServer(this)),
+ m_expectingChannelClose(false)
+{
+ connect(m_connection, SIGNAL(connected()), SLOT(handleConnected()));
+ connect(m_connection, SIGNAL(error(QSsh::SshError)), SLOT(handleConnectionError()));
+}
+
+Tunnel::~Tunnel()
+{
+}
+
+void Tunnel::run()
+{
+ std::cout << "Connecting to SSH server..." << std::endl;
+ m_connection->connectToHost();
+}
+
+void Tunnel::handleConnectionError()
+{
+ std::cerr << "SSH connection error: " << qPrintable(m_connection->errorString()) << std::endl;
+ qApp->exit(EXIT_FAILURE);
+}
+
+void Tunnel::handleConnected()
+{
+ std::cout << "Opening server side..." << std::endl;
+ if (!m_tunnelServer->listen(QHostAddress::LocalHost)) {
+ std::cerr << "Error opening port: "
+ << m_tunnelServer->errorString().toLocal8Bit().constData() << std::endl;
+ qApp->exit(EXIT_FAILURE);
+ return;
+ }
+ m_forwardedPort = m_tunnelServer->serverPort();
+ connect(m_tunnelServer, SIGNAL(newConnection()), SLOT(handleNewConnection()));
+
+ m_tunnel = m_connection->createTunnel(m_forwardedPort);
+ connect(m_tunnel.data(), SIGNAL(initialized()), SLOT(handleInitialized()));
+ connect(m_tunnel.data(), SIGNAL(error(QString)), SLOT(handleTunnelError(QString)));
+ connect(m_tunnel.data(), SIGNAL(readyRead()), SLOT(handleServerData()));
+ connect(m_tunnel.data(), SIGNAL(tunnelClosed()), SLOT(handleTunnelClosed()));
+
+ std::cout << "Initializing tunnel..." << std::endl;
+ m_tunnel->initialize();
+}
+
+void Tunnel::handleInitialized()
+{
+ std::cout << "Writing data into the tunnel..." << std::endl;
+ m_tunnel->write(TestData);
+ QTimer * const timeoutTimer = new QTimer(this);
+ connect(timeoutTimer, SIGNAL(timeout()), SLOT(handleTimeout()));
+ timeoutTimer->start(10000);
+}
+
+void Tunnel::handleServerData()
+{
+ m_dataReceivedFromServer += m_tunnel->readAll();
+ if (m_dataReceivedFromServer == ServerDataPrefix + TestData) {
+ std::cout << "Data exchange successful. Closing server socket..." << std::endl;
+ m_expectingChannelClose = true;
+ m_tunnelSocket->close();
+ }
+}
+
+void Tunnel::handleTunnelError(const QString &reason)
+{
+ std::cerr << "Tunnel error: " << reason.toLocal8Bit().constData() << std::endl;
+ qApp->exit(EXIT_FAILURE);
+}
+
+void Tunnel::handleTunnelClosed()
+{
+ if (m_expectingChannelClose) {
+ std::cout << "Successfully detected channel close." << std::endl;
+ std::cout << "Test finished successfully." << std::endl;
+ qApp->quit();
+ } else {
+ std::cerr << "Error: Remote host closed channel." << std::endl;
+ qApp->exit(EXIT_FAILURE);
+ }
+}
+
+void Tunnel::handleNewConnection()
+{
+ m_tunnelSocket = m_tunnelServer->nextPendingConnection();
+ m_tunnelServer->close();
+ connect(m_tunnelSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(handleSocketError()));
+ connect(m_tunnelSocket, SIGNAL(readyRead()), SLOT(handleClientData()));
+ handleClientData();
+}
+
+void Tunnel::handleSocketError()
+{
+ std::cerr << "Socket error: " << m_tunnelSocket->errorString().toLocal8Bit().constData()
+ << std::endl;
+ qApp->exit(EXIT_FAILURE);
+}
+
+void Tunnel::handleClientData()
+{
+ m_dataReceivedFromClient += m_tunnelSocket->readAll();
+ if (m_dataReceivedFromClient == TestData) {
+ std::cout << "Client data successfully received by server, now sending data to client..."
+ << std::endl;
+ m_tunnelSocket->write(ServerDataPrefix + m_dataReceivedFromClient);
+ }
+}
+
+void Tunnel::handleTimeout()
+{
+ std::cerr << "Error: Timeout waiting for test completion." << std::endl;
+ qApp->exit(EXIT_FAILURE);
+}
diff --git a/tests/manual/ssh/tunnel/tunnel.h b/tests/manual/ssh/tunnel/tunnel.h
new file mode 100644
index 0000000000..fd7ca14381
--- /dev/null
+++ b/tests/manual/ssh/tunnel/tunnel.h
@@ -0,0 +1,81 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#ifndef TUNNEL_H
+#define TUNNEL_H
+
+#include <QObject>
+#include <QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+class QTcpServer;
+class QTcpSocket;
+QT_END_NAMESPACE
+
+namespace QSsh {
+class SshConnection;
+class SshConnectionParameters;
+class SshDirectTcpIpTunnel;
+}
+
+class Tunnel : public QObject
+{
+ Q_OBJECT
+public:
+ Tunnel(const QSsh::SshConnectionParameters &parameters, QObject *parent = 0);
+ ~Tunnel();
+
+ void run();
+
+private slots:
+ void handleConnected();
+ void handleConnectionError();
+ void handleServerData();
+ void handleInitialized();
+ void handleTunnelError(const QString &reason);
+ void handleTunnelClosed();
+ void handleNewConnection();
+ void handleSocketError();
+ void handleClientData();
+ void handleTimeout();
+
+private:
+ QSsh::SshConnection * const m_connection;
+ QSharedPointer<QSsh::SshDirectTcpIpTunnel> m_tunnel;
+ QTcpServer * const m_tunnelServer;
+ QTcpSocket *m_tunnelSocket;
+ quint16 m_forwardedPort;
+ QByteArray m_dataReceivedFromServer;
+ QByteArray m_dataReceivedFromClient;
+ bool m_expectingChannelClose;
+};
+
+#endif // TUNNEL_H
diff --git a/tests/manual/ssh/tunnel/tunnel.pro b/tests/manual/ssh/tunnel/tunnel.pro
new file mode 100644
index 0000000000..f2f960b98a
--- /dev/null
+++ b/tests/manual/ssh/tunnel/tunnel.pro
@@ -0,0 +1,5 @@
+include(../ssh.pri)
+
+TARGET=tunnel
+SOURCES=main.cpp tunnel.cpp argumentscollector.cpp
+HEADERS=tunnel.h argumentscollector.h