// 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 . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "integrationpluginreversessh.h" #include "plugininfo.h" #include #include 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(&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; } }