powersync-plugins/reversessh/integrationpluginreversessh...

198 lines
8.0 KiB
C++

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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");
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;
}
}