/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2017 Bernhard Trinnes * * Copyright (C) 2018 Simon Stürz * * * * 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 * * . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "devicepluginremotessh.h" #include "plugininfo.h" #include #include DevicePluginRemoteSsh::DevicePluginRemoteSsh() { } void DevicePluginRemoteSsh::init() { m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginRemoteSsh::onPluginTimeout); } void DevicePluginRemoteSsh::setupDevice(DeviceSetupInfo *info) { Device *device = info->device(); qCDebug(dcRemoteSsh()) << "Setup" << device->name() << device->params(); if (device->deviceClassId() == reverseSshDeviceClassId) { m_identityFilePath = QString("%1/.ssh/id_rsa_guh").arg(QDir::homePath()); return info->finish(Device::DeviceErrorNoError); } } void DevicePluginRemoteSsh::executeAction(DeviceActionInfo *info) { Device *device = info->device(); Action action = info->action(); if (device->deviceClassId() == reverseSshDeviceClassId ) { if (action.actionTypeId() == reverseSshConnectedActionTypeId) { if (action.param(reverseSshConnectedActionConnectedParamTypeId).value().toBool() == true) { QProcess *process = startReverseSSHProcess(device); m_reverseSSHProcess.insert(process, device); m_startingProcess.insert(process, info); // in case action call is cancelled, detach result reporting connect(info, &DeviceActionInfo::destroyed, process, [this, process]{ m_startingProcess.remove(process); }); return; } else { QProcess *process = m_reverseSSHProcess.key(device); // Check if the application is running... if (!process) return info->finish(Device::DeviceErrorNoError); if (process->state() == QProcess::NotRunning) return info->finish(Device::DeviceErrorNoError); process->kill(); m_killingProcess.insert(process, info); // in case action call is cancelled, detach result reporting connect(info, &DeviceActionInfo::destroyed, process, [this, process]{ m_killingProcess.remove(process); }); return; } } } } 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(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(reverseSshDeviceLocalPortParamTypeId).toInt(); int remotePort = device->paramValue(reverseSshDeviceRemotePortParamTypeId).toInt(); QString user = device->paramValue(reverseSshDeviceUserParamTypeId).toString(); QString password = device->paramValue(reverseSshDevicePasswordParamTypeId).toString(); QString address = device->paramValue(reverseSshDeviceAddressParamTypeId).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(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(sender()); Device *device = m_reverseSSHProcess.value(process); switch (state) { case QProcess::Running: device->setStateValue(reverseSshConnectedStateTypeId, true); if (m_startingProcess.contains(process)) { m_startingProcess.take(process)->finish(Device::DeviceErrorNoError); } break; case QProcess::NotRunning: if (device) device->setStateValue(reverseSshConnectedStateTypeId, false); if (m_startingProcess.contains(process)) { m_startingProcess.take(process)->finish(Device::DeviceErrorInvalidParameter); } if (m_killingProcess.contains(process)) { m_killingProcess.take(process)->finish(Device::DeviceErrorNoError); m_reverseSSHProcess.remove(process); } break; default: break; } }