From 8b09f26ac338f3719528b7e6281664604c368d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 12 Aug 2021 13:54:08 +0200 Subject: [PATCH] Add tunnel proxy server --- libnymea-core/libnymea-core.pro | 2 + libnymea-core/nymeaconfiguration.cpp | 34 ++++ libnymea-core/nymeaconfiguration.h | 8 + libnymea-core/servermanager.cpp | 50 ++++++ libnymea-core/servermanager.h | 6 + libnymea-core/servers/tunnelproxyserver.cpp | 169 ++++++++++++++++++++ libnymea-core/servers/tunnelproxyserver.h | 88 ++++++++++ 7 files changed, 357 insertions(+) create mode 100644 libnymea-core/servers/tunnelproxyserver.cpp create mode 100644 libnymea-core/servers/tunnelproxyserver.h diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index e071fd8c..9436d7a6 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -100,6 +100,7 @@ HEADERS += nymeacore.h \ servers/bluetoothserver.h \ servers/websocketserver.h \ servers/mqttbroker.h \ + servers/tunnelproxyserver.h \ jsonrpc/jsonrpcserverimplementation.h \ jsonrpc/jsonvalidator.h \ jsonrpc/integrationshandler.h \ @@ -191,6 +192,7 @@ SOURCES += nymeacore.cpp \ servers/websocketserver.cpp \ servers/bluetoothserver.cpp \ servers/mqttbroker.cpp \ + servers/tunnelproxyserver.cpp \ jsonrpc/jsonrpcserverimplementation.cpp \ jsonrpc/jsonvalidator.cpp \ jsonrpc/integrationshandler.cpp \ diff --git a/libnymea-core/nymeaconfiguration.cpp b/libnymea-core/nymeaconfiguration.cpp index bc230b1b..31368cd4 100644 --- a/libnymea-core/nymeaconfiguration.cpp +++ b/libnymea-core/nymeaconfiguration.cpp @@ -211,6 +211,21 @@ NymeaConfiguration::NymeaConfiguration(QObject *parent) : mqttPolicies.endGroup(); } + // Tunnel Proxy Server + // FIXME: maybe configure default proxy + if (settings.childGroups().contains("TunnelProxyServer")) { + settings.beginGroup("TunnelProxyServer"); + if (settings.value("disabled").toBool()) { + qCDebug(dcConfiguration) << "Tunnel proxy server disabled by configuration."; + } else if (!settings.childGroups().isEmpty()) { + foreach (const QString &key, settings.childGroups()) { + ServerConfiguration config = readServerConfig("TunnelProxyServer", key); + m_tunnelProxyServerConfigs[config.id] = config; + } + } + settings.endGroup(); + } + // Write defaults for log settings settings.beginGroup("Logs"); settings.setValue("logDBDriver", logDBDriver()); @@ -366,6 +381,25 @@ void NymeaConfiguration::removeWebSocketServerConfiguration(const QString &id) emit webSocketServerConfigurationRemoved(id); } +QHash NymeaConfiguration::tunnelProxyServerConfigurations() const +{ + return m_tunnelProxyServerConfigs; +} + +void NymeaConfiguration::setTunnelProxyServerConfiguration(const ServerConfiguration &config) +{ + m_tunnelProxyServerConfigs[config.id] = config; + storeServerConfig("TunnelProxyServer", config); + emit tunnelProxyServerConfigurationChanged(config.id); +} + +void NymeaConfiguration::removeTunnelProxyServerConfiguration(const QString &id) +{ + m_tunnelProxyServerConfigs.take(id); + deleteServerConfig("TunnelProxyServer", id); + emit tunnelProxyServerConfigurationRemoved(id); +} + QHash NymeaConfiguration::mqttServerConfigurations() const { return m_mqttServerConfigs; diff --git a/libnymea-core/nymeaconfiguration.h b/libnymea-core/nymeaconfiguration.h index 5ba15e2c..fee084d3 100644 --- a/libnymea-core/nymeaconfiguration.h +++ b/libnymea-core/nymeaconfiguration.h @@ -147,6 +147,11 @@ public: void setWebSocketServerConfiguration(const ServerConfiguration &config); void removeWebSocketServerConfiguration(const QString &id); + // Tunnel proxy server + QHash tunnelProxyServerConfigurations() const; + void setTunnelProxyServerConfiguration(const ServerConfiguration &config); + void removeTunnelProxyServerConfiguration(const QString &id); + // MQTT QHash mqttServerConfigurations() const; void setMqttServerConfiguration(const ServerConfiguration &config); @@ -187,6 +192,7 @@ private: QHash m_webSocketServerConfigs; QHash m_mqttServerConfigs; QHash m_mqttPolicies; + QHash m_tunnelProxyServerConfigs; void setServerUuid(const QUuid &uuid); void setWebServerPublicFolder(const QString & path); @@ -216,6 +222,8 @@ signals: void webSocketServerConfigurationRemoved(const QString &configId); void mqttServerConfigurationChanged(const QString &configId); void mqttServerConfigurationRemoved(const QString &configId); + void tunnelProxyServerConfigurationChanged(const QString &configId); + void tunnelProxyServerConfigurationRemoved(const QString &configId); void mqttPolicyChanged(const QString &clientId); void mqttPolicyRemoved(const QString &clientId); diff --git a/libnymea-core/servermanager.cpp b/libnymea-core/servermanager.cpp index dabdc32c..9e6ab07c 100644 --- a/libnymea-core/servermanager.cpp +++ b/libnymea-core/servermanager.cpp @@ -57,6 +57,7 @@ #include "servers/webserver.h" #include "servers/bluetoothserver.h" #include "servers/mqttbroker.h" +#include "servers/tunnelproxyserver.h" #include "network/zeroconf/zeroconfservicepublisher.h" @@ -165,6 +166,28 @@ ServerManager::ServerManager(Platform *platform, NymeaConfiguration *configurati m_bluetoothServer->startServer(); } + foreach (const ServerConfiguration &config, configuration->tunnelProxyServerConfigurations()) { + TunnelProxyServer *tunnelProxyServer = new TunnelProxyServer(configuration->serverName(), configuration->serverUuid(), config, this); + qCDebug(dcServerManager()) << "Creating tunnel proxy server" << config; + m_tunnelProxyServers.insert(config.id, tunnelProxyServer); + connect(tunnelProxyServer, &TunnelProxyServer::runningChanged, this, [this, tunnelProxyServer](bool running){ + if (running) { + // Note: enable authentication in any case, we don't want to expose unprotected access trough the internet + m_jsonServer->registerTransportInterface(tunnelProxyServer, true); + } else { + m_jsonServer->unregisterTransportInterface(tunnelProxyServer); + } + }); + + // FIXME: make use of SSL over tunnel proxy connections + // FIXME: make sure the authentication is enabled for the tunnel proxy + + // Note: only start the tunnel proxy server if cloud is connected + if (configuration->cloudEnabled()) { + tunnelProxyServer->startServer(); + } + } + foreach (const WebServerConfiguration &config, configuration->webServerConfigurations()) { WebServer *webServer = new WebServer(config, m_sslConfiguration, this); m_webServers.insert(config.id, webServer); @@ -191,6 +214,9 @@ ServerManager::ServerManager(Platform *platform, NymeaConfiguration *configurati connect(configuration, &NymeaConfiguration::mqttServerConfigurationRemoved, this, &ServerManager::mqttServerConfigurationRemoved); connect(configuration, &NymeaConfiguration::mqttPolicyChanged, this, &ServerManager::mqttPolicyChanged); connect(configuration, &NymeaConfiguration::mqttPolicyRemoved, this, &ServerManager::mqttPolicyRemoved); + connect(configuration, &NymeaConfiguration::tunnelProxyServerConfigurationChanged, this, &ServerManager::tunnelProxyServerConfigurationChanged); + connect(configuration, &NymeaConfiguration::tunnelProxyServerConfigurationRemoved, this, &ServerManager::tunnelProxyServerConfigurationRemoved); + connect(configuration, &NymeaConfiguration::cloudEnabledChanged, this, &ServerManager::cloudEnabledChanged); } /*! Returns the pointer to the created \l{JsonRPCServer} in this \l{ServerManager}. */ @@ -341,6 +367,30 @@ void ServerManager::mqttPolicyRemoved(const QString &clientId) m_mqttBroker->removePolicy(clientId); } +void ServerManager::tunnelProxyServerConfigurationChanged(const QString &id) +{ + // FIXME: not dynamic configurable for now + Q_UNUSED(id) +} + +void ServerManager::tunnelProxyServerConfigurationRemoved(const QString &id) +{ + // FIXME: not dynamic configurable for now + Q_UNUSED(id) +} + +void ServerManager::cloudEnabledChanged(bool enabled) +{ + qCDebug(dcServerManager()) << "Cloud connection" << (enabled ? "enabled. Starting tunnel proxy servers" : "disabled. Stopping tunnel proxy servers."); + foreach (TunnelProxyServer *server, m_tunnelProxyServers) { + if (enabled) { + server->startServer(); + } else { + server->stopServer(); + } + } +} + bool ServerManager::registerZeroConfService(const ServerConfiguration &configuration, const QString &serverType, const QString &serviceType) { // Note: reversed order diff --git a/libnymea-core/servermanager.h b/libnymea-core/servermanager.h index 90469170..49438294 100644 --- a/libnymea-core/servermanager.h +++ b/libnymea-core/servermanager.h @@ -50,6 +50,7 @@ class WebSocketServer; class WebServer; class BluetoothServer; class MqttBroker; +class TunnelProxyServer; class MockTcpServer; @@ -79,6 +80,10 @@ private slots: void mqttServerConfigurationRemoved(const QString &id); void mqttPolicyChanged(const QString &clientId); void mqttPolicyRemoved(const QString &clientId); + void tunnelProxyServerConfigurationChanged(const QString &id); + void tunnelProxyServerConfigurationRemoved(const QString &id); + + void cloudEnabledChanged(bool enabled); private: bool registerZeroConfService(const ServerConfiguration &configuration, const QString &serverType, const QString &serviceType); @@ -94,6 +99,7 @@ private: QHash m_tcpServers; QHash m_webSocketServers; QHash m_webServers; + QHash m_tunnelProxyServers; MockTcpServer *m_mockTcpServer; MqttBroker *m_mqttBroker; diff --git a/libnymea-core/servers/tunnelproxyserver.cpp b/libnymea-core/servers/tunnelproxyserver.cpp new file mode 100644 index 00000000..100d3e85 --- /dev/null +++ b/libnymea-core/servers/tunnelproxyserver.cpp @@ -0,0 +1,169 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* this project. If not, see . +* +* 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 "tunnelproxyserver.h" +#include "loggingcategories.h" + +NYMEA_LOGGING_CATEGORY(dcTunnelProxyServer, "TunnelProxyServer") + +namespace nymeaserver { + +TunnelProxyServer::TunnelProxyServer(const QString &serverName, const QUuid &serverUuid, const ServerConfiguration &configuration, QObject *parent) : + TransportInterface(configuration, parent), + m_serverName(serverName), + m_serverUuid(serverUuid) +{ + // Note: the authentication must always be enabled on the tunnel proxy server + if (!configuration.authenticationEnabled) { + qCWarning(dcTunnelProxyServer()) << "============================================================================================================================="; + qCWarning(dcTunnelProxyServer()) << "WARNING! The tunnel proxy server has authentication disabled! This is very dangerous and exposes your system to the internet."; + qCWarning(dcTunnelProxyServer()) << "============================================================================================================================="; + } + + // Default to ssl + m_serverUrl.setScheme("ssl"); + m_serverUrl.setHost(configuration.address.toString()); + m_serverUrl.setPort(configuration.port); + + m_tunnelProxySocketServer = new TunnelProxySocketServer(m_serverUuid, m_serverName, this); + connect(m_tunnelProxySocketServer, &TunnelProxySocketServer::stateChanged, this, &TunnelProxyServer::onStateChanged); + connect(m_tunnelProxySocketServer, &TunnelProxySocketServer::runningChanged, this, &TunnelProxyServer::onServerRunningChanged); + connect(m_tunnelProxySocketServer, &TunnelProxySocketServer::errorOccured, this, &TunnelProxyServer::onErrorOccured); + connect(m_tunnelProxySocketServer, &TunnelProxySocketServer::serverErrorOccured, this, &TunnelProxyServer::onServerErrorOccured); + connect(m_tunnelProxySocketServer, &TunnelProxySocketServer::sslErrors, this, &TunnelProxyServer::onSslErrors); + connect(m_tunnelProxySocketServer, &TunnelProxySocketServer::clientConnected, this, &TunnelProxyServer::onClientConnected); + connect(m_tunnelProxySocketServer, &TunnelProxySocketServer::clientDisconnected, this, &TunnelProxyServer::onClientDisconnected); + +} + +TunnelProxyServer::~TunnelProxyServer() +{ + stopServer(); +} + +void TunnelProxyServer::sendData(const QUuid &clientId, const QByteArray &data) +{ + TunnelProxySocket *tunnelProxySocket = m_clients.value(clientId); + if (!tunnelProxySocket) { + qCWarning(dcTunnelProxyServer()) << "Failed to send data to client" << clientId.toString() << "because there is no tunnel socket for this client UUID."; + return; + } + + tunnelProxySocket->writeData(data); +} + +void TunnelProxyServer::sendData(const QList &clients, const QByteArray &data) +{ + foreach (const QUuid &client, clients) { + sendData(client, data); + } +} + +void TunnelProxyServer::terminateClientConnection(const QUuid &clientId) +{ + TunnelProxySocket *tunnelProxySocket = m_clients.value(clientId); + if (tunnelProxySocket) { + tunnelProxySocket->disconnectSocket(); + } +} + +void TunnelProxyServer::setServerName(const QString &serverName) +{ + m_serverName = serverName; +} + +bool TunnelProxyServer::startServer() +{ + + m_tunnelProxySocketServer->startServer(m_serverUrl); + return true; +} + +bool TunnelProxyServer::stopServer() +{ + m_tunnelProxySocketServer->stopServer(); + return true; +} + +void TunnelProxyServer::onStateChanged(TunnelProxySocketServer::State state) +{ + if (state == TunnelProxySocketServer::StateRegister) { + qCDebug(dcTunnelProxyServer()) << "Connected with" << m_tunnelProxySocketServer->remoteProxyServer() + << m_tunnelProxySocketServer->remoteProxyServerName() + << m_tunnelProxySocketServer->remoteProxyServerVersion() + << "API:" << m_tunnelProxySocketServer->remoteProxyApiVersion(); + } +} + +void TunnelProxyServer::onServerRunningChanged(bool running) +{ + qCDebug(dcTunnelProxyServer()) << "The server is" << (running ? "now listening for incomming connections on " + m_serverUuid.toString() : "not running any more."); + emit runningChanged(running); +} + +void TunnelProxyServer::onErrorOccured(QAbstractSocket::SocketError error) +{ + qCDebug(dcTunnelProxyServer()) << "Remote proxy connection error occured" << error; +} + +void TunnelProxyServer::onServerErrorOccured(TunnelProxySocketServer::Error error) +{ + qCWarning(dcTunnelProxyServer()) << "Error occured on server" << m_serverUrl.toString() << error; +} + +void TunnelProxyServer::onSslErrors(const QList &errors) +{ + qCDebug(dcTunnelProxyServer()) << "Remote proxy connection SSL errors occured" << errors; + // FIXME: make this configurable + m_tunnelProxySocketServer->ignoreSslErrors(); +} + +void TunnelProxyServer::onClientConnected(TunnelProxySocket *tunnelProxySocket) +{ + QUuid clientId = QUuid::createUuid(); + qCDebug(dcTunnelProxyServer()) << "Client connected:" << clientId.toString() << tunnelProxySocket->clientName() << "(Remote address:" << tunnelProxySocket->clientPeerAddress().toString() << ")"; + m_clients.insert(clientId, tunnelProxySocket); + + connect(tunnelProxySocket, &TunnelProxySocket::dataReceived, this, [this, clientId](const QByteArray &data){ + emit dataAvailable(clientId, data); + }); + + emit clientConnected(clientId); +} + +void TunnelProxyServer::onClientDisconnected(TunnelProxySocket *tunnelProxySocket) +{ + QUuid clientId = m_clients.key(tunnelProxySocket); + qCDebug(dcTunnelProxyServer()) << "Client disconnected:" << clientId.toString() << tunnelProxySocket->clientName() << "(Remote address:" << tunnelProxySocket->clientPeerAddress().toString() << ")"; + m_clients.remove(clientId); + emit clientDisconnected(clientId); +} + +} diff --git a/libnymea-core/servers/tunnelproxyserver.h b/libnymea-core/servers/tunnelproxyserver.h new file mode 100644 index 00000000..cbe66bd9 --- /dev/null +++ b/libnymea-core/servers/tunnelproxyserver.h @@ -0,0 +1,88 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* this project. If not, see . +* +* 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef TUNNELPROXYSERVER_H +#define TUNNELPROXYSERVER_H + +#include +#include + +#include "transportinterface.h" + +#include +#include + +namespace nymeaserver { + +using namespace remoteproxyclient; + +class TunnelProxyServer : public TransportInterface +{ + Q_OBJECT +public: + explicit TunnelProxyServer(const QString &serverName, const QUuid &serverUuid, const ServerConfiguration &configuration, QObject *parent = nullptr); + ~TunnelProxyServer() override; + + void sendData(const QUuid &clientId, const QByteArray &data) override; + void sendData(const QList &clients, const QByteArray &data) override; + + void terminateClientConnection(const QUuid &clientId) override; + +public slots: + void setServerName(const QString &serverName) override; + bool startServer() override; + bool stopServer() override; + +signals: + void runningChanged(bool running); + +private slots: + void onStateChanged(TunnelProxySocketServer::State state); + void onServerRunningChanged(bool running); + void onErrorOccured(QAbstractSocket::SocketError error); + void onServerErrorOccured(TunnelProxySocketServer::Error error); + void onSslErrors(const QList &errors); + + void onClientConnected(TunnelProxySocket *tunnelProxySocket); + void onClientDisconnected(TunnelProxySocket *tunnelProxySocket); + +private: + TunnelProxySocketServer *m_tunnelProxySocketServer = nullptr; + QString m_serverName; + QUuid m_serverUuid; + QUrl m_serverUrl; + + QHash m_clients; + +}; + +} + +#endif // TUNNELPROXYSERVER_H