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>
This commit is contained in:
Christian Kandeler
2012-06-19 13:03:48 +02:00
parent a57b4cf93e
commit edcf76613b
27 changed files with 1022 additions and 43 deletions

View File

@@ -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();

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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

View File

@@ -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();

View File

@@ -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; }

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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)

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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,

View File

@@ -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;

View File

@@ -5,4 +5,4 @@
#-------------------------------------------------
TEMPLATE = subdirs
SUBDIRS = errorhandling sftp remoteprocess shell sftpfsmodel
SUBDIRS = errorhandling sftp remoteprocess shell sftpfsmodel tunnel

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -0,0 +1,5 @@
include(../ssh.pri)
TARGET=tunnel
SOURCES=main.cpp tunnel.cpp argumentscollector.cpp
HEADERS=tunnel.h argumentscollector.h