powersync-plugins/reversessh/integrationpluginreversessh...

192 lines
7.6 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#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");
qCDebug(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 << "-o ServerAliveInterval=60";
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;
}
}