//// DomainServer.cpp// domain-server/src//// Created by Stephen Birarda on 9/26/13.// Copyright 2013 High Fidelity, Inc.// Copyright 2020 Vircadia contributors.// Copyright 2023 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "DomainServer.h" #include <memory>#include <random>#include <iostream>#include <chrono> #include <QDir>#include <QJsonDocument>#include <QJsonObject>#include <QJsonArray>#include <QProcess>#include <QSharedMemory>#include <QRegularExpression>#include <QStandardPaths>#include <QTimer>#include <QUrlQuery>#include <QCommandLineParser>#include <QUuid> #include <AccountManager.h>#include <AssetClient.h>#include <BuildInfo.h>#include <CrashAnnotations.h>#include <DependencyManager.h>#include <HifiConfigVariantMap.h>#include <HTTPConnection.h>#include <LogUtils.h>#include <NetworkingConstants.h>#include <MetaverseAPI.h>#include <udt/PacketHeaders.h>#include <SettingHandle.h>#include <SharedUtil.h>#include <ShutdownEventListener.h>#include <UUID.h>#include <LogHandler.h>#include <PathUtils.h>#include <NumericalConstants.h>#include <Trace.h>#include <StatTracker.h> #include "AssetsBackupHandler.h"#include "ContentSettingsBackupHandler.h"#include "DomainServerNodeData.h"#include "EntitiesBackupHandler.h"#include "NodeConnectionData.h" #include <Gzip.h> #include <OctreeDataUtils.h>#include <ThreadHelpers.h>#include <crash-handler/CrashHandler.h> using namespace std::chrono; Q_LOGGING_CATEGORY(domain_server, "hifi.domain_server")Q_LOGGING_CATEGORY(domain_server_ice, "hifi.domain_server.ice")Q_LOGGING_CATEGORY(domain_server_auth, "overte.domain_server.auth") const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";const QString DomainServer::REPLACEMENT_FILE_EXTENSION = ".replace";const QString& DOMAIN_SERVER_SETTINGS_KEY = "domain_server";const QString PUBLIC_SOCKET_ADDRESS_KEY = "network_address";const QString PUBLIC_SOCKET_PORT_KEY = "network_port";const int MIN_PORT = 1;const int MAX_PORT = 65535; int const DomainServer::EXIT_CODE_REBOOT = 234923; QString DomainServer::_iceServerAddr { NetworkingConstants::ICE_SERVER_DEFAULT_HOSTNAME };int DomainServer::_iceServerPort { ICE_SERVER_DEFAULT_PORT };bool DomainServer::_overrideDomainID { false };QUuid DomainServer::_overridingDomainID;bool DomainServer::_getTempName { false };QString DomainServer::_userConfigFilename;int DomainServer::_parentPID { -1 };bool DomainServer::_forceCrashReporting{false}; /// @brief The Domain server can proxy requests to the Directory Server, this function handles those forwarding requests./// @param connection The HTTP connection object./// @param requestUrl The full URL of the request. e.g. https://google.com/api/v1/test/// @param metaversePath The path on the Directory Server to route to./// @param requestSubobjectKey (Optional) The parent object key that any data will be inserted into for the forwarded request./// @param requiredData (Optional) This data is required to be present for the request./// @param optionalData (Optional) If provided, this optional data will be forwarded with the request./// @param requireAccessToken Require a valid access token to be sent with this request.bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection, const QUrl& requestUrl, const QString& metaversePath, const QString& requestSubobjectKey, std::initializer_list<QString> requiredData, std::initializer_list<QString> optionalData, bool requireAccessToken) { auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH); if (!accessTokenVariant.isValid() && requireAccessToken) { connection->respond(HTTPConnection::StatusCode400, "User access token has not been set"); return true; } QJsonObject subobject; if (requestUrl.hasQuery()) { QUrlQuery query(requestUrl); for (auto& key : requiredData) { if (query.hasQueryItem(key)) { subobject.insert(key, query.queryItemValue(key)); } else { auto error = "Domain Server: Bad request, expected param '" + key + "'"; connection->respond(HTTPConnection::StatusCode400, error.toLatin1()); return true; } } for (auto& key : optionalData) { if (query.hasQueryItem(key)) { subobject.insert(key, query.queryItemValue(key)); } } } else { auto params = connection->parseUrlEncodedForm(); for (auto& key : requiredData) { auto it = params.find(key); if (it == params.end()) { auto error = "Domain Server: Bad request, expected param '" + key + "'"; connection->respond(HTTPConnection::StatusCode400, error.toLatin1()); return true; } subobject.insert(key, it.value()); } for (auto& key : optionalData) { auto it = params.find(key); if (it != params.end()) { subobject.insert(key, it.value()); } } } QJsonObject root; root.insert(requestSubobjectKey, subobject); QJsonDocument doc { root }; QUrl url { MetaverseAPI::getCurrentMetaverseServerURL().toString() + metaversePath }; QNetworkRequest req(url); req.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::OVERTE_USER_AGENT); req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); if (accessTokenVariant.isValid()) { auto accessTokenHeader = QString("Bearer ") + accessTokenVariant.toString(); req.setRawHeader("Authorization", accessTokenHeader.toLatin1()); } QNetworkReply* reply; auto method = connection->requestOperation(); if (method == QNetworkAccessManager::GetOperation) { reply = NetworkAccessManager::getInstance().get(req); } else if (method == QNetworkAccessManager::PostOperation) { reply = NetworkAccessManager::getInstance().post(req, doc.toJson()); } else if (method == QNetworkAccessManager::PutOperation) { reply = NetworkAccessManager::getInstance().put(req, doc.toJson()); } else { connection->respond(HTTPConnection::StatusCode400, "Error forwarding request, unsupported method"); return true; } connect(reply, &QNetworkReply::finished, this, [reply, connection]() { if (reply->error() != QNetworkReply::NoError) { auto data = reply->readAll(); qDebug() << "Got error response from directory server (" << reply->url() << "): " << data << reply->errorString(); connection->respond(HTTPConnection::StatusCode400, data); return; } connection->respond(HTTPConnection::StatusCode200, reply->readAll()); }); return true;} DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), _gatekeeper(this), _httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this){ static const QString CRASH_REPORTER = "crash_reporting.enable_crash_reporter"; if (_parentPID != -1) { watchParentProcess(_parentPID); } PathUtils::removeTemporaryApplicationDirs(); DependencyManager::set<tracing::Tracer>(); DependencyManager::set<StatTracker>(); LogUtils::init(); LogHandler::getInstance().moveToThread(thread()); LogHandler::getInstance().setupRepeatedMessageFlusher(); qDebug() << "Setting up domain-server"; qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion()); qDebug() << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION; qDebug() << "[VERSION] VERSION:" << BuildInfo::VERSION; qDebug() << "[VERSION] BUILD_TYPE_STRING:" << BuildInfo::BUILD_TYPE_STRING; qDebug() << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES; qDebug() << "[VERSION] We will be using this name to find ICE servers:" << _iceServerAddr; connect(this, &QCoreApplication::aboutToQuit, this, &DomainServer::aboutToQuit); // make sure we have a fresh AccountManager instance // (need this since domain-server can restart itself and maintain static variables) DependencyManager::set<AccountManager>(); // load the user config QString userConfigFilename; if (!_userConfigFilename.isEmpty()) { userConfigFilename = _userConfigFilename; } else { // we weren't passed a user config path static const QString USER_CONFIG_FILE_NAME = "config.json"; userConfigFilename = PathUtils::getAppDataFilePath(USER_CONFIG_FILE_NAME); } _settingsManager.setupConfigMap(userConfigFilename); // setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us #ifdef _WIN32 installNativeEventFilter(&ShutdownEventListener::getInstance());#else ShutdownEventListener::getInstance();#endif auto &ch = CrashHandler::getInstance(); ch.setEnabled(_settingsManager.valueOrDefaultValueForKeyPath(CRASH_REPORTER).toBool()); qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData"); qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData"); // make sure we hear about newly connected nodes from our gatekeeper connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode); // if a connected node loses connection privileges, hang up on it connect(&_gatekeeper, &DomainGatekeeper::killNode, this, &DomainServer::handleKillNode); // if permissions are updated, relay the changes to the Node datastructures connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions, &_gatekeeper, &DomainGatekeeper::updateNodePermissions); connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated, this, &DomainServer::updateReplicatedNodes); connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated, this, &DomainServer::updateDownstreamNodes); connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated, this, &DomainServer::updateUpstreamNodes); setupGroupCacheRefresh(); optionallySetupOAuth(); if (_oauthEnable) { _oauthEnable = optionallyReadX509KeyAndCertificate(); } _settingsManager.apiRefreshGroupInformation(); #if defined(WEBRTC_DATA_CHANNELS) const QString WEBRTC_ENABLE = "webrtc.enable_webrtc"; bool isWebRTCEnabled = _settingsManager.valueForKeyPath(WEBRTC_ENABLE).toBool(); qCDebug(domain_server) << "WebRTC enabled:" << isWebRTCEnabled; // The domain server's WebRTC signaling server is used by the domain server and the assignment clients, so disabling it // disables WebRTC for the server as a whole. if (isWebRTCEnabled) { const QString WEBRTC_WSS_ENABLE = "webrtc.enable_webrtc_websocket_ssl"; bool isWebRTCEnabled = _settingsManager.valueForKeyPath(WEBRTC_WSS_ENABLE).toBool(); qCDebug(domain_server) << "WebRTC WSS enabled:" << isWebRTCEnabled; _webrtcSignalingServer.reset(new WebRTCSignalingServer(this, isWebRTCEnabled)); }#endif setupNodeListAndAssignments(); updateReplicatedNodes(); updateDownstreamNodes(); updateUpstreamNodes(); #if defined(WEBRTC_DATA_CHANNELS) if (isWebRTCEnabled) { setUpWebRTCSignalingServer(); }#endif if (_type != NonMetaverse) { // if we have a directory services domain, we'll use an access token for API calls resetAccountManagerAccessToken(); setupAutomaticNetworking(); } if (!getID().isNull() && _type != NonMetaverse) { // setup periodic heartbeats to directory services API setupHeartbeatToMetaverse(); // send the first heartbeat immediately sendHeartbeatToMetaverse(); } // check for the temporary name parameter if (_getTempName) { getTemporaryName(); } // send signal to DomainMetadata when descriptors changed _metadata = new DomainMetadata(this); connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated, _metadata, &DomainMetadata::descriptorsChanged); // update the metadata when a user (dis)connects connect(this, &DomainServer::userConnected, _metadata, &DomainMetadata::usersChanged); connect(this, &DomainServer::userDisconnected, _metadata, &DomainMetadata::usersChanged); // update the metadata when security changes connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions, [this] { _metadata->securityChanged(true); }); qDebug() << "domain-server is running"; static const QString AC_SUBNET_ALLOWLIST_SETTING_PATH = "security.ac_subnet_allowlist"; static const Subnet LOCALHOST { QHostAddress("127.0.0.1"), 32 }; _acSubnetAllowlist = { LOCALHOST }; auto allowlist = _settingsManager.valueOrDefaultValueForKeyPath(AC_SUBNET_ALLOWLIST_SETTING_PATH).toStringList(); for (auto& subnet : allowlist) { auto netmaskParts = subnet.trimmed().split("/"); if (netmaskParts.size() > 2) { qDebug() << "Ignoring subnet in allowlist, malformed: " << subnet; continue; } // The default netmask is 32 if one has not been specified, which will // match only the ip provided. int netmask = 32; if (netmaskParts.size() == 2) { bool ok; netmask = netmaskParts[1].toInt(&ok); if (!ok) { qDebug() << "Ignoring subnet in allowlist, bad netmask: " << subnet; continue; } } auto ip = QHostAddress(netmaskParts[0]); if (!ip.isNull()) { qDebug() << "Adding AC allowlist subnet: " << subnet << " -> " << (ip.toString() + "/" + QString::number(netmask)); _acSubnetAllowlist.push_back({ ip , netmask }); } else { qDebug() << "Ignoring subnet in allowlist, invalid ip portion: " << subnet; } } if (QDir(getEntitiesDirPath()).mkpath(".")) { qCDebug(domain_server) << "Created entities data directory"; } maybeHandleReplacementEntityFile(); _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager)); connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){ _contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath()))); _contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir(), isAssetServerEnabled()))); _contentManager->addBackupHandler(BackupHandlerPointer(new ContentSettingsBackupHandler(_settingsManager))); }); _contentManager->initialize(true); connect(_contentManager.get(), &DomainContentBackupManager::recoveryCompleted, this, &DomainServer::restart); static const int NODE_PING_MONITOR_INTERVAL_MSECS = 1 * MSECS_PER_SECOND; _nodePingMonitorTimer = new QTimer{ this }; connect(_nodePingMonitorTimer, &QTimer::timeout, this, &DomainServer::nodePingMonitor); _nodePingMonitorTimer->start(NODE_PING_MONITOR_INTERVAL_MSECS); initializeExporter(); initializeMetadataExporter();} void DomainServer::parseCommandLine(int argc, char* argv[]) { QCommandLineParser parser; parser.setApplicationDescription("Overte Domain Server"); const QCommandLineOption versionOption = parser.addVersionOption(); const QCommandLineOption helpOption = parser.addHelpOption(); const QCommandLineOption iceServerAddressOption("i", "ice-server address", "IP:PORT or HOSTNAME:PORT"); parser.addOption(iceServerAddressOption); const QCommandLineOption domainIDOption("d", "domain-server uuid", "uuid"); parser.addOption(domainIDOption); const QCommandLineOption getTempNameOption("get-temp-name", "Request a temporary domain-name"); parser.addOption(getTempNameOption); const QCommandLineOption userConfigOption("user-config", "Pass user config file pass", "path"); parser.addOption(userConfigOption); const QCommandLineOption parentPIDOption(PARENT_PID_OPTION, "PID of the parent process", "parent-pid"); parser.addOption(parentPIDOption); const QCommandLineOption logOption("logOptions", "Logging options, comma separated: color,nocolor,process_id,thread_id,milliseconds,keep_repeats,journald,nojournald", "options"); parser.addOption(logOption); const QCommandLineOption forceCrashReportingOption("forceCrashReporting", "Force crash reporting to initialize."); parser.addOption(forceCrashReportingOption); QStringList arguments; for (int i = 0; i < argc; ++i) { arguments << argv[i]; } if (!parser.parse(arguments)) { std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam QCoreApplication mockApp(argc, argv); // required for call to showHelp() parser.showHelp(); Q_UNREACHABLE(); } if (parser.isSet(versionOption)) { parser.showVersion(); Q_UNREACHABLE(); } if (parser.isSet(helpOption)) { QCoreApplication mockApp(argc, argv); // required for call to showHelp() parser.showHelp(); Q_UNREACHABLE(); } // We want to configure the logging system as early as possible auto &logHandler = LogHandler::getInstance(); if (parser.isSet(logOption)) { if (!logHandler.parseOptions(parser.value(logOption).toUtf8(), logOption.names().first())) { QCoreApplication mockApp(argc, const_cast<char**>(argv)); // required for call to showHelp() parser.showHelp(); Q_UNREACHABLE(); } } if (parser.isSet(iceServerAddressOption)) { // parse the IP and port combination for this target QString hostnamePortString = parser.value(iceServerAddressOption); _iceServerAddr = hostnamePortString.left(hostnamePortString.indexOf(':')); _iceServerPort = (quint16) hostnamePortString.mid(hostnamePortString.indexOf(':') + 1).toUInt(); if (_iceServerPort == 0) { _iceServerPort = ICE_SERVER_DEFAULT_PORT; } if (_iceServerAddr.isEmpty()) { qCWarning(domain_server_ice) << "ALERT: Could not parse an IP address and port combination from" << hostnamePortString; ::exit(0); } } if (parser.isSet(domainIDOption)) { _overridingDomainID = QUuid(parser.value(domainIDOption)); _overrideDomainID = true; qDebug() << "domain-server ID is" << _overridingDomainID; } if (parser.isSet(getTempNameOption)) { _getTempName = true; } if (parser.isSet(userConfigOption)) { _userConfigFilename = parser.value(userConfigOption); } if (parser.isSet(parentPIDOption)) { bool ok = false; int parentPID = parser.value(parentPIDOption).toInt(&ok); if (ok) { _parentPID = parentPID;Lines 1–500 of 3817. Load more
// // DomainServer.cpp // domain-server/src // // Created by Stephen Birarda on 9/26/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. // Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "DomainServer.h" #include <memory> #include <random> #include <iostream> #include <chrono> #include <QDir> #include <QJsonDocument> #include <QJsonObject> #include <QJsonArray> #include <QProcess> #include <QSharedMemory> #include <QRegularExpression> #include <QStandardPaths> #include <QTimer> #include <QUrlQuery> #include <QCommandLineParser> #include <QUuid> #include <AccountManager.h> #include <AssetClient.h> #include <BuildInfo.h> #include <CrashAnnotations.h> #include <DependencyManager.h> #include <HifiConfigVariantMap.h> #include <HTTPConnection.h> #include <LogUtils.h> #include <NetworkingConstants.h> #include <MetaverseAPI.h> #include <udt/PacketHeaders.h> #include <SettingHandle.h> #include <SharedUtil.h> #include <ShutdownEventListener.h> #include <UUID.h> #include <LogHandler.h> #include <PathUtils.h> #include <NumericalConstants.h> #include <Trace.h> #include <StatTracker.h> #include "AssetsBackupHandler.h" #include "ContentSettingsBackupHandler.h" #include "DomainServerNodeData.h" #include "EntitiesBackupHandler.h" #include "NodeConnectionData.h" #include <Gzip.h> #include <OctreeDataUtils.h> #include <ThreadHelpers.h> #include <crash-handler/CrashHandler.h> using namespace std::chrono; Q_LOGGING_CATEGORY(domain_server, "hifi.domain_server") Q_LOGGING_CATEGORY(domain_server_ice, "hifi.domain_server.ice") Q_LOGGING_CATEGORY(domain_server_auth, "overte.domain_server.auth") const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; const QString DomainServer::REPLACEMENT_FILE_EXTENSION = ".replace"; const QString& DOMAIN_SERVER_SETTINGS_KEY = "domain_server"; const QString PUBLIC_SOCKET_ADDRESS_KEY = "network_address"; const QString PUBLIC_SOCKET_PORT_KEY = "network_port"; const int MIN_PORT = 1; const int MAX_PORT = 65535; int const DomainServer::EXIT_CODE_REBOOT = 234923; QString DomainServer::_iceServerAddr { NetworkingConstants::ICE_SERVER_DEFAULT_HOSTNAME }; int DomainServer::_iceServerPort { ICE_SERVER_DEFAULT_PORT }; bool DomainServer::_overrideDomainID { false }; QUuid DomainServer::_overridingDomainID; bool DomainServer::_getTempName { false }; QString DomainServer::_userConfigFilename; int DomainServer::_parentPID { -1 }; bool DomainServer::_forceCrashReporting{false}; /// @brief The Domain server can proxy requests to the Directory Server, this function handles those forwarding requests. /// @param connection The HTTP connection object. /// @param requestUrl The full URL of the request. e.g. https://google.com/api/v1/test /// @param metaversePath The path on the Directory Server to route to. /// @param requestSubobjectKey (Optional) The parent object key that any data will be inserted into for the forwarded request. /// @param requiredData (Optional) This data is required to be present for the request. /// @param optionalData (Optional) If provided, this optional data will be forwarded with the request. /// @param requireAccessToken Require a valid access token to be sent with this request. bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection, const QUrl& requestUrl, const QString& metaversePath, const QString& requestSubobjectKey, std::initializer_list<QString> requiredData, std::initializer_list<QString> optionalData, bool requireAccessToken) { auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH); if (!accessTokenVariant.isValid() && requireAccessToken) { connection->respond(HTTPConnection::StatusCode400, "User access token has not been set"); return true; } QJsonObject subobject; if (requestUrl.hasQuery()) { QUrlQuery query(requestUrl); for (auto& key : requiredData) { if (query.hasQueryItem(key)) { subobject.insert(key, query.queryItemValue(key)); } else { auto error = "Domain Server: Bad request, expected param '" + key + "'"; connection->respond(HTTPConnection::StatusCode400, error.toLatin1()); return true; } } for (auto& key : optionalData) { if (query.hasQueryItem(key)) { subobject.insert(key, query.queryItemValue(key)); } } } else { auto params = connection->parseUrlEncodedForm(); for (auto& key : requiredData) { auto it = params.find(key); if (it == params.end()) { auto error = "Domain Server: Bad request, expected param '" + key + "'"; connection->respond(HTTPConnection::StatusCode400, error.toLatin1()); return true; } subobject.insert(key, it.value()); } for (auto& key : optionalData) { auto it = params.find(key); if (it != params.end()) { subobject.insert(key, it.value()); } } } QJsonObject root; root.insert(requestSubobjectKey, subobject); QJsonDocument doc { root }; QUrl url { MetaverseAPI::getCurrentMetaverseServerURL().toString() + metaversePath }; QNetworkRequest req(url); req.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::OVERTE_USER_AGENT); req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); if (accessTokenVariant.isValid()) { auto accessTokenHeader = QString("Bearer ") + accessTokenVariant.toString(); req.setRawHeader("Authorization", accessTokenHeader.toLatin1()); } QNetworkReply* reply; auto method = connection->requestOperation(); if (method == QNetworkAccessManager::GetOperation) { reply = NetworkAccessManager::getInstance().get(req); } else if (method == QNetworkAccessManager::PostOperation) { reply = NetworkAccessManager::getInstance().post(req, doc.toJson()); } else if (method == QNetworkAccessManager::PutOperation) { reply = NetworkAccessManager::getInstance().put(req, doc.toJson()); } else { connection->respond(HTTPConnection::StatusCode400, "Error forwarding request, unsupported method"); return true; } connect(reply, &QNetworkReply::finished, this, [reply, connection]() { if (reply->error() != QNetworkReply::NoError) { auto data = reply->readAll(); qDebug() << "Got error response from directory server (" << reply->url() << "): " << data << reply->errorString(); connection->respond(HTTPConnection::StatusCode400, data); return; } connection->respond(HTTPConnection::StatusCode200, reply->readAll()); }); return true; } DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), _gatekeeper(this), _httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this) { static const QString CRASH_REPORTER = "crash_reporting.enable_crash_reporter"; if (_parentPID != -1) { watchParentProcess(_parentPID); } PathUtils::removeTemporaryApplicationDirs(); DependencyManager::set<tracing::Tracer>(); DependencyManager::set<StatTracker>(); LogUtils::init(); LogHandler::getInstance().moveToThread(thread()); LogHandler::getInstance().setupRepeatedMessageFlusher(); qDebug() << "Setting up domain-server"; qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion()); qDebug() << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION; qDebug() << "[VERSION] VERSION:" << BuildInfo::VERSION; qDebug() << "[VERSION] BUILD_TYPE_STRING:" << BuildInfo::BUILD_TYPE_STRING; qDebug() << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES; qDebug() << "[VERSION] We will be using this name to find ICE servers:" << _iceServerAddr; connect(this, &QCoreApplication::aboutToQuit, this, &DomainServer::aboutToQuit); // make sure we have a fresh AccountManager instance // (need this since domain-server can restart itself and maintain static variables) DependencyManager::set<AccountManager>(); // load the user config QString userConfigFilename; if (!_userConfigFilename.isEmpty()) { userConfigFilename = _userConfigFilename; } else { // we weren't passed a user config path static const QString USER_CONFIG_FILE_NAME = "config.json"; userConfigFilename = PathUtils::getAppDataFilePath(USER_CONFIG_FILE_NAME); } _settingsManager.setupConfigMap(userConfigFilename); // setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us #ifdef _WIN32 installNativeEventFilter(&ShutdownEventListener::getInstance()); #else ShutdownEventListener::getInstance(); #endif auto &ch = CrashHandler::getInstance(); ch.setEnabled(_settingsManager.valueOrDefaultValueForKeyPath(CRASH_REPORTER).toBool()); qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData"); qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData"); // make sure we hear about newly connected nodes from our gatekeeper connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode); // if a connected node loses connection privileges, hang up on it connect(&_gatekeeper, &DomainGatekeeper::killNode, this, &DomainServer::handleKillNode); // if permissions are updated, relay the changes to the Node datastructures connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions, &_gatekeeper, &DomainGatekeeper::updateNodePermissions); connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated, this, &DomainServer::updateReplicatedNodes); connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated, this, &DomainServer::updateDownstreamNodes); connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated, this, &DomainServer::updateUpstreamNodes); setupGroupCacheRefresh(); optionallySetupOAuth(); if (_oauthEnable) { _oauthEnable = optionallyReadX509KeyAndCertificate(); } _settingsManager.apiRefreshGroupInformation(); #if defined(WEBRTC_DATA_CHANNELS) const QString WEBRTC_ENABLE = "webrtc.enable_webrtc"; bool isWebRTCEnabled = _settingsManager.valueForKeyPath(WEBRTC_ENABLE).toBool(); qCDebug(domain_server) << "WebRTC enabled:" << isWebRTCEnabled; // The domain server's WebRTC signaling server is used by the domain server and the assignment clients, so disabling it // disables WebRTC for the server as a whole. if (isWebRTCEnabled) { const QString WEBRTC_WSS_ENABLE = "webrtc.enable_webrtc_websocket_ssl"; bool isWebRTCEnabled = _settingsManager.valueForKeyPath(WEBRTC_WSS_ENABLE).toBool(); qCDebug(domain_server) << "WebRTC WSS enabled:" << isWebRTCEnabled; _webrtcSignalingServer.reset(new WebRTCSignalingServer(this, isWebRTCEnabled)); } #endif setupNodeListAndAssignments(); updateReplicatedNodes(); updateDownstreamNodes(); updateUpstreamNodes(); #if defined(WEBRTC_DATA_CHANNELS) if (isWebRTCEnabled) { setUpWebRTCSignalingServer(); } #endif if (_type != NonMetaverse) { // if we have a directory services domain, we'll use an access token for API calls resetAccountManagerAccessToken(); setupAutomaticNetworking(); } if (!getID().isNull() && _type != NonMetaverse) { // setup periodic heartbeats to directory services API setupHeartbeatToMetaverse(); // send the first heartbeat immediately sendHeartbeatToMetaverse(); } // check for the temporary name parameter if (_getTempName) { getTemporaryName(); } // send signal to DomainMetadata when descriptors changed _metadata = new DomainMetadata(this); connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated, _metadata, &DomainMetadata::descriptorsChanged); // update the metadata when a user (dis)connects connect(this, &DomainServer::userConnected, _metadata, &DomainMetadata::usersChanged); connect(this, &DomainServer::userDisconnected, _metadata, &DomainMetadata::usersChanged); // update the metadata when security changes connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions, [this] { _metadata->securityChanged(true); }); qDebug() << "domain-server is running"; static const QString AC_SUBNET_ALLOWLIST_SETTING_PATH = "security.ac_subnet_allowlist"; static const Subnet LOCALHOST { QHostAddress("127.0.0.1"), 32 }; _acSubnetAllowlist = { LOCALHOST }; auto allowlist = _settingsManager.valueOrDefaultValueForKeyPath(AC_SUBNET_ALLOWLIST_SETTING_PATH).toStringList(); for (auto& subnet : allowlist) { auto netmaskParts = subnet.trimmed().split("/"); if (netmaskParts.size() > 2) { qDebug() << "Ignoring subnet in allowlist, malformed: " << subnet; continue; } // The default netmask is 32 if one has not been specified, which will // match only the ip provided. int netmask = 32; if (netmaskParts.size() == 2) { bool ok; netmask = netmaskParts[1].toInt(&ok); if (!ok) { qDebug() << "Ignoring subnet in allowlist, bad netmask: " << subnet; continue; } } auto ip = QHostAddress(netmaskParts[0]); if (!ip.isNull()) { qDebug() << "Adding AC allowlist subnet: " << subnet << " -> " << (ip.toString() + "/" + QString::number(netmask)); _acSubnetAllowlist.push_back({ ip , netmask }); } else { qDebug() << "Ignoring subnet in allowlist, invalid ip portion: " << subnet; } } if (QDir(getEntitiesDirPath()).mkpath(".")) { qCDebug(domain_server) << "Created entities data directory"; } maybeHandleReplacementEntityFile(); _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager)); connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){ _contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath()))); _contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir(), isAssetServerEnabled()))); _contentManager->addBackupHandler(BackupHandlerPointer(new ContentSettingsBackupHandler(_settingsManager))); }); _contentManager->initialize(true); connect(_contentManager.get(), &DomainContentBackupManager::recoveryCompleted, this, &DomainServer::restart); static const int NODE_PING_MONITOR_INTERVAL_MSECS = 1 * MSECS_PER_SECOND; _nodePingMonitorTimer = new QTimer{ this }; connect(_nodePingMonitorTimer, &QTimer::timeout, this, &DomainServer::nodePingMonitor); _nodePingMonitorTimer->start(NODE_PING_MONITOR_INTERVAL_MSECS); initializeExporter(); initializeMetadataExporter(); } void DomainServer::parseCommandLine(int argc, char* argv[]) { QCommandLineParser parser; parser.setApplicationDescription("Overte Domain Server"); const QCommandLineOption versionOption = parser.addVersionOption(); const QCommandLineOption helpOption = parser.addHelpOption(); const QCommandLineOption iceServerAddressOption("i", "ice-server address", "IP:PORT or HOSTNAME:PORT"); parser.addOption(iceServerAddressOption); const QCommandLineOption domainIDOption("d", "domain-server uuid", "uuid"); parser.addOption(domainIDOption); const QCommandLineOption getTempNameOption("get-temp-name", "Request a temporary domain-name"); parser.addOption(getTempNameOption); const QCommandLineOption userConfigOption("user-config", "Pass user config file pass", "path"); parser.addOption(userConfigOption); const QCommandLineOption parentPIDOption(PARENT_PID_OPTION, "PID of the parent process", "parent-pid"); parser.addOption(parentPIDOption); const QCommandLineOption logOption("logOptions", "Logging options, comma separated: color,nocolor,process_id,thread_id,milliseconds,keep_repeats,journald,nojournald", "options"); parser.addOption(logOption); const QCommandLineOption forceCrashReportingOption("forceCrashReporting", "Force crash reporting to initialize."); parser.addOption(forceCrashReportingOption); QStringList arguments; for (int i = 0; i < argc; ++i) { arguments << argv[i]; } if (!parser.parse(arguments)) { std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam QCoreApplication mockApp(argc, argv); // required for call to showHelp() parser.showHelp(); Q_UNREACHABLE(); } if (parser.isSet(versionOption)) { parser.showVersion(); Q_UNREACHABLE(); } if (parser.isSet(helpOption)) { QCoreApplication mockApp(argc, argv); // required for call to showHelp() parser.showHelp(); Q_UNREACHABLE(); } // We want to configure the logging system as early as possible auto &logHandler = LogHandler::getInstance(); if (parser.isSet(logOption)) { if (!logHandler.parseOptions(parser.value(logOption).toUtf8(), logOption.names().first())) { QCoreApplication mockApp(argc, const_cast<char**>(argv)); // required for call to showHelp() parser.showHelp(); Q_UNREACHABLE(); } } if (parser.isSet(iceServerAddressOption)) { // parse the IP and port combination for this target QString hostnamePortString = parser.value(iceServerAddressOption); _iceServerAddr = hostnamePortString.left(hostnamePortString.indexOf(':')); _iceServerPort = (quint16) hostnamePortString.mid(hostnamePortString.indexOf(':') + 1).toUInt(); if (_iceServerPort == 0) { _iceServerPort = ICE_SERVER_DEFAULT_PORT; } if (_iceServerAddr.isEmpty()) { qCWarning(domain_server_ice) << "ALERT: Could not parse an IP address and port combination from" << hostnamePortString; ::exit(0); } } if (parser.isSet(domainIDOption)) { _overridingDomainID = QUuid(parser.value(domainIDOption)); _overrideDomainID = true; qDebug() << "domain-server ID is" << _overridingDomainID; } if (parser.isSet(getTempNameOption)) { _getTempName = true; } if (parser.isSet(userConfigOption)) { _userConfigFilename = parser.value(userConfigOption); } if (parser.isSet(parentPIDOption)) { bool ok = false; int parentPID = parser.value(parentPIDOption).toInt(&ok); if (ok) { _parentPID = parentPID;