Rework reverse ssh plugin
parent
88729479ee
commit
56c0c5b3c3
|
|
@ -986,11 +986,15 @@ Description: nymea.io plugin for keba
|
|||
This package will install the nymea.io plugin for keba
|
||||
|
||||
|
||||
Package: nymea-plugin-remotessh
|
||||
Package: nymea-plugin-reversessh
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
sshpass,
|
||||
nymea-plugins-translations,
|
||||
Recommends: openssh-server
|
||||
Replaces: nymea-plugin-remotessh
|
||||
Conflicts: nymea-plugin-remotessh
|
||||
Description: nymea.io plugin to configure remotessh for your nymea:core
|
||||
The nymea daemon is a plugin based IoT (Internet of Things) server. The
|
||||
server works like a translator for devices, things and services and
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginremotessh.so
|
||||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginreversessh.so
|
||||
|
|
@ -48,7 +48,7 @@ PLUGIN_DIRS = \
|
|||
shelly \
|
||||
solarlog \
|
||||
systemmonitor \
|
||||
remotessh \
|
||||
reversessh \
|
||||
senic \
|
||||
serialportcommander \
|
||||
simulation \
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
# Remote ssh
|
||||
|
||||
This plugin allowes to to establish a reverse SSH tunnel to the device where nymea is running. This requires a reverse ssh proxy running somewhere accessable from the system.
|
||||
|
||||
## Supported Things
|
||||
|
||||
* Remote SSH
|
||||
* Reverse SSH connection
|
||||
* Config target IP-address, local and remote port
|
||||
* Username and Password login
|
||||
|
||||
## Requirements
|
||||
|
||||
* The package “nymea-plugin-remotessh” must be installed
|
||||
* Reverse SSH proxy server
|
||||
|
||||
## More
|
||||
|
||||
https://www.howtogeek.com/428413/what-is-reverse-ssh-tunneling-and-how-to-use-it/
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "integrationpluginremotessh.h"
|
||||
#include "plugininfo.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
|
||||
IntegrationPluginRemoteSsh::IntegrationPluginRemoteSsh()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IntegrationPluginRemoteSsh::init()
|
||||
{
|
||||
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10);
|
||||
connect(m_pluginTimer, &PluginTimer::timeout, this, &IntegrationPluginRemoteSsh::onPluginTimeout);
|
||||
}
|
||||
|
||||
void IntegrationPluginRemoteSsh::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
||||
qCDebug(dcRemoteSsh()) << "Setup" << thing->name() << thing->params();
|
||||
|
||||
if (thing->thingClassId() == reverseSshThingClassId) {
|
||||
m_identityFilePath = QString("%1/.ssh/id_rsa_guh").arg(QDir::homePath());
|
||||
return info->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginRemoteSsh::executeAction(ThingActionInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
Action action = info->action();
|
||||
if (thing->thingClassId() == reverseSshThingClassId ) {
|
||||
|
||||
if (action.actionTypeId() == reverseSshConnectedActionTypeId) {
|
||||
|
||||
if (action.param(reverseSshConnectedActionConnectedParamTypeId).value().toBool() == true) {
|
||||
QProcess *process = startReverseSSHProcess(thing);
|
||||
m_reverseSSHProcess.insert(process, thing);
|
||||
m_startingProcess.insert(process, info);
|
||||
// in case action call is cancelled, detach result reporting
|
||||
connect(info, &ThingActionInfo::destroyed, process, [this, process]{
|
||||
m_startingProcess.remove(process);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
QProcess *process = m_reverseSSHProcess.key(thing);
|
||||
|
||||
// Check if the application is running...
|
||||
if (!process)
|
||||
return info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
if (process->state() == QProcess::NotRunning)
|
||||
return info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
process->kill();
|
||||
m_killingProcess.insert(process, info);
|
||||
// in case action call is cancelled, detach result reporting
|
||||
connect(info, &ThingActionInfo::destroyed, process, [this, process]{
|
||||
m_killingProcess.remove(process);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void IntegrationPluginRemoteSsh::thingRemoved(Thing *thing)
|
||||
{
|
||||
if (thing->thingClassId() == reverseSshThingClassId) {
|
||||
QProcess *process = m_reverseSSHProcess.key(thing);
|
||||
if (!process)
|
||||
return;
|
||||
|
||||
m_reverseSSHProcess.remove(process);
|
||||
if (process->state() != QProcess::NotRunning) {
|
||||
process->kill();
|
||||
}
|
||||
process->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginRemoteSsh::processReadyRead()
|
||||
{
|
||||
QByteArray data = static_cast<QProcess*>(sender())->readAll();
|
||||
qCWarning(dcRemoteSsh()) << "process read" << data;
|
||||
}
|
||||
|
||||
|
||||
QProcess * IntegrationPluginRemoteSsh::startReverseSSHProcess(Thing *thing)
|
||||
{
|
||||
qCDebug(dcRemoteSsh()) << "Start reverse SSH";
|
||||
QProcess *process = new QProcess(this);
|
||||
process->setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processFinished(int,QProcess::ExitStatus)));
|
||||
connect(process, &QProcess::stateChanged, this, &IntegrationPluginRemoteSsh::processStateChanged);
|
||||
connect(process, &QProcess::readyRead, this, &IntegrationPluginRemoteSsh::processReadyRead);
|
||||
|
||||
QStringList arguments;
|
||||
int localPort = thing->paramValue(reverseSshThingLocalPortParamTypeId).toInt();
|
||||
int remotePort = thing->paramValue(reverseSshThingRemotePortParamTypeId).toInt();
|
||||
QString user = thing->paramValue(reverseSshThingUserParamTypeId).toString();
|
||||
QString password = thing->paramValue(reverseSshThingPasswordParamTypeId).toString();
|
||||
QString address = thing->paramValue(reverseSshThingAddressParamTypeId).toString();
|
||||
|
||||
arguments << "-p" << password;
|
||||
arguments << "ssh" << "-o StrictHostKeyChecking=no" << "-oUserKnownHostsFile=/dev/null";
|
||||
arguments << "-TN" << "-R" << QString("%1:localhost:%2").arg(remotePort).arg(localPort) << QString("%1@%2").arg(user, address);
|
||||
process->start(QStringLiteral("sshpass"), arguments);
|
||||
qCDebug(dcRemoteSsh()) << "Command:" << process->program() << process->arguments();
|
||||
return process;
|
||||
}
|
||||
|
||||
void IntegrationPluginRemoteSsh::onPluginTimeout()
|
||||
{
|
||||
foreach(QProcess *process, m_reverseSSHProcess.keys()) {
|
||||
if (process->state() == QProcess::NotRunning)
|
||||
qCDebug(dcRemoteSsh()) << "SSH Process not running";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void IntegrationPluginRemoteSsh::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||
{
|
||||
Q_UNUSED(exitCode);
|
||||
QProcess *process = static_cast<QProcess*>(sender());
|
||||
|
||||
if(exitStatus != QProcess::NormalExit || exitCode != 0) {
|
||||
qCWarning(dcRemoteSsh()) << "Error:" << process->readAllStandardError();
|
||||
}
|
||||
|
||||
if (m_reverseSSHProcess.contains(process)) {
|
||||
qCDebug(dcRemoteSsh()) << "SSH process finished";
|
||||
Thing *thing = m_reverseSSHProcess.value(process);
|
||||
thing->setStateValue(reverseSshConnectedStateTypeId, false);
|
||||
m_reverseSSHProcess.remove(process);
|
||||
|
||||
} else if (m_sshKeyGenProcess.contains(process)) {
|
||||
qCDebug(dcRemoteSsh()) << "SSH Key generation process finished" << process->readAll();
|
||||
Thing *thing = m_sshKeyGenProcess.value(process);
|
||||
QFile file(QString(m_identityFilePath + ".pub"));
|
||||
if(!file.open(QIODevice::ReadOnly))
|
||||
qCWarning(dcRemoteSsh()) << "error" << file.errorString();
|
||||
|
||||
QTextStream in(&file);
|
||||
QString sshKey = in.readLine();
|
||||
thing->setStateValue(reverseSshSshKeyStateTypeId, sshKey);
|
||||
process->kill();
|
||||
m_sshKeyGenProcess.remove(process);
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void IntegrationPluginRemoteSsh::processStateChanged(QProcess::ProcessState state)
|
||||
{
|
||||
QProcess *process = static_cast<QProcess*>(sender());
|
||||
Thing *thing = m_reverseSSHProcess.value(process);
|
||||
|
||||
switch (state) {
|
||||
case QProcess::Running:
|
||||
thing->setStateValue(reverseSshConnectedStateTypeId, true);
|
||||
if (m_startingProcess.contains(process)) {
|
||||
m_startingProcess.take(process)->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
break;
|
||||
|
||||
case QProcess::NotRunning:
|
||||
if (thing)
|
||||
thing->setStateValue(reverseSshConnectedStateTypeId, false);
|
||||
|
||||
if (m_startingProcess.contains(process)) {
|
||||
m_startingProcess.take(process)->finish(Thing::ThingErrorInvalidParameter);
|
||||
}
|
||||
|
||||
if (m_killingProcess.contains(process)) {
|
||||
m_killingProcess.take(process)->finish(Thing::ThingErrorNoError);
|
||||
m_reverseSSHProcess.remove(process);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
include(../plugins.pri)
|
||||
|
||||
TARGET = $$qtLibraryTarget(nymea_integrationpluginremotessh)
|
||||
|
||||
SOURCES += \
|
||||
integrationpluginremotessh.cpp \
|
||||
|
||||
HEADERS += \
|
||||
integrationpluginremotessh.h \
|
||||
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
# Reverse ssh
|
||||
|
||||
This plugin allows to establish a reverse SSH tunnel to the device where nymea is running.
|
||||
|
||||
This is useful when maintaining remote nymea setups which may be hidden behind a firewall
|
||||
and cannot be accessed from the public internet. A user can easily enable SSH access for
|
||||
a nymea setup by adding a Thing using the app without having to deal with DNS and NAT.
|
||||
|
||||
## Requirements
|
||||
|
||||
In order to establish a reverse SSH tunnel, a SSH server is required to be accessible from
|
||||
both, the nymea instance and the client log in from. Also a SSH server is required
|
||||
to run on the system where nymea is running (If using the nymea images, this is already the case).
|
||||
|
||||
## Setup
|
||||
|
||||
### SSH Server setup
|
||||
|
||||
The SSH server can be hosted anywhere, for instance on a vserver somewhere in the internet.
|
||||
The following settings must be enabled on the SSH server for it to work (assuming openssh):
|
||||
|
||||
* AllowTcpForwarding yes (To allow this sort of forwarding generally)
|
||||
* GatewayPorts yes (To allow reverse ssh from other hosts than the reverse proxy itself - it would listen to localhost only otherwise)
|
||||
|
||||
Create a user on the SSH server. Note that if sharing the credentials with someone else it
|
||||
might be advisable to confine the SSH server in a container, however, such a setup is beyond
|
||||
the scope of this manual. This will assume that both ends are trusted and SSH credentials
|
||||
for the server can be shared.
|
||||
|
||||
### Nymea setup
|
||||
|
||||
During the thing setup, enter the server connection information for SSH server:
|
||||
|
||||
* SSH server address: The hostname or IP of the SSH server
|
||||
* SSH server port: The SSH port of the SSH server (22 by default)
|
||||
* Local SSH server port: The SSH port of the local SSH server running on the nymea system (22 by default)
|
||||
* Remote port to be opened: This is the port on which the nymea system will be reachable. This can be any port number which isn't in use already, and, unless you intend to log in as root (not advisable) this must be a port higher than 1024
|
||||
|
||||
In the next step, provide the SSH credentials for the user on the SSH server which has been created before. Once the login succeeds, the thing should become connected.
|
||||
|
||||
### Connecting with an SSH client
|
||||
|
||||
Once the above setup succeeded, the nymea system can be reached via SSH using:
|
||||
|
||||
ssh <user>@<server> -p <remote open port>
|
||||
|
||||
where `user` is a user on the nymea system, server is the IP or hostname of the SSH server and `remote open port` is the port that has been picked during the thing setup.
|
||||
|
||||
## More
|
||||
|
||||
https://www.howtogeek.com/428413/what-is-reverse-ssh-tunneling-and-how-to-use-it/
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "integrationpluginreversessh.h"
|
||||
#include "plugininfo.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
|
||||
IntegrationPluginReverseSsh::IntegrationPluginReverseSsh()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
IntegrationPluginReverseSsh::~IntegrationPluginReverseSsh()
|
||||
{
|
||||
foreach (QProcess *process, m_processes) {
|
||||
process->terminate();
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginReverseSsh::startPairing(ThingPairingInfo *info)
|
||||
{
|
||||
info->finish(Thing::ThingErrorNoError, QString(QT_TR_NOOP("Please enter your login credentials for %1.")).arg(info->params().paramValue(reverseSshThingAddressParamTypeId).toString()));
|
||||
}
|
||||
|
||||
void IntegrationPluginReverseSsh::confirmPairing(ThingPairingInfo *info, const QString &user, const QString &secret)
|
||||
{
|
||||
// Perform a test login on the remote server
|
||||
QString address = info->params().paramValue(reverseSshThingAddressParamTypeId).toString();
|
||||
int remotePort = info->params().paramValue(reverseSshThingRemotePortParamTypeId).toInt();
|
||||
|
||||
QStringList arguments;
|
||||
arguments << "-p" << secret << "ssh" << "-o StrictHostKeyChecking=no" << "-oUserKnownHostsFile=/dev/null";
|
||||
arguments << QString("%1@%2").arg(user, address) << "-p" << QString::number(remotePort) << "whoami";
|
||||
|
||||
QProcess *process = new QProcess(this);
|
||||
process->setProgram("sshpass");
|
||||
process->setArguments(arguments);
|
||||
|
||||
arguments.replace(1, "xxxxxx");
|
||||
qCInfo(dcReverseSsh()) << "Testing SSH connection:" << process->program() << arguments.join(" ");
|
||||
|
||||
typedef void (QProcess:: *finishedSignal)(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
connect(process, static_cast<finishedSignal>(&QProcess::finished), this, [=](int exitCode, QProcess::ExitStatus exitStatus){
|
||||
process->deleteLater();
|
||||
qCDebug(dcReverseSsh()) << "Testing process finished. Exit code:" << exitCode << "Exit status:" << exitStatus;
|
||||
|
||||
switch (exitCode) {
|
||||
case 0:
|
||||
pluginStorage()->beginGroup(info->thingId().toString());
|
||||
pluginStorage()->setValue("username", user);
|
||||
pluginStorage()->setValue("password", secret);
|
||||
pluginStorage()->endGroup();
|
||||
qCInfo(dcReverseSsh()) << "Reverse SSH test login successful.";
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
break;
|
||||
case 5:
|
||||
qCWarning(dcReverseSsh()) << "Reverse SSH test login failed.";
|
||||
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Login error on remote SSH server."));
|
||||
break;
|
||||
default:
|
||||
qCWarning(dcReverseSsh()) << "Reverse SSH test login unable to connect to SSH server.";
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Cannot connect to remote SSH server."));
|
||||
}
|
||||
|
||||
});
|
||||
process->start();
|
||||
}
|
||||
|
||||
void IntegrationPluginReverseSsh::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
||||
|
||||
QStringList arguments;
|
||||
int localPort = thing->paramValue(reverseSshThingLocalPortParamTypeId).toInt();
|
||||
int remoteOpenPort = thing->paramValue(reverseSshThingRemoteOpenPortParamTypeId).toInt();
|
||||
int remotePort = thing->paramValue(reverseSshThingRemotePortParamTypeId).toInt();
|
||||
QString address = thing->paramValue(reverseSshThingAddressParamTypeId).toString();
|
||||
|
||||
pluginStorage()->beginGroup(thing->id().toString());
|
||||
QString user = pluginStorage()->value("username").toString();
|
||||
QString password = pluginStorage()->value("password").toString();
|
||||
pluginStorage()->endGroup();
|
||||
|
||||
arguments << "-p" << password << "ssh" << "-o StrictHostKeyChecking=no" << "-oUserKnownHostsFile=/dev/null";
|
||||
arguments << "-TN" << "-R" << QString("%1:localhost:%2").arg(remoteOpenPort).arg(localPort) << QString("%1@%2").arg(user, address) << "-p" << QString::number(remotePort);
|
||||
QProcess *process = new QProcess(thing);
|
||||
process->setProgram("sshpass");
|
||||
process->setArguments(arguments);
|
||||
process->setProcessChannelMode(QProcess::MergedChannels);
|
||||
arguments.replace(1, "xxxxxx");
|
||||
qCDebug(dcReverseSsh()) << "Reverse SSH command:" << process->program() << arguments;
|
||||
|
||||
m_processes.insert(info->thing(), process);
|
||||
|
||||
connect(process, &QProcess::stateChanged, thing, [=](QProcess::ProcessState newState){
|
||||
switch (newState) {
|
||||
case QProcess::Starting:
|
||||
qCDebug(dcReverseSsh()) << "Connection starting for" << thing->name();
|
||||
return ;
|
||||
case QProcess::Running:
|
||||
qCInfo(dcReverseSsh()) << "Reverse SSH connected for" << thing->name();
|
||||
thing->setStateValue(reverseSshConnectedStateTypeId, true);
|
||||
return;
|
||||
case QProcess::NotRunning:
|
||||
qCInfo(dcReverseSsh()) << "Reverse SSH disconnected for" << thing->name();
|
||||
thing->setStateValue(reverseSshConnectedStateTypeId, false);
|
||||
return;
|
||||
}
|
||||
});
|
||||
connect(process, &QProcess::readyRead, thing, [=](){
|
||||
QByteArray data = process->readAll();
|
||||
qCWarning(dcReverseSsh()) << "Reverse SSH connection data for" << thing->name() << data;
|
||||
});
|
||||
|
||||
|
||||
// Start up now if enabled
|
||||
bool enabled = thing->setting(reverseSshSettingsActiveParamTypeId).toBool();
|
||||
if (enabled) {
|
||||
process->start();
|
||||
}
|
||||
|
||||
// And connect to the enabled setting
|
||||
connect(thing, &Thing::settingChanged, this, [=](const ParamTypeId &settingId, const QVariant &value){
|
||||
if (settingId == reverseSshSettingsActiveParamTypeId) {
|
||||
if (value.toBool()) {
|
||||
process->start();
|
||||
} else {
|
||||
process->terminate();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
|
||||
// Create a watchdog to reconnect if a connection drops...
|
||||
if (!m_watchdog) {
|
||||
m_watchdog = hardwareManager()->pluginTimerManager()->registerTimer(10);
|
||||
connect(m_watchdog, &PluginTimer::timeout, this, [this](){
|
||||
foreach (Thing *thing, m_processes.keys()) {
|
||||
QProcess *process = m_processes.value(thing);
|
||||
if (thing->setting(reverseSshSettingsActiveParamTypeId).toBool() && process->state() == QProcess::NotRunning) {
|
||||
qCInfo(dcReverseSsh()) << "Reconnecting reverse SSH for" << thing->name();
|
||||
process->start();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void IntegrationPluginReverseSsh::thingRemoved(Thing *thing)
|
||||
{
|
||||
if (thing->thingClassId() == reverseSshThingClassId) {
|
||||
QProcess *process = m_processes.take(thing);
|
||||
if (process->state() != QProcess::NotRunning) {
|
||||
process->terminate();
|
||||
process->waitForFinished();
|
||||
}
|
||||
}
|
||||
|
||||
if (myThings().isEmpty()) {
|
||||
hardwareManager()->pluginTimerManager()->unregisterTimer(m_watchdog);
|
||||
m_watchdog = nullptr;
|
||||
}
|
||||
}
|
||||
|
|
@ -33,43 +33,29 @@
|
|||
|
||||
#include "plugintimer.h"
|
||||
#include "integrations/integrationplugin.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
class IntegrationPluginRemoteSsh : public IntegrationPlugin
|
||||
class IntegrationPluginReverseSsh : public IntegrationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginremotessh.json")
|
||||
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginreversessh.json")
|
||||
Q_INTERFACES(IntegrationPlugin)
|
||||
|
||||
public:
|
||||
explicit IntegrationPluginRemoteSsh();
|
||||
explicit IntegrationPluginReverseSsh();
|
||||
~IntegrationPluginReverseSsh();
|
||||
|
||||
void init() override;
|
||||
void startPairing(ThingPairingInfo *info) override;
|
||||
void confirmPairing(ThingPairingInfo *info, const QString &user, const QString &secret) override;
|
||||
void setupThing(ThingSetupInfo *info) override;
|
||||
void thingRemoved(Thing *thing) override;
|
||||
void executeAction(ThingActionInfo *info) override;
|
||||
|
||||
private:
|
||||
QHash<QProcess *, Thing *> m_reverseSSHProcess;
|
||||
QHash<QProcess *, Thing *> m_sshKeyGenProcess;
|
||||
|
||||
QHash<QProcess *, ThingActionInfo*> m_startingProcess;
|
||||
QHash<QProcess *, ThingActionInfo*> m_killingProcess;
|
||||
|
||||
PluginTimer *m_pluginTimer = nullptr;
|
||||
|
||||
bool m_aboutToQuit = false;
|
||||
QString m_identityFilePath;
|
||||
|
||||
QProcess *startReverseSSHProcess(Thing *thing);
|
||||
|
||||
private slots:
|
||||
void onPluginTimeout();
|
||||
void processReadyRead();
|
||||
void processStateChanged(QProcess::ProcessState state);
|
||||
void processFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
QHash<Thing*, QProcess*> m_processes;
|
||||
PluginTimer *m_watchdog = nullptr;
|
||||
};
|
||||
|
||||
#endif // INTEGRATIONPLUGINREMOTESSH_H
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "cd75d899-3f53-43fa-9ee8-f6b36646a27d",
|
||||
"name": "RemoteSsh",
|
||||
"displayName": "Remote SSH",
|
||||
"name": "ReverseSsh",
|
||||
"displayName": "Reverse SSH",
|
||||
"vendors": [
|
||||
{
|
||||
"id": "e87ad7b1-1705-46b1-a962-282126646b4d",
|
||||
|
|
@ -13,73 +13,57 @@
|
|||
"name": "reverseSsh",
|
||||
"displayName": "Reverse SSH",
|
||||
"createMethods": ["user"],
|
||||
"setupMethod": "userandpassword",
|
||||
"interfaces": ["connectable"],
|
||||
"settingsTypes": [
|
||||
{
|
||||
"id": "478aea93-68c4-4527-ac91-15b8da77f1e5",
|
||||
"name": "active",
|
||||
"displayName": "Active",
|
||||
"type": "bool",
|
||||
"defaultValue": true
|
||||
}
|
||||
],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "92747d75-d18a-4915-bd48-0edd5cc5f19a",
|
||||
"name": "address",
|
||||
"displayName": "Address",
|
||||
"displayName": "SSH server address",
|
||||
"type": "QString",
|
||||
"inputType": "IPv4Address",
|
||||
"defaultValue": "127.0.0.1"
|
||||
},
|
||||
{
|
||||
"id": "7f7aa198-c719-415e-b31c-7a676b9d8e01",
|
||||
"name": "localPort",
|
||||
"displayName": "Local Port",
|
||||
"type": "int",
|
||||
"defaultValue": 22
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "988aec42-1026-4aef-85d1-329ee1a34208",
|
||||
"name": "remotePort",
|
||||
"displayName": "Remote Port",
|
||||
"displayName": "SSH server port",
|
||||
"type": "int",
|
||||
"defaultValue": 2022
|
||||
"defaultValue": 22
|
||||
},
|
||||
{
|
||||
"id": "c675f7ea-f94a-46e9-bf0f-92682182d6dd",
|
||||
"name": "user",
|
||||
"displayName": "User Name",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"defaultValue": "Enter your user"
|
||||
"id": "7f7aa198-c719-415e-b31c-7a676b9d8e01",
|
||||
"name": "localPort",
|
||||
"displayName": "Local SSH server port",
|
||||
"type": "int",
|
||||
"defaultValue": 22
|
||||
},
|
||||
{
|
||||
"id": "d8cc7177-bf35-4394-ab7b-881184bd8c8b",
|
||||
"name": "password",
|
||||
"displayName": "Password",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"defaultValue": "Enter your password"
|
||||
"id": "e999ce4b-bc8c-4664-8c1c-d502a1589e78",
|
||||
"name": "remoteOpenPort",
|
||||
"displayName": "Remote port to be opened",
|
||||
"type": "int",
|
||||
"defaultValue": 2201,
|
||||
"minValue": 0,
|
||||
"maxValue": 65535
|
||||
}
|
||||
],
|
||||
"stateTypes":[
|
||||
{
|
||||
"id": "19f079f0-1654-44c3-ab10-e7d7f9742e09",
|
||||
"name": "reachable",
|
||||
"displayName": "Server Reachable",
|
||||
"displayNameEvent": "reachable status changed",
|
||||
"type": "bool",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "1ae425b2-d642-42ca-be41-4d06dff5c5cd",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"displayNameEvent": "Connected changed",
|
||||
"displayNameAction": "Connect to Server",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "d8bb619e-6602-4c89-8654-85e111520561",
|
||||
"name": "sshKey",
|
||||
"displayName": "SSH public key",
|
||||
"displayNameEvent": "SSH key changed",
|
||||
"type": "QString",
|
||||
"defaultValue": "-"
|
||||
"displayNameEvent": "Connected changed"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
include(../plugins.pri)
|
||||
|
||||
TARGET = $$qtLibraryTarget(nymea_integrationpluginreversessh)
|
||||
|
||||
SOURCES += \
|
||||
integrationpluginreversessh.cpp \
|
||||
|
||||
HEADERS += \
|
||||
integrationpluginreversessh.h \
|
||||
|
||||
|
Before Width: | Height: | Size: 769 B After Width: | Height: | Size: 769 B |
|
|
@ -2,45 +2,32 @@
|
|||
<!DOCTYPE TS>
|
||||
<TS version="2.1">
|
||||
<context>
|
||||
<name>RemoteSsh</name>
|
||||
<name>IntegrationPluginReverseSsh</name>
|
||||
<message>
|
||||
<source>Remote Access</source>
|
||||
<extracomment>The name of the vendor ({e87ad7b1-1705-46b1-a962-282126646b4d})</extracomment>
|
||||
<source>Please enter your login credentials for %1.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reverse SSH</source>
|
||||
<extracomment>The name of the ThingClass ({a4f12741-4f30-40ca-a319-7f15e9c0c43a})</extracomment>
|
||||
<source>Login error on remote SSH server.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Address</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, Type: thing, ID: {92747d75-d18a-4915-bd48-0edd5cc5f19a})</extracomment>
|
||||
<source>Cannot connect to remote SSH server.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ReverseSsh</name>
|
||||
<message>
|
||||
<source>Active</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, Type: settings, ID: {478aea93-68c4-4527-ac91-15b8da77f1e5})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Local Port</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, Type: thing, ID: {7f7aa198-c719-415e-b31c-7a676b9d8e01})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remote Port</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, Type: thing, ID: {988aec42-1026-4aef-85d1-329ee1a34208})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>User Name</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, Type: thing, ID: {c675f7ea-f94a-46e9-bf0f-92682182d6dd})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, Type: thing, ID: {d8cc7177-bf35-4394-ab7b-881184bd8c8b})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>reachable status changed</source>
|
||||
<extracomment>The name of the EventType ({19f079f0-1654-44c3-ab10-e7d7f9742e09}) of ThingClass reverseSsh</extracomment>
|
||||
<source>Connected</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, EventType: connected, ID: {1ae425b2-d642-42ca-be41-4d06dff5c5cd})
|
||||
----------
|
||||
The name of the StateType ({1ae425b2-d642-42ca-be41-4d06dff5c5cd}) of ThingClass reverseSsh</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
|
|
@ -49,41 +36,35 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connect to Server</source>
|
||||
<extracomment>The name of the ActionType ({1ae425b2-d642-42ca-be41-4d06dff5c5cd}) of ThingClass reverseSsh</extracomment>
|
||||
<source>Local SSH server port</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, Type: thing, ID: {7f7aa198-c719-415e-b31c-7a676b9d8e01})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SSH key changed</source>
|
||||
<extracomment>The name of the EventType ({d8bb619e-6602-4c89-8654-85e111520561}) of ThingClass reverseSsh</extracomment>
|
||||
<source>Remote Access</source>
|
||||
<extracomment>The name of the vendor ({e87ad7b1-1705-46b1-a962-282126646b4d})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remote SSH</source>
|
||||
<extracomment>The name of the plugin RemoteSsh ({cd75d899-3f53-43fa-9ee8-f6b36646a27d})</extracomment>
|
||||
<source>Remote port to be opened</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, Type: thing, ID: {e999ce4b-bc8c-4664-8c1c-d502a1589e78})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Server Reachable</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, EventType: reachable, ID: {19f079f0-1654-44c3-ab10-e7d7f9742e09})
|
||||
<source>Reverse SSH</source>
|
||||
<extracomment>The name of the ThingClass ({a4f12741-4f30-40ca-a319-7f15e9c0c43a})
|
||||
----------
|
||||
The name of the StateType ({19f079f0-1654-44c3-ab10-e7d7f9742e09}) of ThingClass reverseSsh</extracomment>
|
||||
The name of the plugin ReverseSsh ({cd75d899-3f53-43fa-9ee8-f6b36646a27d})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connected</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, ActionType: connected, ID: {1ae425b2-d642-42ca-be41-4d06dff5c5cd})
|
||||
----------
|
||||
The name of the ParamType (ThingClass: reverseSsh, EventType: connected, ID: {1ae425b2-d642-42ca-be41-4d06dff5c5cd})
|
||||
----------
|
||||
The name of the StateType ({1ae425b2-d642-42ca-be41-4d06dff5c5cd}) of ThingClass reverseSsh</extracomment>
|
||||
<source>SSH server address</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, Type: thing, ID: {92747d75-d18a-4915-bd48-0edd5cc5f19a})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SSH public key</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, EventType: sshKey, ID: {d8bb619e-6602-4c89-8654-85e111520561})
|
||||
----------
|
||||
The name of the StateType ({d8bb619e-6602-4c89-8654-85e111520561}) of ThingClass reverseSsh</extracomment>
|
||||
<source>SSH server port</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: reverseSsh, Type: thing, ID: {988aec42-1026-4aef-85d1-329ee1a34208})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
Loading…
Reference in New Issue