nymea/libnymea-core/servermanager.cpp

386 lines
17 KiB
C++

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stürz <simon.stuerz@guh.io> *
* Copyright (C) 2017 Michael Zanetti <michael.zanetti@guh.io> *
* *
* This file is part of nymea. *
* *
* nymea 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, version 2 of the License. *
* *
* nymea 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. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*!
\class nymeaserver::ServerManager
\brief This class represents the manager of all server interfaces of the nymea server.
\ingroup server
\inmodule core
The \l{ServerManager} starts the \l{JsonRPCServer} and the \l{RestServer}. He also loads
and provides the SSL configurations for the secure \l{WebServer} and \l{WebSocketServer}
connection.
\sa JsonRPCServer, RestServer
*/
#include "servermanager.h"
#include "nymeacore.h"
#include "certificategenerator.h"
#include "nymeasettings.h"
#include "platform/platform.h"
#include "platform/platformzeroconfcontroller.h"
#include "jsonrpc/jsonrpcserver.h"
#include "servers/mocktcpserver.h"
#include "servers/tcpserver.h"
#include "servers/rest/restserver.h"
#include "servers/websocketserver.h"
#include "servers/webserver.h"
#include "servers/bluetoothserver.h"
#include "servers/mqttbroker.h"
#include "network/zeroconf/zeroconfservicepublisher.h"
#include <QSslCertificate>
#include <QSslConfiguration>
#include <QSslKey>
namespace nymeaserver {
/*! Constructs a \l{ServerManager} with the given \a configuration and \a parent. */
ServerManager::ServerManager(Platform *platform, NymeaConfiguration *configuration, QObject *parent) :
QObject(parent),
m_platform(platform),
m_sslConfiguration(QSslConfiguration())
{
if (!QSslSocket::supportsSsl()) {
qCWarning(dcServerManager()) << "SSL is not supported/installed on this platform.";
} else {
qCDebug(dcServerManager()) << "SSL library version:" << QSslSocket::sslLibraryVersionString();
QString configCertificateFileName = configuration->sslCertificate();
QString configKeyFileName = configuration->sslCertificateKey();
QString fallbackCertificateFileName = NymeaSettings::storagePath() + "/certs/nymead-certificate.crt";
QString fallbackKeyFileName = NymeaSettings::storagePath() + "/certs/nymead-certificate.key";
bool certsLoaded = false;
if (loadCertificate(configKeyFileName, configCertificateFileName)) {
qCDebug(dcServerManager()) << "Using SSL certificate:" << configKeyFileName;
certsLoaded = true;
} else if (loadCertificate(fallbackKeyFileName, fallbackCertificateFileName)) {
certsLoaded = true;
qCWarning(dcServerManager()) << "Using fallback self-signed SSL certificate:" << fallbackCertificateFileName;
} else {
qCDebug(dcServerManager()) << "Generating self signed certificates...";
CertificateGenerator::generate(fallbackCertificateFileName, fallbackKeyFileName);
if (loadCertificate(fallbackKeyFileName, fallbackCertificateFileName)) {
qCWarning(dcServerManager()) << "Using newly created self-signed SSL certificate:" << fallbackCertificateFileName;
certsLoaded = true;
} else {
qCWarning(dcServerManager()) << "Failed to load SSL certificates. SSL encryption disabled.";
}
}
if (certsLoaded) {
// Enable this when we can
// Debian jessie doesn't have better options yet and the client apps currently only support up to TLS 1.1
// m_sslConfiguration.setProtocol(QSsl::TlsV1_1OrLater);
m_sslConfiguration.setPrivateKey(m_certificateKey);
m_sslConfiguration.setLocalCertificate(m_certificate);
}
}
// Interfaces
m_jsonServer = new JsonRPCServer(m_sslConfiguration, this);
m_restServer = new RestServer(m_sslConfiguration, this);
// Transports
MockTcpServer *tcpServer = new MockTcpServer(this);
m_jsonServer->registerTransportInterface(tcpServer, true);
tcpServer->startServer();
foreach (const ServerConfiguration &config, configuration->tcpServerConfigurations()) {
TcpServer *tcpServer = new TcpServer(config, m_sslConfiguration, this);
m_jsonServer->registerTransportInterface(tcpServer, config.authenticationEnabled);
m_tcpServers.insert(config.id, tcpServer);
if (tcpServer->startServer()) {
registerZeroConfService(config, "nymea-tcp", "_jsonrpc._tcp");
}
}
foreach (const ServerConfiguration &config, configuration->webSocketServerConfigurations()) {
WebSocketServer *webSocketServer = new WebSocketServer(config, m_sslConfiguration, this);
m_jsonServer->registerTransportInterface(webSocketServer, config.authenticationEnabled);
m_webSocketServers.insert(config.id, webSocketServer);
if (webSocketServer->startServer()) {
registerZeroConfService(config, "nymea-ws", "_ws._tcp");
}
}
m_bluetoothServer = new BluetoothServer(this);
m_jsonServer->registerTransportInterface(m_bluetoothServer, true);
if (configuration->bluetoothServerEnabled()) {
m_bluetoothServer->startServer();
}
foreach (const WebServerConfiguration &config, configuration->webServerConfigurations()) {
WebServer *webServer = new WebServer(config, m_sslConfiguration, this);
m_restServer->registerWebserver(webServer);
m_webServers.insert(config.id, webServer);
if (webServer->startServer()) {
registerZeroConfService(config, "nymea-http", "_http._tcp");
}
}
m_mqttBroker = new MqttBroker(this);
foreach (const ServerConfiguration &config, configuration->mqttServerConfigurations()) {
if (m_mqttBroker->startServer(config)) {
registerZeroConfService(config, "nymea-mqtt", "_mqtt._tcp");
}
}
m_mqttBroker->updatePolicies(configuration->mqttPolicies().values());
connect(configuration, &NymeaConfiguration::tcpServerConfigurationChanged, this, &ServerManager::tcpServerConfigurationChanged);
connect(configuration, &NymeaConfiguration::tcpServerConfigurationRemoved, this, &ServerManager::tcpServerConfigurationRemoved);
connect(configuration, &NymeaConfiguration::webSocketServerConfigurationChanged, this, &ServerManager::webSocketServerConfigurationChanged);
connect(configuration, &NymeaConfiguration::webSocketServerConfigurationRemoved, this, &ServerManager::webSocketServerConfigurationRemoved);
connect(configuration, &NymeaConfiguration::webServerConfigurationChanged, this, &ServerManager::webServerConfigurationChanged);
connect(configuration, &NymeaConfiguration::webServerConfigurationRemoved, this, &ServerManager::webServerConfigurationRemoved);
connect(configuration, &NymeaConfiguration::mqttServerConfigurationChanged, this, &ServerManager::mqttServerConfigurationChanged);
connect(configuration, &NymeaConfiguration::mqttServerConfigurationRemoved, this, &ServerManager::mqttServerConfigurationRemoved);
connect(configuration, &NymeaConfiguration::mqttPolicyChanged, this, &ServerManager::mqttPolicyChanged);
connect(configuration, &NymeaConfiguration::mqttPolicyRemoved, this, &ServerManager::mqttPolicyRemoved);
}
/*! Returns the pointer to the created \l{JsonRPCServer} in this \l{ServerManager}. */
JsonRPCServer *ServerManager::jsonServer() const
{
return m_jsonServer;
}
/*! Returns the pointer to the created \l{RestServer} in this \l{ServerManager}. */
RestServer *ServerManager::restServer() const
{
return m_restServer;
}
/*! Returns the pointer to the created \l{BluetoothServer} in this \l{ServerManager}. */
BluetoothServer *ServerManager::bluetoothServer() const
{
return m_bluetoothServer;
}
/*! Returns the pointer to the created MockTcpServer in this \l{ServerManager}. */
MockTcpServer *ServerManager::mockTcpServer() const
{
return m_mockTcpServer;
}
MqttBroker *ServerManager::mqttBroker() const
{
return m_mqttBroker;
}
void ServerManager::tcpServerConfigurationChanged(const QString &id)
{
ServerConfiguration config = NymeaCore::instance()->configuration()->tcpServerConfigurations().value(id);
TcpServer *server = m_tcpServers.value(id);
if (server) {
qDebug(dcServerManager()) << "Restarting TCP server for" << config.address << config.port << "SSL" << (config.sslEnabled ? "enabled" : "disabled") << "Authentication" << (config.authenticationEnabled ? "enabled" : "disabled");
unregisterZeroConfService(config.id, "tcp");
server->stopServer();
server->setConfiguration(config);
} else {
qDebug(dcServerManager()) << "Received a TCP Server config change event but don't have a TCP Server instance for it. Creating new Server instance.";
server = new TcpServer(config, m_sslConfiguration, this);
m_tcpServers.insert(config.id, server);
}
m_jsonServer->registerTransportInterface(server, config.authenticationEnabled);
if (server->startServer()) {
registerZeroConfService(config, "tcp", "_jsonrpc._tcp");
}
}
void ServerManager::tcpServerConfigurationRemoved(const QString &id)
{
if (!m_tcpServers.contains(id)) {
qWarning(dcServerManager()) << "Received a TCP Server config removed event but don't have a TCP Server instance for it.";
return;
}
TcpServer *server = m_tcpServers.take(id);
m_jsonServer->unregisterTransportInterface(server);
unregisterZeroConfService(id, "tcp");
server->stopServer();
server->deleteLater();
}
void ServerManager::webSocketServerConfigurationChanged(const QString &id)
{
WebSocketServer *server = m_webSocketServers.value(id);
ServerConfiguration config = NymeaCore::instance()->configuration()->webSocketServerConfigurations().value(id);
if (server) {
qDebug(dcServerManager()) << "Restarting WebSocket server for" << config.address << config.port << "SSL" << (config.sslEnabled ? "enabled" : "disabled") << "Authentication" << (config.authenticationEnabled ? "enabled" : "disabled");
unregisterZeroConfService(id, "ws");
server->stopServer();
server->setConfiguration(config);
} else {
qDebug(dcServerManager()) << "Received a WebSocket Server config change event but don't have a WebSocket Server instance for it. Creating new instance.";
server = new WebSocketServer(config, m_sslConfiguration, this);
m_webSocketServers.insert(server->configuration().id, server);
}
m_jsonServer->registerTransportInterface(server, config.authenticationEnabled);
if (server->startServer()) {
registerZeroConfService(config, "ws", "_ws._tcp");
}
}
void ServerManager::webSocketServerConfigurationRemoved(const QString &id)
{
if (!m_webSocketServers.contains(id)) {
qWarning(dcServerManager()) << "Received a WebSocket Server config removed event but don't have a WebSocket Server instance for it.";
return;
}
WebSocketServer *server = m_webSocketServers.take(id);
m_jsonServer->unregisterTransportInterface(server);
unregisterZeroConfService(id, "ws");
server->stopServer();
server->deleteLater();
}
void ServerManager::webServerConfigurationChanged(const QString &id)
{
WebServerConfiguration config = NymeaCore::instance()->configuration()->webServerConfigurations().value(id);
WebServer *server = m_webServers.value(id);
if (server) {
qDebug(dcServerManager()) << "Restarting Web server for" << config.address << config.port << "SSL" << (config.sslEnabled ? "enabled" : "disabled") << "Authentication" << (config.authenticationEnabled ? "enabled" : "disabled");
unregisterZeroConfService(id, "http");
server->stopServer();
server->reconfigureServer(config);
} else {
qDebug(dcServerManager()) << "Received a Web Server config change event but don't have a Web Server instance for it. Creating new WebServer instance on" << config.address.toString() << config.port << "(SSL:" << config.sslEnabled << ")";
server = new WebServer(config, m_sslConfiguration, this);
m_restServer->registerWebserver(server);
m_webServers.insert(config.id, server);
}
if (server->startServer()) {
registerZeroConfService(config, "http", "_http._tcp");
}
}
void ServerManager::webServerConfigurationRemoved(const QString &id)
{
if (!m_webServers.contains(id)) {
qWarning(dcServerManager()) << "Received a Web Server config removed event but don't have a Web Server instance for it.";
return;
}
WebServer *server = m_webServers.take(id);
unregisterZeroConfService(id, "http");
server->stopServer();
server->deleteLater();
}
void ServerManager::mqttServerConfigurationChanged(const QString &id)
{
ServerConfiguration config = NymeaCore::instance()->configuration()->mqttServerConfigurations().value(id);
if (m_mqttBroker->isRunning(id)) {
unregisterZeroConfService(id, "mqtt");
m_mqttBroker->stopServer(id);
}
if (m_mqttBroker->startServer(config, m_sslConfiguration)) {
registerZeroConfService(config, "mqtt", "_mqtt._tcp");
}
}
void ServerManager::mqttServerConfigurationRemoved(const QString &id)
{
unregisterZeroConfService(id, "mqtt");
m_mqttBroker->stopServer(id);
}
void ServerManager::mqttPolicyChanged(const QString &clientId)
{
m_mqttBroker->updatePolicy(NymeaCore::instance()->configuration()->mqttPolicies().value(clientId));
}
void ServerManager::mqttPolicyRemoved(const QString &clientId)
{
m_mqttBroker->removePolicy(clientId);
}
bool ServerManager::registerZeroConfService(const ServerConfiguration &configuration, const QString &serverType, const QString &serviceType)
{
// Note: reversed order
QHash<QString, QString> txt;
txt.insert("jsonrpcVersion", JSON_PROTOCOL_VERSION);
txt.insert("serverVersion", NYMEA_VERSION_STRING);
txt.insert("manufacturer", "guh GmbH");
txt.insert("uuid", NymeaCore::instance()->configuration()->serverUuid().toString());
txt.insert("name", NymeaCore::instance()->configuration()->serverName());
txt.insert("sslEnabled", configuration.sslEnabled ? "true" : "false");
QString name = "nymea-" + serverType + "-" + configuration.id;
if (!m_platform->zeroConfController()->zeroConfServicePublisher()->registerService(name, configuration.address, static_cast<quint16>(configuration.port), serviceType, txt)) {
qCWarning(dcServerManager()) << "Could not register ZeroConf service for" << configuration;
return false;
}
return true;
}
void ServerManager::unregisterZeroConfService(const QString &configId, const QString &serverType)
{
m_platform->zeroConfController()->zeroConfServicePublisher()->unregisterService("nymea-" + serverType + "-" + configId);
}
bool ServerManager::loadCertificate(const QString &certificateKeyFileName, const QString &certificateFileName)
{
QFile certificateKeyFile(certificateKeyFileName);
if (!certificateKeyFile.open(QIODevice::ReadOnly)) {
qCWarning(dcServerManager()) << "Could not open" << certificateKeyFile.fileName() << ":" << certificateKeyFile.errorString();
return false;
}
m_certificateKey = QSslKey(certificateKeyFile.readAll(), QSsl::Rsa);
qCDebug(dcServerManager()) << "Loaded private certificate key " << certificateKeyFileName;
certificateKeyFile.close();
QFile certificateFile(certificateFileName);
if (!certificateFile.open(QIODevice::ReadOnly)) {
qCWarning(dcServerManager()) << "Could not open" << certificateFile.fileName() << ":" << certificateFile.errorString();
return false;
}
m_certificate = QSslCertificate(certificateFile.readAll());
qCDebug(dcServerManager()) << "Loaded certificate file " << certificateFileName;
certificateFile.close();
return true;
}
/*! Set the server name for all servers to the given \a serverName. */
void ServerManager::setServerName(const QString &serverName)
{
qCDebug(dcServerManager()) << "Server name changed" << serverName;
foreach (WebSocketServer *websocketServer, m_webSocketServers.values()) {
websocketServer->setServerName(serverName);
}
foreach (WebServer *webServer, m_webServers.values()) {
webServer->setServerName(serverName);
}
foreach (TcpServer *tcpServer, m_tcpServers.values()) {
tcpServer->setServerName(serverName);
}
}
}