Add remote ssh plugin

master
Simon Stürz 2018-03-14 09:22:09 +01:00 committed by Michael Zanetti
parent 6218d39a73
commit f58b0c9573
7 changed files with 396 additions and 1 deletions

16
debian/control vendored
View File

@ -557,6 +557,21 @@ Description: nymea.io plugin for keba
This package will install the nymea.io plugin for keba
Package: nymea-plugin-remotessh
Architecture: any
Depends: ${shlibs:Depends},
${misc:Depends},
nymea-plugins-translations,
Description: nymea.io plugin for remote ssh connection
The nymea daemon is a plugin based IoT (Internet of Things) server. The
server works like a translator for devices, things and services and
allows them to interact.
With the powerful rule engine you are able to connect any device available
in the system and create individual scenes and behaviors for your environment.
.
This package will install the nymea.io plugin for remote ssh connection
Package: nymea-plugins-translations
Section: misc
Architecture: all
@ -611,6 +626,7 @@ Depends: nymea-plugin-commandlauncher,
nymea-plugin-genericelements,
nymea-plugin-avahimonitor,
nymea-plugin-gpio,
nymea-plugin-remotessh,
Replaces: guh-plugins-maker
Description: Plugins for nymea IoT server - Meta package for makers, tinkers and hackers
The nymea daemon is a plugin based IoT (Internet of Things) server. The

View File

@ -0,0 +1 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginremotessh.so

View File

@ -35,6 +35,7 @@ PLUGIN_DIRS = \
snapd \
simulation \
keba \
remotessh \
CONFIG+=all

View File

@ -0,0 +1,205 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2017 Bernhard Trinnes <bernhard.trinnes@guh.io> *
* Copyright (C) 2018 Simon Stürz <simon.stuerz@guh.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library 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 library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "devicepluginremotessh.h"
#include "plugininfo.h"
#include <QFile>
#include <QDir>
DevicePluginRemoteSsh::DevicePluginRemoteSsh()
{
}
void DevicePluginRemoteSsh::init()
{
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10);
connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginRemoteSsh::onPluginTimeout);
}
DeviceManager::DeviceSetupStatus DevicePluginRemoteSsh::setupDevice(Device *device)
{
qCDebug(dcRemoteSsh()) << "Setup" << device->name() << device->params();
if (device->deviceClassId() == reverseSshDeviceClassId) {
m_identityFilePath = QString("%1/.ssh/id_rsa_guh").arg(QDir::homePath());
return DeviceManager::DeviceSetupStatusSuccess;
}
return DeviceManager::DeviceSetupStatusFailure;
}
DeviceManager::DeviceError DevicePluginRemoteSsh::executeAction(Device *device, const Action &action)
{
if (device->deviceClassId() == reverseSshDeviceClassId ) {
if (action.actionTypeId() == reverseSshConnectedActionTypeId) {
if (action.param(reverseSshConnectedStateParamTypeId).value().toBool() == true) {
QProcess *process = startReverseSSHProcess(device);
m_reverseSSHProcess.insert(process, device);
m_startingProcess.insert(process, action.id());
return DeviceManager::DeviceErrorAsync;
} else {
QProcess *process = m_reverseSSHProcess.key(device);
// Check if the application is running...
if (!process)
return DeviceManager::DeviceErrorNoError;
if (process->state() == QProcess::NotRunning)
return DeviceManager::DeviceErrorNoError;
process->kill();
m_killingProcess.insert(process, action.id());
return DeviceManager::DeviceErrorAsync;
}
return DeviceManager::DeviceErrorNoError;
}
return DeviceManager::DeviceErrorActionTypeNotFound;
}
return DeviceManager::DeviceErrorDeviceClassNotFound;
}
void DevicePluginRemoteSsh::deviceRemoved(Device *device)
{
if (device->deviceClassId() == reverseSshDeviceClassId) {
QProcess *process = m_reverseSSHProcess.key(device);
if (!process)
return;
m_reverseSSHProcess.remove(process);
if (process->state() != QProcess::NotRunning) {
process->kill();
}
process->deleteLater();
}
}
void DevicePluginRemoteSsh::processReadyRead()
{
QByteArray data = static_cast<QProcess*>(sender())->readAll();
qCWarning(dcRemoteSsh()) << "process read" << data;
}
QProcess * DevicePluginRemoteSsh::startReverseSSHProcess(Device *device)
{
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, &DevicePluginRemoteSsh::processStateChanged);
connect(process, &QProcess::readyRead, this, &DevicePluginRemoteSsh::processReadyRead);
QStringList arguments;
int localPort = device->paramValue(reverseSshLocalPortParamTypeId).toInt();
int remotePort = device->paramValue(reverseSshRemotePortParamTypeId).toInt();
QString user = device->paramValue(reverseSshUserParamTypeId).toString();
QString password = device->paramValue(reverseSshPasswordParamTypeId).toString();
QString address = device->paramValue(reverseSshAddressParamTypeId).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 DevicePluginRemoteSsh::onPluginTimeout()
{
foreach(QProcess *process, m_reverseSSHProcess.keys()) {
if (process->state() == QProcess::NotRunning)
qCDebug(dcRemoteSsh()) << "SSH Process not running";
}
}
void DevicePluginRemoteSsh::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";
Device *device = m_reverseSSHProcess.value(process);
device->setStateValue(reverseSshConnectedStateTypeId, false);
m_reverseSSHProcess.remove(process);
} else if (m_sshKeyGenProcess.contains(process)) {
qCDebug(dcRemoteSsh()) << "SSH Key generation process finished" << process->readAll();
Device *device = 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();
device->setStateValue(reverseSshSshKeyStateTypeId, sshKey);
process->kill();
m_sshKeyGenProcess.remove(process);
file.close();
}
}
void DevicePluginRemoteSsh::processStateChanged(QProcess::ProcessState state)
{
QProcess *process = static_cast<QProcess*>(sender());
Device *device = m_reverseSSHProcess.value(process);
switch (state) {
case QProcess::Running:
device->setStateValue(reverseSshConnectedStateTypeId, true);
if (m_startingProcess.contains(process)) {
emit actionExecutionFinished(m_startingProcess.value(process), DeviceManager::DeviceErrorNoError);
m_startingProcess.remove(process);
}
break;
case QProcess::NotRunning:
if (device)
device->setStateValue(reverseSshConnectedStateTypeId, false);
if (m_startingProcess.contains(process)) {
emit actionExecutionFinished(m_startingProcess.value(process), DeviceManager::DeviceErrorInvalidParameter);
m_startingProcess.remove(process);
}
if (m_killingProcess.contains(process)) {
emit actionExecutionFinished(m_killingProcess.value(process), DeviceManager::DeviceErrorNoError);
m_reverseSSHProcess.remove(process);
m_killingProcess.remove(process);
}
break;
default:
break;
}
}

View File

@ -0,0 +1,68 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2017 Bernhard Trinnes <bernhard.trinnes@guh.io> *
* Copyright (C) 2018 Simon Stürz <simon.stuerz@guh.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library 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 library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DEVICEPLUGINREMOTESSH_H
#define DEVICEPLUGINREMOTESSH_H
#include "plugintimer.h"
#include "plugin/deviceplugin.h"
#include <QProcess>
class DevicePluginRemoteSsh : public DevicePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginremotessh.json")
Q_INTERFACES(DevicePlugin)
public:
explicit DevicePluginRemoteSsh();
void init() override;
DeviceManager::DeviceSetupStatus setupDevice(Device *device) override;
void deviceRemoved(Device *device) override;
DeviceManager::DeviceError executeAction(Device *device, const Action &action) override;
private:
QHash<QProcess *, Device *> m_reverseSSHProcess;
QHash<QProcess *, Device *> m_sshKeyGenProcess;
QHash<QProcess *, ActionId> m_startingProcess;
QHash<QProcess *, ActionId> m_killingProcess;
PluginTimer *m_pluginTimer = nullptr;
bool m_aboutToQuit = false;
QString m_identityFilePath;
QProcess *startReverseSSHProcess(Device *device);
private slots:
void onPluginTimeout();
void processReadyRead();
void processStateChanged(QProcess::ProcessState state);
void processFinished(int exitCode, QProcess::ExitStatus exitStatus);
};
#endif // DEVICEPLUGINREMOTESSH_H

View File

@ -0,0 +1,94 @@
{
"id": "cd75d899-3f53-43fa-9ee8-f6b36646a27d",
"name": "RemoteSsh",
"displayName": "Remote SSH",
"vendors": [
{
"id": "e87ad7b1-1705-46b1-a962-282126646b4d",
"name": "remoteAccess",
"displayName": "Remote Access",
"deviceClasses": [
{
"id": "a4f12741-4f30-40ca-a319-7f15e9c0c43a",
"name": "reverseSsh",
"displayName": "Reverse SSH",
"deviceIcon": "Network",
"createMethods": ["user"],
"basicTags": ["Service"],
"interfaces": ["connectable"],
"paramTypes": [
{
"id": "92747d75-d18a-4915-bd48-0edd5cc5f19a",
"name": "address",
"displayName": "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
},
{
"id": "988aec42-1026-4aef-85d1-329ee1a34208",
"name": "remotePort",
"displayName": "Remote Port",
"type": "int",
"defaultValue": 2022
},
{
"id": "c675f7ea-f94a-46e9-bf0f-92682182d6dd",
"name": "user",
"displayName": "User Name",
"type": "QString",
"inputType": "TextLine",
"defaultValue": "Enter your user"
},
{
"id": "d8cc7177-bf35-4394-ab7b-881184bd8c8b",
"name": "password",
"displayName": "Password",
"type": "QString",
"inputType": "TextLine",
"defaultValue": "Enter your password"
}
],
"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": "-"
}
]
}
]
}
]
}

10
remotessh/remotessh.pro Normal file
View File

@ -0,0 +1,10 @@
include(../plugins.pri)
TARGET = $$qtLibraryTarget(nymea_devicepluginremotessh)
SOURCES += \
devicepluginremotessh.cpp \
HEADERS += \
devicepluginremotessh.h \